Skip to content

Commit b58a398

Browse files
committed
🔨 contracts: allow safe to deploy hyperlane components
1 parent 74220df commit b58a398

3 files changed

Lines changed: 271 additions & 179 deletions

File tree

‎contracts/.gas-snapshot‎

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5847848, ~: 5347535)
1+
ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5888117, ~: 5393451)
22
ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 15501257)
33
ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38700546)
44
ExaPluginTest:testFork_collectCollateral_collects() (gas: 32196281)
@@ -202,20 +202,23 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437
202202
ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447)
203203
ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093)
204204
ExaPreviewerTest:test_utilizations_returns() (gas: 134922)
205-
HypEXATest:test_deployRouter_setsHookAndIsm() (gas: 177970)
206-
HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 574363)
207-
HypEXATest:test_pause_pausesHookAndIsmViaPauser() (gas: 144889)
208-
HypEXATest:test_process_reverts_whenIsmPaused() (gas: 678724)
209-
HypEXATest:test_proposeBridgeRole_reverts_whenAlreadyGranted() (gas: 71828)
210-
HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 17501362)
211-
HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 1908590)
212-
HypEXATest:test_refreshDefaults_updatesMailboxDefaults() (gas: 635375)
213-
HypEXATest:test_rotateRouterPausable_restoresProcessAfterPauserIsmPaused() (gas: 19193761)
214-
HypEXATest:test_rotateRouterPausable_restoresTransferRemoteAfterPauserHookPaused() (gas: 2030732)
215-
HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1175537)
216-
HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1742730)
217-
HypEXATest:test_transferRemote_reverts_whenHookPaused() (gas: 1041866)
218-
HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 720315)
205+
HypEXATest:test_deployRouter_enrollsBase_whenNoRemotesOnOptimism() (gas: 30654748)
206+
HypEXATest:test_deployRouter_enrollsOptimism_whenNoRemotesOnBase() (gas: 30758707)
207+
HypEXATest:test_deployRouter_reverts_whenNoRemotesOnUnsupportedChain() (gas: 20601101)
208+
HypEXATest:test_deployRouter_setsHookAndIsm() (gas: 270936)
209+
HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 578581)
210+
HypEXATest:test_hookAndIsm_trackMailboxDefaults_withoutRedeploy() (gas: 154299)
211+
HypEXATest:test_pause_pausesHookAndIsmViaPauser() (gas: 144877)
212+
HypEXATest:test_process_reverts_whenIsmPaused() (gas: 712859)
213+
HypEXATest:test_proposeBridgeRole_reverts_whenAlreadyGranted() (gas: 247221)
214+
HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 20387421)
215+
HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 2263986)
216+
HypEXATest:test_rotateRouterPausable_restoresProcessAfterPauserIsmPaused() (gas: 22139948)
217+
HypEXATest:test_rotateRouterPausable_restoresTransferRemoteAfterPauserHookPaused() (gas: 2126569)
218+
HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1312360)
219+
HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1960009)
220+
HypEXATest:test_transferRemote_reverts_whenHookPaused() (gas: 1106969)
221+
HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 754963)
219222
IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935)
220223
IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708)
221224
IssuerCheckerTest:test_setIssuer_reverts_whenZeroAddress() (gas: 35130)
@@ -226,24 +229,24 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526
226229
IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548)
227230
MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414)
228231
MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410)
229-
RedeployerTest:test_deployEXA_allowsProxyAdminV4Upgrade_onBase() (gas: 26759980)
230-
RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 56684340)
231-
RedeployerTest:test_deployEXA_migratesProxyAdmin_onBase() (gas: 24041787)
232-
RedeployerTest:test_deployEXA_preservesERC20_onBase() (gas: 24024274)
233-
RedeployerTest:test_deployEXA_revokesOldProxyAdmin_onBase() (gas: 25633694)
234-
RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 16342253)
235-
RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 31628388)
236-
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 260788684)
237-
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 356380326)
238-
RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 34819889)
239-
RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 16367196)
240-
RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 32176524)
241-
RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 19883553)
242-
RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 16372538)
243-
RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 31120230)
244-
RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 34991733)
245-
RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 40027233)
246-
RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 44571529)
232+
RedeployerTest:test_deployEXA_allowsProxyAdminV4Upgrade_onBase() (gas: 26756929)
233+
RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 56681289)
234+
RedeployerTest:test_deployEXA_migratesProxyAdmin_onBase() (gas: 24038736)
235+
RedeployerTest:test_deployEXA_preservesERC20_onBase() (gas: 24021223)
236+
RedeployerTest:test_deployEXA_revokesOldProxyAdmin_onBase() (gas: 25630643)
237+
RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 16339201)
238+
RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 31625336)
239+
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 260785642)
240+
RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 356377284)
241+
RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 34816837)
242+
RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 16364144)
243+
RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 32173472)
244+
RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 19880501)
245+
RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 16369486)
246+
RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 31117178)
247+
RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 34988681)
248+
RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 40024170)
249+
RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 44565432)
247250
RefunderTest:test_refund_refunds() (gas: 263363)
248251
RefunderTest:test_refund_reverts_whenExpired() (gas: 88359)
249252
RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861)

