Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
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
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
9729cc0
fix(ev-deployer): use case-insensitive grep in e2e genesis address ch…
randygrok Mar 25, 2026
a881b75
merge(ev-deployer): merge main into ev-deployer-merkle-tree-hook
randygrok Mar 26, 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
3 changes: 3 additions & 0 deletions .github/workflows/ev_deployer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Install Hyperlane soldeer dependencies
run: cd contracts/lib/hyperlane-monorepo/solidity && forge soldeer install

- name: Run bytecode verification tests
run: cargo test -p ev-deployer -- --ignored --test-threads=1

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/hyperlane-monorepo"]
path = contracts/lib/hyperlane-monorepo
url = https://github.com/hyperlane-xyz/hyperlane-monorepo.git
Comment on lines +4 to +6
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Submodule declaration appears incomplete without committed gitlink.

If only .gitmodules is added but the submodule gitlink is not committed, actions/checkout with recursive submodules will not materialize contracts/lib/hyperlane-monorepo, and the workflow step at Line 44 in .github/workflows/ev_deployer.yml will fail on cd .../solidity.

Please ensure the submodule pointer (gitlink) is included in the PR, pinned to the intended commit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitmodules around lines 4 - 6, The .gitmodules entry for the submodule
"contracts/lib/hyperlane-monorepo" is present but the repository lacks the
corresponding gitlink (submodule pointer), so CI won't check out the submodule;
add and commit the submodule gitlink pinned to the intended commit: either run
git submodule add <url> contracts/lib/hyperlane-monorepo (or update the
submodule to the target commit and run git add on the submodule directory) so
the index contains the 160000 gitlink entry, commit that change alongside the
.gitmodules update, and push the commit so workflows (ev_deployer.yml) can
recurse and find the solidity code.

76 changes: 64 additions & 12 deletions bin/ev-deployer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,7 @@ 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"
```
See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicated sentence in configuration section.

Line 17 repeats the same examples/devnet.toml guidance already stated at Line 15.

Proposed docs cleanup
-See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured.
📝 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
See [`examples/devnet.toml`](examples/devnet.toml) for a complete example with all contracts configured.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ev-deployer/README.md` at line 17, Remove the duplicated sentence "See
[`examples/devnet.toml`](examples/devnet.toml) for a complete example with all
contracts configured." from the README so it only appears once; keep the
original occurrence and delete the redundant repeat to avoid repetition in the
configuration section.


### Config reference

Expand All @@ -38,6 +31,55 @@ owner = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
| `address` | address | Address to deploy at |
| `owner` | address | Owner (must not be zero) |

#### `[contracts.fee_vault]`

| Field | Type | Description |
|----------------------|---------|--------------------------------------------------|
| `address` | address | Address to deploy at |
| `owner` | address | Owner address |
| `destination_domain` | u32 | Hyperlane destination domain (default: 0) |
| `recipient_address` | bytes32 | Hyperlane recipient address (default: zero) |
| `minimum_amount` | u64 | Minimum amount for bridging (default: 0) |
| `call_fee` | u64 | Call fee for sendToCelestia (default: 0) |
| `bridge_share_bps` | u64 | Basis points for bridge share, 0–10000 (default: 0, treated as 10000) |
| `other_recipient` | address | Other recipient for split accounting (default: zero) |
| `hyp_native_minter` | address | HypNativeMinter address (default: zero) |

#### `[contracts.mailbox]`

| Field | Type | Description |
|-----------------|---------|-----------------------------------------------------|
| `address` | address | Address to deploy at |
| `owner` | address | Owner address (default: zero) |
| `default_ism` | address | Default interchain security module (default: zero) |
| `default_hook` | address | Default post-dispatch hook (default: zero) |
| `required_hook` | address | Required post-dispatch hook, e.g. MerkleTreeHook (default: zero) |

#### `[contracts.merkle_tree_hook]`

| Field | Type | Description |
|-----------|---------|----------------------------------------------------|
| `address` | address | Address to deploy at |
| `owner` | address | Owner address (default: zero) |
| `mailbox` | address | Mailbox address (patched into bytecode as immutable)|

