Skip to main content

espresso_contract_deployer/proposals/
deployment_info.rs

1//! Compile-time embedded deployment-info for all supported networks.
2//!
3//! TOMLs are embedded at compile time so the binary is self-contained and
4//! verification never depends on the local filesystem.
5
6use alloy::primitives::Address;
7use anyhow::{Result, anyhow};
8use serde::Deserialize;
9
10// ── Embedded TOML sources ─────────────────────────────────────────────────────
11
12const DECAF_TOML: &str = include_str!("../../../deployment-info/deployments/decaf.toml");
13const HOODI_TOML: &str = include_str!("../../../deployment-info/deployments/hoodi.toml");
14const MAINNET_TOML: &str = include_str!("../../../deployment-info/deployments/mainnet.toml");
15
16// ── Raw TOML types ────────────────────────────────────────────────────────────
17
18// LightClient is not supported yet: its runtime bytecode links the external
19// PlonkVerifier library, so the binding contains 0xff*20 address placeholders
20// and `compare_normalized` cannot byte-compare it. Supporting it requires
21// resolving the linked library address and verifying the library's bytecode
22// too; deferred until a LightClient upgrade needs this tool.
23#[derive(Debug, Deserialize)]
24struct RawDeploymentInfo {
25    ops_timelock: Option<RawTimelockSection>,
26    safe_exit_timelock: Option<RawTimelockSection>,
27    esp_token: Option<RawContractSection>,
28    fee_contract: Option<RawContractSection>,
29    reward_claim: Option<RawContractSection>,
30    stake_table: Option<RawContractSection>,
31}
32
33#[derive(Debug, Deserialize)]
34struct RawTimelockSection {
35    address: Address,
36    proposers: Option<Vec<RawMember>>,
37    executors: Option<Vec<RawMember>>,
38}
39
40#[derive(Debug, Deserialize)]
41struct RawContractSection {
42    address: Address,
43}
44
45#[derive(Debug, Deserialize)]
46struct RawMember {
47    address: Address,
48    #[allow(dead_code)]
49    name: String,
50}
51
52// ── Public types ──────────────────────────────────────────────────────────────
53
54/// Resolved timelock wiring for a single timelock (ops or safe_exit).
55#[derive(Debug, Clone)]
56pub struct TimelockInfo {
57    pub address: Address,
58    pub proposers: Vec<Address>,
59    pub executors: Vec<Address>,
60}
61
62/// Full deployment-info for a network.
63#[derive(Debug, Clone)]
64pub struct DeploymentInfo {
65    pub ops_timelock: TimelockInfo,
66    pub safe_exit_timelock: TimelockInfo,
67    /// ESP token proxy address.
68    pub esp_token: Address,
69    /// Fee contract proxy address.
70    pub fee_contract: Address,
71    /// RewardClaim proxy address.
72    pub reward_claim: Address,
73    /// StakeTable proxy address (used for StakeTableV2 and StakeTableV3).
74    pub stake_table: Address,
75}
76
77/// Return embedded deployment-info for `network`.
78///
79/// Known networks: "mainnet", "decaf", "hoodi".
80/// Returns `Err` for any other name.
81pub fn deployment_info(network: &str) -> Result<DeploymentInfo> {
82    let src = match network {
83        "mainnet" => MAINNET_TOML,
84        "decaf" => DECAF_TOML,
85        "hoodi" => HOODI_TOML,
86        other => {
87            return Err(anyhow!(
88                "unknown network {:?}; known: mainnet, decaf, hoodi",
89                other
90            ));
91        },
92    };
93    parse_deployment_info(src, network)
94}
95
96fn parse_deployment_info(src: &str, network: &str) -> Result<DeploymentInfo> {
97    let raw: RawDeploymentInfo = toml::from_str(src)
98        .map_err(|e| anyhow!("failed to parse deployment-info for {network}: {e}"))?;
99
100    let ops = raw
101        .ops_timelock
102        .ok_or_else(|| anyhow!("no [ops_timelock] in deployment-info for {network}"))?;
103    let safe_exit = raw
104        .safe_exit_timelock
105        .ok_or_else(|| anyhow!("no [safe_exit_timelock] in deployment-info for {network}"))?;
106
107    let esp_token = raw
108        .esp_token
109        .ok_or_else(|| anyhow!("no [esp_token] in deployment-info for {network}"))?
110        .address;
111    let fee_contract = raw
112        .fee_contract
113        .ok_or_else(|| anyhow!("no [fee_contract] in deployment-info for {network}"))?
114        .address;
115    let reward_claim = raw
116        .reward_claim
117        .ok_or_else(|| anyhow!("no [reward_claim] in deployment-info for {network}"))?
118        .address;
119    let stake_table = raw
120        .stake_table
121        .ok_or_else(|| anyhow!("no [stake_table] in deployment-info for {network}"))?
122        .address;
123
124    Ok(DeploymentInfo {
125        ops_timelock: TimelockInfo {
126            address: ops.address,
127            proposers: ops
128                .proposers
129                .unwrap_or_default()
130                .into_iter()
131                .map(|m| m.address)
132                .collect(),
133            executors: ops
134                .executors
135                .unwrap_or_default()
136                .into_iter()
137                .map(|m| m.address)
138                .collect(),
139        },
140        safe_exit_timelock: TimelockInfo {
141            address: safe_exit.address,
142            proposers: safe_exit
143                .proposers
144                .unwrap_or_default()
145                .into_iter()
146                .map(|m| m.address)
147                .collect(),
148            executors: safe_exit
149                .executors
150                .unwrap_or_default()
151                .into_iter()
152                .map(|m| m.address)
153                .collect(),
154        },
155        esp_token,
156        fee_contract,
157        reward_claim,
158        stake_table,
159    })
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_deployment_info_unknown_network_errors() {
168        let err = deployment_info("bogusnet").unwrap_err();
169        assert!(err.to_string().contains("unknown network"), "{err}");
170    }
171
172    #[test]
173    fn test_deployment_info_decaf_timelock_addresses() {
174        let info = deployment_info("decaf").unwrap();
175        let ops_addr: Address = "0x8e3b6563d683b87964104a2c3a4bf542bb70767f"
176            .parse()
177            .unwrap();
178        let safe_exit_addr: Address = "0x0eb0ef3b5a46a444c38da452055bddb273550d5c"
179            .parse()
180            .unwrap();
181        assert_eq!(info.ops_timelock.address, ops_addr);
182        assert_eq!(info.safe_exit_timelock.address, safe_exit_addr);
183        assert_eq!(info.ops_timelock.proposers.len(), 1);
184        assert_eq!(info.ops_timelock.executors.len(), 1);
185        assert_eq!(info.safe_exit_timelock.proposers.len(), 1);
186        assert_eq!(info.safe_exit_timelock.executors.len(), 1);
187    }
188
189    #[test]
190    fn test_deployment_info_mainnet_timelock_addresses() {
191        let info = deployment_info("mainnet").unwrap();
192        let ops_addr: Address = "0x67861f1ef4db9bcaddd8c5e86db92386dd4ec700"
193            .parse()
194            .unwrap();
195        let safe_exit_addr: Address = "0x6e7941fe8f9c751363b5c156419a0c8912dea6b2"
196            .parse()
197            .unwrap();
198        assert_eq!(info.ops_timelock.address, ops_addr);
199        assert_eq!(info.safe_exit_timelock.address, safe_exit_addr);
200        assert_eq!(info.ops_timelock.proposers.len(), 2);
201        assert_eq!(info.ops_timelock.executors.len(), 1);
202    }
203
204    #[test]
205    fn test_deployment_info_decaf_proposer_address() {
206        let info = deployment_info("decaf").unwrap();
207        let espresso_labs: Address = "0xb76834e371b666feee48e5d7d9a97ca08b5a0620"
208            .parse()
209            .unwrap();
210        assert_eq!(info.ops_timelock.proposers[0], espresso_labs);
211        assert_eq!(info.ops_timelock.executors[0], espresso_labs);
212    }
213
214    #[test]
215    fn test_deployment_info_mainnet_multiple_proposers() {
216        let info = deployment_info("mainnet").unwrap();
217        assert_eq!(info.ops_timelock.proposers.len(), 2);
218        assert_eq!(info.ops_timelock.executors.len(), 1);
219    }
220}