1use std::{path::Path, str::FromStr, sync::Arc};
6
7use alloy_genesis::GenesisAccount;
8use alloy_primitives::{Address, B256, U256};
9use eyre::eyre;
10use reth_chainspec::ChainSpec;
11use reth_cli::chainspec::ChainSpecParser;
12use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
13use revm::database::{EmptyDB, State, StateBuilder};
14use revm_database::states::bundle_state::BundleRetention;
15use serde_json::Value;
16
17use arbos::arbos_types::ParsedInitMessage;
18
19use crate::genesis;
20
21#[derive(Debug, Clone, Default)]
22#[non_exhaustive]
23pub struct ArbChainSpecParser;
24
25impl ChainSpecParser for ArbChainSpecParser {
26 type ChainSpec = ChainSpec;
27
28 const SUPPORTED_CHAINS: &'static [&'static str] = EthereumChainSpecParser::SUPPORTED_CHAINS;
29
30 fn parse(s: &str) -> eyre::Result<Arc<ChainSpec>> {
31 if EthereumChainSpecParser::SUPPORTED_CHAINS.contains(&s) {
32 return EthereumChainSpecParser::parse(s);
33 }
34
35 let raw = if Path::new(s).exists() {
36 std::fs::read_to_string(s).map_err(|e| eyre!("read chain spec {s}: {e}"))?
37 } else {
38 s.to_string()
39 };
40
41 let mut value: Value =
42 serde_json::from_str(&raw).map_err(|e| eyre!("parse chain spec JSON: {e}"))?;
43
44 let initial_arbos = value
45 .pointer("/config/arbitrum/InitialArbOSVersion")
46 .and_then(Value::as_u64)
47 .unwrap_or(0);
48 let chain_id = value
49 .pointer("/config/chainId")
50 .and_then(Value::as_u64)
51 .unwrap_or(0);
52 let initial_owner = value
53 .pointer("/config/arbitrum/InitialChainOwner")
54 .and_then(Value::as_str)
55 .and_then(|s| Address::from_str(s.trim_start_matches("0x")).ok())
56 .unwrap_or(Address::ZERO);
57 let arbos_init = parse_arbos_init(&value);
58
59 let allow_debug = value
60 .pointer("/config/arbitrum/AllowDebugPrecompiles")
61 .and_then(Value::as_bool)
62 .unwrap_or(false);
63 arb_precompiles::set_allow_debug_precompiles(allow_debug);
64
65 if initial_arbos > 0 && chain_id > 0 {
66 inject_arbos_alloc(
67 &mut value,
68 chain_id,
69 initial_arbos,
70 initial_owner,
71 arbos_init,
72 )?;
73 }
74
75 let augmented = serde_json::to_string(&value)?;
76 EthereumChainSpecParser::parse(&augmented)
77 }
78}
79
80fn parse_arbos_init(value: &Value) -> genesis::ArbOSInit {
81 let native = value
82 .pointer("/config/arbitrum/ArbOSInit/nativeTokenSupplyManagementEnabled")
83 .or_else(|| value.pointer("/config/arbitrum/nativeTokenSupplyManagementEnabled"))
84 .and_then(Value::as_bool)
85 .unwrap_or(false);
86 let filtering = value
87 .pointer("/config/arbitrum/ArbOSInit/transactionFilteringEnabled")
88 .or_else(|| value.pointer("/config/arbitrum/transactionFilteringEnabled"))
89 .and_then(Value::as_bool)
90 .unwrap_or(false);
91 genesis::ArbOSInit {
92 native_token_supply_management_enabled: native,
93 transaction_filtering_enabled: filtering,
94 }
95}
96
97fn inject_arbos_alloc(
98 value: &mut Value,
99 chain_id: u64,
100 arbos_version: u64,
101 chain_owner: Address,
102 arbos_init: genesis::ArbOSInit,
103) -> eyre::Result<()> {
104 let alloc_obj = value
105 .as_object_mut()
106 .ok_or_else(|| eyre!("chain spec is not a JSON object"))?
107 .entry("alloc")
108 .or_insert_with(|| Value::Object(serde_json::Map::new()))
109 .as_object_mut()
110 .ok_or_else(|| eyre!("alloc is not a JSON object"))?;
111
112 let entries = compute_arbos_alloc(chain_id, arbos_version, chain_owner, arbos_init)?;
113 for (addr, account) in entries {
114 let key = address_lower_no_prefix(addr);
115 let prefixed = format!("0x{key}");
116 let existing_key = if alloc_obj.contains_key(&key) {
117 Some(key.clone())
118 } else if alloc_obj.contains_key(&prefixed) {
119 Some(prefixed.clone())
120 } else {
121 None
122 };
123 let injected = serde_json::to_value(&account)?;
124 match existing_key {
125 None => {
126 alloc_obj.insert(prefixed, injected);
127 }
128 Some(k) => {
129 let user = alloc_obj.get_mut(&k).unwrap();
135 if !user.is_object() || !injected.is_object() {
136 continue;
137 }
138 let user_obj = user.as_object_mut().unwrap();
139 let injected_obj = injected.as_object().unwrap();
140 for (field, val) in injected_obj {
141 if field == "storage" {
142 continue;
143 }
144 user_obj.entry(field.clone()).or_insert(val.clone());
145 }
146 let injected_storage = injected
147 .get("storage")
148 .and_then(|s| s.as_object())
149 .cloned()
150 .unwrap_or_default();
151 let storage = user_obj
152 .entry("storage")
153 .or_insert_with(|| Value::Object(serde_json::Map::new()))
154 .as_object_mut()
155 .ok_or_else(|| eyre!("alloc[{k}].storage is not an object"))?;
156 for (slot, val) in injected_storage {
157 storage.entry(slot).or_insert(val);
158 }
159 }
160 }
161 }
162 Ok(())
163}
164
165fn address_lower_no_prefix(addr: Address) -> String {
166 let s = format!("{addr:x}");
167 let mut padded = String::with_capacity(40);
168 for _ in 0..(40 - s.len()) {
169 padded.push('0');
170 }
171 padded.push_str(&s);
172 padded
173}
174
175pub fn compute_arbos_alloc(
180 chain_id: u64,
181 arbos_version: u64,
182 chain_owner: Address,
183 arbos_init: genesis::ArbOSInit,
184) -> eyre::Result<Vec<(Address, GenesisAccount)>> {
185 let mut state: State<EmptyDB> = StateBuilder::new()
186 .with_database(EmptyDB::default())
187 .with_bundle_update()
188 .build();
189
190 let init_msg = ParsedInitMessage {
191 chain_id: U256::from(chain_id),
192 initial_l1_base_fee: U256::ZERO,
193 serialized_chain_config: Vec::new(),
194 };
195
196 genesis::initialize_arbos_state(
197 &mut state,
198 &init_msg,
199 chain_id,
200 arbos_version,
201 chain_owner,
202 arbos_init,
203 )
204 .map_err(|e| eyre!("initialize_arbos_state: {e}"))?;
205
206 state.merge_transitions(BundleRetention::PlainState);
207 let bundle = state.take_bundle();
208
209 let mut out = Vec::new();
210 for (addr, account) in bundle.state.iter() {
211 let info = match &account.info {
212 Some(info) => info,
213 None => continue,
214 };
215
216 let mut storage = std::collections::BTreeMap::new();
217 for (slot, slot_value) in account.storage.iter() {
218 if slot_value.present_value.is_zero() {
219 continue;
220 }
221 storage.insert(
222 B256::from(slot.to_be_bytes::<32>()),
223 B256::from(slot_value.present_value.to_be_bytes::<32>()),
224 );
225 }
226
227 let code = match &info.code {
228 Some(c) if !c.original_bytes().is_empty() => Some(c.original_bytes()),
229 _ => None,
230 };
231
232 let entry = GenesisAccount {
233 balance: info.balance,
234 nonce: Some(info.nonce),
235 code,
236 storage: if storage.is_empty() {
237 None
238 } else {
239 Some(storage)
240 },
241 private_key: None,
242 };
243 out.push((*addr, entry));
244 }
245 out.sort_by_key(|(a, _)| *a);
246 Ok(out)
247}