#### `[contracts.noop_ism]`

| Field | Type | Description |
|-----------|---------|----------------------|
| `address` | address | Address to deploy at |

#### `[contracts.protocol_fee]`

| Field | Type | Description |
|--------------------|---------|---------------------------------------------------|
| `address` | address | Address to deploy at |
| `owner` | address | Owner address (default: zero) |
| `max_protocol_fee` | u64 | Maximum protocol fee in wei |
| `protocol_fee` | u64 | Protocol fee charged per dispatch in wei (default: 0) |
| `beneficiary` | address | Beneficiary address that receives collected fees (default: zero) |


## Usage

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

```json
{
"admin_proxy": "0x000000000000000000000000000000000000Ad00"
"admin_proxy": "0x000000000000000000000000000000000000Ad00",
"fee_vault": "0x000000000000000000000000000000000000FE00",
"mailbox": "0x0000000000000000000000000000000000001200",
"merkle_tree_hook": "0x0000000000000000000000000000000000001100",
"noop_ism": "0x0000000000000000000000000000000000001300",
"protocol_fee": "0x0000000000000000000000000000000000001400"
}
```

Expand All @@ -100,9 +147,14 @@ 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 |
| `fee_vault` | Fee vault with Hyperlane bridging support |
| `mailbox` | Hyperlane core messaging hub |
| `merkle_tree_hook` | Hyperlane required hook (Merkle tree for messages) |
| `noop_ism` | Hyperlane ISM that accepts all messages |
| `protocol_fee` | Hyperlane post-dispatch hook that charges a protocol fee|

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

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

[contracts.fee_vault]
address = "0x000000000000000000000000000000000000FE00"
owner = "0x000000000000000000000000000000000000Ad00"
destination_domain = 0
recipient_address = "0x0000000000000000000000000000000000000000000000000000000000000000"
minimum_amount = 0
call_fee = 0
bridge_share_bps = 10000
other_recipient = "0x0000000000000000000000000000000000000000"
hyp_native_minter = "0x0000000000000000000000000000000000000000"

[contracts.mailbox]
Copy link
Contributor

Choose a reason for hiding this comment

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

Should hyperlane contain its own namespace under contracts in order to logically group components. For example, mailbox, isms, hooks contracts are directly parts of hyperlane protocol, where as admin proxy / fee vault are not.

Suggested change
[contracts.mailbox]
[contracts.hyperlane.mailbox]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

makes total sense

address = "0x0000000000000000000000000000000000001200"
owner = "0x000000000000000000000000000000000000Ad00"
default_ism = "0x0000000000000000000000000000000000001300"
default_hook = "0x0000000000000000000000000000000000001400"
required_hook = "0x0000000000000000000000000000000000001100"

[contracts.merkle_tree_hook]
address = "0x0000000000000000000000000000000000001100"
owner = "0x000000000000000000000000000000000000Ad00"
mailbox = "0x0000000000000000000000000000000000001200"

[contracts.noop_ism]
address = "0x0000000000000000000000000000000000001300"

[contracts.protocol_fee]
address = "0x0000000000000000000000000000000000001400"
owner = "0x000000000000000000000000000000000000Ad00"
max_protocol_fee = 1000000000000000000
protocol_fee = 0
beneficiary = "0x000000000000000000000000000000000000Ad00"
186 changes: 185 additions & 1 deletion bin/ev-deployer/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! TOML config types, parsing, and validation.

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

