arb_precompiles/
arbnativetokenmanager.rs

1use 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
14/// ArbNativeTokenManager precompile address (0x73).
15pub 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
23/// Gas cost for mint/burn: WarmStorageReadCost + CallValueTransferGas.
24const MINT_BURN_GAS: u64 = 100 + 9000;
25
26/// LOG2 with one 32-byte data word: base + 2 topics + data.
27const 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
56// ── helpers ──────────────────────────────────────────────────────────
57
58fn 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
74/// Check if caller is a native token owner via the NativeTokenOwners address set.
75fn is_native_token_owner(
76    input: &mut PrecompileInput<'_>,
77    addr: Address,
78) -> Result<bool, PrecompileError> {
79    // NativeTokenOwners is at subspace [10] in ArbOS state.
80    // byAddress sub-storage is at [0] within the address set.
81    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}