From 67151c3a306a6642ec1a7456a6de622e9273664d Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:43:29 +0100 Subject: [PATCH 1/3] feat(ev-deployer): add Permit2 contract support Add Uniswap Permit2 as a genesis-deployable contract with EIP-712 immutable patching (_CACHED_CHAIN_ID, _CACHED_DOMAIN_SEPARATOR). --- .gitmodules | 3 + bin/ev-deployer/examples/devnet.toml | 3 + bin/ev-deployer/src/config.rs | 9 + bin/ev-deployer/src/contracts/mod.rs | 1 + bin/ev-deployer/src/contracts/permit2.rs | 223 +++++++++++++++++++++++ bin/ev-deployer/src/genesis.rs | 6 + bin/ev-deployer/src/main.rs | 6 + bin/ev-deployer/src/output.rs | 7 + bin/ev-deployer/tests/e2e_genesis.sh | 24 ++- contracts/lib/permit2 | 1 + 10 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 bin/ev-deployer/src/contracts/permit2.rs create mode 160000 contracts/lib/permit2 diff --git a/.gitmodules b/.gitmodules index 735b8dc..0cebd2a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "contracts/lib/hyperlane-monorepo"] path = contracts/lib/hyperlane-monorepo url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git +[submodule "contracts/lib/permit2"] + path = contracts/lib/permit2 + url = https://github.com/Uniswap/permit2 diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 87bafe0..0cf8d45 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -20,3 +20,6 @@ hyp_native_minter = "0x0000000000000000000000000000000000000000" address = "0x0000000000000000000000000000000000001100" owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" + +[contracts.permit2] +address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" diff --git a/bin/ev-deployer/src/config.rs b/bin/ev-deployer/src/config.rs index bed1e62..c3f674b 100644 --- a/bin/ev-deployer/src/config.rs +++ b/bin/ev-deployer/src/config.rs @@ -31,6 +31,8 @@ pub(crate) struct ContractsConfig { pub fee_vault: Option, /// `MerkleTreeHook` contract config (optional). pub merkle_tree_hook: Option, + /// `Permit2` contract config (optional). + pub permit2: Option, } /// `AdminProxy` configuration. @@ -84,6 +86,13 @@ pub(crate) struct MerkleTreeHookConfig { pub mailbox: Address, } +/// `Permit2` configuration (Uniswap token approval manager). +#[derive(Debug, Deserialize)] +pub(crate) struct Permit2Config { + /// Address to deploy at. + pub address: Address, +} + impl DeployConfig { /// Load and validate config from a TOML file. pub(crate) fn load(path: &Path) -> eyre::Result { diff --git a/bin/ev-deployer/src/contracts/mod.rs b/bin/ev-deployer/src/contracts/mod.rs index c142cc0..43bfaf5 100644 --- a/bin/ev-deployer/src/contracts/mod.rs +++ b/bin/ev-deployer/src/contracts/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod admin_proxy; pub(crate) mod fee_vault; pub(crate) mod immutables; pub(crate) mod merkle_tree_hook; +pub(crate) mod permit2; use alloy_primitives::{Address, Bytes, B256}; use std::collections::BTreeMap; diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs new file mode 100644 index 0000000..fd62f2e --- /dev/null +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -0,0 +1,223 @@ +//! `Permit2` bytecode and immutable encoding. +//! +//! Uniswap's `Permit2` provides gas-efficient token approval management +//! via signature-based permits (EIP-2612 style) for any ERC-20 token. +//! +//! ## Immutables (in bytecode, not storage) +//! +//! | Variable | Type | Offset | +//! |-----------------------------|---------|--------| +//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! +//! Both come from the EIP-712 base contract (`src/EIP712.sol`). The +//! constructor normally caches `block.chainid` and the resulting domain +//! separator so that `DOMAIN_SEPARATOR()` can skip recomputation when the +//! chain ID hasn't changed. For a genesis deploy we patch the correct +//! values directly into the bytecode. +//! +//! ## Storage layout +//! +//! Permit2 has no storage that needs initialization at genesis. All state +//! (allowances, nonces, …) starts at zero. + +use crate::{ + config::Permit2Config, + contracts::{ + immutables::{patch_bytes, patch_u256, ImmutableRef}, + GenesisContract, + }, +}; +use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; +use std::collections::BTreeMap; + +/// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) +/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// +/// Compiled with placeholder immutables (all zeros). Actual values are patched +/// at genesis time via [`build`]. +/// +/// Regenerate with: +/// ```sh +/// cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode +/// ``` +const PERMIT2_BYTECODE: &[u8] = &hex!("6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000000003611b69577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a"); + +// ── Immutable reference offsets (from compiled artifact `immutableReferences`) ── + +/// `_CACHED_CHAIN_ID` (uint256) — from `EIP712.sol`. +const CACHED_CHAIN_ID_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6945, + length: 32, +}]; + +/// `_CACHED_DOMAIN_SEPARATOR` (bytes32) — from `EIP712.sol`. +const CACHED_DOMAIN_SEPARATOR_REFS: &[ImmutableRef] = &[ImmutableRef { + start: 6983, + length: 32, +}]; + +/// EIP-712 type hash: `keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)")` +const EIP712_TYPE_HASH: B256 = B256::new(hex!( + "8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866" +)); + +/// `keccak256("Permit2")` +const HASHED_NAME: B256 = B256::new(hex!( + "9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a" +)); + +/// Build a genesis alloc entry for `Permit2`. +pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { + let mut bytecode = PERMIT2_BYTECODE.to_vec(); + + // Patch _CACHED_CHAIN_ID + let chain_id_u256 = U256::from(chain_id); + patch_u256(&mut bytecode, CACHED_CHAIN_ID_REFS, chain_id_u256); + + // Compute and patch _CACHED_DOMAIN_SEPARATOR: + // keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, chainId, contractAddress)) + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let domain_separator = keccak256(buf); + patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + + GenesisContract { + address: config.address, + code: Bytes::from(bytecode), + storage: BTreeMap::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, Address}; + use std::{path::PathBuf, process::Command}; + + fn test_config() -> Permit2Config { + Permit2Config { + address: address!("000000000022D473030F116dDEE9F6B43aC78BA3"), + } + } + + #[test] + fn no_storage_entries() { + let contract = build(&test_config(), 1234); + assert!( + contract.storage.is_empty(), + "Permit2 should have no storage at genesis" + ); + } + + #[test] + fn bytecode_is_patched_with_chain_id() { + let contract = build(&test_config(), 42); + let code = contract.code.to_vec(); + let word = &code[6945..6945 + 32]; + assert_eq!(word[31], 42); + assert_eq!(word[0..31], [0u8; 31]); + } + + #[test] + fn bytecode_is_patched_with_domain_separator() { + let config = test_config(); + let chain_id: u64 = 1234; + let contract = build(&config, chain_id); + let code = contract.code.to_vec(); + + // Compute expected domain separator + let mut buf = [0u8; 128]; + buf[0..32].copy_from_slice(EIP712_TYPE_HASH.as_slice()); + buf[32..64].copy_from_slice(HASHED_NAME.as_slice()); + buf[64..96].copy_from_slice(&B256::from(U256::from(chain_id)).0); + buf[96..128].copy_from_slice(config.address.into_word().as_slice()); + let expected = keccak256(buf); + + let word = &code[6983..6983 + 32]; + assert_eq!(word, expected.as_slice()); + } + + #[test] + fn domain_separator_changes_with_chain_id() { + let config = test_config(); + let c1 = build(&config, 1); + let c2 = build(&config, 2); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + } + + #[test] + fn domain_separator_changes_with_address() { + let c1 = build( + &Permit2Config { + address: Address::repeat_byte(0x01), + }, + 1234, + ); + let c2 = build( + &Permit2Config { + address: Address::repeat_byte(0x02), + }, + 1234, + ); + + let ds1 = &c1.code[6983..6983 + 32]; + let ds2 = &c2.code[6983..6983 + 32]; + assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + } + + #[test] + fn eip712_constants_are_correct() { + // Verify the hardcoded constants match the expected values + assert_eq!( + EIP712_TYPE_HASH, + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"), + ); + assert_eq!(HASHED_NAME, keccak256("Permit2")); + } + + #[test] + #[ignore = "requires forge CLI"] + fn permit2_bytecode_matches_solidity_source() { + let contracts_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .unwrap() + .join("contracts") + .join("lib") + .join("permit2"); + + let output = Command::new("forge") + .args(["inspect", "Permit2", "deployedBytecode", "--root"]) + .arg(&contracts_root) + .output() + .expect("forge not found"); + + assert!( + output.status.success(), + "forge inspect failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + let forge_hex = String::from_utf8(output.stdout) + .unwrap() + .trim() + .strip_prefix("0x") + .unwrap() + .to_lowercase(); + + let hardcoded_hex = hex::encode(PERMIT2_BYTECODE); + + assert_eq!( + forge_hex, hardcoded_hex, + "Permit2 bytecode mismatch! Regenerate with: \ + cd contracts/lib/permit2 && forge inspect Permit2 deployedBytecode" + ); + } +} diff --git a/bin/ev-deployer/src/genesis.rs b/bin/ev-deployer/src/genesis.rs index fee4355..ff5b4ec 100644 --- a/bin/ev-deployer/src/genesis.rs +++ b/bin/ev-deployer/src/genesis.rs @@ -28,6 +28,11 @@ pub(crate) fn build_alloc(config: &DeployConfig) -> Value { insert_contract(&mut alloc, &contract); } + if let Some(ref p2_config) = config.contracts.permit2 { + let contract = contracts::permit2::build(p2_config, config.chain.chain_id); + insert_contract(&mut alloc, &contract); + } + Value::Object(alloc) } @@ -105,6 +110,7 @@ mod tests { }), fee_vault: None, merkle_tree_hook: None, + permit2: None, }, } } diff --git a/bin/ev-deployer/src/main.rs b/bin/ev-deployer/src/main.rs index a5c9d30..aab6e6e 100644 --- a/bin/ev-deployer/src/main.rs +++ b/bin/ev-deployer/src/main.rs @@ -115,6 +115,12 @@ fn main() -> eyre::Result<()> { .as_ref() .map(|c| c.address) .ok_or_else(|| eyre::eyre!("merkle_tree_hook not configured"))?, + "permit2" => cfg + .contracts + .permit2 + .as_ref() + .map(|c| c.address) + .ok_or_else(|| eyre::eyre!("permit2 not configured"))?, other => eyre::bail!("unknown contract: {other}"), }; diff --git a/bin/ev-deployer/src/output.rs b/bin/ev-deployer/src/output.rs index 59ae29a..10e4167 100644 --- a/bin/ev-deployer/src/output.rs +++ b/bin/ev-deployer/src/output.rs @@ -28,5 +28,12 @@ pub(crate) fn build_manifest(config: &DeployConfig) -> Value { ); } + if let Some(ref p2) = config.contracts.permit2 { + manifest.insert( + "permit2".to_string(), + Value::String(format!("{}", p2.address)), + ); + } + Value::Object(manifest) } diff --git a/bin/ev-deployer/tests/e2e_genesis.sh b/bin/ev-deployer/tests/e2e_genesis.sh index 79900e5..2d47481 100755 --- a/bin/ev-deployer/tests/e2e_genesis.sh +++ b/bin/ev-deployer/tests/e2e_genesis.sh @@ -81,8 +81,10 @@ grep -q "000000000000000000000000000000000000Ad00" "$GENESIS" \ || fail "AdminProxy address not found in genesis" grep -q "000000000000000000000000000000000000FE00" "$GENESIS" \ || fail "FeeVault address not found in genesis" +grep -q "000000000022D473030F116dDEE9F6B43aC78BA3" "$GENESIS" \ + || fail "Permit2 address not found in genesis" -pass "genesis contains both contract addresses" +pass "genesis contains all contract addresses" # ── Step 3: Start ev-reth ──────────────────────────────── @@ -162,6 +164,26 @@ expected_slot6="0x00000000000000000000000000000000000000000000000000000000000027 || fail "FeeVault slot 6 (bridgeShareBps) mismatch: got $fv_slot6, expected $expected_slot6" pass "FeeVault slot 6 (bridgeShareBps) = 10000" +# ── Step 6: Verify Permit2 ───────────────────────────── + +PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3" + +echo "=== Verifying Permit2 at $PERMIT2 ===" + +# Check code is present +p2_code=$(rpc_call "eth_getCode" "[\"$PERMIT2\", \"latest\"]") +[[ "$p2_code" != "0x" && "$p2_code" != "0x0" && ${#p2_code} -gt 10 ]] \ + || fail "Permit2 has no bytecode (got: $p2_code)" +pass "Permit2 has bytecode (${#p2_code} hex chars)" + +# Call DOMAIN_SEPARATOR() — selector 0x3644e515 +# Should return the cached domain separator matching chain_id=1234 and the contract address +p2_domain_sep=$(rpc_call "eth_call" "[{\"to\":\"$PERMIT2\",\"data\":\"0x3644e515\"}, \"latest\"]") +expected_domain_sep="0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95" +[[ "$(echo "$p2_domain_sep" | tr '[:upper:]' '[:lower:]')" == "$(echo "$expected_domain_sep" | tr '[:upper:]' '[:lower:]')" ]] \ + || fail "Permit2 DOMAIN_SEPARATOR() mismatch: got $p2_domain_sep, expected $expected_domain_sep" +pass "Permit2 DOMAIN_SEPARATOR() correct for chain_id=1234" + # ── Done ───────────────────────────────────────────────── echo "" diff --git a/contracts/lib/permit2 b/contracts/lib/permit2 new file mode 160000 index 0000000..cc56ad0 --- /dev/null +++ b/contracts/lib/permit2 @@ -0,0 +1 @@ +Subproject commit cc56ad0f3439c502c246fc5cfcc3db92bb8b7219 From 56548ec8819f5ba33ec716dd75410f109c0aa407 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 11:44:28 +0100 Subject: [PATCH 2/3] docs(ev-deployer): add comment explaining canonical Permit2 address --- bin/ev-deployer/examples/devnet.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/ev-deployer/examples/devnet.toml b/bin/ev-deployer/examples/devnet.toml index 0cf8d45..1c56e36 100644 --- a/bin/ev-deployer/examples/devnet.toml +++ b/bin/ev-deployer/examples/devnet.toml @@ -22,4 +22,6 @@ owner = "0x000000000000000000000000000000000000Ad00" mailbox = "0x0000000000000000000000000000000000001200" [contracts.permit2] +# Canonical Uniswap Permit2 address (same on all chains via CREATE2). +# Using it here so frontends, SDKs and routers work out of the box. address = "0x000000000022D473030F116dDEE9F6B43aC78BA3" From bfa5a23bd2889dbc21342fd0b1c32454b5f105b7 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Thu, 19 Mar 2026 12:02:05 +0100 Subject: [PATCH 3/3] style(ev-deployer): fix fmt, clippy and rustdoc warnings in permit2 --- bin/ev-deployer/src/contracts/permit2.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/ev-deployer/src/contracts/permit2.rs b/bin/ev-deployer/src/contracts/permit2.rs index fd62f2e..ee56f85 100644 --- a/bin/ev-deployer/src/contracts/permit2.rs +++ b/bin/ev-deployer/src/contracts/permit2.rs @@ -7,8 +7,8 @@ //! //! | Variable | Type | Offset | //! |-----------------------------|---------|--------| -//! | `_CACHED_CHAIN_ID` | uint256 | [6945] | -//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | [6983] | +//! | `_CACHED_CHAIN_ID` | uint256 | \[6945\] | +//! | `_CACHED_DOMAIN_SEPARATOR` | bytes32 | \[6983\] | //! //! Both come from the EIP-712 base contract (`src/EIP712.sol`). The //! constructor normally caches `block.chainid` and the resulting domain @@ -32,7 +32,7 @@ use alloy_primitives::{hex, keccak256, Bytes, B256, U256}; use std::collections::BTreeMap; /// `Permit2` runtime bytecode compiled from Uniswap/permit2 (commit cc56ad0) -/// with solc 0.8.17 (via-ir, optimizer 1_000_000 runs, `bytecode_hash="none"`). +/// with solc 0.8.17 (via-ir, optimizer `1_000_000` runs, `bytecode_hash="none"`). /// /// Compiled with placeholder immutables (all zeros). Actual values are patched /// at genesis time via [`build`]. @@ -83,7 +83,11 @@ pub(crate) fn build(config: &Permit2Config, chain_id: u64) -> GenesisContract { buf[64..96].copy_from_slice(&B256::from(chain_id_u256).0); buf[96..128].copy_from_slice(config.address.into_word().as_slice()); let domain_separator = keccak256(buf); - patch_bytes(&mut bytecode, CACHED_DOMAIN_SEPARATOR_REFS, &domain_separator.0); + patch_bytes( + &mut bytecode, + CACHED_DOMAIN_SEPARATOR_REFS, + &domain_separator.0, + ); GenesisContract { address: config.address, @@ -149,7 +153,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different chain IDs should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different chain IDs should produce different domain separators" + ); } #[test] @@ -169,7 +176,10 @@ mod tests { let ds1 = &c1.code[6983..6983 + 32]; let ds2 = &c2.code[6983..6983 + 32]; - assert_ne!(ds1, ds2, "different addresses should produce different domain separators"); + assert_ne!( + ds1, ds2, + "different addresses should produce different domain separators" + ); } #[test]