Expand Down Expand Up @@ -28,6 +28,16 @@ pub(crate) struct ChainConfig {
pub(crate) struct ContractsConfig {
/// `AdminProxy` contract config (optional).
pub admin_proxy: Option<AdminProxyConfig>,
/// `FeeVault` contract config (optional).
pub fee_vault: Option<FeeVaultConfig>,
/// `MerkleTreeHook` contract config (optional).
pub merkle_tree_hook: Option<MerkleTreeHookConfig>,
/// `Mailbox` contract config (optional).
pub mailbox: Option<MailboxConfig>,
/// `NoopIsm` contract config (optional).
pub noop_ism: Option<NoopIsmConfig>,
/// `ProtocolFee` contract config (optional).
pub protocol_fee: Option<ProtocolFeeConfig>,
}

/// `AdminProxy` configuration.
Expand All @@ -39,6 +49,92 @@ pub(crate) struct AdminProxyConfig {
pub owner: Address,
}

/// `FeeVault` configuration.
#[derive(Debug, Deserialize)]
pub(crate) struct FeeVaultConfig {
/// Address to deploy at.
pub address: Address,
/// Owner address.
pub owner: Address,
/// Hyperlane destination domain.
#[serde(default)]
pub destination_domain: u32,
/// Hyperlane recipient address (bytes32).
#[serde(default)]
pub recipient_address: B256,
/// Minimum amount for bridging.
#[serde(default)]
pub minimum_amount: u64,
/// Call fee for sendToCelestia.
#[serde(default)]
pub call_fee: u64,
/// Basis points for bridge share (0-10000). 0 defaults to 10000.
#[serde(default)]
pub bridge_share_bps: u64,
/// Other recipient for split accounting.
#[serde(default)]
pub other_recipient: Address,
/// `HypNativeMinter` address.
#[serde(default)]
pub hyp_native_minter: Address,
}

/// `MerkleTreeHook` configuration (Hyperlane required hook).
#[derive(Debug, Deserialize)]
pub(crate) struct MerkleTreeHookConfig {
/// Address to deploy at.
pub address: Address,
/// Owner address (for post-genesis hook/ISM changes).
#[serde(default)]
pub owner: Address,
/// Mailbox address (patched into bytecode as immutable).
pub mailbox: Address,
}

/// `ProtocolFee` configuration (Hyperlane post-dispatch hook that charges a protocol fee).
#[derive(Debug, Deserialize)]
pub(crate) struct ProtocolFeeConfig {
/// Address to deploy at.
pub address: Address,
/// Owner address.
#[serde(default)]
pub owner: Address,
/// Maximum protocol fee in wei.
pub max_protocol_fee: u64,
/// Protocol fee charged per dispatch in wei.
#[serde(default)]
pub protocol_fee: u64,
/// Beneficiary address that receives collected fees.
#[serde(default)]
pub beneficiary: Address,
}

/// `Mailbox` configuration (Hyperlane core messaging hub).
#[derive(Debug, Deserialize)]
pub(crate) struct MailboxConfig {
/// Address to deploy at.
pub address: Address,
/// Owner address.
#[serde(default)]
pub owner: Address,
/// Default interchain security module.
#[serde(default)]
pub default_ism: Address,
/// Default post-dispatch hook.
#[serde(default)]
pub default_hook: Address,
/// Required post-dispatch hook (e.g. `MerkleTreeHook`).
#[serde(default)]
pub required_hook: Address,
}

/// `NoopIsm` configuration (Hyperlane ISM that accepts all messages).
#[derive(Debug, Deserialize)]
pub(crate) struct NoopIsmConfig {
/// 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 +153,43 @@ impl DeployConfig {
);
}

if let Some(ref fv) = self.contracts.fee_vault {
eyre::ensure!(
!fv.owner.is_zero(),
"fee_vault.owner must not be the zero address"
);
eyre::ensure!(
fv.bridge_share_bps <= 10000,
"fee_vault.bridge_share_bps must be 0-10000, got {}",
fv.bridge_share_bps
);
}

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

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

if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) {
eyre::ensure!(
ap.address != fv.address,
"contracts.admin_proxy.address and contracts.fee_vault.address must be distinct"
);
}
Comment on lines +186 to +191
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Address collision validation is incomplete—silent overwrites possible.

Only admin_proxy vs fee_vault is checked, but there are 6 contract types now. If any two contracts share the same address, build_alloc() will silently overwrite one entry with another (per the insert_contract() pattern in genesis.rs), leading to a corrupted genesis state.

Consider collecting all configured addresses and validating they are distinct:

