@@ -4,54 +4,117 @@ pragma solidity ^0.8.28;
4
4
import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol " ;
5
5
import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol " ;
6
6
7
- import { VM } from "enso-weiroll/VM .sol " ;
7
+ import { Ownable } from "openzeppelin-contracts/access/Ownable .sol " ;
8
8
import { IERC20 , SafeERC20 } from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol " ;
9
9
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 {
11
33
using OFTComposeMsgCodec for bytes ;
12
34
using SafeERC20 for IERC20 ;
13
35
14
36
address private constant _NATIVE_ASSET = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ;
15
37
38
+ IRouter public immutable router;
16
39
address public immutable endpoint;
17
40
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 );
20
46
21
47
error NotEndpoint (address sender );
22
48
error NotSelf ();
23
- error TransferFailed (bytes32 guid , address receiver );
49
+ error TransferFailed (address receiver );
24
50
25
- constructor (address _endpoint ) {
51
+ constructor (address _endpoint , address _router , uint256 _reserveGas ) Ownable (msg .sender ) {
52
+ router = IRouter (_router);
26
53
endpoint = _endpoint;
54
+ reserveGas = _reserveGas;
27
55
}
28
56
57
+ // layer zero callback
29
58
function lzCompose (address , bytes32 _guid , bytes calldata _message , address , bytes calldata ) external payable {
30
59
if (msg .sender != endpoint) revert NotEndpoint (msg .sender );
31
60
61
+ uint256 amount = _message.amountLD ();
32
62
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);
48
77
}
49
78
}
79
+
50
80
}
51
81
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 {
53
84
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 ));
55
118
}
56
119
57
120
receive () external payable { }
0 commit comments