Skip to content

Commit a05f0f5

Browse files
committed
feat: cherry-pick MinimalInterchainAccountRouter solidity changes
Cherry-picked solidity changes from 6c715a7 (#8323): - Extract AbstractInterchainAccountRouter base contract - Add MinimalInterchainAccountRouter for Igra - Refactor InterchainAccountRouter to extend abstract base - Update and add tests
1 parent d41d088 commit a05f0f5

5 files changed

Lines changed: 795 additions & 679 deletions

File tree

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.13;
3+
4+
/*@@@@@@@ @@@@@@@@@
5+
@@@@@@@@@ @@@@@@@@@
6+
@@@@@@@@@ @@@@@@@@@
7+
@@@@@@@@@ @@@@@@@@@
8+
@@@@@@@@@@@@@@@@@@@@@@@@@
9+
@@@@@ HYPERLANE @@@@@@@
10+
@@@@@@@@@@@@@@@@@@@@@@@@@
11+
@@@@@@@@@ @@@@@@@@@
12+
@@@@@@@@@ @@@@@@@@@
13+
@@@@@@@@@ @@@@@@@@@
14+
@@@@@@@@@ @@@@@@@@*/
15+
16+
// ============ Internal Imports ============
17+
import {OwnableMulticall} from "./libs/OwnableMulticall.sol";
18+
import {InterchainAccountMessage} from "./libs/InterchainAccountMessage.sol";
19+
import {CallLib} from "./libs/Call.sol";
20+
import {MinimalProxy} from "../libs/MinimalProxy.sol";
21+
import {TypeCasts} from "../libs/TypeCasts.sol";
22+
import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol";
23+
import {Router} from "../client/Router.sol";
24+
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol";
25+
import {IInterchainSecurityModule} from "../interfaces/IInterchainSecurityModule.sol";
26+
27+
// ============ External Imports ============
28+
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
29+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
30+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
31+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
32+
33+
// solhint-disable-next-line hyperlane/enumerable-domain-mapping
34+
abstract contract AbstractInterchainAccountRouter is Router {
35+
using TypeCasts for address;
36+
using TypeCasts for bytes32;
37+
using StandardHookMetadata for bytes;
38+
using SafeERC20 for IERC20;
39+
40+
// ============ Constants ============
41+
42+
address public immutable implementation;
43+
bytes32 public immutable bytecodeHash;
44+
45+
// ============ Public Storage ============
46+
mapping(uint32 destinationDomain => bytes32 ism) public isms;
47+
48+
// ============ Upgrade Gap ============
49+
50+
uint256[47] private __GAP;
51+
52+
// ============ Events ============
53+
54+
event RemoteIsmEnrolled(uint32 indexed domain, bytes32 ism);
55+
56+
event RemoteCallDispatched(
57+
uint32 indexed destination,
58+
address indexed owner,
59+
bytes32 router,
60+
bytes32 ism,
61+
bytes32 salt
62+
);
63+
64+
event InterchainAccountCreated(
65+
address indexed account,
66+
uint32 origin,
67+
bytes32 router,
68+
bytes32 owner,
69+
address ism,
70+
bytes32 salt
71+
);
72+
73+
// solhint-disable-next-line hyperlane/no-virtual-override
74+
function interchainSecurityModule()
75+
external
76+
view
77+
virtual
78+
override
79+
returns (IInterchainSecurityModule)
80+
{
81+
return IInterchainSecurityModule(address(this));
82+
}
83+
84+
function enrollRemoteRouterAndIsm(
85+
uint32 _destination,
86+
bytes32 _router,
87+
bytes32 _ism
88+
) external onlyOwner {
89+
_enrollRemoteRouterAndIsm(_destination, _router, _ism);
90+
}
91+
92+
function enrollRemoteRouterAndIsms(
93+
uint32[] calldata _destinations,
94+
bytes32[] calldata _routers,
95+
bytes32[] calldata _isms
96+
) external onlyOwner {
97+
require(
98+
_destinations.length == _routers.length &&
99+
_destinations.length == _isms.length,
100+
"length mismatch"
101+
);
102+
for (uint256 i = 0; i < _destinations.length; i++) {
103+
_enrollRemoteRouterAndIsm(_destinations[i], _routers[i], _isms[i]);
104+
}
105+
}
106+
107+
receive() external payable {}
108+
109+
function approveFeeTokenForHook(address _feeToken, address _hook) external {
110+
IERC20(_feeToken).forceApprove(_hook, type(uint256).max);
111+
}
112+
113+
function getDeployedInterchainAccount(
114+
uint32 _origin,
115+
address _owner,
116+
address _router,
117+
address _ism
118+
) public virtual returns (OwnableMulticall) {
119+
return
120+
getDeployedInterchainAccount(
121+
_origin,
122+
_owner.addressToBytes32(),
123+
_router.addressToBytes32(),
124+
_ism,
125+
InterchainAccountMessage.EMPTY_SALT
126+
);
127+
}
128+
129+
function getDeployedInterchainAccount(
130+
uint32 _origin,
131+
bytes32 _owner,
132+
bytes32 _router,
133+
address _ism,
134+
bytes32 _userSalt
135+
) public virtual returns (OwnableMulticall) {
136+
bytes32 _deploySalt = _getSalt(
137+
_origin,
138+
_owner,
139+
_router,
140+
_ism.addressToBytes32(),
141+
_userSalt
142+
);
143+
address payable _account = _getLocalInterchainAccount(_deploySalt);
144+
if (!Address.isContract(_account)) {
145+
bytes memory _bytecode = MinimalProxy.bytecode(implementation);
146+
_account = payable(Create2.deploy(0, _deploySalt, _bytecode));
147+
emit InterchainAccountCreated(
148+
_account,
149+
_origin,
150+
_router,
151+
_owner,
152+
_ism,
153+
_userSalt
154+
);
155+
}
156+
return OwnableMulticall(_account);
157+
}
158+
159+
function getLocalInterchainAccount(
160+
uint32 _origin,
161+
address _owner,
162+
address _router,
163+
address _ism
164+
) external view virtual returns (OwnableMulticall) {
165+
return
166+
OwnableMulticall(
167+
_getLocalInterchainAccount(
168+
_getSalt(
169+
_origin,
170+
_owner.addressToBytes32(),
171+
_router.addressToBytes32(),
172+
_ism.addressToBytes32(),
173+
InterchainAccountMessage.EMPTY_SALT
174+
)
175+
)
176+
);
177+
}
178+
179+
function quoteGasPayment(
180+
uint32 _destination,
181+
uint256 _gasLimit
182+
) public view virtual returns (uint256 _gasPayment) {
183+
return
184+
_Router_quoteDispatch(
185+
_destination,
186+
new bytes(0),
187+
StandardHookMetadata.overrideGasLimit(_gasLimit),
188+
address(hook)
189+
);
190+
}
191+
192+
function callRemoteWithOverrides(
193+
uint32 _destination,
194+
bytes32 _router,
195+
bytes32 _ism,
196+
CallLib.Call[] calldata _calls,
197+
bytes memory _hookMetadata
198+
) public payable virtual returns (bytes32) {
199+
emit RemoteCallDispatched(
200+
_destination,
201+
msg.sender,
202+
_router,
203+
_ism,
204+
InterchainAccountMessage.EMPTY_SALT
205+
);
206+
bytes memory _body = InterchainAccountMessage.encode(
207+
msg.sender,
208+
_ism,
209+
_calls
210+
);
211+
return
212+
_dispatchMessageWithValue(
213+
_destination,
214+
_router,
215+
_body,
216+
_hookMetadata,
217+
hook,
218+
msg.value
219+
);
220+
}
221+
222+
function _dispatchMessageWithValue(
223+
uint32 _destination,
224+
bytes32 _router,
225+
bytes memory _body,
226+
bytes memory _hookMetadata,
227+
IPostDispatchHook _hook,
228+
uint256 _value
229+
) internal returns (bytes32) {
230+
require(_router != bytes32(0), "no router specified for destination");
231+
232+
address _feeToken = _hookMetadata.feeToken();
233+
if (_feeToken != address(0)) {
234+
uint256 _fee = _Router_quoteDispatch(
235+
_destination,
236+
bytes(""),
237+
_hookMetadata,
238+
address(_hook)
239+
);
240+
241+
IERC20(_feeToken).safeTransferFrom(msg.sender, address(this), _fee);
242+
IERC20(_feeToken).forceApprove(address(_hook), type(uint256).max);
243+
}
244+
245+
return
246+
mailbox.dispatch{value: _value}(
247+
_destination,
248+
_router,
249+
_body,
250+
_hookMetadata,
251+
_hook
252+
);
253+
}
254+
255+
function _implementationBytecode(
256+
address router
257+
) internal pure returns (bytes memory) {
258+
return
259+
abi.encodePacked(
260+
type(OwnableMulticall).creationCode,
261+
abi.encode(router)
262+
);
263+
}
264+
265+
function _proxyBytecodeHash(
266+
address _implementation
267+
) internal pure returns (bytes32) {
268+
return keccak256(MinimalProxy.bytecode(_implementation));
269+
}
270+
271+
/// @dev Required for use of Router, compiler will not include this function in the bytecode
272+
function _handle(uint32, bytes32, bytes calldata) internal pure override {
273+
assert(false);
274+
}
275+
276+
// solhint-disable-next-line hyperlane/no-virtual-override
277+
function _enrollRemoteRouter(
278+
uint32 _destination,
279+
bytes32 _address
280+
) internal virtual override {
281+
_enrollRemoteRouterAndIsm(
282+
_destination,
283+
_address,
284+
InterchainAccountMessage.EMPTY_SALT
285+
);
286+
}
287+
288+
function _enrollRemoteIsm(uint32 _destination, bytes32 _ism) internal {
289+
isms[_destination] = _ism;
290+
emit RemoteIsmEnrolled(_destination, _ism);
291+
}
292+
293+
function _enrollRemoteRouterAndIsm(
294+
uint32 _destination,
295+
bytes32 _router,
296+
bytes32 _ism
297+
) internal {
298+
require(
299+
routers(_destination) == InterchainAccountMessage.EMPTY_SALT &&
300+
isms[_destination] == InterchainAccountMessage.EMPTY_SALT,
301+
"router and ISM defaults are immutable once set"
302+
);
303+
Router._enrollRemoteRouter(_destination, _router);
304+
_enrollRemoteIsm(_destination, _ism);
305+
}
306+
307+
function _getSalt(
308+
uint32 _origin,
309+
bytes32 _owner,
310+
bytes32 _router,
311+
bytes32 _ism,
312+
bytes32 _userSalt
313+
) internal pure returns (bytes32) {
314+
return
315+
keccak256(
316+
abi.encodePacked(_origin, _owner, _router, _ism, _userSalt)
317+
);
318+
}
319+
320+
function _getLocalInterchainAccount(
321+
bytes32 _salt
322+
) internal view returns (address payable) {
323+
return payable(Create2.computeAddress(_salt, bytecodeHash));
324+
}
325+
}

0 commit comments

Comments
 (0)