Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
89eccaa
feat: add ev-deployer CLI for genesis contract allocation
randygrok Mar 13, 2026
0c8f54e
test: add bytecode verification tests for ev-deployer contracts
randygrok Mar 13, 2026
2ba2b80
docs: add ev-deployer README with config and usage guide
randygrok Mar 13, 2026
a540858
fix(ci): serialize bytecode verification tests to avoid solc race con…
randygrok Mar 13, 2026
b9e2670
style: apply cargo fmt to ev-deployer
randygrok Mar 13, 2026
18ed817
ci(ev-deployer): split workflow into separate bytecode and unit test …
randygrok Mar 13, 2026
f7d0e71
style: fix fmt and clippy lint errors in ev-deployer
randygrok Mar 16, 2026
46ea9a6
feat(ev-deployer): add MerkleTreeHook contract with immutable bytecod…
randygrok Mar 18, 2026
e5f4eb9
Merge branch 'main' into ev-deployer-part1-core
randygrok Mar 18, 2026
be1b241
Merge remote-tracking branch 'origin/main' into ev-deployer-part1-core
randygrok Mar 19, 2026
7e19222
ci(ev-deployer): add e2e genesis test to CI workflow
randygrok Mar 19, 2026
e9fa70e
Merge remote-tracking branch 'origin/ev-deployer-part1-core' into ev-…
randygrok Mar 19, 2026
946026d
ci(ev-deployer): install soldeer deps before bytecode verification
randygrok Mar 19, 2026
9dd5011
test(ev-deployer): add MerkleTreeHook verification to e2e genesis test
randygrok Mar 19, 2026
217be0c
fix(ev-deployer): escape brackets in doc comments to fix rustdoc
randygrok Mar 19, 2026
67151c3
feat(ev-deployer): add Permit2 contract support
randygrok Mar 19, 2026
56548ec
docs(ev-deployer): add comment explaining canonical Permit2 address
randygrok Mar 19, 2026
fa8a428
merge: resolve e2e test conflicts with ev-deployer-merkle-tree-hook
randygrok Mar 19, 2026
bfa5a23
style(ev-deployer): fix fmt, clippy and rustdoc warnings in permit2
randygrok Mar 19, 2026
4da01ea
feat(ev-deployer): add Mailbox, NoopIsm, and ProtocolFee genesis cont…
randygrok Mar 19, 2026
3faa629
fix(ev-deployer): regenerate Mailbox and ProtocolFee bytecodes from c…
randygrok Mar 19, 2026
eb413d1
merge: resolve conflicts with ev-deployer-merkle-tree-hook (mailbox, …
jgimeno Mar 19, 2026
aeffc0d
fix(ev-deployer): address PR review feedback
jgimeno Mar 19, 2026
a2d194e
merge(ev-deployer): integrate ev-deployer-part1-core duplicate addres…
jgimeno Mar 19, 2026
e8a39f8
refactor(ev-deployer): remove FeeVault contract from part 1
randygrok Mar 24, 2026
089ef22
refactor(ev-deployer): remove AdminProxy contract from part 1
randygrok Mar 24, 2026
6b85563
Revert "refactor(ev-deployer): remove AdminProxy contract from part 1"
randygrok Mar 24, 2026
93b3eaa
fix(ev-deployer): make [contracts] section optional in config
randygrok Mar 24, 2026
70111fd
feat(ev-deployer): add init command to generate starter config
randygrok Mar 24, 2026
fa0e71f
fix(ev-deployer): clean up command ordering and stale fee_vault refer…
randygrok Mar 24, 2026
1acd3c8
docs(ev-deployer): document init command in README
randygrok Mar 24, 2026
cb838e8
merge(ev-deployer): integrate ev-deployer-part1-core keeping all cont…
randygrok Mar 24, 2026
ee68354
fix(ev-deployer): remove extra blank lines from merge to pass rustfmt
randygrok Mar 24, 2026
ef5ac9e
docs(ev-deployer): document all supported contracts in README and ini…
randygrok Mar 24, 2026
65bbf9e
fix(ev-deployer): normalize alloc keys for collision detection
randygrok Mar 24, 2026
08c9eb4
style(ev-deployer): fix fmt and clippy lint in genesis.rs
randygrok Mar 25, 2026
46b75bf
Merge remote-tracking branch 'origin/ev-deployer-part1-core' into ev-…
randygrok Mar 25, 2026
e365cfe
merge: resolve e2e test conflict with ev-deployer-merkle-tree-hook (p…
randygrok Mar 25, 2026
7f9e238
docs(ev-deployer): add Permit2 to init template and README
randygrok Mar 25, 2026
b1e5ae3
fix(ev-deployer): use case-insensitive grep in e2e genesis address ch…
randygrok Mar 25, 2026
5d2be71
refactor(ev-deployer): remove Hyperlane contracts, keep only AdminPro…
randygrok Mar 27, 2026
c32b633
merge: integrate main into ev-deployer-part3-permit2
randygrok Mar 27, 2026
04beb6b
refactor(contracts): remove Hyperlane dependency from FeeVault
randygrok Mar 27, 2026
c6e679b
style(contracts): fix forge fmt formatting
randygrok Mar 27, 2026
cae3723
docs: clarify FeeVault is optional, document when to use it vs plain …
randygrok Mar 27, 2026
4653ccc
fix(ev-deployer): validate permit2 zero-address and duplicate deploy …
randygrok Mar 30, 2026
221aa28
style(ev-deployer): remove unnecessary #[allow(dead_code)] from confi…
randygrok Mar 30, 2026
877c66b
chore(ev-deployer): add permit2 config tests and update contracts skill
randygrok Mar 30, 2026
588a46a
style(ev-deployer): fix rustfmt formatting in config.rs
randygrok Mar 30, 2026
8e8bf8c
docs(ev-deployer): use full canonical Permit2 address in config refer…
randygrok Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .claude/skills/contracts.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "fee bridging to Celestia", "Hyperlane integration", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and bridged.
description: This skill should be used when the user asks about "ev-reth contracts", "FeeVault", "AdminProxy", "Permit2", "fee distribution", "Foundry deployment scripts", "genesis allocations", or wants to understand how base fees are redirected and distributed.
---

# Contracts Onboarding
Expand All @@ -9,13 +9,15 @@ description: This skill should be used when the user asks about "ev-reth contrac
The contracts live in `contracts/` and use Foundry for development. There are two main contracts:

1. **AdminProxy** (`src/AdminProxy.sol`) - Bootstrap contract for admin addresses at genesis
2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees, bridges to Celestia via Hyperlane (cross-chain messaging protocol)
2. **FeeVault** (`src/FeeVault.sol`) - Collects base fees and distributes them between configured recipients
3. **Permit2** (`lib/permit2`) - Uniswap's canonical token approval manager, deployed at genesis via `ev-deployer` (no Foundry deploy script — bytecode is embedded in Rust)

## Key Files

### Contract Sources
- `contracts/src/AdminProxy.sol` - Transparent proxy pattern for admin control
- `contracts/src/FeeVault.sol` - Fee collection and bridging logic
- `contracts/src/FeeVault.sol` - Fee collection and distribution logic
- `contracts/lib/permit2` - Uniswap Permit2 submodule (bytecode used by ev-deployer)

### Deployment Scripts
- `contracts/script/DeployFeeVault.s.sol` - FeeVault deployment with CREATE2
Expand All @@ -34,9 +36,12 @@ The AdminProxy contract provides a bootstrap mechanism for setting admin address
### FeeVault
The FeeVault serves as the destination for redirected base fees (instead of burning them). Key responsibilities:
- Receive base fees from block production
- Bridge accumulated fees to Celestia via Hyperlane
- Distribute accumulated fees between configured recipients
- Manage withdrawal permissions

### Permit2
Uniswap's canonical token approval manager deployed at genesis. Unlike AdminProxy and FeeVault, Permit2 has no Foundry deploy script — its bytecode is embedded directly in the Rust `ev-deployer` (`bin/ev-deployer/src/contracts/permit2.rs`), which patches EIP-712 immutables (chain ID, domain separator) at genesis time.

## Connection to Rust Code

The contracts integrate with ev-reth through:
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/permit2"]
path = contracts/lib/permit2
url = https://github.com/Uniswap/permit2
25 changes: 12 additions & 13 deletions bin/ev-deployer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,6 @@ The binary is output to `target/release/ev-deployer`.

EV Deployer uses a TOML config file to define what contracts to include and how to configure them. See [`examples/devnet.toml`](examples/devnet.toml) for a complete example.

```toml
[chain]
chain_id = 1234

[contracts.admin_proxy]
address = "0x000000000000000000000000000000000000Ad00"
owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
```

### Config reference

#### `[chain]`
Expand All @@ -38,6 +29,12 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
| `address` | address | Address to deploy at |
| `owner` | address | Owner (must not be zero) |

#### `[contracts.permit2]`

| Field | Type | Description |
|-----------|---------|----------------------------------------------------------|
| `address` | address | Address to deploy at (canonical: `0x000000000022D473030F116dDEE9F6B43aC78BA3`) |

## Usage

### Generate a starter config
Expand Down Expand Up @@ -88,7 +85,8 @@ Output:

```json
{
"admin_proxy": "0x000000000000000000000000000000000000Ad00"
"admin_proxy": "0x000000000000000000000000000000000000Ad00",
"permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
}
```

Expand All @@ -100,9 +98,10 @@ ev-deployer compute-address --config deploy.toml --contract admin_proxy

## Contracts

| Contract | Description |
|----------------|-----------------------------------------------------|
| `admin_proxy` | Proxy contract with owner-based access control |
| Contract | Description |
|---------------|----------------------------------------------------|
| `admin_proxy` | Proxy contract with owner-based access control |
| `permit2` | Uniswap canonical token approval manager |

Runtime bytecodes are embedded in the binary — no external toolchain is needed at deploy time.

Expand Down
5 changes: 5 additions & 0 deletions bin/ev-deployer/examples/devnet.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ chain_id = 1234
[contracts.admin_proxy]
address = "0x000000000000000000000000000000000000Ad00"
owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"

[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"
105 changes: 102 additions & 3 deletions bin/ev-deployer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

use alloy_primitives::Address;
use serde::Deserialize;
use std::path::Path;
use std::{collections::HashSet, path::Path};

/// Top-level deploy configuration.
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct DeployConfig {
/// Chain configuration.
pub chain: ChainConfig,
Expand All @@ -17,7 +16,6 @@ pub(crate) struct DeployConfig {

/// Chain-level settings.
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct ChainConfig {
/// The chain ID.
pub chain_id: u64,
Expand All @@ -28,6 +26,22 @@ pub(crate) struct ChainConfig {
pub(crate) struct ContractsConfig {
/// `AdminProxy` contract config (optional).
pub admin_proxy: Option<AdminProxyConfig>,
/// `Permit2` contract config (optional).
pub permit2: Option<Permit2Config>,
}

impl ContractsConfig {
/// Collect all configured deploy addresses.
fn all_addresses(&self) -> Vec<Address> {
let mut addrs = Vec::new();
if let Some(ref ap) = self.admin_proxy {
addrs.push(ap.address);
}
if let Some(ref p2) = self.permit2 {
addrs.push(p2.address);
}
addrs
}
}

/// `AdminProxy` configuration.
Expand All @@ -39,6 +53,13 @@ pub(crate) struct AdminProxyConfig {
pub owner: 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<Self> {
Expand All @@ -57,6 +78,19 @@ impl DeployConfig {
);
}

if let Some(ref p2) = self.contracts.permit2 {
eyre::ensure!(
!p2.address.is_zero(),
"permit2.address must not be the zero address"
);
}

// Detect duplicate deploy addresses across all contracts.
let mut seen = HashSet::new();
for addr in self.contracts.all_addresses() {
eyre::ensure!(seen.insert(addr), "duplicate deploy address: {addr}");
}

Ok(())
}
}
Expand Down Expand Up @@ -106,6 +140,71 @@ chain_id = 1
assert!(config.contracts.admin_proxy.is_none());
}

#[test]
fn reject_zero_permit2_address() {
let toml = r#"
[chain]
chain_id = 1

[contracts.permit2]
address = "0x0000000000000000000000000000000000000000"
"#;
let config: DeployConfig = toml::from_str(toml).unwrap();
assert!(config.validate().is_err());
}

#[test]
fn reject_duplicate_deploy_address() {
let toml = r#"
[chain]
chain_id = 1

[contracts.admin_proxy]
address = "0x000000000000000000000000000000000000Ad00"
owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"

[contracts.permit2]
address = "0x000000000000000000000000000000000000Ad00"
"#;
let config: DeployConfig = toml::from_str(toml).unwrap();
let err = config.validate().unwrap_err().to_string();
assert!(err.contains("duplicate deploy address"), "{err}");
}

#[test]
fn permit2_only() {
let toml = r#"
[chain]
chain_id = 1

[contracts.permit2]
address = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
"#;
let config: DeployConfig = toml::from_str(toml).unwrap();
config.validate().unwrap();
assert!(config.contracts.permit2.is_some());
assert!(config.contracts.admin_proxy.is_none());
}

#[test]
fn both_contracts() {
let toml = r#"
[chain]
chain_id = 1

[contracts.admin_proxy]
address = "0x000000000000000000000000000000000000Ad00"
owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"

[contracts.permit2]
address = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
"#;
let config: DeployConfig = toml::from_str(toml).unwrap();
config.validate().unwrap();
assert!(config.contracts.admin_proxy.is_some());
assert!(config.contracts.permit2.is_some());
}

#[test]
fn admin_proxy_only() {
let toml = r#"
Expand Down
76 changes: 76 additions & 0 deletions bin/ev-deployer/src/contracts/immutables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Bytecode patching for Solidity immutable variables.
//!
//! Solidity `immutable` values are embedded in the **runtime bytecode** by the
//! compiler, not in storage. When compiling with placeholder values (e.g.
//! `address(0)`, `uint32(0)`), the compiler leaves zero-filled regions at known
//! byte offsets. This module replaces those regions with the actual values from
//! the deploy config at genesis-generation time.

use alloy_primitives::{B256, U256};

/// A single immutable reference inside a bytecode blob.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ImmutableRef {
/// Byte offset into the **runtime** bytecode.
pub start: usize,
/// Number of bytes (always 32 for EVM words).
pub length: usize,
}

/// Patch a mutable bytecode slice, writing `value` at every listed offset.
///
/// # Panics
///
/// Panics if any reference extends past the end of `bytecode`.
pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) {
for r in refs {
assert!(
r.start + r.length <= bytecode.len(),
"immutable ref out of bounds: start={} length={} bytecode_len={}",
r.start,
r.length,
bytecode.len()
);
bytecode[r.start..r.start + r.length].copy_from_slice(value);
}
Comment on lines +25 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Enforce immutable word-size invariant explicitly.

patch_bytes accepts arbitrary length, but copy_from_slice(value) requires exactly 32 bytes. Add an explicit check so failures are deterministic and self-explanatory.

Suggested fix
 pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) {
     for r in refs {
+        assert!(
+            r.length == value.len(),
+            "immutable ref length mismatch: expected={} got={} start={}",
+            value.len(),
+            r.length,
+            r.start
+        );
         assert!(
             r.start + r.length <= bytecode.len(),
             "immutable ref out of bounds: start={} length={} bytecode_len={}",
             r.start,
             r.length,
             bytecode.len()
         );
         bytecode[r.start..r.start + r.length].copy_from_slice(value);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) {
for r in refs {
assert!(
r.start + r.length <= bytecode.len(),
"immutable ref out of bounds: start={} length={} bytecode_len={}",
r.start,
r.length,
bytecode.len()
);
bytecode[r.start..r.start + r.length].copy_from_slice(value);
}
pub(crate) fn patch_bytes(bytecode: &mut [u8], refs: &[ImmutableRef], value: &[u8; 32]) {
for r in refs {
assert!(
r.length == value.len(),
"immutable ref length mismatch: expected={} got={} start={}",
value.len(),
r.length,
r.start
);
assert!(
r.start + r.length <= bytecode.len(),
"immutable ref out of bounds: start={} length={} bytecode_len={}",
r.start,
r.length,
bytecode.len()
);
bytecode[r.start..r.start + r.length].copy_from_slice(value);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ev-deployer/src/contracts/immutables.rs` around lines 25 - 35, The
patch_bytes function currently assumes each ImmutableRef length equals the
32-byte value but doesn't enforce it, causing copy_from_slice to panic with
unclear errors; add an explicit check inside the loop that r.length ==
value.len() (or assert_eq!(r.length, 32, "...")) before the bounds assert/copy
so failures are deterministic and self-explanatory, referencing the ImmutableRef
fields (r.start, r.length) and value length in the assertion message.

}

/// Convenience: patch with an ABI-encoded `uint256`.
pub(crate) fn patch_u256(bytecode: &mut [u8], refs: &[ImmutableRef], val: U256) {
let word = B256::from(val);
patch_bytes(bytecode, refs, &word.0);
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn patch_single_ref() {
let mut bytecode = vec![0u8; 64];
let refs = [ImmutableRef {
start: 10,
length: 32,
}];
let value = B256::from(U256::from(42u64));
patch_bytes(&mut bytecode, &refs, &value.0);

assert_eq!(bytecode[41], 42);
// bytes before are untouched
assert_eq!(bytecode[9], 0);
// bytes after are untouched
assert_eq!(bytecode[42], 0);
}

#[test]
#[should_panic(expected = "immutable ref out of bounds")]
fn patch_out_of_bounds_panics() {
let mut bytecode = vec![0u8; 16];
let refs = [ImmutableRef {
start: 0,
length: 32,
}];
let value = [0u8; 32];
patch_bytes(&mut bytecode, &refs, &value);
}
}
2 changes: 2 additions & 0 deletions bin/ev-deployer/src/contracts/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Contract bytecode and storage encoding.

pub(crate) mod admin_proxy;
pub(crate) mod immutables;
pub(crate) mod permit2;

use alloy_primitives::{Address, Bytes, B256};
use std::collections::BTreeMap;
Expand Down
233 changes: 233 additions & 0 deletions bin/ev-deployer/src/contracts/permit2.rs

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions bin/ev-deployer/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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);
}
Comment on lines +20 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent silent alloc overwrite when two configured contracts share one address.

At Line 20-23, adding Permit2 means two configured contracts can now collide. insert_contract uses Map::insert, so one entry is silently replaced instead of failing.

🛠️ Proposed fix (fail on duplicate deploy-config addresses)
-pub(crate) fn build_alloc(config: &DeployConfig) -> Value {
+pub(crate) fn build_alloc(config: &DeployConfig) -> eyre::Result<Value> {
     let mut alloc = Map::new();

     if let Some(ref ap_config) = config.contracts.admin_proxy {
         let contract = contracts::admin_proxy::build(ap_config);
-        insert_contract(&mut alloc, &contract);
+        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);
+        insert_contract(&mut alloc, &contract)?;
     }

-    Value::Object(alloc)
+    Ok(Value::Object(alloc))
 }
@@
-    let alloc = build_alloc(config);
+    let alloc = build_alloc(config)?;
@@
-fn insert_contract(alloc: &mut Map<String, Value>, contract: &GenesisContract) {
+fn insert_contract(alloc: &mut Map<String, Value>, contract: &GenesisContract) -> eyre::Result<()> {
     let addr_key = normalize_addr(&format!("{}", contract.address));
@@
-    alloc.insert(addr_key, Value::Object(entry));
+    if alloc.insert(addr_key.clone(), Value::Object(entry)).is_some() {
+        eyre::bail!("duplicate deploy address in config: {addr_key}");
+    }
+    Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ev-deployer/src/genesis.rs` around lines 20 - 23, The insertion of the
Permit2 contract can silently overwrite an existing entry because
insert_contract uses Map::insert; update insert_contract (or the call site) to
detect duplicate deploy-config addresses and fail instead of replacing: before
calling insert_contract (or within insert_contract), check whether the
allocation map already contains the contract's address (use the same unique
identifier produced by contracts::permit2::build, e.g., the contract.address or
contract.id), and if it does return an error or panic with a clear message
indicating a duplicate configured contract address (include the conflicting
address and the two contract identifiers); alternatively use Map::entry and on
Occupied(_) return Err to prevent the silent overwrite.


Value::Object(alloc)
}

Expand Down Expand Up @@ -102,6 +107,7 @@ mod tests {
address: address!("000000000000000000000000000000000000ad00"),
owner: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
}),
permit2: None,
},
}
}
Expand Down
4 changes: 4 additions & 0 deletions bin/ev-deployer/src/init_template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ chain_id = 0
# [contracts.admin_proxy]
# address = "0x000000000000000000000000000000000000Ad00"
# owner = "0x..."

# Permit2: Uniswap canonical token approval manager.
# [contracts.permit2]
# address = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
6 changes: 6 additions & 0 deletions bin/ev-deployer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ fn main() -> eyre::Result<()> {
.as_ref()
.map(|c| c.address)
.ok_or_else(|| eyre::eyre!("admin_proxy 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}"),
};

Expand Down
7 changes: 7 additions & 0 deletions bin/ev-deployer/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,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)
}
Loading
Loading