espresso_contract_deployer/proposals/
deployment_info.rs1use alloy::primitives::Address;
7use anyhow::{Result, anyhow};
8use serde::Deserialize;
9
10const 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#[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#[derive(Debug, Clone)]
56pub struct TimelockInfo {
57 pub address: Address,
58 pub proposers: Vec<Address>,
59 pub executors: Vec<Address>,
60}
61
62#[derive(Debug, Clone)]
64pub struct DeploymentInfo {
65 pub ops_timelock: TimelockInfo,
66 pub safe_exit_timelock: TimelockInfo,
67 pub esp_token: Address,
69 pub fee_contract: Address,
71 pub reward_claim: Address,
73 pub stake_table: Address,
75}
76
77pub 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}