🛠️ Suggested approach to validate all addresses are distinct
+        // Collect all configured contract addresses and ensure they are distinct.
+        let mut addresses: Vec<(&str, Address)> = Vec::new();
+        if let Some(ref ap) = self.contracts.admin_proxy {
+            addresses.push(("admin_proxy", ap.address));
+        }
+        if let Some(ref fv) = self.contracts.fee_vault {
+            addresses.push(("fee_vault", fv.address));
+        }
+        if let Some(ref mth) = self.contracts.merkle_tree_hook {
+            addresses.push(("merkle_tree_hook", mth.address));
+        }
+        if let Some(ref mb) = self.contracts.mailbox {
+            addresses.push(("mailbox", mb.address));
+        }
+        if let Some(ref ni) = self.contracts.noop_ism {
+            addresses.push(("noop_ism", ni.address));
+        }
+        if let Some(ref pf) = self.contracts.protocol_fee {
+            addresses.push(("protocol_fee", pf.address));
+        }
+        for i in 0..addresses.len() {
+            for j in (i + 1)..addresses.len() {
+                eyre::ensure!(
+                    addresses[i].1 != addresses[j].1,
+                    "contracts.{}.address and contracts.{}.address must be distinct",
+                    addresses[i].0,
+                    addresses[j].0
+                );
+            }
+        }
+
-        if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) {
-            eyre::ensure!(
-                ap.address != fv.address,
-                "contracts.admin_proxy.address and contracts.fee_vault.address must be distinct"
-            );
-        }
📝 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
if let (Some(ap), Some(fv)) = (&self.contracts.admin_proxy, &self.contracts.fee_vault) {
eyre::ensure!(
ap.address != fv.address,
"contracts.admin_proxy.address and contracts.fee_vault.address must be distinct"
);
}
// Collect all configured contract addresses and ensure they are distinct.
let mut addresses: Vec<(&str, Address)> = Vec::new();
if let Some(ref ap) = self.contracts.admin_proxy {
addresses.push(("admin_proxy", ap.address));
}
if let Some(ref fv) = self.contracts.fee_vault {
addresses.push(("fee_vault", fv.address));
}
if let Some(ref mth) = self.contracts.merkle_tree_hook {
addresses.push(("merkle_tree_hook", mth.address));
}
if let Some(ref mb) = self.contracts.mailbox {
addresses.push(("mailbox", mb.address));
}
if let Some(ref ni) = self.contracts.noop_ism {
addresses.push(("noop_ism", ni.address));
}
if let Some(ref pf) = self.contracts.protocol_fee {
addresses.push(("protocol_fee", pf.address));
}
for i in 0..addresses.len() {
for j in (i + 1)..addresses.len() {
eyre::ensure!(
addresses[i].1 != addresses[j].1,
"contracts.{}.address and contracts.{}.address must be distinct",
addresses[i].0,
addresses[j].0
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/ev-deployer/src/config.rs` around lines 186 - 191, The current validation
only compares contracts.admin_proxy and contracts.fee_vault, leaving other
contract addresses unchecked and allowing silent overwrites when
build_alloc()/insert_contract() merges entries; update the validation in the
Config (where self.contracts is checked) to collect all present contract
addresses from each optional contract field (e.g., admin_proxy, fee_vault and
the other four contract variants) into a map or vector, detect duplicates (same
address appearing for more than one contract), and return an error via
eyre::ensure! (or similar) listing the conflicting contract names; reference
self.contracts and build_alloc()/insert_contract() to locate the relevant logic.


Ok(())
}
}
Expand Down Expand Up @@ -107,6 +240,57 @@ chain_id = 1
}

#[test]
fn parse_merkle_tree_hook_config() {
let toml = r#"
[chain]
chain_id = 1234

[contracts.merkle_tree_hook]
address = "0x0000000000000000000000000000000000001100"
owner = "0x000000000000000000000000000000000000ad00"
mailbox = "0x0000000000000000000000000000000000001200"
"#;
let config: DeployConfig = toml::from_str(toml).unwrap();
config.validate().unwrap();
assert!(config.contracts.merkle_tree_hook.is_some());
let mth = config.contracts.merkle_tree_hook.unwrap();
assert!(!mth.mailbox.is_zero());
}

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

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

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

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

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

#[test]

fn admin_proxy_only() {
let toml = r#"
[chain]
Expand Down
Loading
Loading