arb_precompiles/
arbnativetokenmanager.rs1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, Log, B256, U256};
3use alloy_sol_types::{SolEvent, SolInterface};
4use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
5
6use crate::{
7 interfaces::IArbNativeTokenManager,
8 storage_slot::{
9 derive_subspace_key, map_slot_b256, ARBOS_STATE_ADDRESS, NATIVE_TOKEN_SUBSPACE,
10 ROOT_STORAGE_KEY,
11 },
12};
13
14pub const ARBNATIVETOKENMANAGER_ADDRESS: Address = Address::new([
16 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
17 0x00, 0x00, 0x00, 0x73,
18]);
19
20const SLOAD_GAS: u64 = 800;
21const COPY_GAS: u64 = 3;
22
23const MINT_BURN_GAS: u64 = 100 + 9000;
25
26const EVENT_GAS: u64 = 375 + 2 * 375 + 8 * 32;
28
29pub fn create_arbnativetokenmanager_precompile() -> DynPrecompile {
30 DynPrecompile::new_stateful(PrecompileId::custom("arbnativetokenmanager"), handler)
31}
32
33fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
34 if let Some(result) =
35 crate::check_precompile_version(arb_chainspec::arbos_version::ARBOS_VERSION_41)
36 {
37 return result;
38 }
39
40 let gas_limit = input.gas;
41 crate::init_precompile_gas(input.data.len());
42
43 let call = match IArbNativeTokenManager::ArbNativeTokenManagerCalls::abi_decode(input.data) {
44 Ok(c) => c,
45 Err(_) => return crate::burn_all_revert(gas_limit),
46 };
47
48 use IArbNativeTokenManager::ArbNativeTokenManagerCalls;
49 let result = match call {
50 ArbNativeTokenManagerCalls::mintNativeToken(c) => handle_mint(&mut input, c.amount),
51 ArbNativeTokenManagerCalls::burnNativeToken(c) => handle_burn(&mut input, c.amount),
52 };
53 crate::gas_check(gas_limit, result)
54}
55
56fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
59 input
60 .internals_mut()
61 .load_account(ARBOS_STATE_ADDRESS)
62 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
63 Ok(())
64}
65
66fn sload_arbos(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
67 let val = input
68 .internals_mut()
69 .sload(ARBOS_STATE_ADDRESS, slot)
70 .map_err(|_| PrecompileError::other("sload failed"))?;
71 Ok(val.data)
72}
73
74fn is_native_token_owner(
76 input: &mut PrecompileInput<'_>,
77 addr: Address,
78) -> Result<bool, PrecompileError> {
79 let owner_key = derive_subspace_key(ROOT_STORAGE_KEY, NATIVE_TOKEN_SUBSPACE);
82 let by_address_key = derive_subspace_key(owner_key.as_slice(), &[0]);
83 let addr_hash = B256::left_padding_from(addr.as_slice());
84 let slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
85 let val = sload_arbos(input, slot)?;
86 Ok(val != U256::ZERO)
87}
88
89fn handle_mint(input: &mut PrecompileInput<'_>, amount: U256) -> PrecompileResult {
90 let gas_limit = input.gas;
91 let caller = input.caller;
92 load_arbos(input)?;
93
94 if !is_native_token_owner(input, caller)? {
95 return Err(PrecompileError::other("caller is not a native token owner"));
96 }
97
98 input
99 .internals_mut()
100 .balance_incr(caller, amount)
101 .map_err(|e| PrecompileError::other(format!("balance_incr: {e:?}")))?;
102
103 let topic1 = B256::left_padding_from(caller.as_slice());
104 let event_data = amount.to_be_bytes::<32>().to_vec();
105 input.internals_mut().log(Log::new_unchecked(
106 ARBNATIVETOKENMANAGER_ADDRESS,
107 vec![
108 IArbNativeTokenManager::NativeTokenMinted::SIGNATURE_HASH,
109 topic1,
110 ],
111 event_data.into(),
112 ));
113
114 let gas_used = (SLOAD_GAS + SLOAD_GAS + MINT_BURN_GAS + EVENT_GAS + COPY_GAS).min(gas_limit);
115 Ok(PrecompileOutput::new(gas_used, vec![].into()))
116}
117
118fn handle_burn(input: &mut PrecompileInput<'_>, amount: U256) -> PrecompileResult {
119 let gas_limit = input.gas;
120 let caller = input.caller;
121 load_arbos(input)?;
122
123 if !is_native_token_owner(input, caller)? {
124 return Err(PrecompileError::other("caller is not a native token owner"));
125 }
126
127 let acct = input
128 .internals_mut()
129 .load_account(caller)
130 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
131 let current_balance = acct.data.info.balance;
132
133 if current_balance < amount {
134 return Err(PrecompileError::other("burn amount exceeds balance"));
135 }
136
137 let new_balance = current_balance - amount;
138 input
139 .internals_mut()
140 .set_balance(caller, new_balance)
141 .map_err(|e| PrecompileError::other(format!("set_balance: {e:?}")))?;
142
143 let topic1 = B256::left_padding_from(caller.as_slice());
144 let event_data = amount.to_be_bytes::<32>().to_vec();
145 input.internals_mut().log(Log::new_unchecked(
146 ARBNATIVETOKENMANAGER_ADDRESS,
147 vec![
148 IArbNativeTokenManager::NativeTokenBurned::SIGNATURE_HASH,
149 topic1,
150 ],
151 event_data.into(),
152 ));
153
154 let gas_used = (SLOAD_GAS + SLOAD_GAS + MINT_BURN_GAS + EVENT_GAS + COPY_GAS).min(gas_limit);
155 Ok(PrecompileOutput::new(gas_used, vec![].into()))
156}