Skip to content

Commit 084f910

Browse files
Merge pull request #16 from EnsoBuild/stargate-fix
fix stargate receiver to control access to funds
2 parents 9737ee8 + d7238a7 commit 084f910

7 files changed

+158
-64
lines changed

script/SocketDeployer.s.sol

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.13;
33

4-
import "forge-std/Script.sol";
54
import "../src/EnsoShortcuts.sol";
5+
import "forge-std/Script.sol";
66

77
contract Deployer is Script {
8-
mapping(uint => address) socketReceivers;
8+
mapping(uint256 => address) socketReceivers;
99

1010
constructor() {
1111
// Ethereum
@@ -21,10 +21,10 @@ contract Deployer is Script {
2121
socketReceivers[137] = 0x8DfeB2e0B392f0033C8685E35FB4763d88a70d12;
2222

2323
// Arbitrum
24-
socketReceivers[42161] = 0x88616cB9499F32Ff6A784B66B60aABF0bCf0df39;
24+
socketReceivers[42_161] = 0x88616cB9499F32Ff6A784B66B60aABF0bCf0df39;
2525

2626
// Avalanche
27-
socketReceivers[43114] = 0x83b2cda6A33128324ee9cb2f0360bA8a42Cec2C6;
27+
socketReceivers[43_114] = 0x83b2cda6A33128324ee9cb2f0360bA8a42Cec2C6;
2828
}
2929

3030
function run() public returns (EnsoShortcuts socketEnsoShortcuts) {
@@ -33,8 +33,6 @@ contract Deployer is Script {
3333
address socketReceiver = socketReceivers[block.chainid];
3434

3535
vm.broadcast(deployerPrivateKey);
36-
socketEnsoShortcuts = new EnsoShortcuts{salt: "SocketEnsoShortcuts"}(
37-
socketReceiver
38-
);
36+
socketEnsoShortcuts = new EnsoShortcuts{ salt: "SocketEnsoShortcuts" }(socketReceiver);
3937
}
40-
}
38+
}

script/StargateDeployer.s.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "../src/bridge/StargateV2Receiver.sol";
55
import "forge-std/Script.sol";
66

77
contract StargateDeployer is Script {
8-
function run() public returns (address stargateHelper, address endpoint) {
8+
function run() public returns (address stargateHelper, address endpoint, address router) {
99
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
1010

1111
vm.startBroadcast(deployerPrivateKey);
@@ -14,19 +14,25 @@ contract StargateDeployer is Script {
1414

1515
if (chainId == 324) {
1616
endpoint = 0xd07C30aF3Ff30D96BDc9c6044958230Eb797DDBF; // zksync
17+
router = 0x1BD8CefD703CF6b8fF886AD2E32653C32bc62b5C;
1718
} else if (chainId == 130 || chainId == 146 || chainId == 480 || chainId == 80_094) {
1819
endpoint = 0x6F475642a6e85809B1c36Fa62763669b1b48DD5B; // unichain, sonic, worldchain, berachain
20+
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
1921
} else if (chainId == 57_073) {
2022
endpoint = 0xca29f3A6f966Cb2fc0dE625F8f325c0C46dbE958; // ink
23+
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
2124
} else if (chainId == 1868) {
2225
endpoint = 0x4bCb6A963a9563C33569D7A512D35754221F3A19; // soneium
26+
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
2327
} else if (chainId == 999) {
2428
endpoint = 0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9; // hyper
29+
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
2530
} else {
2631
endpoint = 0x1a44076050125825900e736c501f859c50fE728c; // default
32+
router = 0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf;
2733
}
2834

29-
stargateHelper = address(new StargateV2Receiver{ salt: "StargateV2Receiver" }(endpoint));
35+
stargateHelper = address(new StargateV2Receiver{ salt: "StargateV2Receiver" }(endpoint, router, 100000));
3036

3137
vm.stopBroadcast();
3238
}

script/UniswapV4Deployer.s.sol

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.28;
33

4-
import "forge-std/Script.sol";
54
import "../src/helpers/UniswapV4Helpers.sol";
5+
import "forge-std/Script.sol";
66

77
contract UniswapV4Deployer is Script {
88
function run() public returns (UniswapV4Helpers uniswapV4Helpers, address poolManager) {
@@ -28,17 +28,17 @@ contract UniswapV4Deployer is Script {
2828
poolManager = 0x360E68faCcca8cA495c1B759Fd9EEe466db9FB32;
2929
} else if (chainId == 8453) {
3030
poolManager = 0x498581fF718922c3f8e6A244956aF099B2652b2b;
31-
} else if (chainId == 42161) {
31+
} else if (chainId == 42_161) {
3232
poolManager = 0x360E68faCcca8cA495c1B759Fd9EEe466db9FB32;
33-
} else if (chainId == 43114) {
33+
} else if (chainId == 43_114) {
3434
poolManager = 0x06380C0e0912312B5150364B9DC4542BA0DbBc85;
35-
} else if (chainId == 57073) {
35+
} else if (chainId == 57_073) {
3636
poolManager = 0x360E68faCcca8cA495c1B759Fd9EEe466db9FB32;
3737
} else {
3838
revert("No pool manager");
3939
}
4040

41-
uniswapV4Helpers = new UniswapV4Helpers{salt: "UniswapV4Helpers"}(poolManager);
41+
uniswapV4Helpers = new UniswapV4Helpers{ salt: "UniswapV4Helpers" }(poolManager);
4242

4343
vm.stopBroadcast();
4444
}

src/bridge/StargateV2Receiver.sol

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,117 @@ pragma solidity ^0.8.28;
44
import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol";
55
import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
66

7-
import { VM } from "enso-weiroll/VM.sol";
7+
import { Ownable } from "openzeppelin-contracts/access/Ownable.sol";
88
import { IERC20, SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
99

10-
contract StargateV2Receiver is VM, ILayerZeroComposer {
10+
interface IRouter {
11+
enum TokenType {
12+
Native,
13+
ERC20,
14+
ERC721,
15+
ERC1155
16+
}
17+
18+
struct Token {
19+
TokenType tokenType;
20+
bytes data;
21+
}
22+
23+
function routeSingle(
24+
Token calldata tokenIn,
25+
bytes calldata data
26+
)
27+
external
28+
payable
29+
returns (bytes memory response);
30+
}
31+
32+
contract StargateV2Receiver is Ownable, ILayerZeroComposer {
1133
using OFTComposeMsgCodec for bytes;
1234
using SafeERC20 for IERC20;
1335

1436
address private constant _NATIVE_ASSET = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
1537

38+
IRouter public immutable router;
1639
address public immutable endpoint;
1740

18-
event ShortcutExecutionSuccessful(bytes32 guid, bytes32 requestId);
19-
event ShortcutExecutionFailed(bytes32 guid, bytes32 requestId);
41+
uint256 public immutable reserveGas;
42+
43+
event ShortcutExecutionSuccessful(bytes32 guid);
44+
event ShortcutExecutionFailed(bytes32 guid);
45+
event InsufficientGas(bytes32 guid);
2046

2147
error NotEndpoint(address sender);
2248
error NotSelf();
23-
error TransferFailed(bytes32 guid, address receiver);
49+
error TransferFailed(address receiver);
2450

25-
constructor(address _endpoint) {
51+
constructor(address _endpoint, address _router, uint256 _reserveGas) Ownable(msg.sender) {
52+
router = IRouter(_router);
2653
endpoint = _endpoint;
54+
reserveGas = _reserveGas;
2755
}
2856

57+
// layer zero callback
2958
function lzCompose(address, bytes32 _guid, bytes calldata _message, address, bytes calldata) external payable {
3059
if (msg.sender != endpoint) revert NotEndpoint(msg.sender);
3160

61+
uint256 amount = _message.amountLD();
3262
bytes memory composeMsg = _message.composeMsg();
33-
(address token, address receiver, bytes32 requestId, bytes32[] memory commands, bytes[] memory state) =
34-
abi.decode(composeMsg, (address, address, bytes32, bytes32[], bytes[]));
35-
36-
// try to execute shortcut
37-
try this.execute(commands, state) {
38-
emit ShortcutExecutionSuccessful(_guid, requestId);
39-
} catch {
40-
// if shortcut fails send funds to receiver
41-
emit ShortcutExecutionFailed(_guid, requestId);
42-
uint256 amount = _message.amountLD();
43-
if (token == _NATIVE_ASSET) {
44-
(bool success,) = receiver.call{ value: amount }("");
45-
if (!success) revert TransferFailed(_guid, receiver);
46-
} else {
47-
IERC20(token).safeTransfer(receiver, amount);
63+
(address token, address receiver, bytes memory shortcutData) = abi.decode(composeMsg, (address, address, bytes));
64+
65+
uint256 availableGas = gasleft();
66+
if (availableGas < reserveGas) {
67+
emit InsufficientGas(_guid);
68+
_transfer(token, receiver, amount);
69+
} else {
70+
// try to execute shortcut
71+
try this.execute{ gas: availableGas - reserveGas }(token, amount, shortcutData) {
72+
emit ShortcutExecutionSuccessful(_guid);
73+
} catch {
74+
// if shortcut fails send funds to receiver
75+
emit ShortcutExecutionFailed(_guid);
76+
_transfer(token, receiver, amount);
4877
}
4978
}
79+
5080
}
5181

52-
function execute(bytes32[] calldata commands, bytes[] memory state) public {
82+
// execute shortcut using router
83+
function execute(address token, uint256 amount, bytes calldata data) public {
5384
if (msg.sender != address(this)) revert NotSelf();
54-
_execute(commands, state);
85+
IRouter.Token memory tokenIn;
86+
uint256 value;
87+
if (token == _NATIVE_ASSET) {
88+
tokenIn = IRouter.Token(IRouter.TokenType.Native, abi.encode(amount));
89+
value = amount;
90+
} else {
91+
tokenIn = IRouter.Token(IRouter.TokenType.ERC20, abi.encode(token, amount));
92+
IERC20(token).forceApprove(address(router), amount);
93+
}
94+
router.routeSingle{ value: value }(tokenIn, data);
95+
}
96+
97+
// sweep funds to the contract owner in order to refund user
98+
function sweep(address[] memory tokens) external onlyOwner {
99+
address receiver = owner();
100+
address token;
101+
for (uint256 i = 0; i < tokens.length; ++i) {
102+
token = tokens[i];
103+
_transfer(token, receiver, _balance(token));
104+
}
105+
}
106+
107+
function _transfer(address token, address receiver, uint256 amount) internal {
108+
if (token == _NATIVE_ASSET) {
109+
(bool success,) = receiver.call{ value: amount }("");
110+
if (!success) revert TransferFailed(receiver);
111+
} else {
112+
IERC20(token).safeTransfer(receiver, amount);
113+
}
114+
}
115+
116+
function _balance(address token) internal view returns (uint256 balance) {
117+
balance = token == _NATIVE_ASSET ? address(this).balance : IERC20(token).balanceOf(address(this));
55118
}
56119

57120
receive() external payable { }

src/helpers/UniswapV4Helpers.sol

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,17 @@ contract UniswapV4Helpers {
212212
returns (bytes memory)
213213
{
214214
return encodeMintFromDeltasWithHooks(
215-
currency0, currency1, fee, tickSpacing, tickLower, tickUpper, amount0Max, amount1Max, recipient, refund, address(0)
215+
currency0,
216+
currency1,
217+
fee,
218+
tickSpacing,
219+
tickLower,
220+
tickUpper,
221+
amount0Max,
222+
amount1Max,
223+
recipient,
224+
refund,
225+
address(0)
216226
);
217227
}
218228
}

src/helpers/Verifier.sol

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,36 @@ contract Verifier {
77
bytes32[] memory commands,
88
bytes[] memory state,
99
bytes memory signature
10-
) public pure returns (bool) {
10+
)
11+
public
12+
pure
13+
returns (bool)
14+
{
1115
bytes32 messageHash = getMessageHash(commands, state);
1216
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
1317

1418
return recoverSigner(ethSignedMessageHash, signature) == signer;
1519
}
1620

17-
function getMessageHash(
18-
bytes32[] memory commands,
19-
bytes[] memory state
20-
) public pure returns (bytes32) {
21+
function getMessageHash(bytes32[] memory commands, bytes[] memory state) public pure returns (bytes32) {
2122
return keccak256(abi.encode(commands, state));
2223
}
2324

24-
function getEthSignedMessageHash(bytes32 messageHash)
25-
public
26-
pure
27-
returns (bytes32)
28-
{
25+
function getEthSignedMessageHash(bytes32 messageHash) public pure returns (bytes32) {
2926
/*
3027
Signature is produced by signing a keccak256 hash with the following format:
3128
"\x19Ethereum Signed Message\n" + len(msg) + msg
3229
*/
33-
return keccak256(
34-
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
35-
);
30+
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
3631
}
3732

38-
function recoverSigner(
39-
bytes32 ethSignedMessageHash,
40-
bytes memory signature
41-
) internal pure returns (address) {
33+
function recoverSigner(bytes32 ethSignedMessageHash, bytes memory signature) internal pure returns (address) {
4234
(bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
4335

4436
return ecrecover(ethSignedMessageHash, v, r, s);
4537
}
4638

47-
function splitSignature(bytes memory sig)
48-
internal
49-
pure
50-
returns (bytes32 r, bytes32 s, uint8 v)
51-
{
39+
function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
5240
require(sig.length == 65, "invalid signature length");
5341

5442
assembly {
@@ -71,4 +59,4 @@ contract Verifier {
7159

7260
// implicitly return (r, s, v)
7361
}
74-
}
62+
}

0 commit comments

Comments
 (0)