‎contracts/script/HypEXA.s.sol‎

Lines changed: 112 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import {
77
import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol";
88
import { TimelockController } from "openzeppelin-contracts/contracts/governance/TimelockController.sol";
99

10+
import { DefaultHook } from "@hyperlane-xyz/core/contracts/hooks/DefaultHook.sol";
1011
import { PausableHook } from "@hyperlane-xyz/core/contracts/hooks/PausableHook.sol";
1112
import { StaticAggregationHook } from "@hyperlane-xyz/core/contracts/hooks/aggregation/StaticAggregationHook.sol";
12-
import { IMailbox } from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
1313
import { PausableIsm } from "@hyperlane-xyz/core/contracts/isms/PausableIsm.sol";
14+
import { DefaultFallbackRoutingIsm } from "@hyperlane-xyz/core/contracts/isms/routing/DefaultFallbackRoutingIsm.sol";
1415
import { HypERC20Collateral } from "@hyperlane-xyz/core/contracts/token/HypERC20Collateral.sol";
1516
import { HypXERC20 } from "@hyperlane-xyz/core/contracts/token/extensions/HypXERC20.sol";
1617

@@ -19,55 +20,70 @@ import { BaseScript } from "./Base.s.sol";
1920
/// @title HypEXA
2021
/// @notice Deploys and maintains the Hyperlane HypXERC20 router for EXA.
2122
contract HypEXA is BaseScript {
22-
function deployRouter(address token, uint32[] calldata remoteDomains) external returns (HypXERC20 router) {
23+
function deployRouter(uint32[] calldata remoteDomains) external returns (HypXERC20 router) {
2324
address admin = acct("admin");
25+
address exa = protocol("EXA", true, getChain("optimism").chainId);
2426
router = HypXERC20(CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA"))));
2527
if (address(router).code.length != 0) return router;
28+
29+
uint32[] memory domains = remoteDomains;
30+
if (domains.length == 0) {
31+
domains = new uint32[](1);
32+
if (block.chainid == getChain("optimism").chainId) domains[0] = uint32(getChain("base").chainId);
33+
else if (block.chainid == getChain("base").chainId) domains[0] = uint32(getChain("optimism").chainId);
34+
else revert UnsupportedChain(block.chainid);
35+
}
36+
2637
vm.startBroadcast(admin);
2738

2839
(address hook, address ism) = _deployAggregations(
2940
_deployPausableHook(acct("exactly")),
3041
_deployPausableHook(acct("pauser")),
42+
_deployDefaultHook(),
3143
_deployPausableIsm(acct("exactly")),
32-
_deployPausableIsm(acct("pauser"))
44+
_deployPausableIsm(acct("pauser")),
45+
_deployDefaultIsm()
3346
);
3447

48+
address impl = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXAImpl")));
49+
if (impl.code.length == 0) {
50+
impl = CREATE3_FACTORY.deploy(
51+
keccak256(abi.encode("HypEXAImpl")),
52+
abi.encodePacked(type(HypXERC20).creationCode, abi.encode(exa, 1, 1, acct("hyperlaneMailbox")))
53+
);
54+
}
55+
3556
router = HypXERC20(
3657
CREATE3_FACTORY.deploy(
3758
keccak256(abi.encode("HypEXA")),
3859
abi.encodePacked(
3960
type(TransparentUpgradeableProxy).creationCode,
40-
abi.encode(
41-
address(new HypXERC20(token, 1, 1, acct("hyperlaneMailbox"))),
42-
protocol("ProxyAdmin"),
43-
abi.encodeCall(HypERC20Collateral.initialize, (hook, ism, admin))
44-
)
61+
abi.encode(impl, protocol("ProxyAdmin"), abi.encodeCall(HypERC20Collateral.initialize, (hook, ism, admin)))
4562
)
4663
)
4764
);
4865

49-
if (remoteDomains.length > 0) {
50-
bytes32[] memory addresses = new bytes32[](remoteDomains.length);
51-
bytes32 remote = bytes32(uint256(uint160(address(router))));
52-
for (uint256 i = 0; i < remoteDomains.length; ++i) {
53-
addresses[i] = remote;
54-
}
55-
router.enrollRemoteRouters(remoteDomains, addresses);
66+
bytes32[] memory addresses = new bytes32[](domains.length);
67+
bytes32 remote = bytes32(uint256(uint160(address(router))));
68+
for (uint256 i = 0; i < domains.length; ++i) {
69+
addresses[i] = remote;
5670
}
71+
router.enrollRemoteRouters(domains, addresses);
5772

5873
router.transferOwnership(acct("exactly"));
5974
vm.stopBroadcast();
6075
}
6176

62-
function proposeBridgeRole(address token, bytes32 salt) external {
77+
function proposeBridgeRole(bytes32 salt) external {
6378
address router = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("HypEXA")));
6479
if (router.code.length == 0) revert RouterNotDeployed();
65-
if (IAccessControl(token).hasRole(keccak256("BRIDGE_ROLE"), router)) revert AlreadyGranted();
80+
address exa = protocol("EXA", true, getChain("optimism").chainId);
81+
if (IAccessControl(exa).hasRole(keccak256("BRIDGE_ROLE"), router)) revert AlreadyGranted();
6682
TimelockController timelock = TimelockController(payable(protocol("TimelockController")));
6783
uint256 delay = timelock.getMinDelay();
6884
vm.broadcast(acct("deployer"));
6985
timelock.schedule(
70-
token, 0, abi.encodeCall(IAccessControl.grantRole, (keccak256("BRIDGE_ROLE"), router)), bytes32(0), salt, delay
86+
exa, 0, abi.encodeCall(IAccessControl.grantRole, (keccak256("BRIDGE_ROLE"), router)), bytes32(0), salt, delay
7187
);
7288
}
7389

@@ -83,59 +99,88 @@ contract HypEXA is BaseScript {
8399
_expectOwner(hooks[0], acct("exactly"));
84100
_expectOwner(modules[0], acct("exactly"));
85101
(hook, ism) = _deployAggregations(
86-
hooks[0], _deployPausableHook(acct("pauser")), modules[0], _deployPausableIsm(acct("pauser"))
102+
hooks[0],
103+
_deployPausableHook(acct("pauser")),
104+
hooks[2],
105+
modules[0],
106+
_deployPausableIsm(acct("pauser")),
107+
modules[2]
87108
);
88109
HypXERC20(router).setHook(hook);
89110
HypXERC20(router).setInterchainSecurityModule(ism);
90111

91112
vm.stopBroadcast();
92113
}
93114

94-
/// @notice Refreshes router hook and ism with new defaults from the Mailbox.
95-
function refreshDefaults() external returns (address hook, address ism) {
96-
address router = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("HypEXA")));
97-
if (router.code.length == 0) revert RouterNotDeployed();
98-
vm.startBroadcast(acct("exactly"));
99-
100-
address[] memory hooks = StaticAggregationHook(address(HypXERC20(router).hook())).hooks("");
101-
(address[] memory modules,) =
102-
IStaticAggregationIsm(address(HypXERC20(router).interchainSecurityModule())).modulesAndThreshold("");
103-
_expectOwner(hooks[0], acct("exactly"));
104-
_expectOwner(hooks[1], acct("pauser"));
105-
_expectOwner(modules[0], acct("exactly"));
106-
_expectOwner(modules[1], acct("pauser"));
107-
(hook, ism) = _deployAggregations(hooks[0], hooks[1], modules[0], modules[1]);
108-
HypXERC20(router).setHook(hook);
109-
HypXERC20(router).setInterchainSecurityModule(ism);
110-
111-
vm.stopBroadcast();
112-
}
113-
114-
function _deployAggregations(address exactlyHook, address pauserHook, address exactlyIsm, address pauserIsm)
115-
internal
116-
returns (address aggregationHook, address aggregationIsm)
117-
{
118-
address mailbox = acct("hyperlaneMailbox");
115+
function _deployAggregations(
116+
address exactlyHook,
117+
address pauserHook,
118+
address defaultHook,
119+
address exactlyIsm,
120+
address pauserIsm,
121+
address defaultIsm
122+
) internal returns (address aggregationHook, address aggregationIsm) {
119123
address[] memory hooks = new address[](3);
120124
hooks[0] = exactlyHook;
121125
hooks[1] = pauserHook;
122-
hooks[2] = address(IMailbox(mailbox).defaultHook());
126+
hooks[2] = defaultHook;
123127
aggregationHook = IStaticAggregationHookFactory(acct("hyperlaneAggregationHookFactory")).deploy(hooks);
124128

125129
address[] memory isms = new address[](3);
126130
isms[0] = exactlyIsm;
127131
isms[1] = pauserIsm;
128-
isms[2] = address(IMailbox(mailbox).defaultIsm());
132+
isms[2] = defaultIsm;
129133
aggregationIsm = IStaticAggregationIsmFactory(acct("hyperlaneAggregationIsmFactory")).deploy(isms, 3);
130134
}
131135

136+
function _deployDefaultHook() internal returns (address hook) {
137+
address admin = acct("admin");
138+
hook = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXADefaultHook")));
139+
if (hook.code.length == 0) {
140+
hook = CREATE3_FACTORY.deploy(
141+
keccak256(abi.encode("HypEXADefaultHook")),
142+
abi.encodePacked(type(DefaultHook).creationCode, abi.encode(acct("hyperlaneMailbox")))
143+
);
144+
}
145+
}
146+
147+
function _deployDefaultIsm() internal returns (address ism) {
148+
address admin = acct("admin");
149+
address impl = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXADefaultIsmImpl")));
150+
if (impl.code.length == 0) {
151+
impl = CREATE3_FACTORY.deploy(
152+
keccak256(abi.encode("HypEXADefaultIsmImpl")),
153+
abi.encodePacked(type(DefaultFallbackRoutingIsm).creationCode, abi.encode(acct("hyperlaneMailbox")))
154+
);
155+
}
156+
ism = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXADefaultIsm")));
157+
if (ism.code.length == 0) {
158+
ism = CREATE3_FACTORY.deploy(
159+
keccak256(abi.encode("HypEXADefaultIsm")),
160+
abi.encodePacked(
161+
type(TransparentUpgradeableProxy).creationCode,
162+
abi.encode(impl, protocol("ProxyAdmin"), abi.encodeCall(IRoutingIsmInitializer.initialize, (acct("exactly"))))
163+
)
164+
);
165+
}
166+
}
167+
132168
function _deployPausableHook(address owner) internal returns (address hook) {
133-
hook = address(new PausableHook());
134-
PausableHook(hook).transferOwnership(owner);
169+
address factory = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("PausableHookFactory")));
170+
if (factory.code.length == 0) {
171+
factory =
172+
CREATE3_FACTORY.deploy(keccak256(abi.encode("PausableHookFactory")), type(PausableHookFactory).creationCode);
173+
}
174+
hook = address(PausableHookFactory(factory).deploy(owner));
135175
}
136176

137177
function _deployPausableIsm(address owner) internal returns (address ism) {
138-
ism = address(new PausableIsm(owner));
178+
address factory = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("PausableIsmFactory")));
179+
if (factory.code.length == 0) {
180+
factory =
181+
CREATE3_FACTORY.deploy(keccak256(abi.encode("PausableIsmFactory")), type(PausableIsmFactory).creationCode);
182+
}
183+
ism = address(PausableIsmFactory(factory).deploy(owner));
139184
}
140185

141186
function _expectOwner(address target, address owner) internal view {
@@ -146,12 +191,30 @@ contract HypEXA is BaseScript {
146191
error AlreadyGranted();
147192
error RouterNotDeployed();
148193
error UnexpectedOwner(address target, address owner);
194+
error UnsupportedChain(uint256 chainid);
195+
196+
contract PausableHookFactory {
197+
function deploy(address owner) external returns (PausableHook hook) {
198+
hook = new PausableHook();
199+
hook.transferOwnership(owner);
200+
}
201+
}
202+
203+
contract PausableIsmFactory {
204+
function deploy(address owner) external returns (PausableIsm ism) {
205+
ism = new PausableIsm(owner);
206+
}
207+
}
149208

150209
interface IStaticAggregationHookFactory {
151210
function deploy(address[] calldata values) external returns (address);
152211
function getAddress(address[] calldata values) external view returns (address);
153212
}
154213

214+
interface IRoutingIsmInitializer {
215+
function initialize(address owner) external;
216+
}
217+
155218
interface IStaticAggregationIsm {
156219
function modulesAndThreshold(bytes calldata) external view returns (address[] memory, uint8);
157220
}

0 commit comments

Comments
 (0)