From f2df3f7d31c98b8fef1bc3ea36f26d4a6bc9a0c9 Mon Sep 17 00:00:00 2001 From: itofarina Date: Mon, 2 Mar 2026 17:23:41 -0300 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=9A=A7=20contracts:=20update=20protoc?= =?UTF-8?q?ol=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/package.json | 2 +- contracts/remappings.txt | 1 + package.json | 2 +- pnpm-lock.yaml | 18 ++++++++++++++++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index eb3f58e681..ebfba28edc 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -23,7 +23,7 @@ "webauthn-sol": "base/webauthn-sol#v1.0.0" }, "devDependencies": { - "@exactly/protocol": "^0.2.22", + "@exactly/protocol": "exactly/protocol#5833408", "@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@^4.9.6", "@openzeppelin/contracts-upgradeable": "^5.6.1", "@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@^4.9.6", diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 91209330a0..06c1e018bf 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -9,6 +9,7 @@ webauthn-owner-plugin/=node_modules/webauthn-owner-plugin/src webauthn-sol/=node_modules/webauthn-sol/src @exactly/protocol/=node_modules/@exactly/protocol/contracts/ +node_modules/@exactly/protocol/:@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ @openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable-v4/ @openzeppelin/contracts-upgradeable-v4/=node_modules/@openzeppelin/contracts-upgradeable-v4/ @openzeppelin/contracts-v4/=node_modules/@openzeppelin/contracts-v4/ diff --git a/package.json b/package.json index ed17636189..fe86c6d0a7 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,7 @@ "typescript" ] }, - "neverBuiltDependencies": [], + "onlyBuiltDependencies": ["@exactly/protocol"], "peerDependencyRules": { "allowedVersions": { "@hono/mcp>hono-rate-limiter": "0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 442a3d2b4a..e2ef280b11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -657,8 +657,8 @@ importers: version: https://codeload.github.com/base/webauthn-sol/tar.gz/619f20ab0f074fef41066ee4ab24849a913263b2 devDependencies: '@exactly/protocol': - specifier: ^0.2.22 - version: 0.2.22 + specifier: exactly/protocol#5833408 + version: https://codeload.github.com/exactly/protocol/tar.gz/5833408b2a8582f113adc07bbb302de6fdc5f7ff '@openzeppelin/contracts-upgradeable': specifier: ^5.6.1 version: 5.6.1(@openzeppelin/contracts@5.6.1) @@ -2848,6 +2848,11 @@ packages: resolution: {integrity: sha512-QqQTWoWSc+pZ830YpQ0KtAcagDuC65Ez2dDZBhkHy8Hks2VmHih5QjXnvHXS42Bytp+2p6wkPAXfkUewfsFPdQ==} engines: {node: '>=18'} + '@exactly/protocol@https://codeload.github.com/exactly/protocol/tar.gz/5833408b2a8582f113adc07bbb302de6fdc5f7ff': + resolution: {tarball: https://codeload.github.com/exactly/protocol/tar.gz/5833408b2a8582f113adc07bbb302de6fdc5f7ff} + version: 0.2.22 + engines: {node: '>=18'} + '@expo-google-fonts/material-symbols@0.4.27': resolution: {integrity: sha512-cnb3DZnWUWpezGFkJ8y4MT5f/lw6FcgDzeJzic+T+vpQHLHG1cg3SC3i1w1i8Bk4xKR4HPY3t9iIRNvtr5ml8A==} @@ -16001,6 +16006,15 @@ snapshots: solady: 0.1.26 solmate: '@rari-capital/solmate@https://codeload.github.com/transmissions11/solmate/tar.gz/eaa7041378f9a6c12f943de08a6c41b31a9870fc' + '@exactly/protocol@https://codeload.github.com/exactly/protocol/tar.gz/5833408b2a8582f113adc07bbb302de6fdc5f7ff': + dependencies: + '@openzeppelin/contracts': 5.6.1 + '@openzeppelin/contracts-upgradeable': 5.6.1(@openzeppelin/contracts@5.6.1) + '@openzeppelin/contracts-upgradeable-v4': '@openzeppelin/contracts-upgradeable@4.9.6' + '@openzeppelin/contracts-v4': '@openzeppelin/contracts@4.9.6' + solady: 0.1.26 + solmate: '@rari-capital/solmate@https://codeload.github.com/transmissions11/solmate/tar.gz/eaa7041378f9a6c12f943de08a6c41b31a9870fc' + '@expo-google-fonts/material-symbols@0.4.27': {} '@expo/cli@55.0.19(@expo/metro-runtime@55.0.7)(bufferutil@4.1.0)(expo-constants@55.0.9)(expo-font@55.0.4)(expo-router@55.0.8)(expo@55.0.9)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(bufferutil@4.1.0)(react@19.2.0)(utf-8-validate@5.0.10))(react@19.2.0)(typescript@5.9.3)(utf-8-validate@5.0.10)': From 945ab15572a47a3188d5288920361244e3ae3400 Mon Sep 17 00:00:00 2001 From: itofarina Date: Tue, 3 Mar 2026 14:28:21 -0300 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=85=20contracts:=20update=20redeploye?= =?UTF-8?q?r=20test=20with=20latest=20exa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 6 +++--- contracts/test/Redeployer.t.sol | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 8593776b30..0ce15962c5 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1,4 +1,4 @@ -ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5856150, ~: 5360703) +ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5839098, ~: 5347535) ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 15501257) ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38649064) ExaPluginTest:testFork_collectCollateral_collects() (gas: 32148122) @@ -212,7 +212,7 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 54792391) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 55268709) RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 15868804) RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 31111328) RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 260295770) @@ -224,7 +224,7 @@ RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 19385733) RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 15897009) RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 30601997) RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 34474337) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 37486962) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 38611896) RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 43616725) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) diff --git a/contracts/test/Redeployer.t.sol b/contracts/test/Redeployer.t.sol index 6a1a1c1d1b..9b3aced4f0 100644 --- a/contracts/test/Redeployer.t.sol +++ b/contracts/test/Redeployer.t.sol @@ -46,8 +46,7 @@ contract RedeployerTest is ForkTest { EXA token = EXA(exaOP); assertEq(token.name(), "exactly"); assertEq(token.symbol(), "EXA"); - assertEq(token.totalSupply(), 10_000_000e18, "token should have same minted supply"); - assertEq(token.balanceOf(address(redeployer.proxyAdmin())), 10_000_000e18, "ProxyAdmin should have tokens"); + assertEq(token.totalSupply(), 0, "base should have zero supply"); assertEq(token.decimals(), 18, "token should have 18 decimals"); } From 77547fac8a6d6b7325c7348683e644e757ba5275 Mon Sep 17 00:00:00 2001 From: itofarina Date: Mon, 2 Mar 2026 17:26:59 -0300 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9E=95=20contracts:=20add=20hyperlane=20?= =?UTF-8?q?dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/package.json | 1 + contracts/remappings.txt | 1 + pnpm-lock.yaml | 64 +++++++++++++++------------------------- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index ebfba28edc..6daa496516 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -27,6 +27,7 @@ "@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@^4.9.6", "@openzeppelin/contracts-upgradeable": "^5.6.1", "@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@^4.9.6", + "@hyperlane-xyz/core": "^11.0.0", "account-abstraction": "eth-infinitism/account-abstraction#v0.6.0", "forge-std": "foundry-rs/forge-std#v1.15.0", "fresh-crypto-lib": "rdubois-crypto/FreshCryptoLib#fd2a0e6e64609ade0b0c165e4d66de8106a70db6", diff --git a/contracts/remappings.txt b/contracts/remappings.txt index 06c1e018bf..5ffcccff86 100644 --- a/contracts/remappings.txt +++ b/contracts/remappings.txt @@ -14,6 +14,7 @@ node_modules/@exactly/protocol/:@openzeppelin/contracts/=node_modules/@openzeppe @openzeppelin/contracts-upgradeable-v4/=node_modules/@openzeppelin/contracts-upgradeable-v4/ @openzeppelin/contracts-v4/=node_modules/@openzeppelin/contracts-v4/ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts-v4/ +@hyperlane-xyz/=node_modules/@hyperlane-xyz/ account-abstraction/=node_modules/account-abstraction/contracts/ forge-std/=node_modules/forge-std/src/ solmate/=node_modules/solmate/ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e2ef280b11..dd9b1a599b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -659,6 +659,9 @@ importers: '@exactly/protocol': specifier: exactly/protocol#5833408 version: https://codeload.github.com/exactly/protocol/tar.gz/5833408b2a8582f113adc07bbb302de6fdc5f7ff + '@hyperlane-xyz/core': + specifier: ^11.0.0 + version: 11.3.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@openzeppelin/contracts-upgradeable': specifier: ^5.6.1 version: 5.6.1(@openzeppelin/contracts@5.6.1) @@ -3168,6 +3171,14 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@hyperlane-xyz/core@11.3.1': + resolution: {integrity: sha512-PX/qoJnPXXnIkE/16o/OPXxbgyIV6RHWYlpuh4QIB6uMvJUK4lbNxSQnfo9bVlH7eKbaD3NL2ENV28053ig/SQ==} + engines: {node: '>=16'} + peerDependencies: + '@ethersproject/abi': '*' + '@ethersproject/providers': '*' + '@types/sinon-chai': '*' + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -15731,7 +15742,6 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - optional: true '@ethersproject/abstract-provider@5.8.0': dependencies: @@ -15742,7 +15752,6 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/transactions': 5.8.0 '@ethersproject/web': 5.8.0 - optional: true '@ethersproject/abstract-signer@5.8.0': dependencies: @@ -15751,7 +15760,6 @@ snapshots: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 - optional: true '@ethersproject/address@5.8.0': dependencies: @@ -15760,35 +15768,29 @@ snapshots: '@ethersproject/keccak256': 5.8.0 '@ethersproject/logger': 5.8.0 '@ethersproject/rlp': 5.8.0 - optional: true '@ethersproject/base64@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 - optional: true '@ethersproject/basex@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/properties': 5.8.0 - optional: true '@ethersproject/bignumber@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 bn.js: 5.2.3 - optional: true '@ethersproject/bytes@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/constants@5.8.0': dependencies: '@ethersproject/bignumber': 5.8.0 - optional: true '@ethersproject/contracts@5.8.0': dependencies: @@ -15815,7 +15817,6 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - optional: true '@ethersproject/hdnode@5.8.0': dependencies: @@ -15854,15 +15855,12 @@ snapshots: dependencies: '@ethersproject/bytes': 5.8.0 js-sha3: 0.8.0 - optional: true - '@ethersproject/logger@5.8.0': - optional: true + '@ethersproject/logger@5.8.0': {} '@ethersproject/networks@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/pbkdf2@5.8.0': dependencies: @@ -15873,7 +15871,6 @@ snapshots: '@ethersproject/properties@5.8.0': dependencies: '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)': dependencies: @@ -15900,26 +15897,22 @@ snapshots: transitivePeerDependencies: - bufferutil - utf-8-validate - optional: true '@ethersproject/random@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/rlp@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/sha2@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/logger': 5.8.0 hash.js: 1.1.7 - optional: true '@ethersproject/signing-key@5.8.0': dependencies: @@ -15929,14 +15922,12 @@ snapshots: bn.js: 5.2.3 elliptic: 6.6.1 hash.js: 1.1.7 - optional: true '@ethersproject/strings@5.8.0': dependencies: '@ethersproject/bytes': 5.8.0 '@ethersproject/constants': 5.8.0 '@ethersproject/logger': 5.8.0 - optional: true '@ethersproject/transactions@5.8.0': dependencies: @@ -15949,7 +15940,6 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/rlp': 5.8.0 '@ethersproject/signing-key': 5.8.0 - optional: true '@ethersproject/units@5.8.0': dependencies: @@ -15984,7 +15974,6 @@ snapshots: '@ethersproject/logger': 5.8.0 '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - optional: true '@ethersproject/wordlists@5.8.0': dependencies: @@ -16530,6 +16519,11 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@hyperlane-xyz/core@11.3.1(@ethersproject/abi@5.8.0)(@ethersproject/providers@5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + dependencies: + '@ethersproject/abi': 5.8.0 + '@ethersproject/providers': 5.8.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + '@iconify/types@2.0.0': {} '@iconify/utils@3.1.0': @@ -18394,7 +18388,7 @@ snapshots: '@scure/bip32@1.7.0': dependencies: - '@noble/curves': 1.9.1 + '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 @@ -21457,11 +21451,9 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - bn.js@4.12.3: - optional: true + bn.js@4.12.3: {} - bn.js@5.2.3: - optional: true + bn.js@5.2.3: {} body-parser@2.2.2: dependencies: @@ -21508,8 +21500,7 @@ snapshots: dependencies: fill-range: 7.1.1 - brorand@1.1.0: - optional: true + brorand@1.1.0: {} brotli@1.3.3: dependencies: @@ -22517,7 +22508,6 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - optional: true embedded-postgres@18.3.0-beta.16(patch_hash=cb5e37525b1810f2af136570b38d5e0cec4cc2455408896ed1943d27f3f61b38): dependencies: @@ -24371,7 +24361,6 @@ snapshots: dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 - optional: true hasown@2.0.2: dependencies: @@ -24599,7 +24588,6 @@ snapshots: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - optional: true hoist-non-react-statics@3.3.2: dependencies: @@ -25183,8 +25171,7 @@ snapshots: js-cookie@3.0.1: {} - js-sha3@0.8.0: - optional: true + js-sha3@0.8.0: {} js-tokens@10.0.0: {} @@ -26668,11 +26655,9 @@ snapshots: mimic-response@4.0.0: {} - minimalistic-assert@1.0.1: - optional: true + minimalistic-assert@1.0.1: {} - minimalistic-crypto-utils@1.0.1: - optional: true + minimalistic-crypto-utils@1.0.1: {} minimatch@10.2.4: dependencies: @@ -29787,7 +29772,6 @@ snapshots: optionalDependencies: bufferutil: 4.1.0 utf-8-validate: 5.0.10 - optional: true ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10): optionalDependencies: From 81360189ff743c914e4fb6487d8dfe8b4a06a9aa Mon Sep 17 00:00:00 2001 From: itofarina Date: Tue, 3 Mar 2026 14:42:06 -0300 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=94=A8=20contracts:=20add=20hyperlane?= =?UTF-8?q?=20router=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/beige-sails-worry.md | 2 + contracts/.gas-snapshot | 52 +++++++------ contracts/deploy.json | 4 + contracts/script/Redeployer.s.sol | 56 +++++++++++++- contracts/test/HypEXA.t.sol | 122 ++++++++++++++++++++++++++++++ cspell.json | 2 + 6 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 .changeset/beige-sails-worry.md create mode 100644 contracts/test/HypEXA.t.sol diff --git a/.changeset/beige-sails-worry.md b/.changeset/beige-sails-worry.md new file mode 100644 index 0000000000..a49ba48448 --- /dev/null +++ b/.changeset/beige-sails-worry.md @@ -0,0 +1,2 @@ +--- +--- \ No newline at end of file diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 0ce15962c5..360fef12d5 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1,11 +1,11 @@ -ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5839098, ~: 5347535) +ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5862630, ~: 5360703) ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 15501257) -ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38649064) -ExaPluginTest:testFork_collectCollateral_collects() (gas: 32148122) -ExaPluginTest:testFork_crossRepay_repays() (gas: 33970276) -ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25585110) -ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32727092) -ExaPluginTest:testFork_swap_swaps() (gas: 28088327) +ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38655300) +ExaPluginTest:testFork_collectCollateral_collects() (gas: 32153956) +ExaPluginTest:testFork_crossRepay_repays() (gas: 33976110) +ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25587509) +ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32733097) +ExaPluginTest:testFork_swap_swaps() (gas: 28093630) ExaPluginTest:test_allowPlugin_emitsPluginAllowed() (gas: 58464) ExaPluginTest:test_allowPlugin_reverts_whenAddressZero() (gas: 32411) ExaPluginTest:test_allowPlugin_reverts_whenNotAdmin() (gas: 40964) @@ -32,12 +32,12 @@ ExaPluginTest:test_collectCredit_collects_whenHealthFactorHigherThanOne() (gas: ExaPluginTest:test_collectCredit_collects_whenProposalCausesInsufficientLiquidity() (gas: 1064982) ExaPluginTest:test_collectCredit_collects_whenProposalLeavesHealthFactorLowerThanOne() (gas: 1055379) ExaPluginTest:test_collectCredit_collects_withEnoughSlippage() (gas: 782246) -ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 986741) +ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 986858) ExaPluginTest:test_collectCredit_passes_whenProposalLeavesEnoughLiquidity() (gas: 1082579) ExaPluginTest:test_collectCredit_reverts_asNotKeeper() (gas: 342148) ExaPluginTest:test_collectCredit_reverts_whenDisagreement() (gas: 538817) ExaPluginTest:test_collectCredit_reverts_whenExpired() (gas: 352767) -ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483654) +ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483771) ExaPluginTest:test_collectCredit_reverts_whenReplay() (gas: 825827) ExaPluginTest:test_collectCredit_reverts_whenTimelocked() (gas: 334568) ExaPluginTest:test_collectCredit_toleratesTimeDrift() (gas: 798846) @@ -58,7 +58,7 @@ ExaPluginTest:test_collectInstallments_reverts_whenTimelocked() (gas: 336703) ExaPluginTest:test_collectInstallments_toleratesTimeDrift() (gas: 1161587) ExaPluginTest:test_collect_collects_whenProposalsLeaveNoLiquidity() (gas: 1783636) ExaPluginTest:test_collect_collects_whenTooMuchProposedDebt() (gas: 1923835) -ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 980825) +ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 980941) ExaPluginTest:test_crossRepay_avoidsZeroSharesDeposit() (gas: 1392815) ExaPluginTest:test_crossRepay_consumesProposal() (gas: 2204416) ExaPluginTest:test_crossRepay_repays() (gas: 2268206) @@ -202,6 +202,10 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437 ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447) ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) +HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124347) +HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055313) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22244215) +HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229885) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) IssuerCheckerTest:test_setIssuer_reverts_whenZeroAddress() (gas: 35130) @@ -212,20 +216,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 55268709) -RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 15868804) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 31111328) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 260295770) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 355860463) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 34302845) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 15892721) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 31658476) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 19385733) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 15897009) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 30601997) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 34474337) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 38611896) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 43616725) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58748506) +RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19319780) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34564251) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263743488) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359313625) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37755839) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19343859) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35111610) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22839761) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19348187) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34055156) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37927379) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42098042) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50514963) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/deploy.json b/contracts/deploy.json index b920373dc6..90666c9730 100644 --- a/contracts/deploy.json +++ b/contracts/deploy.json @@ -34,6 +34,10 @@ "11155420": "0xe2D63e18Fb136ef84557f79052BFE8EFa90F69Bd", "default": "0xe61Bdef3FFF4C3CF7A07996DCB8802b5C85B665a" }, + "mailbox": { + "10": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", + "8453": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + }, "swapper": { "84532": "0xE4F8C9904a79EBd88Ff73ed3F86a3CFd1b489eDf", "11155420": "0x3E2D4b69C52932CB5b2a9Ee744CB585bb201c771", diff --git a/contracts/script/Redeployer.s.sol b/contracts/script/Redeployer.s.sol index f830d53c5f..44f56f8b97 100644 --- a/contracts/script/Redeployer.s.sol +++ b/contracts/script/Redeployer.s.sol @@ -16,6 +16,9 @@ import { ACCOUNT_IMPL, ENTRYPOINT } from "webauthn-owner-plugin/../script/Factor import { EXA } from "@exactly/protocol/periphery/EXA.sol"; +import { HypERC20Collateral } from "@hyperlane-xyz/core/contracts/token/HypERC20Collateral.sol"; +import { HypXERC20 } from "@hyperlane-xyz/core/contracts/token/extensions/HypXERC20.sol"; + import { ExaAccountFactory } from "../src/ExaAccountFactory.sol"; import { IAuditor, @@ -188,9 +191,59 @@ contract Redeployer is BaseScript { /// @notice Deploys EXA token and upgrades the proxy to it. function deployEXA(address proxy) external { - vm.startBroadcast(acct("admin")); + address admin = acct("admin"); + vm.startBroadcast(admin); exa = EXA(CREATE3_FACTORY.deploy(keccak256(abi.encode("EXA")), vm.getCode("EXA.sol:EXA"))); proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize, ())); + proxyAdmin.upgradeAndCall( + ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin)) + ); + vm.stopBroadcast(); + } + + /// @notice Deploys the latest EXA implementation via CREATE3. + function deployEXAImpl() external { + vm.broadcast(acct("admin")); + exa = EXA(CREATE3_FACTORY.deploy(keccak256(abi.encode("EXA")), vm.getCode("EXA.sol:EXA"))); + } + + /// @notice Upgrades an existing EXA proxy to the latest implementation. + function upgradeEXA(address proxy) external { + address admin = acct("admin"); + ProxyAdmin p = ProxyAdmin(address(uint160(uint256(vm.load(proxy, ERC1967Utils.ADMIN_SLOT))))); + vm.broadcast(p.owner()); + p.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin))); + } + + function deployRouter(address token) external returns (HypXERC20 router) { + if (address(proxyAdmin).code.length == 0) revert ProxyAdminNotDeployed(); + address admin = acct("admin"); + router = HypXERC20(CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")))); + if (address(router).code.length != 0) return router; + vm.startBroadcast(admin); + router = HypXERC20( + CREATE3_FACTORY.deploy( + keccak256(abi.encode("HypEXA")), + abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + address(new HypXERC20(token, 1, 1, acct("mailbox"))), + address(proxyAdmin), + abi.encodeCall(HypERC20Collateral.initialize, (address(0), address(0), admin)) + ) + ) + ) + ); + vm.stopBroadcast(); + } + + function setupRouter(address token, uint32 remoteDomain) external { + address admin = acct("admin"); + address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA"))); + if (router.code.length == 0) revert RouterNotDeployed(); + vm.startBroadcast(admin); + if (!EXA(token).hasRole(keccak256("BRIDGE_ROLE"), router)) EXA(token).grantRole(keccak256("BRIDGE_ROLE"), router); + HypXERC20(router).enrollRemoteRouter(remoteDomain, bytes32(uint256(uint160(router)))); vm.stopBroadcast(); } @@ -285,6 +338,7 @@ error DummyNotDeployed(); error NonceNotFound(); error NotPrepared(); error ProxyAdminNotDeployed(); +error RouterNotDeployed(); error TargetNonceTooLow(); contract Dummy { } // solhint-disable-line no-empty-blocks diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol new file mode 100644 index 0000000000..6de497a936 --- /dev/null +++ b/contracts/test/HypEXA.t.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import { EXA } from "@exactly/protocol/periphery/EXA.sol"; +import { TypeCasts } from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; +import { HypXERC20 } from "@hyperlane-xyz/core/contracts/token/extensions/HypXERC20.sol"; + +import { Redeployer, RouterNotDeployed } from "../script/Redeployer.s.sol"; +import { ForkTest } from "./Fork.t.sol"; + +contract HypEXATest is ForkTest { + using TypeCasts for address; + + uint256 internal opFork; + uint256 internal baseFork; + HypXERC20 internal opRouter; + HypXERC20 internal baseRouter; + address internal admin; + address internal opMailbox; + address internal baseMailbox; + EXA internal exa = EXA(0x1e925De1c68ef83bD98eE3E130eF14a50309C01B); + address internal exaHolder = 0x92024C4bDa9DA602b711B9AbB610d072018eb58b; + + uint32 internal constant OP_DOMAIN = 10; + uint32 internal constant BASE_DOMAIN = 8453; + + function setUp() external { + baseFork = vm.createSelectFork("base", 42_380_000); + baseMailbox = acct("mailbox"); + Redeployer baseRedeployer = new Redeployer(); + baseRedeployer.setUp(); + if (address(baseRedeployer.proxyAdmin()).code.length == 0) baseRedeployer.prepare(); + baseRedeployer.proxyThrough(baseRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); + baseRedeployer.deployEXA(address(exa)); + baseRouter = baseRedeployer.deployRouter(address(exa)); + baseRedeployer.setupRouter(address(exa), OP_DOMAIN); + + opFork = vm.createSelectFork("optimism", 147_967_000); + opMailbox = acct("mailbox"); + admin = acct("admin"); + Redeployer opRedeployer = new Redeployer(); + opRedeployer.setUp(); + opRedeployer.prepare(); + opRedeployer.deployEXAImpl(); + opRedeployer.upgradeEXA(address(exa)); + opRouter = opRedeployer.deployRouter(address(exa)); + opRedeployer.setupRouter(address(exa), BASE_DOMAIN); + } + + // solhint-disable func-name-mixedcase + + function test_roundTrip_opToBaseToOp() external { + address receiver = makeAddr("receiver"); + uint256 amount = 100e18; + uint256 opSupply = exa.totalSupply(); + + uint256 fee = opRouter.quoteGasPayment(BASE_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + opRouter.transferRemote{ value: fee }(BASE_DOMAIN, exaHolder.addressToBytes32(), amount); + assertEq(exa.totalSupply(), opSupply - amount, "op didn't burn"); + + vm.selectFork(baseFork); + vm.prank(baseMailbox); + baseRouter.handle( + OP_DOMAIN, address(opRouter).addressToBytes32(), abi.encodePacked(exaHolder.addressToBytes32(), amount) + ); + assertEq(exa.balanceOf(exaHolder), amount, "base didn't credit holder"); + assertEq(exa.totalSupply(), amount, "base didn't mint"); + + fee = baseRouter.quoteGasPayment(OP_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + baseRouter.transferRemote{ value: fee }(OP_DOMAIN, receiver.addressToBytes32(), amount); + assertEq(exa.totalSupply(), 0, "base didn't burn"); + + vm.selectFork(opFork); + vm.prank(opMailbox); + opRouter.handle( + BASE_DOMAIN, address(baseRouter).addressToBytes32(), abi.encodePacked(receiver.addressToBytes32(), amount) + ); + assertEq(exa.balanceOf(receiver), amount, "op didn't credit receiver"); + assertEq(exa.totalSupply(), opSupply, "op didn't restore supply"); + } + + function test_transferRemote_reverts_withoutBridgeRole() external { + vm.prank(admin); + exa.revokeRole(keccak256("BRIDGE_ROLE"), address(opRouter)); + + uint256 fee = opRouter.quoteGasPayment(BASE_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + vm.expectRevert(); + opRouter.transferRemote{ value: fee }(BASE_DOMAIN, makeAddr("receiver").addressToBytes32(), 100e18); + } + + function test_handle_reverts_withoutBridgeRole() external { + vm.selectFork(baseFork); + vm.prank(admin); + exa.revokeRole(keccak256("BRIDGE_ROLE"), address(baseRouter)); + + vm.prank(baseMailbox); + vm.expectRevert(); + baseRouter.handle( + OP_DOMAIN, + address(opRouter).addressToBytes32(), + abi.encodePacked(makeAddr("receiver").addressToBytes32(), uint256(100e18)) + ); + } + + function test_setupRouter_reverts_whenRouterNotDeployed() external { + vm.createSelectFork("base", 42_380_001); + + Redeployer redeployer = new Redeployer(); + redeployer.setUp(); + + vm.expectRevert(RouterNotDeployed.selector); + redeployer.setupRouter(address(exa), OP_DOMAIN); + } + + // solhint-enable func-name-mixedcase +} diff --git a/cspell.json b/cspell.json index 54e7eee97a..4469467630 100644 --- a/cspell.json +++ b/cspell.json @@ -79,6 +79,7 @@ "hexlify", "hideable", "hono", + "hyperlane", "IBAN", "IERC", "indoc", @@ -190,6 +191,7 @@ "worklet", "worklets", "xcrun", + "XERC", "xhdpi", "xxhdpi", "xxxhdpi", From a1d7de81ced53e614b2f1f5792d348d6a7324c82 Mon Sep 17 00:00:00 2001 From: itofarina Date: Tue, 3 Mar 2026 15:59:03 -0300 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=85=20contracts:=20add=20polygon=20hy?= =?UTF-8?q?perlane=20bridge=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 53 ++++++++++++++++--------------- contracts/deploy.json | 1 + contracts/test/HypEXA.t.sol | 63 +++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 26 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 360fef12d5..70ceef80e6 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1,11 +1,11 @@ -ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5862630, ~: 5360703) +ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5887874, ~: 5393451) ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 15501257) -ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38655300) -ExaPluginTest:testFork_collectCollateral_collects() (gas: 32153956) -ExaPluginTest:testFork_crossRepay_repays() (gas: 33976110) -ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25587509) -ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32733097) -ExaPluginTest:testFork_swap_swaps() (gas: 28093630) +ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38658419) +ExaPluginTest:testFork_collectCollateral_collects() (gas: 32156873) +ExaPluginTest:testFork_crossRepay_repays() (gas: 33979027) +ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25588708) +ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32736100) +ExaPluginTest:testFork_swap_swaps() (gas: 28096281) ExaPluginTest:test_allowPlugin_emitsPluginAllowed() (gas: 58464) ExaPluginTest:test_allowPlugin_reverts_whenAddressZero() (gas: 32411) ExaPluginTest:test_allowPlugin_reverts_whenNotAdmin() (gas: 40964) @@ -32,12 +32,12 @@ ExaPluginTest:test_collectCredit_collects_whenHealthFactorHigherThanOne() (gas: ExaPluginTest:test_collectCredit_collects_whenProposalCausesInsufficientLiquidity() (gas: 1064982) ExaPluginTest:test_collectCredit_collects_whenProposalLeavesHealthFactorLowerThanOne() (gas: 1055379) ExaPluginTest:test_collectCredit_collects_withEnoughSlippage() (gas: 782246) -ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 986858) +ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 986917) ExaPluginTest:test_collectCredit_passes_whenProposalLeavesEnoughLiquidity() (gas: 1082579) ExaPluginTest:test_collectCredit_reverts_asNotKeeper() (gas: 342148) ExaPluginTest:test_collectCredit_reverts_whenDisagreement() (gas: 538817) ExaPluginTest:test_collectCredit_reverts_whenExpired() (gas: 352767) -ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483771) +ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483830) ExaPluginTest:test_collectCredit_reverts_whenReplay() (gas: 825827) ExaPluginTest:test_collectCredit_reverts_whenTimelocked() (gas: 334568) ExaPluginTest:test_collectCredit_toleratesTimeDrift() (gas: 798846) @@ -58,7 +58,7 @@ ExaPluginTest:test_collectInstallments_reverts_whenTimelocked() (gas: 336703) ExaPluginTest:test_collectInstallments_toleratesTimeDrift() (gas: 1161587) ExaPluginTest:test_collect_collects_whenProposalsLeaveNoLiquidity() (gas: 1783636) ExaPluginTest:test_collect_collects_whenTooMuchProposedDebt() (gas: 1923835) -ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 980941) +ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 981000) ExaPluginTest:test_crossRepay_avoidsZeroSharesDeposit() (gas: 1392815) ExaPluginTest:test_crossRepay_consumesProposal() (gas: 2204416) ExaPluginTest:test_crossRepay_repays() (gas: 2268206) @@ -202,9 +202,10 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437 ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447) ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) -HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124347) -HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055313) -HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22244215) +HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124362) +HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055291) +HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562365) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22245409) HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229885) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) @@ -216,20 +217,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58748506) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58749150) RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19319780) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34564251) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263743488) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359313625) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37755839) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19343859) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35111610) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22839761) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19348187) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34055156) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37927379) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42098042) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50514963) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34565281) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263744700) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359314753) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37756861) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19343918) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35112691) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22841221) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19348311) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34056251) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37928425) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42098621) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50515481) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/deploy.json b/contracts/deploy.json index 90666c9730..55084587b7 100644 --- a/contracts/deploy.json +++ b/contracts/deploy.json @@ -36,6 +36,7 @@ }, "mailbox": { "10": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", + "137": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "8453": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, "swapper": { diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol index 6de497a936..610e628faf 100644 --- a/contracts/test/HypEXA.t.sol +++ b/contracts/test/HypEXA.t.sol @@ -13,18 +13,33 @@ contract HypEXATest is ForkTest { uint256 internal opFork; uint256 internal baseFork; + uint256 internal polygonFork; HypXERC20 internal opRouter; HypXERC20 internal baseRouter; + HypXERC20 internal polygonRouter; address internal admin; address internal opMailbox; address internal baseMailbox; + address internal polygonMailbox; EXA internal exa = EXA(0x1e925De1c68ef83bD98eE3E130eF14a50309C01B); address internal exaHolder = 0x92024C4bDa9DA602b711B9AbB610d072018eb58b; uint32 internal constant OP_DOMAIN = 10; uint32 internal constant BASE_DOMAIN = 8453; + uint32 internal constant POLYGON_DOMAIN = 137; function setUp() external { + polygonFork = vm.createSelectFork("polygon", 83_700_000); + polygonMailbox = acct("mailbox"); + Redeployer polygonRedeployer = new Redeployer(); + polygonRedeployer.setUp(); + if (address(polygonRedeployer.proxyAdmin()).code.length == 0) polygonRedeployer.prepare(); + polygonRedeployer.proxyThrough(polygonRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); + polygonRedeployer.deployEXA(address(exa)); + polygonRouter = polygonRedeployer.deployRouter(address(exa)); + polygonRedeployer.setupRouter(address(exa), OP_DOMAIN); + polygonRedeployer.setupRouter(address(exa), BASE_DOMAIN); + baseFork = vm.createSelectFork("base", 42_380_000); baseMailbox = acct("mailbox"); Redeployer baseRedeployer = new Redeployer(); @@ -34,6 +49,7 @@ contract HypEXATest is ForkTest { baseRedeployer.deployEXA(address(exa)); baseRouter = baseRedeployer.deployRouter(address(exa)); baseRedeployer.setupRouter(address(exa), OP_DOMAIN); + baseRedeployer.setupRouter(address(exa), POLYGON_DOMAIN); opFork = vm.createSelectFork("optimism", 147_967_000); opMailbox = acct("mailbox"); @@ -45,6 +61,7 @@ contract HypEXATest is ForkTest { opRedeployer.upgradeEXA(address(exa)); opRouter = opRedeployer.deployRouter(address(exa)); opRedeployer.setupRouter(address(exa), BASE_DOMAIN); + opRedeployer.setupRouter(address(exa), POLYGON_DOMAIN); } // solhint-disable func-name-mixedcase @@ -83,6 +100,52 @@ contract HypEXATest is ForkTest { assertEq(exa.totalSupply(), opSupply, "op didn't restore supply"); } + function test_roundTrip_opToPolygonToBaseToOp() external { + uint256 amount = 100e18; + uint256 opSupply = exa.totalSupply(); + + uint256 fee = opRouter.quoteGasPayment(POLYGON_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + opRouter.transferRemote{ value: fee }(POLYGON_DOMAIN, exaHolder.addressToBytes32(), amount); + assertEq(exa.totalSupply(), opSupply - amount, "op didn't burn"); + + vm.selectFork(polygonFork); + uint256 polygonSupply = exa.totalSupply(); + vm.prank(polygonMailbox); + polygonRouter.handle( + OP_DOMAIN, address(opRouter).addressToBytes32(), abi.encodePacked(exaHolder.addressToBytes32(), amount) + ); + assertEq(exa.totalSupply(), polygonSupply + amount, "polygon didn't mint"); + + fee = polygonRouter.quoteGasPayment(BASE_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + polygonRouter.transferRemote{ value: fee }(BASE_DOMAIN, exaHolder.addressToBytes32(), amount); + assertEq(exa.totalSupply(), polygonSupply, "polygon didn't burn"); + + vm.selectFork(baseFork); + uint256 baseSupply = exa.totalSupply(); + vm.prank(baseMailbox); + baseRouter.handle( + POLYGON_DOMAIN, address(polygonRouter).addressToBytes32(), abi.encodePacked(exaHolder.addressToBytes32(), amount) + ); + assertEq(exa.totalSupply(), baseSupply + amount, "base didn't mint"); + + fee = baseRouter.quoteGasPayment(OP_DOMAIN); + vm.deal(exaHolder, fee); + vm.prank(exaHolder); + baseRouter.transferRemote{ value: fee }(OP_DOMAIN, exaHolder.addressToBytes32(), amount); + assertEq(exa.totalSupply(), baseSupply, "base didn't burn"); + + vm.selectFork(opFork); + vm.prank(opMailbox); + opRouter.handle( + BASE_DOMAIN, address(baseRouter).addressToBytes32(), abi.encodePacked(exaHolder.addressToBytes32(), amount) + ); + assertEq(exa.totalSupply(), opSupply, "op didn't restore supply"); + } + function test_transferRemote_reverts_withoutBridgeRole() external { vm.prank(admin); exa.revokeRole(keccak256("BRIDGE_ROLE"), address(opRouter)); From 9b10a25e784cb5ef87bb63ef343bf6c2cc6741ff Mon Sep 17 00:00:00 2001 From: itofarina Date: Fri, 17 Apr 2026 13:51:12 -0300 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=94=A8=20contracts:=20split=20hyperla?= =?UTF-8?q?ne=20router=20ownership=20and=20upgrader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 52 +++++++++++++++---------------- contracts/deploy.json | 4 +++ contracts/script/Redeployer.s.sol | 13 +++----- contracts/test/HypEXA.t.sol | 26 +++++++++++----- 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 70ceef80e6..93f4c90328 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -1,11 +1,11 @@ ExaAccountFactoryTest:testFuzz_createAccount_EOAOwners(uint256,address[63]) (runs: 256, μ: 5887874, ~: 5393451) ExaAccountFactoryTest:test_deploy_deploysToSameAddress() (gas: 15501257) -ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38658419) -ExaPluginTest:testFork_collectCollateral_collects() (gas: 32156873) -ExaPluginTest:testFork_crossRepay_repays() (gas: 33979027) -ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25588708) -ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32736100) -ExaPluginTest:testFork_swap_swaps() (gas: 28096281) +ExaPluginTest:testFork_claimAndVestEscrowedEXA_claimsAndVests() (gas: 38666217) +ExaPluginTest:testFork_collectCollateral_collects() (gas: 32164167) +ExaPluginTest:testFork_crossRepay_repays() (gas: 33986321) +ExaPluginTest:testFork_repay_whenFlashLoanerHasFees() (gas: 25591707) +ExaPluginTest:testFork_stakeEXA_stakes() (gas: 32743609) +ExaPluginTest:testFork_swap_swaps() (gas: 28102911) ExaPluginTest:test_allowPlugin_emitsPluginAllowed() (gas: 58464) ExaPluginTest:test_allowPlugin_reverts_whenAddressZero() (gas: 32411) ExaPluginTest:test_allowPlugin_reverts_whenNotAdmin() (gas: 40964) @@ -32,12 +32,12 @@ ExaPluginTest:test_collectCredit_collects_whenHealthFactorHigherThanOne() (gas: ExaPluginTest:test_collectCredit_collects_whenProposalCausesInsufficientLiquidity() (gas: 1064982) ExaPluginTest:test_collectCredit_collects_whenProposalLeavesHealthFactorLowerThanOne() (gas: 1055379) ExaPluginTest:test_collectCredit_collects_withEnoughSlippage() (gas: 782246) -ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 986917) +ExaPluginTest:test_collectCredit_collects_withPrevIssuerSignature() (gas: 987065) ExaPluginTest:test_collectCredit_passes_whenProposalLeavesEnoughLiquidity() (gas: 1082579) ExaPluginTest:test_collectCredit_reverts_asNotKeeper() (gas: 342148) ExaPluginTest:test_collectCredit_reverts_whenDisagreement() (gas: 538817) ExaPluginTest:test_collectCredit_reverts_whenExpired() (gas: 352767) -ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483830) +ExaPluginTest:test_collectCredit_reverts_whenPrevSignatureNotValidAnymore() (gas: 483978) ExaPluginTest:test_collectCredit_reverts_whenReplay() (gas: 825827) ExaPluginTest:test_collectCredit_reverts_whenTimelocked() (gas: 334568) ExaPluginTest:test_collectCredit_toleratesTimeDrift() (gas: 798846) @@ -58,7 +58,7 @@ ExaPluginTest:test_collectInstallments_reverts_whenTimelocked() (gas: 336703) ExaPluginTest:test_collectInstallments_toleratesTimeDrift() (gas: 1161587) ExaPluginTest:test_collect_collects_whenProposalsLeaveNoLiquidity() (gas: 1783636) ExaPluginTest:test_collect_collects_whenTooMuchProposedDebt() (gas: 1923835) -ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 981000) +ExaPluginTest:test_crossRepay_avoidsFrozenDeposit() (gas: 981146) ExaPluginTest:test_crossRepay_avoidsZeroSharesDeposit() (gas: 1392815) ExaPluginTest:test_crossRepay_consumesProposal() (gas: 2204416) ExaPluginTest:test_crossRepay_repays() (gas: 2268206) @@ -202,11 +202,11 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437 ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447) ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) -HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124362) +HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124391) HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055291) HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562365) -HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22245409) -HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229885) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22202891) +HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229914) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) IssuerCheckerTest:test_setIssuer_reverts_whenZeroAddress() (gas: 35130) @@ -217,20 +217,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58749150) -RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19319780) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34565281) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263744700) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359314753) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37756861) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19343918) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35112691) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22841221) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19348311) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34056251) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37928425) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42098621) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50515481) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58707918) +RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19276842) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34524973) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263704898) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359274666) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37716508) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19301127) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35072507) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22801806) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19305684) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34015952) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37888132) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42057129) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50430927) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/deploy.json b/contracts/deploy.json index 55084587b7..b8a6950b6f 100644 --- a/contracts/deploy.json +++ b/contracts/deploy.json @@ -12,6 +12,10 @@ "11155420": "0xDb90CDB64CfF03f254e4015C4F705C3F3C834400", "default": "0xe61Bdef3FFF4C3CF7A07996DCB8802b5C85B665a" }, + "exactly": { + "10": "0xC0d6Bc5d052d1e74523AD79dD5A954276c9286D3", + "8453": "0x7A65824d74B0C20730B6eE4929ABcc41Cbe843Aa" + }, "keeper": { "31337": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "84532": "0xEbFa3f3306B2F62400D644714A20F6eF32c63C6B", diff --git a/contracts/script/Redeployer.s.sol b/contracts/script/Redeployer.s.sol index 44f56f8b97..6576f9b536 100644 --- a/contracts/script/Redeployer.s.sol +++ b/contracts/script/Redeployer.s.sol @@ -216,7 +216,6 @@ contract Redeployer is BaseScript { } function deployRouter(address token) external returns (HypXERC20 router) { - if (address(proxyAdmin).code.length == 0) revert ProxyAdminNotDeployed(); address admin = acct("admin"); router = HypXERC20(CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")))); if (address(router).code.length != 0) return router; @@ -228,23 +227,21 @@ contract Redeployer is BaseScript { type(TransparentUpgradeableProxy).creationCode, abi.encode( address(new HypXERC20(token, 1, 1, acct("mailbox"))), - address(proxyAdmin), + protocol("ProxyAdmin"), abi.encodeCall(HypERC20Collateral.initialize, (address(0), address(0), admin)) ) ) ) ); + router.transferOwnership(acct("exactly")); vm.stopBroadcast(); } - function setupRouter(address token, uint32 remoteDomain) external { - address admin = acct("admin"); - address router = CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA"))); + function setupRouter(uint32 remoteDomain) external { + address router = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("HypEXA"))); if (router.code.length == 0) revert RouterNotDeployed(); - vm.startBroadcast(admin); - if (!EXA(token).hasRole(keccak256("BRIDGE_ROLE"), router)) EXA(token).grantRole(keccak256("BRIDGE_ROLE"), router); + vm.broadcast(acct("exactly")); HypXERC20(router).enrollRemoteRouter(remoteDomain, bytes32(uint256(uint160(router)))); - vm.stopBroadcast(); } /// @notice Upgrades a proxy to the cached ExaAccountFactory implementation. diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol index 610e628faf..2fac4f24bf 100644 --- a/contracts/test/HypEXA.t.sol +++ b/contracts/test/HypEXA.t.sol @@ -36,9 +36,17 @@ contract HypEXATest is ForkTest { if (address(polygonRedeployer.proxyAdmin()).code.length == 0) polygonRedeployer.prepare(); polygonRedeployer.proxyThrough(polygonRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); polygonRedeployer.deployEXA(address(exa)); + set("exactly", makeAddr("exactly")); // no exactly on polygon — test-only chain + set("ProxyAdmin", address(polygonRedeployer.proxyAdmin())); // no protocol deployment on polygon polygonRouter = polygonRedeployer.deployRouter(address(exa)); - polygonRedeployer.setupRouter(address(exa), OP_DOMAIN); - polygonRedeployer.setupRouter(address(exa), BASE_DOMAIN); + unset("ProxyAdmin"); + unset("exactly"); + vm.prank(acct("admin")); + exa.grantRole(keccak256("BRIDGE_ROLE"), address(polygonRouter)); + set("exactly", makeAddr("exactly")); + polygonRedeployer.setupRouter(OP_DOMAIN); + polygonRedeployer.setupRouter(BASE_DOMAIN); + unset("exactly"); baseFork = vm.createSelectFork("base", 42_380_000); baseMailbox = acct("mailbox"); @@ -48,8 +56,10 @@ contract HypEXATest is ForkTest { baseRedeployer.proxyThrough(baseRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); baseRedeployer.deployEXA(address(exa)); baseRouter = baseRedeployer.deployRouter(address(exa)); - baseRedeployer.setupRouter(address(exa), OP_DOMAIN); - baseRedeployer.setupRouter(address(exa), POLYGON_DOMAIN); + vm.prank(acct("admin")); + exa.grantRole(keccak256("BRIDGE_ROLE"), address(baseRouter)); + baseRedeployer.setupRouter(OP_DOMAIN); + baseRedeployer.setupRouter(POLYGON_DOMAIN); opFork = vm.createSelectFork("optimism", 147_967_000); opMailbox = acct("mailbox"); @@ -60,8 +70,10 @@ contract HypEXATest is ForkTest { opRedeployer.deployEXAImpl(); opRedeployer.upgradeEXA(address(exa)); opRouter = opRedeployer.deployRouter(address(exa)); - opRedeployer.setupRouter(address(exa), BASE_DOMAIN); - opRedeployer.setupRouter(address(exa), POLYGON_DOMAIN); + vm.prank(admin); + exa.grantRole(keccak256("BRIDGE_ROLE"), address(opRouter)); + opRedeployer.setupRouter(BASE_DOMAIN); + opRedeployer.setupRouter(POLYGON_DOMAIN); } // solhint-disable func-name-mixedcase @@ -178,7 +190,7 @@ contract HypEXATest is ForkTest { redeployer.setUp(); vm.expectRevert(RouterNotDeployed.selector); - redeployer.setupRouter(address(exa), OP_DOMAIN); + redeployer.setupRouter(OP_DOMAIN); } // solhint-enable func-name-mixedcase From 80d3ee3079bff2c650d615d29cf1be7d78265492 Mon Sep 17 00:00:00 2001 From: itofarina Date: Fri, 17 Apr 2026 16:59:19 -0300 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=94=A7=20contracts:=20add=20propose?= =?UTF-8?q?=20bridge=20role=20to=20router=20on=20redeployer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 41 +++++++++++++++------------- contracts/script/Redeployer.s.sol | 15 +++++++++++ contracts/test/HypEXA.t.sol | 45 ++++++++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 93f4c90328..942a5b8160 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -202,11 +202,14 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437 ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447) ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) -HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124391) -HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055291) -HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562365) -HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22202891) -HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229914) +HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124347) +HypEXATest:test_proposeBridgeRole_reverts_whenAlreadyGranted() (gas: 71218) +HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 22453196) +HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 1146892) +HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055313) +HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562388) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22450279) +HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229936) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) IssuerCheckerTest:test_setIssuer_reverts_whenZeroAddress() (gas: 35130) @@ -217,20 +220,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58707918) -RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19276842) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34524973) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263704898) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359274666) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37716508) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19301127) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35072507) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22801806) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19305684) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34015952) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 37888132) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42057129) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50430927) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58954868) +RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19524219) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34772372) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263951848) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359522043) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37963863) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19548482) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35319862) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 23049249) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19553039) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34263351) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 38135487) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42304587) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50925291) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/script/Redeployer.s.sol b/contracts/script/Redeployer.s.sol index 6576f9b536..62b74f8657 100644 --- a/contracts/script/Redeployer.s.sol +++ b/contracts/script/Redeployer.s.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import { TransparentUpgradeableProxy } from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; +import { TimelockController } from "openzeppelin-contracts/contracts/governance/TimelockController.sol"; import { ERC1967Utils } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol"; import { ProxyAdmin } from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; import { @@ -244,6 +246,18 @@ contract Redeployer is BaseScript { HypXERC20(router).enrollRemoteRouter(remoteDomain, bytes32(uint256(uint160(router)))); } + function proposeBridgeRole(address token, bytes32 salt) external { + address router = CREATE3_FACTORY.getDeployed(acct("admin"), keccak256(abi.encode("HypEXA"))); + if (router.code.length == 0) revert RouterNotDeployed(); + if (IAccessControl(token).hasRole(keccak256("BRIDGE_ROLE"), router)) revert AlreadyGranted(); + TimelockController timelock = TimelockController(payable(protocol("TimelockController"))); + uint256 delay = timelock.getMinDelay(); + vm.broadcast(acct("deployer")); + timelock.schedule( + token, 0, abi.encodeCall(IAccessControl.grantRole, (keccak256("BRIDGE_ROLE"), router)), bytes32(0), salt, delay + ); + } + /// @notice Upgrades a proxy to the cached ExaAccountFactory implementation. function deployExaFactory(address proxy) external { if (address(factory).code.length == 0) revert NotPrepared(); @@ -331,6 +345,7 @@ contract Redeployer is BaseScript { } error AdminIsDeployer(); +error AlreadyGranted(); error DummyNotDeployed(); error NonceNotFound(); error NotPrepared(); diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol index 2fac4f24bf..2f49da0536 100644 --- a/contracts/test/HypEXA.t.sol +++ b/contracts/test/HypEXA.t.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.0; +import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; +import { TimelockController } from "openzeppelin-contracts/contracts/governance/TimelockController.sol"; + import { EXA } from "@exactly/protocol/periphery/EXA.sol"; import { TypeCasts } from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; import { HypXERC20 } from "@hyperlane-xyz/core/contracts/token/extensions/HypXERC20.sol"; -import { Redeployer, RouterNotDeployed } from "../script/Redeployer.s.sol"; +import { AlreadyGranted, Redeployer, RouterNotDeployed } from "../script/Redeployer.s.sol"; import { ForkTest } from "./Fork.t.sol"; contract HypEXATest is ForkTest { @@ -17,6 +20,7 @@ contract HypEXATest is ForkTest { HypXERC20 internal opRouter; HypXERC20 internal baseRouter; HypXERC20 internal polygonRouter; + Redeployer internal opRedeployer; address internal admin; address internal opMailbox; address internal baseMailbox; @@ -40,10 +44,8 @@ contract HypEXATest is ForkTest { set("ProxyAdmin", address(polygonRedeployer.proxyAdmin())); // no protocol deployment on polygon polygonRouter = polygonRedeployer.deployRouter(address(exa)); unset("ProxyAdmin"); - unset("exactly"); vm.prank(acct("admin")); exa.grantRole(keccak256("BRIDGE_ROLE"), address(polygonRouter)); - set("exactly", makeAddr("exactly")); polygonRedeployer.setupRouter(OP_DOMAIN); polygonRedeployer.setupRouter(BASE_DOMAIN); unset("exactly"); @@ -64,7 +66,7 @@ contract HypEXATest is ForkTest { opFork = vm.createSelectFork("optimism", 147_967_000); opMailbox = acct("mailbox"); admin = acct("admin"); - Redeployer opRedeployer = new Redeployer(); + opRedeployer = new Redeployer(); opRedeployer.setUp(); opRedeployer.prepare(); opRedeployer.deployEXAImpl(); @@ -193,5 +195,40 @@ contract HypEXATest is ForkTest { redeployer.setupRouter(OP_DOMAIN); } + function test_proposeBridgeRole_reverts_whenRouterNotDeployed() external { + vm.createSelectFork("base", 42_380_001); + + Redeployer redeployer = new Redeployer(); + redeployer.setUp(); + + vm.expectRevert(RouterNotDeployed.selector); + redeployer.proposeBridgeRole(address(exa), keccak256("HypEXA.BRIDGE_ROLE")); + } + + function test_proposeBridgeRole_schedulesGrantOnTimelock() external { + vm.selectFork(opFork); + vm.prank(admin); + exa.revokeRole(keccak256("BRIDGE_ROLE"), address(opRouter)); + + bytes32 salt = keccak256("HypEXA.BRIDGE_ROLE"); + opRedeployer.proposeBridgeRole(address(exa), salt); + + TimelockController timelock = TimelockController(payable(protocol("TimelockController"))); + bytes32 id = timelock.hashOperation( + address(exa), + 0, + abi.encodeCall(IAccessControl.grantRole, (keccak256("BRIDGE_ROLE"), address(opRouter))), + bytes32(0), + salt + ); + assertTrue(timelock.isOperationPending(id), "grant not scheduled"); + } + + function test_proposeBridgeRole_reverts_whenAlreadyGranted() external { + vm.selectFork(opFork); + vm.expectRevert(AlreadyGranted.selector); + opRedeployer.proposeBridgeRole(address(exa), keccak256("HypEXA.BRIDGE_ROLE")); + } + // solhint-enable func-name-mixedcase } From 12b8d8126f97c004420697bd33b3668f370e97c8 Mon Sep 17 00:00:00 2001 From: itofarina Date: Fri, 24 Apr 2026 13:05:48 -0300 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=94=A8=20contracts:=20use=20exactly?= =?UTF-8?q?=20as=20exa=20default=20admin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 38 +++++++++++++++---------------- contracts/script/Redeployer.s.sol | 10 ++++---- contracts/test/HypEXA.t.sol | 16 ++++++------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 942a5b8160..87cdf1a3e9 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -202,14 +202,14 @@ ExaPreviewerTest:test_collect_reverts_whenProposalsLeaveNoLiquidity() (gas: 1437 ExaPreviewerTest:test_markets_returnsMarkets() (gas: 150447) ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) -HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 124347) +HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 136161) HypEXATest:test_proposeBridgeRole_reverts_whenAlreadyGranted() (gas: 71218) -HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 22453196) -HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 1146892) +HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 22469037) +HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 1183670) HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055313) HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562388) -HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22450279) -HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 229936) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22466120) +HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 241639) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) IssuerCheckerTest:test_setIssuer_reverts_whenZeroAddress() (gas: 35130) @@ -220,20 +220,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58954868) -RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19524219) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34772372) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263951848) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359522043) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37963863) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19548482) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35319862) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 23049249) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19553039) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34263351) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 38135487) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42304587) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50925291) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58985047) +RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19540060) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34788213) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263967652) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359537884) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37979704) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19564323) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35335703) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 23065090) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19568880) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34279192) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 38151328) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42334809) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50956942) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/script/Redeployer.s.sol b/contracts/script/Redeployer.s.sol index 62b74f8657..c26e96976b 100644 --- a/contracts/script/Redeployer.s.sol +++ b/contracts/script/Redeployer.s.sol @@ -193,12 +193,11 @@ contract Redeployer is BaseScript { /// @notice Deploys EXA token and upgrades the proxy to it. function deployEXA(address proxy) external { - address admin = acct("admin"); - vm.startBroadcast(admin); + vm.startBroadcast(acct("admin")); exa = EXA(CREATE3_FACTORY.deploy(keccak256(abi.encode("EXA")), vm.getCode("EXA.sol:EXA"))); proxyAdmin.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize, ())); proxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin)) + ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (acct("exactly"))) ); vm.stopBroadcast(); } @@ -211,10 +210,11 @@ contract Redeployer is BaseScript { /// @notice Upgrades an existing EXA proxy to the latest implementation. function upgradeEXA(address proxy) external { - address admin = acct("admin"); ProxyAdmin p = ProxyAdmin(address(uint160(uint256(vm.load(proxy, ERC1967Utils.ADMIN_SLOT))))); vm.broadcast(p.owner()); - p.upgradeAndCall(ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (admin))); + p.upgradeAndCall( + ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (acct("exactly"))) + ); } function deployRouter(address token) external returns (HypXERC20 router) { diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol index 2f49da0536..4c114c668c 100644 --- a/contracts/test/HypEXA.t.sol +++ b/contracts/test/HypEXA.t.sol @@ -21,7 +21,6 @@ contract HypEXATest is ForkTest { HypXERC20 internal baseRouter; HypXERC20 internal polygonRouter; Redeployer internal opRedeployer; - address internal admin; address internal opMailbox; address internal baseMailbox; address internal polygonMailbox; @@ -39,12 +38,12 @@ contract HypEXATest is ForkTest { polygonRedeployer.setUp(); if (address(polygonRedeployer.proxyAdmin()).code.length == 0) polygonRedeployer.prepare(); polygonRedeployer.proxyThrough(polygonRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); - polygonRedeployer.deployEXA(address(exa)); set("exactly", makeAddr("exactly")); // no exactly on polygon — test-only chain set("ProxyAdmin", address(polygonRedeployer.proxyAdmin())); // no protocol deployment on polygon + polygonRedeployer.deployEXA(address(exa)); polygonRouter = polygonRedeployer.deployRouter(address(exa)); unset("ProxyAdmin"); - vm.prank(acct("admin")); + vm.prank(makeAddr("exactly")); exa.grantRole(keccak256("BRIDGE_ROLE"), address(polygonRouter)); polygonRedeployer.setupRouter(OP_DOMAIN); polygonRedeployer.setupRouter(BASE_DOMAIN); @@ -58,21 +57,20 @@ contract HypEXATest is ForkTest { baseRedeployer.proxyThrough(baseRedeployer.findNonce(acct("deployer"), address(exa), 1000) + 1); baseRedeployer.deployEXA(address(exa)); baseRouter = baseRedeployer.deployRouter(address(exa)); - vm.prank(acct("admin")); + vm.prank(acct("exactly")); exa.grantRole(keccak256("BRIDGE_ROLE"), address(baseRouter)); baseRedeployer.setupRouter(OP_DOMAIN); baseRedeployer.setupRouter(POLYGON_DOMAIN); opFork = vm.createSelectFork("optimism", 147_967_000); opMailbox = acct("mailbox"); - admin = acct("admin"); opRedeployer = new Redeployer(); opRedeployer.setUp(); opRedeployer.prepare(); opRedeployer.deployEXAImpl(); opRedeployer.upgradeEXA(address(exa)); opRouter = opRedeployer.deployRouter(address(exa)); - vm.prank(admin); + vm.prank(acct("exactly")); exa.grantRole(keccak256("BRIDGE_ROLE"), address(opRouter)); opRedeployer.setupRouter(BASE_DOMAIN); opRedeployer.setupRouter(POLYGON_DOMAIN); @@ -161,7 +159,7 @@ contract HypEXATest is ForkTest { } function test_transferRemote_reverts_withoutBridgeRole() external { - vm.prank(admin); + vm.prank(acct("exactly")); exa.revokeRole(keccak256("BRIDGE_ROLE"), address(opRouter)); uint256 fee = opRouter.quoteGasPayment(BASE_DOMAIN); @@ -173,7 +171,7 @@ contract HypEXATest is ForkTest { function test_handle_reverts_withoutBridgeRole() external { vm.selectFork(baseFork); - vm.prank(admin); + vm.prank(acct("exactly")); exa.revokeRole(keccak256("BRIDGE_ROLE"), address(baseRouter)); vm.prank(baseMailbox); @@ -207,7 +205,7 @@ contract HypEXATest is ForkTest { function test_proposeBridgeRole_schedulesGrantOnTimelock() external { vm.selectFork(opFork); - vm.prank(admin); + vm.prank(acct("exactly")); exa.revokeRole(keccak256("BRIDGE_ROLE"), address(opRouter)); bytes32 salt = keccak256("HypEXA.BRIDGE_ROLE"); From 4bfb4412e65594e8a8db5e959cc7ca43b9e178df Mon Sep 17 00:00:00 2001 From: itofarina Date: Fri, 24 Apr 2026 13:38:50 -0300 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=94=A5=20contracts:=20drop=20upgrade?= =?UTF-8?q?=20exa=20function=20from=20redeployer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/.gas-snapshot | 32 +++++++++++++++---------------- contracts/script/Redeployer.s.sol | 9 --------- contracts/test/HypEXA.t.sol | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/contracts/.gas-snapshot b/contracts/.gas-snapshot index 87cdf1a3e9..54d322c3a7 100644 --- a/contracts/.gas-snapshot +++ b/contracts/.gas-snapshot @@ -204,11 +204,11 @@ ExaPreviewerTest:test_pendingProposals_returnsPendingProposals() (gas: 1984093) ExaPreviewerTest:test_utilizations_returns() (gas: 134922) HypEXATest:test_handle_reverts_withoutBridgeRole() (gas: 136161) HypEXATest:test_proposeBridgeRole_reverts_whenAlreadyGranted() (gas: 71218) -HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 22469037) +HypEXATest:test_proposeBridgeRole_reverts_whenRouterNotDeployed() (gas: 22344704) HypEXATest:test_proposeBridgeRole_schedulesGrantOnTimelock() (gas: 1183670) HypEXATest:test_roundTrip_opToBaseToOp() (gas: 1055313) HypEXATest:test_roundTrip_opToPolygonToBaseToOp() (gas: 1562388) -HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22466120) +HypEXATest:test_setupRouter_reverts_whenRouterNotDeployed() (gas: 22341787) HypEXATest:test_transferRemote_reverts_withoutBridgeRole() (gas: 241639) IssuerCheckerTest:test_setIssuer_emits_IssuerSet() (gas: 82935) IssuerCheckerTest:test_setIssuer_reverts_whenNotAdmin() (gas: 43708) @@ -220,20 +220,20 @@ IssuerCheckerTest:test_setPrevIssuerWindow_emits_PrevIssuerWindowSet() (gas: 526 IssuerCheckerTest:test_setPrevIssuerWindow_reverts_whenNotAdmin() (gas: 45548) MockSwapperTest:test_swapExactAmountIn_swaps() (gas: 269414) MockSwapperTest:test_swapExactAmountOut_swaps() (gas: 269410) -RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58985047) -RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19540060) -RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34788213) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263967652) -RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359537884) -RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37979704) -RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19564323) -RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35335703) -RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 23065090) -RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19568880) -RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34279192) -RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 38151328) -RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42334809) -RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50956942) +RedeployerTest:test_deployEXA_deploysAtSameAddress_onBase() (gas: 58860868) +RedeployerTest:test_deployExaFactoryWithProxy_reverts_whenNotPrepared() (gas: 19415727) +RedeployerTest:test_deployExaFactoryWithProxy_succeeds_whenAlreadyUpgraded() (gas: 34663902) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onEthereum() (gas: 263843561) +RedeployerTest:test_deployExaFactory_deploysAtSameAddress_onPolygon() (gas: 359413573) +RedeployerTest:test_deployExaFactory_deploysViaCreate3AtSameAddress_onPolygon() (gas: 37855415) +RedeployerTest:test_deployExaFactory_reverts_whenNotPrepared() (gas: 19440012) +RedeployerTest:test_deployExaFactory_succeeds_whenVersionAlreadyDeployed() (gas: 35211436) +RedeployerTest:test_prepare_reusesExistingDeps_onBase() (gas: 22940779) +RedeployerTest:test_prepare_reverts_whenAdminIsDeployer() (gas: 19444569) +RedeployerTest:test_prepare_succeeds_whenCalledTwice() (gas: 34154925) +RedeployerTest:test_recoversNativeETHOnPolygon() (gas: 38027039) +RedeployerTest:test_serialProxies_reverts_whenAttackerUpgradesProxy() (gas: 42210375) +RedeployerTest:test_serialProxies_reverts_whenTargetNonceTooLow() (gas: 50708483) RefunderTest:test_refund_refunds() (gas: 263363) RefunderTest:test_refund_reverts_whenExpired() (gas: 88359) RefunderTest:test_refund_reverts_whenNotKeeper() (gas: 68861) diff --git a/contracts/script/Redeployer.s.sol b/contracts/script/Redeployer.s.sol index c26e96976b..a3c34e632c 100644 --- a/contracts/script/Redeployer.s.sol +++ b/contracts/script/Redeployer.s.sol @@ -208,15 +208,6 @@ contract Redeployer is BaseScript { exa = EXA(CREATE3_FACTORY.deploy(keccak256(abi.encode("EXA")), vm.getCode("EXA.sol:EXA"))); } - /// @notice Upgrades an existing EXA proxy to the latest implementation. - function upgradeEXA(address proxy) external { - ProxyAdmin p = ProxyAdmin(address(uint160(uint256(vm.load(proxy, ERC1967Utils.ADMIN_SLOT))))); - vm.broadcast(p.owner()); - p.upgradeAndCall( - ITransparentUpgradeableProxy(proxy), address(exa), abi.encodeCall(EXA.initialize2, (acct("exactly"))) - ); - } - function deployRouter(address token) external returns (HypXERC20 router) { address admin = acct("admin"); router = HypXERC20(CREATE3_FACTORY.getDeployed(admin, keccak256(abi.encode("HypEXA")))); diff --git a/contracts/test/HypEXA.t.sol b/contracts/test/HypEXA.t.sol index 4c114c668c..6dccf81a42 100644 --- a/contracts/test/HypEXA.t.sol +++ b/contracts/test/HypEXA.t.sol @@ -3,6 +3,11 @@ pragma solidity ^0.8.0; import { IAccessControl } from "openzeppelin-contracts/contracts/access/IAccessControl.sol"; import { TimelockController } from "openzeppelin-contracts/contracts/governance/TimelockController.sol"; +import { ERC1967Utils } from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { ProxyAdmin } from "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + ITransparentUpgradeableProxy +} from "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import { EXA } from "@exactly/protocol/periphery/EXA.sol"; import { TypeCasts } from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; @@ -68,7 +73,7 @@ contract HypEXATest is ForkTest { opRedeployer.setUp(); opRedeployer.prepare(); opRedeployer.deployEXAImpl(); - opRedeployer.upgradeEXA(address(exa)); + _upgradeEXA(address(exa), address(opRedeployer.exa())); opRouter = opRedeployer.deployRouter(address(exa)); vm.prank(acct("exactly")); exa.grantRole(keccak256("BRIDGE_ROLE"), address(opRouter)); @@ -229,4 +234,12 @@ contract HypEXATest is ForkTest { } // solhint-enable func-name-mixedcase + + function _upgradeEXA(address proxy, address implementation) internal { + ProxyAdmin p = ProxyAdmin(address(uint160(uint256(vm.load(proxy, ERC1967Utils.ADMIN_SLOT))))); + vm.prank(p.owner()); + p.upgradeAndCall( + ITransparentUpgradeableProxy(proxy), implementation, abi.encodeCall(EXA.initialize2, (acct("exactly"))) + ); + } }