Skip to content

Commit efa455d

Browse files
use estimated gas as part of compose message
1 parent 68b1cda commit efa455d

File tree

4 files changed

+26
-61
lines changed

4 files changed

+26
-61
lines changed

script/EnsoReceiverDeployer.s.sol

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ contract EnsoReceiverDeployer is Script {
1212
address OWNER = 0x826e0BB2276271eFdF2a500597f37b94f6c153bA;
1313
address BACKEND_SIGNER = 0xFE503EE14863F6aCEE10BCdc66aC5e2301b3A946;
1414

15-
function run()
16-
public
17-
returns (EnsoReceiver implementation, ERC4337CloneFactory factory)
18-
{
15+
function run() public returns (EnsoReceiver implementation, ERC4337CloneFactory factory) {
1916
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
2017

2118
vm.startBroadcast(deployerPrivateKey);

script/LayerZeroDeployer.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ contract LayerZeroDeployer is Script {
4545
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
4646
}
4747

48-
lzReceiver = address(new LayerZeroReceiver{ salt: "LayerZeroReceiver" }(endpoint, router, deployer, 100_000));
48+
lzReceiver = address(new LayerZeroReceiver{ salt: "LayerZeroReceiver" }(endpoint, router, deployer));
4949

5050
vm.stopBroadcast();
5151
}

src/bridge/LayerZeroReceiver.sol

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
1313
using SafeERC20 for IERC20;
1414

1515
address private constant _NATIVE_ASSET = address(0);
16-
uint256 private constant _TRY_CATCH_GAS = 2000;
1716

1817
address public immutable endpoint;
1918
IEnsoRouter public immutable router;
2019

21-
uint256 public reserveGas;
22-
2320
mapping(address => bool) public validOFT;
2421
mapping(address => bool) public validRegistrar;
2522
mapping(bytes32 => bool) public messageExecuted;
@@ -33,7 +30,7 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
3330
event ReserveGasUpdated(uint256 amount);
3431
event FundsCollected(address token, uint256 amount);
3532

36-
error InsufficientGas(bytes32 guid);
33+
error InsufficientGas(bytes32 guid, uint256 estimatedGas, uint256 availableGas);
3734
error NotEndpoint(address sender);
3835
error NotRegistrar(address sender);
3936
error NotSelf();
@@ -45,15 +42,13 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
4542
error InvalidMsgValue(uint256 actual, uint256 expected);
4643
error MessageExecuted(bytes32 key);
4744

48-
constructor(address _endpoint, address _router, address _owner, uint256 _reserveGas) Ownable(_owner) {
45+
constructor(address _endpoint, address _router, address _owner) Ownable(_owner) {
4946
if (_endpoint == address(0)) revert EndpointNotSet();
5047
if (_router == address(0)) revert RouterNotSet();
5148
endpoint = _endpoint;
5249
router = IEnsoRouter(_router);
5350
validRegistrar[_owner] = true;
5451
emit RegistrarAdded(_owner);
55-
reserveGas = _reserveGas;
56-
emit ReserveGasUpdated(_reserveGas);
5752
}
5853

5954
// layer zero callback
@@ -76,20 +71,16 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
7671

7772
uint256 amount = _message.amountLD();
7873
bytes memory composeMsg = _message.composeMsg();
79-
(address receiver, uint256 nativeDrop, bytes memory shortcutData) = abi.decode(composeMsg, (address, uint256, bytes));
74+
(address receiver, uint256 nativeDrop, uint256 estimatedGas, bytes memory shortcutData) =
75+
abi.decode(composeMsg, (address, uint256, uint256, bytes));
8076
if (msg.value != nativeDrop) revert InvalidMsgValue(msg.value, nativeDrop);
81-
8277
uint256 availableGas = gasleft();
83-
if (availableGas < reserveGas) revert InsufficientGas(_guid);
78+
if (availableGas < estimatedGas) revert InsufficientGas(_guid, estimatedGas, availableGas);
79+
8480
// try to execute shortcut
85-
try this.execute{ gas: availableGas - reserveGas }(token, amount, shortcutData, msg.value) {
81+
try this.execute(token, amount, shortcutData, msg.value) {
8682
emit ShortcutExecutionSuccessful(_guid);
8783
} catch (bytes memory err) {
88-
if (err.length == 0 && gasleft() < reserveGas - _TRY_CATCH_GAS) {
89-
// assume that the shortcut failed due to an out of gas error,
90-
// to discourage griefing we will revert instead of transferring funds.
91-
revert InsufficientGas(_guid);
92-
}
9384
// if shortcut fails send funds to receiver
9485
emit ShortcutExecutionFailed(_guid, err);
9586
_transfer(token, receiver, amount);
@@ -153,17 +144,8 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
153144
emit RegistrarRemoved(account);
154145
}
155146

156-
function setReserveGas(uint256 amount) external onlyOwner {
157-
reserveGas = amount;
158-
emit ReserveGasUpdated(amount);
159-
}
160-
161147
// sweep funds to the contract owner in order to refund user
162-
function sweep(
163-
bytes32 messageKey,
164-
address token,
165-
uint256 amount
166-
) external onlyOwner {
148+
function sweep(bytes32 messageKey, address token, uint256 amount) external onlyOwner {
167149
// message key is passed to block subsequent calls to lzCompose in case a failing message becomes executable.
168150
// internal message data is not validated in case the message itself is malformed or incorrect
169151
// (e.g. oft returns incorrect token)
@@ -174,11 +156,7 @@ contract LayerZeroReceiver is Ownable, ILayerZeroComposer {
174156
emit FundsCollected(token, amount);
175157
}
176158

177-
function getMessageKey(
178-
address from,
179-
bytes32 guid,
180-
bytes calldata message
181-
) public pure returns (bytes32) {
159+
function getMessageKey(address from, bytes32 guid, bytes calldata message) public pure returns (bytes32) {
182160
return keccak256(abi.encode(from, guid, message));
183161
}
184162

test/Bridge.t.sol

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ contract BridgeTest is Test {
4646
vm.selectFork(_ethereumFork);
4747
router = new EnsoRouter();
4848
shortcuts = EnsoShortcuts(payable(router.shortcuts()));
49-
lzReceiver = new LayerZeroReceiver(address(this), address(router), address(this), 100_000);
49+
lzReceiver = new LayerZeroReceiver(address(this), address(router), address(this));
5050

5151
address[] memory ofts = new address[](2);
5252
ofts[0] = ethPool;
@@ -60,7 +60,7 @@ contract BridgeTest is Test {
6060
uint256 balanceBefore = weth.balanceOf(address(this));
6161

6262
(bytes32[] memory commands, bytes[] memory state) = _buildWethDeposit(ETH_AMOUNT);
63-
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, commands, state);
63+
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, 0, commands, state);
6464

6565
// transfer funds
6666
(bool success,) = address(lzReceiver).call{ value: ETH_AMOUNT }("");
@@ -78,7 +78,7 @@ contract BridgeTest is Test {
7878

7979
// TOO MUCH VALUE ATTEMPTED TO TRANSFER
8080
(bytes32[] memory commands, bytes[] memory state) = _buildWethDeposit(ETH_AMOUNT * 100);
81-
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, commands, state);
81+
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, 0, commands, state);
8282

8383
// transfer funds
8484
(bool success,) = address(lzReceiver).call{ value: ETH_AMOUNT }("");
@@ -91,32 +91,21 @@ contract BridgeTest is Test {
9191
assertEq(balanceBefore, address(this).balance);
9292
}
9393

94-
function testEthBridgeWithShortcutOutOfGas() public {
94+
function testEthBridgeWithLessThanEstimateGas() public {
9595
vm.selectFork(_ethereumFork);
9696

9797
(bytes32[] memory commands, bytes[] memory state) = _buildWethDeposit(ETH_AMOUNT);
98-
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, commands, state);
98+
// exact gas amount needed for execution
99+
uint256 estimatedGas = 94_910;
100+
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, estimatedGas, commands, state);
99101

100102
// transfer funds
101103
(bool success,) = address(lzReceiver).call{ value: ETH_AMOUNT }("");
102104
if (!success) revert TransferFailed();
103105
// trigger compose with insufficient gas
104-
vm.expectRevert();
105-
lzReceiver.lzCompose{ gas: 108_000 }(ethPool, bytes32(0), message, address(0), "");
106-
}
107-
108-
function testEthBridgeWithLessThanReserveGas() public {
109-
vm.selectFork(_ethereumFork);
110-
111-
(bytes32[] memory commands, bytes[] memory state) = _buildWethDeposit(ETH_AMOUNT);
112-
bytes memory message = _buildLzComposeMessage(ETH_AMOUNT, 0, commands, state);
113-
114-
// transfer funds
115-
(bool success,) = address(lzReceiver).call{ value: ETH_AMOUNT }("");
116-
if (!success) revert TransferFailed();
117-
// trigger compose with insufficient gas
118-
vm.expectRevert();
119-
lzReceiver.lzCompose{ gas: 99_000 }(ethPool, bytes32(0), message, address(0), "");
106+
vm.expectRevert(abi.encodeWithSelector(LayerZeroReceiver.InsufficientGas.selector, bytes32(0), estimatedGas, estimatedGas - 1));
107+
// exactly 1 less gas than needed for lz compose
108+
lzReceiver.lzCompose{ gas: 105_317 }(ethPool, bytes32(0), message, address(0), "");
120109
}
121110

122111
function testUsdcBridge() public {
@@ -125,7 +114,7 @@ contract BridgeTest is Test {
125114
uint256 balanceBefore = IERC20(usdc).balanceOf(vitalik);
126115

127116
(bytes32[] memory commands, bytes[] memory state) = _buildTransfer(usdc, vitalik, USDC_AMOUNT);
128-
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, 0, commands, state);
117+
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, 0, 0, commands, state);
129118

130119
// transfer funds
131120
vm.startPrank(usdcPool);
@@ -145,7 +134,7 @@ contract BridgeTest is Test {
145134

146135
(bytes32[] memory commands, bytes[] memory state) =
147136
_buildTokenAndValueTransfer(usdc, vitalik, USDC_AMOUNT, ETH_AMOUNT);
148-
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, ETH_AMOUNT, commands, state);
137+
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, ETH_AMOUNT, 0, commands, state);
149138

150139
// transfer funds
151140
vm.startPrank(usdcPool);
@@ -168,7 +157,7 @@ contract BridgeTest is Test {
168157

169158
// TOO MUCH VALUE ATTEMPTED TO TRANSFER
170159
(bytes32[] memory commands, bytes[] memory state) = _buildTransfer(usdc, vitalik, USDC_AMOUNT * 100);
171-
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, 0, commands, state);
160+
bytes memory message = _buildLzComposeMessage(USDC_AMOUNT, 0, 0, commands, state);
172161

173162
// transfer funds
174163
vm.startPrank(usdcPool);
@@ -215,6 +204,7 @@ contract BridgeTest is Test {
215204
function _buildLzComposeMessage(
216205
uint256 amount,
217206
uint256 nativeDrop,
207+
uint256 estimatedGas,
218208
bytes32[] memory commands,
219209
bytes[] memory state
220210
)
@@ -226,7 +216,7 @@ contract BridgeTest is Test {
226216
bytes memory shortcutData =
227217
abi.encodeWithSelector(shortcuts.executeShortcut.selector, bytes32(0), bytes32(0), commands, state);
228218
// encode callback data
229-
bytes memory callbackData = abi.encode(address(this), nativeDrop, shortcutData);
219+
bytes memory callbackData = abi.encode(address(this), nativeDrop, estimatedGas, shortcutData);
230220
// encode message
231221
message = OFTComposeMsgCodec.encode(
232222
uint64(0),

0 commit comments

Comments
 (0)