arb_precompiles/
arbnativetokenmanager.rs

1use std::sync::Arc;
2
3use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
4use alloy_primitives::{Address, B256, Log, U256};
5use alloy_sol_types::{SolEvent, SolInterface};
6use arb_context::ArbPrecompileCtx;
7use arb_storage::ARBOS_STATE_ADDRESS;
8use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult};
9
10use crate::{ArbPrecompileError, interfaces::IArbNativeTokenManager};
11
12/// ArbNativeTokenManager precompile address (0x73).
13pub const ARBNATIVETOKENMANAGER_ADDRESS: Address = Address::new([
14    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
15    0x00, 0x00, 0x00, 0x73,
16]);
17
18const SLOAD_GAS: u64 = 800;
19
20/// Gas cost for mint/burn: WarmStorageReadCost + CallValueTransferGas.
21const MINT_BURN_GAS: u64 = 100 + 9000;
22
23/// LOG2 with one 32-byte data word: base + 2 topics + data.
24const EVENT_GAS: u64 = 375 + 2 * 375 + 8 * 32;
25
26pub fn create_arbnativetokenmanager_precompile(ctx: Arc<ArbPrecompileCtx>) -> DynPrecompile {
27    DynPrecompile::new_stateful(
28        PrecompileId::custom("arbnativetokenmanager"),
29        move |input| handler(input, &ctx),
30    )
31}
32
33fn handler(mut input: PrecompileInput<'_>, ctx: &ArbPrecompileCtx) -> PrecompileResult {
34    if let Some(result) =
35        crate::check_precompile_version(ctx, arb_chainspec::arbos_version::ARBOS_VERSION_41)
36    {
37        return result;
38    }
39
40    let mut gas_used = 0u64;
41    let gas_limit = input.gas;
42    crate::init_precompile_gas(&mut gas_used, ctx, input.data.len());
43
44    let call = match IArbNativeTokenManager::ArbNativeTokenManagerCalls::abi_decode(input.data) {
45        Ok(c) => c,
46        Err(_) => return crate::burn_all_revert(gas_limit),
47    };
48    if let Some(r) = crate::reject_nonpayable_value(input.value, input.data, gas_limit, &[]) {
49        return r;
50    }
51    if let Some(r) = crate::reject_static_write(
52        input.is_static,
53        input.data,
54        gas_limit,
55        &[[0xa6, 0xf0, 0xf7, 0xc7], [0x1c, 0x67, 0x9a, 0x3c]],
56    ) {
57        return r;
58    }
59    if let Some(r) = crate::reject_delegate_nonpure(
60        input.target_address != input.bytecode_address,
61        input.data,
62        gas_limit,
63        &[],
64    ) {
65        return r;
66    }
67
68    use IArbNativeTokenManager::ArbNativeTokenManagerCalls;
69    let result = match call {
70        ArbNativeTokenManagerCalls::mintNativeToken(c) => {
71            handle_mint(&mut input, &mut gas_used, c.amount, ctx)
72        }
73        ArbNativeTokenManagerCalls::burnNativeToken(c) => {
74            handle_burn(&mut input, &mut gas_used, c.amount, ctx)
75        }
76    };
77    crate::gas_check(ctx, gas_limit, gas_used, result)
78}
79
80// ── helpers ──────────────────────────────────────────────────────────
81
82fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), ArbPrecompileError> {
83    input
84        .internals_mut()
85        .load_account(ARBOS_STATE_ADDRESS)
86        .map_err(ArbPrecompileError::fatal)?;
87    Ok(())
88}
89
90/// Check if caller is a native token owner via the NativeTokenOwners address set.
91fn is_native_token_owner(
92    input: &mut PrecompileInput<'_>,
93    addr: Address,
94    ctx: &ArbPrecompileCtx,
95) -> Result<bool, ArbPrecompileError> {
96    let internals = input.internals_mut();
97    let arb_state = ctx
98        .block
99        .arbos_state(internals)
100        .map_err(ArbPrecompileError::fatal)?;
101    arb_state
102        .native_token_owners
103        .is_member(internals, addr)
104        .map_err(ArbPrecompileError::fatal)
105}
106
107fn handle_mint(
108    input: &mut PrecompileInput<'_>,
109    gas_used: &mut u64,
110    amount: U256,
111    ctx: &ArbPrecompileCtx,
112) -> PrecompileResult {
113    let gas_limit = input.gas;
114    let caller = input.caller;
115    load_arbos(input)?;
116
117    if !is_native_token_owner(input, caller, ctx)? {
118        return crate::burn_all_revert(gas_limit);
119    }
120
121    input
122        .internals_mut()
123        .balance_incr(caller, amount)
124        .map_err(ArbPrecompileError::fatal)?;
125
126    let topic1 = B256::left_padding_from(caller.as_slice());
127    let event_data = amount.to_be_bytes::<32>().to_vec();
128    input.internals_mut().log(Log::new_unchecked(
129        ARBNATIVETOKENMANAGER_ADDRESS,
130        vec![
131            IArbNativeTokenManager::NativeTokenMinted::SIGNATURE_HASH,
132            topic1,
133        ],
134        event_data.into(),
135    ));
136
137    crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
138    crate::charge_computation(gas_used, ctx, MINT_BURN_GAS);
139    crate::charge_history_growth(gas_used, ctx, EVENT_GAS);
140    Ok(PrecompileOutput::new(
141        (*gas_used).min(gas_limit),
142        vec![].into(),
143    ))
144}
145
146fn handle_burn(
147    input: &mut PrecompileInput<'_>,
148    gas_used: &mut u64,
149    amount: U256,
150    ctx: &ArbPrecompileCtx,
151) -> PrecompileResult {
152    let gas_limit = input.gas;
153    let caller = input.caller;
154    load_arbos(input)?;
155
156    if !is_native_token_owner(input, caller, ctx)? {
157        return crate::burn_all_revert(gas_limit);
158    }
159
160    let acct = input
161        .internals_mut()
162        .load_account(caller)
163        .map_err(ArbPrecompileError::fatal)?;
164    let current_balance = acct.data.info.balance;
165
166    if current_balance < amount {
167        crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
168        crate::charge_computation(gas_used, ctx, MINT_BURN_GAS);
169        return Ok(PrecompileOutput::new_reverted(
170            (*gas_used).min(gas_limit),
171            Default::default(),
172        ));
173    }
174
175    let new_balance = current_balance - amount;
176    input
177        .internals_mut()
178        .set_balance(caller, new_balance)
179        .map_err(ArbPrecompileError::fatal)?;
180
181    let topic1 = B256::left_padding_from(caller.as_slice());
182    let event_data = amount.to_be_bytes::<32>().to_vec();
183    input.internals_mut().log(Log::new_unchecked(
184        ARBNATIVETOKENMANAGER_ADDRESS,
185        vec![
186            IArbNativeTokenManager::NativeTokenBurned::SIGNATURE_HASH,
187            topic1,
188        ],
189        event_data.into(),
190    ));
191
192    crate::charge_storage_read(gas_used, ctx, SLOAD_GAS);
193    crate::charge_computation(gas_used, ctx, MINT_BURN_GAS);
194    crate::charge_history_growth(gas_used, ctx, EVENT_GAS);
195    Ok(PrecompileOutput::new(
196        (*gas_used).min(gas_limit),
197        vec![].into(),
198    ))
199}