[evm]: split IntentGatewayV2 into delegatecall modules - #1265
Conversation
2c566fe to
8c23063
Compare
|
I would still wait for another set of 👀. cc: @Wizdave97 |
I'll check it out |
|
Would be good to merge this asap so we can unblock @royvardhan |
The implementation sat 209 bytes under the EIP-170 limit. It now keeps every entry point, its guards and the shared validation, and delegatecalls the bodies to two separately deployed modules whose addresses are immutables: - IntrinsicModule: same-chain fill and cancel (IntrinsicIntents, unchanged). - ExtrinsicModule: cross-chain fill, both cancel routes, and the onAccept and onGetResponse handlers with governance and Execute (ExtrinsicIntents, kept as a HyperApp; its callbacks became virtual). The implementation is `IntentsBase, HyperApp, ReentrancyGuardTransient, Initializable` and inherits nothing from the intents contracts. fillOrder and cancelOrder delegatecall typed module functions; onAccept and onGetResponse forward msg.data. Execute now delegatecalls the extrinsic module's own address, so setRelayer and upgradeToAndCall live only there and leave the gateway ABI; a relayer rotation is its own execute_on_gateway call. migrate() takes no argument and bumps VERSION 2 to 3. _owner is removed. __self, relayer() and _setRelayer moved to IntentsBase. Runtime sizes: implementation 14,610 (was 24,367), intrinsic 6,843, extrinsic 14,535. Fills and cancels cost 4.3k to 6.2k more gas, settlement deliveries 2.6k to 2.9k, governance 3.3k. Deploy scripts share IntentGatewayScript, which deploys the modules via CREATE2 before the implementation and prints the execute_on_gateway data. A new suite asserts the storage layouts agree slot for slot from the forge artifacts (foundry.toml now emits storageLayout), that modules refuse direct calls, that the constructor rejects codeless modules, that module reverts bubble verbatim, that either module can be swapped by an implementation upgrade, and that the release's upgrade with migrate() moves a version-2 proxy to 3. IIntentGatewayV2 in core follows the ABI; core bumped to 2.3.5. Closes #1262
8c23063 to
3bac107
Compare
Review: fund-loss focusI found no path that lets anyone take funds from solvers, users or the protocol. The review split into four areas:
Nothing reached high or critical. The findings worth acting on are ways a governance or deploy mistake could lock escrow for good. 1. An implementation built with the wrong module addresses locks all escrow for good (medium: large impact, but needs an operator mistake)
Why it's new: before the split, every implementation carried Mitigation: the script computes both addresses from each module's Suggested fix: have 2. Upgrading to a wrong target with empty init data also locks the gateway (low, and not new)
3.
|
Closes #1262
Why
IntentGatewayV2sat 209 bytes under the EIP-170 limit, with #980 and #1259 needing room. The implementation now delegatecalls its bodies to two separately deployed modules. The proxy, its address and the governance upgrade path are unchanged.Layout
IntentGatewayV2isIntentsBase, HyperApp, ReentrancyGuardTransient, Initializable. It keeps every entry point and its guards,placeOrder,select, the shared validation offillOrderandcancelOrder,initialize,migrate()and the views.fillOrderandcancelOrderdelegatecall typed module functions.onAcceptandonGetResponseforwardmsg.dataafteronlyHost.IntrinsicModule is IntrinsicIntents: same-chain fill and cancel.IntrinsicIntentsis untouched.ExtrinsicModule is ExtrinsicIntents: cross-chain fill, both cancel routes, and theonAcceptandonGetResponsehandlers with governance andExecute.ExtrinsicIntentsstays aHyperApp; its diff isvirtualon the two callbacks,relayer()and_setRelayermoving toIntentsBase, andExecutedelegatecalling the module's own address instead of the implementation.IntentsBase, declare no storage, refuse direct calls, and their reverts bubble byte for byte through a six-line assembly wrapper.Details and the upgrade procedure are in
evm/src/apps/intentsv2/README.md.Sizes and gas
Per-test
forge snapshotagainst main: fills and cancels cost 4.3k to 6.2k more gas (a cold access to the module plus re-encoding the order), settlement deliveries 2.6k to 2.9k, governance requests about 3.3k.What changes for operators
setRelayerandupgradeToAndCalllive only on the extrinsic module and leave the gateway ABI. Governance reaches them throughexecute_on_gatewayexactly as before; the selectors are unchanged.execute_on_gatewaycalls.migrate()takes no argument and bumpsVERSIONfrom 2 to 3. This release's upgrade carriesmigrate()as init data; the deploy script prints the exactdataforexecute_on_gateway.(intrinsic, extrinsic);_owneris gone.intrinsicModule()andextrinsicModule()are new getters.DeployIntentGatewayImplandDeployIntentGatewayshareIntentGatewayScript: CREATE2 modules first, reused if already at their address, then the implementation.deploy.sh --mode fullverifies the modules too.Tests
migrate()and ABI adaptations.IntentGatewayModulesTest(10 tests): storage layouts read from the forge artifacts agree slot for slot (foundry.tomlnow emitsstorageLayout), modules refuse direct calls, the constructor rejects codeless modules, module reverts bubble verbatim (a custom error and DAI's string reason), either module can be swapped by an implementation upgrade, and the release's own upgrade moves a version-2 proxy to 3 with escrow and peers intact.migrate()and checks every readable piece of state.Not in this PR
_owner,migrate(address),setRelayerandupgradeToAndCall. Nothing in those packages calls them, so they stay call-compatible; regenerate them in a follow-up.IIntentGatewayV2insdk/packages/corefollows the gateway (migrate(), the two host-only functions removed) with a patch bump to 2.3.5.