arb_precompiles/
arbnativetokenmanager.rs1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, B256, U256};
3use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
4
5use crate::storage_slot::{
6 derive_subspace_key, map_slot_b256, ARBOS_STATE_ADDRESS, NATIVE_TOKEN_SUBSPACE,
7 ROOT_STORAGE_KEY,
8};
9
10pub const ARBNATIVETOKENMANAGER_ADDRESS: Address = Address::new([
12 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13 0x00, 0x00, 0x00, 0x73,
14]);
15
16const MINT_NATIVE_TOKEN: [u8; 4] = [0xf2, 0xe2, 0x34, 0x70];
18const BURN_NATIVE_TOKEN: [u8; 4] = [0xa7, 0x54, 0x40, 0x2b];
19
20const SLOAD_GAS: u64 = 800;
21const COPY_GAS: u64 = 3;
22
23const MINT_BURN_GAS: u64 = 100 + 9000;
25
26pub fn create_arbnativetokenmanager_precompile() -> DynPrecompile {
27 DynPrecompile::new_stateful(PrecompileId::custom("arbnativetokenmanager"), handler)
28}
29
30fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
31 if let Some(result) =
33 crate::check_precompile_version(arb_chainspec::arbos_version::ARBOS_VERSION_41)
34 {
35 return result;
36 }
37
38 let data = input.data;
39 if data.len() < 4 {
40 return Err(PrecompileError::other("input too short"));
41 }
42
43 let selector: [u8; 4] = [data[0], data[1], data[2], data[3]];
44
45 let result = match selector {
46 MINT_NATIVE_TOKEN => handle_mint(&mut input),
47 BURN_NATIVE_TOKEN => handle_burn(&mut input),
48 _ => Err(PrecompileError::other("unknown selector")),
49 };
50 crate::gas_check(input.gas, result)
51}
52
53fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
56 input
57 .internals_mut()
58 .load_account(ARBOS_STATE_ADDRESS)
59 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
60 Ok(())
61}
62
63fn sload_arbos(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
64 let val = input
65 .internals_mut()
66 .sload(ARBOS_STATE_ADDRESS, slot)
67 .map_err(|_| PrecompileError::other("sload failed"))?;
68 Ok(val.data)
69}
70
71fn is_native_token_owner(
73 input: &mut PrecompileInput<'_>,
74 addr: Address,
75) -> Result<bool, PrecompileError> {
76 let owner_key = derive_subspace_key(ROOT_STORAGE_KEY, NATIVE_TOKEN_SUBSPACE);
79 let by_address_key = derive_subspace_key(owner_key.as_slice(), &[0]);
80 let addr_hash = B256::left_padding_from(addr.as_slice());
81 let slot = map_slot_b256(by_address_key.as_slice(), &addr_hash);
82 let val = sload_arbos(input, slot)?;
83 Ok(val != U256::ZERO)
84}
85
86fn handle_mint(input: &mut PrecompileInput<'_>) -> PrecompileResult {
88 let data = input.data;
89 if data.len() < 36 {
90 return Err(PrecompileError::other("input too short"));
91 }
92
93 let gas_limit = input.gas;
94 let amount = U256::from_be_slice(&data[4..36]);
95 let caller = input.caller;
96 load_arbos(input)?;
97
98 if !is_native_token_owner(input, caller)? {
99 return Err(PrecompileError::other("caller is not a native token owner"));
100 }
101
102 input
104 .internals_mut()
105 .balance_incr(caller, amount)
106 .map_err(|e| PrecompileError::other(format!("balance_incr: {e:?}")))?;
107
108 let gas_used = (SLOAD_GAS + MINT_BURN_GAS + COPY_GAS).min(gas_limit);
109 Ok(PrecompileOutput::new(gas_used, vec![].into()))
110}
111
112fn handle_burn(input: &mut PrecompileInput<'_>) -> PrecompileResult {
114 let data = input.data;
115 if data.len() < 36 {
116 return Err(PrecompileError::other("input too short"));
117 }
118
119 let gas_limit = input.gas;
120 let amount = U256::from_be_slice(&data[4..36]);
121 let caller = input.caller;
122 load_arbos(input)?;
123
124 if !is_native_token_owner(input, caller)? {
125 return Err(PrecompileError::other("caller is not a native token owner"));
126 }
127
128 let acct = input
130 .internals_mut()
131 .load_account(caller)
132 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
133 let current_balance = acct.data.info.balance;
134
135 if current_balance < amount {
136 return Err(PrecompileError::other("burn amount exceeds balance"));
137 }
138
139 let new_balance = current_balance - amount;
141 input
142 .internals_mut()
143 .set_balance(caller, new_balance)
144 .map_err(|e| PrecompileError::other(format!("set_balance: {e:?}")))?;
145
146 let gas_used = (SLOAD_GAS + MINT_BURN_GAS + COPY_GAS).min(gas_limit);
147 Ok(PrecompileOutput::new(gas_used, vec![].into()))
148}