arb_stylus/
host.rs

1use alloy_primitives::{Address, B256, U256};
2use wasmer::FunctionEnvMut;
3
4use arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_CHARGING_FIXES;
5
6use crate::{
7    env::WasmEnv,
8    error::{Escape, MaybeEscape},
9    evm_api::{EvmApi, UserOutcomeKind},
10    ink::Gas,
11    meter::{GasMeteredMachine, MeteredMachine},
12    pricing::{evm_gas, hostio as hio},
13};
14
15macro_rules! hostio {
16    ($env:expr) => {
17        WasmEnv::program($env)?
18    };
19}
20
21/// Read the program's arguments into WASM memory.
22pub fn read_args<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
23    let mut info = hostio!(&mut env);
24    let trace_on = crate::trace::is_active();
25    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
26    info.buy_ink(hio::READ_ARGS_BASE_INK)?;
27    let args = info.env.args.clone();
28    info.pay_for_write(args.len() as u32)?;
29    info.write_slice(ptr, &args)?;
30    if trace_on {
31        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
32        crate::trace::record(
33            "read_args",
34            Default::default(),
35            alloy_primitives::Bytes::copy_from_slice(&args),
36            start_ink,
37            end_ink,
38            None,
39        );
40    }
41    Ok(())
42}
43
44/// Write the program's result from WASM memory.
45pub fn write_result<E: EvmApi>(
46    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
47    ptr: u32,
48    len: u32,
49) -> MaybeEscape {
50    let mut info = hostio!(&mut env);
51    let trace_on = crate::trace::is_active();
52    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
53    info.buy_ink(hio::WRITE_RESULT_BASE_INK)?;
54    info.pay_for_read(len)?;
55    info.pay_for_read(len)?; // read from geth
56    let data = info.read_slice(ptr, len)?;
57    info.env.outs = data.clone();
58    if trace_on {
59        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
60        crate::trace::record(
61            "write_result",
62            alloy_primitives::Bytes::from(data),
63            Default::default(),
64            start_ink,
65            end_ink,
66            None,
67        );
68    }
69    Ok(())
70}
71
72/// Exit the program early with a status code.
73pub fn exit_early<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, status: u32) -> MaybeEscape {
74    if crate::trace::is_active() {
75        crate::trace::record(
76            "exit_early",
77            alloy_primitives::Bytes::copy_from_slice(&status.to_be_bytes()),
78            Default::default(),
79            0,
80            0,
81            None,
82        );
83    }
84    let _info = hostio!(&mut env);
85    Err(Escape::Exit(status))
86}
87
88/// Load a 32-byte storage value.
89pub fn storage_load_bytes32<E: EvmApi>(
90    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
91    key_ptr: u32,
92    dest_ptr: u32,
93) -> MaybeEscape {
94    let mut info = hostio!(&mut env);
95    let trace_on = crate::trace::is_active();
96    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
97    info.buy_ink(hio::STORAGE_LOAD_BASE_INK)?;
98    let arbos_version = info.env.evm_data.arbos_version;
99    // Preserve wrong behavior for old arbos versions
100    let evm_api_gas = if arbos_version < ARBOS_VERSION_STYLUS_CHARGING_FIXES {
101        Gas(crate::pricing::EVM_API_INK.0)
102    } else {
103        info.pricing().ink_to_gas(crate::pricing::EVM_API_INK)
104    };
105    info.require_gas(
106        evm_gas::COLD_SLOAD_GAS + evm_gas::STORAGE_CACHE_REQUIRED_ACCESS_GAS + evm_api_gas.0,
107    )?;
108    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
109    let (value, gas_cost) = info
110        .env
111        .evm_api
112        .get_bytes32(key, evm_api_gas)
113        .map_err(|e| Escape::Internal(e.to_string()))?;
114    info.buy_gas(gas_cost.0)?;
115    info.write_slice(dest_ptr, value.as_slice())?;
116    if trace_on {
117        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
118        crate::trace::record(
119            "storage_load_bytes32",
120            alloy_primitives::Bytes::copy_from_slice(key.as_slice()),
121            alloy_primitives::Bytes::copy_from_slice(value.as_slice()),
122            start_ink,
123            end_ink,
124            None,
125        );
126    }
127    Ok(())
128}
129
130/// Cache a 32-byte storage value for later flushing.
131pub fn storage_cache_bytes32<E: EvmApi>(
132    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
133    key_ptr: u32,
134    value_ptr: u32,
135) -> MaybeEscape {
136    let mut info = hostio!(&mut env);
137    let trace_on = crate::trace::is_active();
138    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
139    info.buy_ink(hio::STORAGE_CACHE_BASE_INK)?;
140    info.require_gas(evm_gas::SSTORE_SENTRY_GAS + evm_gas::STORAGE_CACHE_REQUIRED_ACCESS_GAS)?;
141    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
142    let value = B256::from(info.read_fixed::<32>(value_ptr)?);
143    let gas_cost = info
144        .env
145        .evm_api
146        .cache_bytes32(key, value)
147        .map_err(|e| Escape::Internal(e.to_string()))?;
148    info.buy_gas(gas_cost.0)?;
149    if trace_on {
150        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
151        let mut args = Vec::with_capacity(64);
152        args.extend_from_slice(key.as_slice());
153        args.extend_from_slice(value.as_slice());
154        crate::trace::record(
155            "storage_cache_bytes32",
156            alloy_primitives::Bytes::from(args),
157            Default::default(),
158            start_ink,
159            end_ink,
160            None,
161        );
162    }
163    Ok(())
164}
165
166/// Flush the storage cache.
167pub fn storage_flush_cache<E: EvmApi>(
168    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
169    clear: u32,
170) -> MaybeEscape {
171    crate::trace::record_leaf(
172        "storage_flush_cache",
173        Default::default(),
174        Default::default(),
175    );
176    let mut info = hostio!(&mut env);
177    info.buy_ink(hio::STORAGE_FLUSH_BASE_INK)?;
178    info.require_gas(evm_gas::SSTORE_SENTRY_GAS)?;
179    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
180    let (gas_cost, status) = info
181        .env
182        .evm_api
183        .flush_storage_cache(clear != 0, Gas(gas_left.0 + 1))
184        .map_err(|e| Escape::Internal(e.to_string()))?;
185
186    // Failure must skip buy_gas so WASM ink — and the EVM caller's gas-used —
187    // reflect exactly what was spent before the failed flush. Other
188    // non-Success outcomes charge first (typically underflowing into
189    // OutOfInk, which consumes the full forwarded gas at the EVM level).
190    match status {
191        UserOutcomeKind::Success => {
192            if info.env.evm_data.arbos_version >= ARBOS_VERSION_STYLUS_CHARGING_FIXES {
193                info.buy_gas(gas_cost.0)?;
194            }
195            Ok(())
196        }
197        UserOutcomeKind::Failure => Escape::logical("storage flush failed"),
198        _ => {
199            if info.env.evm_data.arbos_version >= ARBOS_VERSION_STYLUS_CHARGING_FIXES {
200                info.buy_gas(gas_cost.0)?;
201            }
202            Escape::logical("storage flush failed")
203        }
204    }
205}
206
207/// Load a transient storage value.
208pub fn transient_load_bytes32<E: EvmApi>(
209    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
210    key_ptr: u32,
211    dest_ptr: u32,
212) -> MaybeEscape {
213    let mut info = hostio!(&mut env);
214    let trace_on = crate::trace::is_active();
215    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
216    info.buy_ink(hio::TRANSIENT_LOAD_BASE_INK)?;
217    info.buy_gas(evm_gas::TLOAD_GAS)?;
218    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
219    let value = info
220        .env
221        .evm_api
222        .get_transient_bytes32(key)
223        .map_err(|e| Escape::Internal(e.to_string()))?;
224    info.write_slice(dest_ptr, value.as_slice())?;
225    if trace_on {
226        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
227        crate::trace::record(
228            "transient_load_bytes32",
229            alloy_primitives::Bytes::copy_from_slice(key.as_slice()),
230            alloy_primitives::Bytes::copy_from_slice(value.as_slice()),
231            start_ink,
232            end_ink,
233            None,
234        );
235    }
236    Ok(())
237}
238
239/// Store a transient storage value.
240pub fn transient_store_bytes32<E: EvmApi>(
241    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
242    key_ptr: u32,
243    value_ptr: u32,
244) -> MaybeEscape {
245    let mut info = hostio!(&mut env);
246    let trace_on = crate::trace::is_active();
247    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
248    info.buy_ink(hio::TRANSIENT_STORE_BASE_INK)?;
249    info.buy_gas(evm_gas::TSTORE_GAS)?;
250    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
251    let value = B256::from(info.read_fixed::<32>(value_ptr)?);
252    let status = info
253        .env
254        .evm_api
255        .set_transient_bytes32(key, value)
256        .map_err(|e| Escape::Internal(e.to_string()))?;
257    if status == UserOutcomeKind::Failure {
258        return Escape::logical("transient store failed");
259    }
260    if trace_on {
261        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
262        let mut args = Vec::with_capacity(64);
263        args.extend_from_slice(key.as_slice());
264        args.extend_from_slice(value.as_slice());
265        crate::trace::record(
266            "transient_store_bytes32",
267            alloy_primitives::Bytes::from(args),
268            Default::default(),
269            start_ink,
270            end_ink,
271            None,
272        );
273    }
274    Ok(())
275}
276
277/// Execute a CALL.
278pub fn call_contract<E: EvmApi>(
279    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
280    contract_ptr: u32,
281    calldata_ptr: u32,
282    calldata_len: u32,
283    value_ptr: u32,
284    gas: u64,
285    ret_len_ptr: u32,
286) -> Result<u8, Escape> {
287    let mut info = hostio!(&mut env);
288    let trace_on = crate::trace::is_active();
289    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
290    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
291    info.pay_for_read(calldata_len)?;
292    info.pay_for_read(calldata_len)?; // read from geth
293    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
294    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
295    let value = U256::from_be_bytes(info.read_fixed::<32>(value_ptr)?);
296    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
297    let gas_req = Gas(gas.min(gas_left.0));
298    if trace_on {
299        crate::trace::enter_subcall();
300    }
301    let result = info
302        .env
303        .evm_api
304        .contract_call(contract, &calldata, gas_left, gas_req, value);
305    let steps = if trace_on {
306        crate::trace::exit_subcall()
307    } else {
308        Vec::new()
309    };
310    let (ret_len, gas_cost, status) = result.map_err(|e| Escape::Internal(e.to_string()))?;
311    info.buy_gas(gas_cost.0)?;
312    info.env.evm_return_data_len = ret_len;
313    info.write_u32(ret_len_ptr, ret_len)?;
314    if trace_on {
315        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
316        let mut args = Vec::with_capacity(20 + 32 + 8 + calldata.len());
317        args.extend_from_slice(contract.as_slice());
318        args.extend_from_slice(&value.to_be_bytes::<32>());
319        args.extend_from_slice(&gas.to_be_bytes());
320        args.extend_from_slice(&calldata);
321        crate::trace::record_with_steps(
322            "call_contract",
323            alloy_primitives::Bytes::from(args),
324            alloy_primitives::Bytes::from(vec![status as u8]),
325            start_ink,
326            end_ink,
327            Some(contract),
328            steps,
329        );
330    }
331    Ok(status as u8)
332}
333
334/// Execute a DELEGATECALL.
335pub fn delegate_call_contract<E: EvmApi>(
336    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
337    contract_ptr: u32,
338    calldata_ptr: u32,
339    calldata_len: u32,
340    gas: u64,
341    ret_len_ptr: u32,
342) -> Result<u8, Escape> {
343    let mut info = hostio!(&mut env);
344    let trace_on = crate::trace::is_active();
345    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
346    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
347    info.pay_for_read(calldata_len)?;
348    info.pay_for_read(calldata_len)?; // read from geth
349    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
350    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
351    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
352    let gas_req = Gas(gas.min(gas_left.0));
353    if trace_on {
354        crate::trace::enter_subcall();
355    }
356    let result = info
357        .env
358        .evm_api
359        .delegate_call(contract, &calldata, gas_left, gas_req);
360    let steps = if trace_on {
361        crate::trace::exit_subcall()
362    } else {
363        Vec::new()
364    };
365    let (ret_len, gas_cost, status) = result.map_err(|e| Escape::Internal(e.to_string()))?;
366    info.buy_gas(gas_cost.0)?;
367    info.env.evm_return_data_len = ret_len;
368    info.write_u32(ret_len_ptr, ret_len)?;
369    if trace_on {
370        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
371        let mut args = Vec::with_capacity(20 + 8 + calldata.len());
372        args.extend_from_slice(contract.as_slice());
373        args.extend_from_slice(&gas.to_be_bytes());
374        args.extend_from_slice(&calldata);
375        crate::trace::record_with_steps(
376            "delegate_call_contract",
377            alloy_primitives::Bytes::from(args),
378            alloy_primitives::Bytes::from(vec![status as u8]),
379            start_ink,
380            end_ink,
381            Some(contract),
382            steps,
383        );
384    }
385    Ok(status as u8)
386}
387
388/// Execute a STATICCALL.
389pub fn static_call_contract<E: EvmApi>(
390    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
391    contract_ptr: u32,
392    calldata_ptr: u32,
393    calldata_len: u32,
394    gas: u64,
395    ret_len_ptr: u32,
396) -> Result<u8, Escape> {
397    let mut info = hostio!(&mut env);
398    let trace_on = crate::trace::is_active();
399    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
400    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
401    info.pay_for_read(calldata_len)?;
402    info.pay_for_read(calldata_len)?; // read from geth
403    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
404    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
405    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
406    let gas_req = Gas(gas.min(gas_left.0));
407    if trace_on {
408        crate::trace::enter_subcall();
409    }
410    let result = info
411        .env
412        .evm_api
413        .static_call(contract, &calldata, gas_left, gas_req);
414    let steps = if trace_on {
415        crate::trace::exit_subcall()
416    } else {
417        Vec::new()
418    };
419    let (ret_len, gas_cost, status) = result.map_err(|e| Escape::Internal(e.to_string()))?;
420    info.buy_gas(gas_cost.0)?;
421    info.env.evm_return_data_len = ret_len;
422    info.write_u32(ret_len_ptr, ret_len)?;
423    if trace_on {
424        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
425        let mut args = Vec::with_capacity(20 + 8 + calldata.len());
426        args.extend_from_slice(contract.as_slice());
427        args.extend_from_slice(&gas.to_be_bytes());
428        args.extend_from_slice(&calldata);
429        crate::trace::record_with_steps(
430            "static_call_contract",
431            alloy_primitives::Bytes::from(args),
432            alloy_primitives::Bytes::from(vec![status as u8]),
433            start_ink,
434            end_ink,
435            Some(contract),
436            steps,
437        );
438    }
439    Ok(status as u8)
440}
441
442/// Deploy a contract via CREATE.
443pub fn create1<E: EvmApi>(
444    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
445    code_ptr: u32,
446    code_len: u32,
447    endowment_ptr: u32,
448    contract_ptr: u32,
449    ret_len_ptr: u32,
450) -> MaybeEscape {
451    let mut info = hostio!(&mut env);
452    let trace_on = crate::trace::is_active();
453    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
454    info.buy_ink(hio::CREATE1_BASE_INK)?;
455    info.pay_for_read(code_len)?;
456    info.pay_for_read(code_len)?; // read from geth
457    let code = info.read_slice(code_ptr, code_len)?;
458    let endowment = U256::from_be_bytes(info.read_fixed::<32>(endowment_ptr)?);
459    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
460    if trace_on {
461        crate::trace::enter_subcall();
462    }
463    let result = info.env.evm_api.create1(code.clone(), endowment, gas_left);
464    let steps = if trace_on {
465        crate::trace::exit_subcall()
466    } else {
467        Vec::new()
468    };
469    let (response, ret_len, gas_cost) = result.map_err(|e| Escape::Internal(e.to_string()))?;
470    let address = match response {
471        crate::evm_api::CreateResponse::Success(addr) => addr,
472        crate::evm_api::CreateResponse::Fail(reason) => {
473            return Err(Escape::Internal(reason));
474        }
475    };
476    info.buy_gas(gas_cost.0)?;
477    info.env.evm_return_data_len = ret_len;
478    info.write_u32(ret_len_ptr, ret_len)?;
479    info.write_slice(contract_ptr, address.as_slice())?;
480    if trace_on {
481        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
482        let mut args = Vec::with_capacity(32 + code.len());
483        args.extend_from_slice(&endowment.to_be_bytes::<32>());
484        args.extend_from_slice(&code);
485        crate::trace::record_with_steps(
486            "create1",
487            alloy_primitives::Bytes::from(args),
488            alloy_primitives::Bytes::copy_from_slice(address.as_slice()),
489            start_ink,
490            end_ink,
491            Some(address),
492            steps,
493        );
494    }
495    Ok(())
496}
497
498/// Deploy a contract via CREATE2.
499pub fn create2<E: EvmApi>(
500    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
501    code_ptr: u32,
502    code_len: u32,
503    endowment_ptr: u32,
504    salt_ptr: u32,
505    contract_ptr: u32,
506    ret_len_ptr: u32,
507) -> MaybeEscape {
508    let mut info = hostio!(&mut env);
509    let trace_on = crate::trace::is_active();
510    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
511    info.buy_ink(hio::CREATE2_BASE_INK)?;
512    info.pay_for_read(code_len)?;
513    info.pay_for_read(code_len)?; // read from geth
514    let code = info.read_slice(code_ptr, code_len)?;
515    let endowment = U256::from_be_bytes(info.read_fixed::<32>(endowment_ptr)?);
516    let salt = B256::from(info.read_fixed::<32>(salt_ptr)?);
517    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
518    if trace_on {
519        crate::trace::enter_subcall();
520    }
521    let result = info
522        .env
523        .evm_api
524        .create2(code.clone(), endowment, salt, gas_left);
525    let steps = if trace_on {
526        crate::trace::exit_subcall()
527    } else {
528        Vec::new()
529    };
530    let (response, ret_len, gas_cost) = result.map_err(|e| Escape::Internal(e.to_string()))?;
531    let address = match response {
532        crate::evm_api::CreateResponse::Success(addr) => addr,
533        crate::evm_api::CreateResponse::Fail(reason) => {
534            return Err(Escape::Internal(reason));
535        }
536    };
537    info.buy_gas(gas_cost.0)?;
538    info.env.evm_return_data_len = ret_len;
539    info.write_u32(ret_len_ptr, ret_len)?;
540    info.write_slice(contract_ptr, address.as_slice())?;
541    if trace_on {
542        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
543        let mut args = Vec::with_capacity(64 + code.len());
544        args.extend_from_slice(&endowment.to_be_bytes::<32>());
545        args.extend_from_slice(salt.as_slice());
546        args.extend_from_slice(&code);
547        crate::trace::record_with_steps(
548            "create2",
549            alloy_primitives::Bytes::from(args),
550            alloy_primitives::Bytes::copy_from_slice(address.as_slice()),
551            start_ink,
552            end_ink,
553            Some(address),
554            steps,
555        );
556    }
557    Ok(())
558}
559
560/// Read return data into WASM memory.
561pub fn read_return_data<E: EvmApi>(
562    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
563    dest_ptr: u32,
564    offset: u32,
565    size: u32,
566) -> Result<u32, Escape> {
567    let mut info = hostio!(&mut env);
568    let trace_on = crate::trace::is_active();
569    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
570    info.buy_ink(hio::READ_RETURN_DATA_BASE_INK)?;
571    let max = info.env.evm_return_data_len.saturating_sub(offset);
572    info.pay_for_write(size.min(max))?;
573    if max == 0 {
574        if trace_on {
575            let mut args = Vec::with_capacity(8);
576            args.extend_from_slice(&offset.to_be_bytes());
577            args.extend_from_slice(&size.to_be_bytes());
578            let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
579            crate::trace::record(
580                "read_return_data",
581                alloy_primitives::Bytes::from(args),
582                Default::default(),
583                start_ink,
584                end_ink,
585                None,
586            );
587        }
588        return Ok(0);
589    }
590    let data = info.env.evm_api.get_return_data();
591    let offset_us = offset as usize;
592    let size_us = size as usize;
593    let available = data.len().saturating_sub(offset_us);
594    let copy_len = available.min(size_us);
595    let copied = if copy_len > 0 {
596        let slice = &data[offset_us..offset_us + copy_len];
597        info.write_slice(dest_ptr, slice)?;
598        slice.to_vec()
599    } else {
600        Vec::new()
601    };
602    if trace_on {
603        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
604        let mut args = Vec::with_capacity(8);
605        args.extend_from_slice(&offset.to_be_bytes());
606        args.extend_from_slice(&size.to_be_bytes());
607        crate::trace::record(
608            "read_return_data",
609            alloy_primitives::Bytes::from(args),
610            alloy_primitives::Bytes::from(copied),
611            start_ink,
612            end_ink,
613            None,
614        );
615    }
616    Ok(copy_len as u32)
617}
618
619/// Get the size of the return data.
620pub fn return_data_size<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
621    let mut info = hostio!(&mut env);
622    let trace_on = crate::trace::is_active();
623    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
624    info.buy_ink(hio::RETURN_DATA_SIZE_BASE_INK)?;
625    let size = info.env.evm_return_data_len;
626    if trace_on {
627        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
628        crate::trace::record(
629            "return_data_size",
630            Default::default(),
631            alloy_primitives::Bytes::copy_from_slice(&size.to_be_bytes()),
632            start_ink,
633            end_ink,
634            None,
635        );
636    }
637    Ok(size)
638}
639
640/// Emit a log.
641pub fn emit_log<E: EvmApi>(
642    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
643    data_ptr: u32,
644    data_len: u32,
645    topics: u32,
646) -> MaybeEscape {
647    let mut info = hostio!(&mut env);
648    let trace_on = crate::trace::is_active();
649    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
650    info.buy_ink(hio::EMIT_LOG_BASE_INK)?;
651    if topics > 4 || data_len < topics * 32 {
652        return Escape::logical("bad topic data");
653    }
654    info.pay_for_read(data_len)?;
655    info.pay_for_evm_log(topics, data_len - topics * 32)?;
656    let data = info.read_slice(data_ptr, data_len)?;
657    info.env
658        .evm_api
659        .emit_log(data.clone(), topics)
660        .map_err(|e| Escape::Internal(e.to_string()))?;
661    if trace_on {
662        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
663        let mut args = Vec::with_capacity(4 + data.len());
664        args.extend_from_slice(&topics.to_be_bytes());
665        args.extend_from_slice(&data);
666        crate::trace::record(
667            "emit_log",
668            alloy_primitives::Bytes::from(args),
669            Default::default(),
670            start_ink,
671            end_ink,
672            None,
673        );
674    }
675    Ok(())
676}
677
678/// Get an account's balance.
679pub fn account_balance<E: EvmApi>(
680    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
681    addr_ptr: u32,
682    dest_ptr: u32,
683) -> MaybeEscape {
684    let mut info = hostio!(&mut env);
685    let trace_on = crate::trace::is_active();
686    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
687    info.buy_ink(hio::ACCOUNT_BALANCE_BASE_INK)?;
688    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
689    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
690    let (balance, gas_cost) = info
691        .env
692        .evm_api
693        .account_balance(address)
694        .map_err(|e| Escape::Internal(e.to_string()))?;
695    info.buy_gas(gas_cost.0)?;
696    info.write_slice(dest_ptr, &balance.to_be_bytes::<32>())?;
697    if trace_on {
698        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
699        crate::trace::record(
700            "account_balance",
701            alloy_primitives::Bytes::copy_from_slice(address.as_slice()),
702            alloy_primitives::Bytes::copy_from_slice(&balance.to_be_bytes::<32>()),
703            start_ink,
704            end_ink,
705            Some(address),
706        );
707    }
708    Ok(())
709}
710
711/// Get an account's code.
712pub fn account_code<E: EvmApi>(
713    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
714    addr_ptr: u32,
715    offset: u32,
716    size: u32,
717    dest_ptr: u32,
718) -> Result<u32, Escape> {
719    let mut info = hostio!(&mut env);
720    let trace_on = crate::trace::is_active();
721    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
722    info.buy_ink(hio::ACCOUNT_CODE_BASE_INK)?;
723    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
724    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
725    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
726    let arbos_version = info.env.evm_data.arbos_version;
727    let (code, gas_cost) = info
728        .env
729        .evm_api
730        .account_code(arbos_version, address, gas_left)
731        .map_err(|e| Escape::Internal(e.to_string()))?;
732    info.buy_gas(gas_cost.0)?;
733    info.pay_for_write(code.len() as u32)?;
734    let offset_usize = offset as usize;
735    let size_usize = size as usize;
736    let available = code.len().saturating_sub(offset_usize);
737    let copy_len = available.min(size_usize);
738    if copy_len > 0 {
739        info.write_slice(dest_ptr, &code[offset_usize..offset_usize + copy_len])?;
740    }
741    if trace_on {
742        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
743        let mut args = Vec::with_capacity(28);
744        args.extend_from_slice(address.as_slice());
745        args.extend_from_slice(&offset.to_be_bytes());
746        args.extend_from_slice(&size.to_be_bytes());
747        crate::trace::record(
748            "account_code",
749            alloy_primitives::Bytes::from(args),
750            alloy_primitives::Bytes::copy_from_slice(&code[..code.len().min(copy_len)]),
751            start_ink,
752            end_ink,
753            Some(address),
754        );
755    }
756    Ok(copy_len as u32)
757}
758
759/// Get an account's code size.
760pub fn account_code_size<E: EvmApi>(
761    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
762    addr_ptr: u32,
763) -> Result<u32, Escape> {
764    let mut info = hostio!(&mut env);
765    let trace_on = crate::trace::is_active();
766    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
767    info.buy_ink(hio::ACCOUNT_CODE_SIZE_BASE_INK)?;
768    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
769    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
770    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
771    let arbos_version = info.env.evm_data.arbos_version;
772    let (code, gas_cost) = info
773        .env
774        .evm_api
775        .account_code(arbos_version, address, gas_left)
776        .map_err(|e| Escape::Internal(e.to_string()))?;
777    info.buy_gas(gas_cost.0)?;
778    let len = code.len() as u32;
779    if trace_on {
780        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
781        crate::trace::record(
782            "account_code_size",
783            alloy_primitives::Bytes::copy_from_slice(address.as_slice()),
784            alloy_primitives::Bytes::copy_from_slice(&len.to_be_bytes()),
785            start_ink,
786            end_ink,
787            Some(address),
788        );
789    }
790    Ok(len)
791}
792
793/// Get an account's code hash.
794pub fn account_codehash<E: EvmApi>(
795    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
796    addr_ptr: u32,
797    dest_ptr: u32,
798) -> MaybeEscape {
799    let mut info = hostio!(&mut env);
800    let trace_on = crate::trace::is_active();
801    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
802    info.buy_ink(hio::ACCOUNT_CODE_HASH_BASE_INK)?;
803    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
804    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
805    let (hash, gas_cost) = info
806        .env
807        .evm_api
808        .account_codehash(address)
809        .map_err(|e| Escape::Internal(e.to_string()))?;
810    info.buy_gas(gas_cost.0)?;
811    info.write_slice(dest_ptr, hash.as_slice())?;
812    if trace_on {
813        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
814        crate::trace::record(
815            "account_codehash",
816            alloy_primitives::Bytes::copy_from_slice(address.as_slice()),
817            alloy_primitives::Bytes::copy_from_slice(hash.as_slice()),
818            start_ink,
819            end_ink,
820            Some(address),
821        );
822    }
823    Ok(())
824}
825
826/// Get remaining EVM gas.
827pub fn evm_gas_left<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
828    let mut info = hostio!(&mut env);
829    let trace_on = crate::trace::is_active();
830    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
831    info.buy_ink(hio::EVM_GAS_LEFT_BASE_INK)?;
832    let ink = info.ink_ready()?;
833    let gas = info.pricing().ink_to_gas(ink).0;
834    if trace_on {
835        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
836        crate::trace::record(
837            "evm_gas_left",
838            Default::default(),
839            alloy_primitives::Bytes::copy_from_slice(&gas.to_be_bytes()),
840            start_ink,
841            end_ink,
842            None,
843        );
844    }
845    Ok(gas)
846}
847
848/// Get remaining ink.
849pub fn evm_ink_left<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
850    let mut info = hostio!(&mut env);
851    let trace_on = crate::trace::is_active();
852    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
853    info.buy_ink(hio::EVM_INK_LEFT_BASE_INK)?;
854    let ink = info.ink_ready()?.0;
855    if trace_on {
856        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
857        crate::trace::record(
858            "evm_ink_left",
859            Default::default(),
860            alloy_primitives::Bytes::copy_from_slice(&ink.to_be_bytes()),
861            start_ink,
862            end_ink,
863            None,
864        );
865    }
866    Ok(ink)
867}
868
869/// Write the block base fee.
870pub fn block_basefee<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
871    let mut info = hostio!(&mut env);
872    let trace_on = crate::trace::is_active();
873    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
874    info.buy_ink(hio::BLOCK_BASEFEE_BASE_INK)?;
875    let basefee = info.env.evm_data.block_basefee;
876    info.write_slice(ptr, basefee.as_slice())?;
877    if trace_on {
878        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
879        crate::trace::record(
880            "block_basefee",
881            Default::default(),
882            alloy_primitives::Bytes::copy_from_slice(basefee.as_slice()),
883            start_ink,
884            end_ink,
885            None,
886        );
887    }
888    Ok(())
889}
890
891/// Get the chain ID.
892pub fn chainid<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
893    let mut info = hostio!(&mut env);
894    let trace_on = crate::trace::is_active();
895    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
896    info.buy_ink(hio::CHAIN_ID_BASE_INK)?;
897    let id = info.env.evm_data.chain_id;
898    if trace_on {
899        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
900        crate::trace::record(
901            "chainid",
902            Default::default(),
903            alloy_primitives::Bytes::copy_from_slice(&id.to_be_bytes()),
904            start_ink,
905            end_ink,
906            None,
907        );
908    }
909    Ok(id)
910}
911
912/// Write the block coinbase address.
913pub fn block_coinbase<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
914    let mut info = hostio!(&mut env);
915    let trace_on = crate::trace::is_active();
916    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
917    info.buy_ink(hio::BLOCK_COINBASE_BASE_INK)?;
918    let coinbase = info.env.evm_data.block_coinbase;
919    info.write_slice(ptr, coinbase.as_slice())?;
920    if trace_on {
921        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
922        crate::trace::record(
923            "block_coinbase",
924            Default::default(),
925            alloy_primitives::Bytes::copy_from_slice(coinbase.as_slice()),
926            start_ink,
927            end_ink,
928            None,
929        );
930    }
931    Ok(())
932}
933
934/// Get the block gas limit.
935pub fn block_gas_limit<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
936    let mut info = hostio!(&mut env);
937    let trace_on = crate::trace::is_active();
938    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
939    info.buy_ink(hio::BLOCK_GAS_LIMIT_BASE_INK)?;
940    let limit = info.env.evm_data.block_gas_limit;
941    if trace_on {
942        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
943        crate::trace::record(
944            "block_gas_limit",
945            Default::default(),
946            alloy_primitives::Bytes::copy_from_slice(&limit.to_be_bytes()),
947            start_ink,
948            end_ink,
949            None,
950        );
951    }
952    Ok(limit)
953}
954
955/// Get the block number.
956pub fn block_number<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
957    let mut info = hostio!(&mut env);
958    let trace_on = crate::trace::is_active();
959    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
960    info.buy_ink(hio::BLOCK_NUMBER_BASE_INK)?;
961    let n = info.env.evm_data.block_number;
962    if trace_on {
963        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
964        crate::trace::record(
965            "block_number",
966            Default::default(),
967            alloy_primitives::Bytes::copy_from_slice(&n.to_be_bytes()),
968            start_ink,
969            end_ink,
970            None,
971        );
972    }
973    Ok(n)
974}
975
976/// Get the block timestamp.
977pub fn block_timestamp<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
978    let mut info = hostio!(&mut env);
979    let trace_on = crate::trace::is_active();
980    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
981    info.buy_ink(hio::BLOCK_TIMESTAMP_BASE_INK)?;
982    let ts = info.env.evm_data.block_timestamp;
983    if trace_on {
984        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
985        crate::trace::record(
986            "block_timestamp",
987            Default::default(),
988            alloy_primitives::Bytes::copy_from_slice(&ts.to_be_bytes()),
989            start_ink,
990            end_ink,
991            None,
992        );
993    }
994    Ok(ts)
995}
996
997/// Write the contract address.
998pub fn contract_address<E: EvmApi>(
999    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1000    ptr: u32,
1001) -> MaybeEscape {
1002    let mut info = hostio!(&mut env);
1003    let trace_on = crate::trace::is_active();
1004    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1005    info.buy_ink(hio::ADDRESS_BASE_INK)?;
1006    let addr = info.env.evm_data.contract_address;
1007    info.write_slice(ptr, addr.as_slice())?;
1008    if trace_on {
1009        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1010        crate::trace::record(
1011            "contract_address",
1012            Default::default(),
1013            alloy_primitives::Bytes::copy_from_slice(addr.as_slice()),
1014            start_ink,
1015            end_ink,
1016            Some(addr),
1017        );
1018    }
1019    Ok(())
1020}
1021
1022/// 256-bit division.
1023pub fn math_div<E: EvmApi>(
1024    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1025    a_ptr: u32,
1026    b_ptr: u32,
1027) -> MaybeEscape {
1028    let mut info = hostio!(&mut env);
1029    let trace_on = crate::trace::is_active();
1030    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1031    info.buy_ink(hio::MATH_DIV_BASE_INK)?;
1032    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
1033    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
1034    let result = if b.is_zero() { U256::ZERO } else { a / b };
1035    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
1036    if trace_on {
1037        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1038        let mut args = Vec::with_capacity(64);
1039        args.extend_from_slice(&a.to_be_bytes::<32>());
1040        args.extend_from_slice(&b.to_be_bytes::<32>());
1041        crate::trace::record(
1042            "math_div",
1043            alloy_primitives::Bytes::from(args),
1044            alloy_primitives::Bytes::copy_from_slice(&result.to_be_bytes::<32>()),
1045            start_ink,
1046            end_ink,
1047            None,
1048        );
1049    }
1050    Ok(())
1051}
1052
1053/// 256-bit modulo.
1054pub fn math_mod<E: EvmApi>(
1055    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1056    a_ptr: u32,
1057    b_ptr: u32,
1058) -> MaybeEscape {
1059    let mut info = hostio!(&mut env);
1060    let trace_on = crate::trace::is_active();
1061    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1062    info.buy_ink(hio::MATH_MOD_BASE_INK)?;
1063    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
1064    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
1065    let result = if b.is_zero() { U256::ZERO } else { a % b };
1066    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
1067    if trace_on {
1068        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1069        let mut args = Vec::with_capacity(64);
1070        args.extend_from_slice(&a.to_be_bytes::<32>());
1071        args.extend_from_slice(&b.to_be_bytes::<32>());
1072        crate::trace::record(
1073            "math_mod",
1074            alloy_primitives::Bytes::from(args),
1075            alloy_primitives::Bytes::copy_from_slice(&result.to_be_bytes::<32>()),
1076            start_ink,
1077            end_ink,
1078            None,
1079        );
1080    }
1081    Ok(())
1082}
1083
1084/// 256-bit power.
1085pub fn math_pow<E: EvmApi>(
1086    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1087    base_ptr: u32,
1088    exp_ptr: u32,
1089) -> MaybeEscape {
1090    let mut info = hostio!(&mut env);
1091    let trace_on = crate::trace::is_active();
1092    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1093    info.buy_ink(hio::MATH_POW_BASE_INK)?;
1094    let base = U256::from_be_bytes(info.read_fixed::<32>(base_ptr)?);
1095    let exp_bytes = info.read_fixed::<32>(exp_ptr)?;
1096    info.buy_ink(crate::pricing::pow_price(&exp_bytes))?;
1097    let exp = U256::from_be_bytes(exp_bytes);
1098    let result = base.pow(exp);
1099    info.write_slice(base_ptr, &result.to_be_bytes::<32>())?;
1100    if trace_on {
1101        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1102        let mut args = Vec::with_capacity(64);
1103        args.extend_from_slice(&base.to_be_bytes::<32>());
1104        args.extend_from_slice(&exp.to_be_bytes::<32>());
1105        crate::trace::record(
1106            "math_pow",
1107            alloy_primitives::Bytes::from(args),
1108            alloy_primitives::Bytes::copy_from_slice(&result.to_be_bytes::<32>()),
1109            start_ink,
1110            end_ink,
1111            None,
1112        );
1113    }
1114    Ok(())
1115}
1116
1117/// 256-bit addmod.
1118pub fn math_add_mod<E: EvmApi>(
1119    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1120    a_ptr: u32,
1121    b_ptr: u32,
1122    mod_ptr: u32,
1123) -> MaybeEscape {
1124    let mut info = hostio!(&mut env);
1125    let trace_on = crate::trace::is_active();
1126    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1127    info.buy_ink(hio::MATH_ADD_MOD_BASE_INK)?;
1128    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
1129    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
1130    let modulus = U256::from_be_bytes(info.read_fixed::<32>(mod_ptr)?);
1131    let result = if modulus.is_zero() {
1132        U256::ZERO
1133    } else {
1134        a.add_mod(b, modulus)
1135    };
1136    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
1137    if trace_on {
1138        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1139        let mut args = Vec::with_capacity(96);
1140        args.extend_from_slice(&a.to_be_bytes::<32>());
1141        args.extend_from_slice(&b.to_be_bytes::<32>());
1142        args.extend_from_slice(&modulus.to_be_bytes::<32>());
1143        crate::trace::record(
1144            "math_add_mod",
1145            alloy_primitives::Bytes::from(args),
1146            alloy_primitives::Bytes::copy_from_slice(&result.to_be_bytes::<32>()),
1147            start_ink,
1148            end_ink,
1149            None,
1150        );
1151    }
1152    Ok(())
1153}
1154
1155/// 256-bit mulmod.
1156pub fn math_mul_mod<E: EvmApi>(
1157    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1158    a_ptr: u32,
1159    b_ptr: u32,
1160    mod_ptr: u32,
1161) -> MaybeEscape {
1162    let mut info = hostio!(&mut env);
1163    let trace_on = crate::trace::is_active();
1164    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1165    info.buy_ink(hio::MATH_MUL_MOD_BASE_INK)?;
1166    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
1167    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
1168    let modulus = U256::from_be_bytes(info.read_fixed::<32>(mod_ptr)?);
1169    let result = if modulus.is_zero() {
1170        U256::ZERO
1171    } else {
1172        a.mul_mod(b, modulus)
1173    };
1174    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
1175    if trace_on {
1176        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1177        let mut args = Vec::with_capacity(96);
1178        args.extend_from_slice(&a.to_be_bytes::<32>());
1179        args.extend_from_slice(&b.to_be_bytes::<32>());
1180        args.extend_from_slice(&modulus.to_be_bytes::<32>());
1181        crate::trace::record(
1182            "math_mul_mod",
1183            alloy_primitives::Bytes::from(args),
1184            alloy_primitives::Bytes::copy_from_slice(&result.to_be_bytes::<32>()),
1185            start_ink,
1186            end_ink,
1187            None,
1188        );
1189    }
1190    Ok(())
1191}
1192
1193/// Get the reentrant counter.
1194pub fn msg_reentrant<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
1195    let mut info = hostio!(&mut env);
1196    let trace_on = crate::trace::is_active();
1197    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1198    info.buy_ink(hio::MSG_REENTRANT_BASE_INK)?;
1199    let r = info.env.evm_data.reentrant;
1200    if trace_on {
1201        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1202        crate::trace::record(
1203            "msg_reentrant",
1204            Default::default(),
1205            alloy_primitives::Bytes::copy_from_slice(&r.to_be_bytes()),
1206            start_ink,
1207            end_ink,
1208            None,
1209        );
1210    }
1211    Ok(r)
1212}
1213
1214/// Write the message sender address.
1215pub fn msg_sender<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
1216    let mut info = hostio!(&mut env);
1217    let trace_on = crate::trace::is_active();
1218    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1219    info.buy_ink(hio::MSG_SENDER_BASE_INK)?;
1220    let sender = info.env.evm_data.msg_sender;
1221    info.write_slice(ptr, sender.as_slice())?;
1222    if trace_on {
1223        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1224        crate::trace::record(
1225            "msg_sender",
1226            Default::default(),
1227            alloy_primitives::Bytes::copy_from_slice(sender.as_slice()),
1228            start_ink,
1229            end_ink,
1230            Some(sender),
1231        );
1232    }
1233    Ok(())
1234}
1235
1236/// Write the message value.
1237pub fn msg_value<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
1238    let mut info = hostio!(&mut env);
1239    let trace_on = crate::trace::is_active();
1240    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1241    info.buy_ink(hio::MSG_VALUE_BASE_INK)?;
1242    let v = info.env.evm_data.msg_value;
1243    info.write_slice(ptr, v.as_slice())?;
1244    if trace_on {
1245        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1246        crate::trace::record(
1247            "msg_value",
1248            Default::default(),
1249            alloy_primitives::Bytes::copy_from_slice(v.as_slice()),
1250            start_ink,
1251            end_ink,
1252            None,
1253        );
1254    }
1255    Ok(())
1256}
1257
1258/// Write the transaction gas price.
1259pub fn tx_gas_price<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
1260    let mut info = hostio!(&mut env);
1261    let trace_on = crate::trace::is_active();
1262    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1263    info.buy_ink(hio::TX_GAS_PRICE_BASE_INK)?;
1264    let p_ = info.env.evm_data.tx_gas_price;
1265    info.write_slice(ptr, p_.as_slice())?;
1266    if trace_on {
1267        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1268        crate::trace::record(
1269            "tx_gas_price",
1270            Default::default(),
1271            alloy_primitives::Bytes::copy_from_slice(p_.as_slice()),
1272            start_ink,
1273            end_ink,
1274            None,
1275        );
1276    }
1277    Ok(())
1278}
1279
1280/// Get the ink price.
1281pub fn tx_ink_price<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
1282    let mut info = hostio!(&mut env);
1283    let trace_on = crate::trace::is_active();
1284    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1285    info.buy_ink(hio::TX_INK_PRICE_BASE_INK)?;
1286    let price = info.pricing().ink_price;
1287    if trace_on {
1288        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1289        crate::trace::record(
1290            "tx_ink_price",
1291            Default::default(),
1292            alloy_primitives::Bytes::copy_from_slice(&price.to_be_bytes()),
1293            start_ink,
1294            end_ink,
1295            None,
1296        );
1297    }
1298    Ok(price)
1299}
1300
1301/// Write the transaction origin address.
1302pub fn tx_origin<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
1303    let mut info = hostio!(&mut env);
1304    let trace_on = crate::trace::is_active();
1305    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1306    info.buy_ink(hio::TX_ORIGIN_BASE_INK)?;
1307    let origin = info.env.evm_data.tx_origin;
1308    info.write_slice(ptr, origin.as_slice())?;
1309    if trace_on {
1310        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1311        crate::trace::record(
1312            "tx_origin",
1313            Default::default(),
1314            alloy_primitives::Bytes::copy_from_slice(origin.as_slice()),
1315            start_ink,
1316            end_ink,
1317            Some(origin),
1318        );
1319    }
1320    Ok(())
1321}
1322
1323/// Charge for WASM memory growth.
1324pub fn pay_for_memory_grow<E: EvmApi>(
1325    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1326    pages: u16,
1327) -> MaybeEscape {
1328    crate::trace::record_leaf(
1329        "pay_for_memory_grow",
1330        Default::default(),
1331        Default::default(),
1332    );
1333    let mut info = hostio!(&mut env);
1334    if pages == 0 {
1335        info.buy_ink(hio::PAY_FOR_MEMORY_GROW_BASE_INK)?;
1336        return Ok(());
1337    }
1338    let gas_cost = info
1339        .env
1340        .evm_api
1341        .add_pages(pages)
1342        .map_err(|e| Escape::Internal(e.to_string()))?;
1343    info.buy_gas(gas_cost.0)?;
1344    Ok(())
1345}
1346
1347/// Compute keccak256 hash.
1348pub fn native_keccak256<E: EvmApi>(
1349    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1350    input_ptr: u32,
1351    input_len: u32,
1352    output_ptr: u32,
1353) -> MaybeEscape {
1354    let mut info = hostio!(&mut env);
1355    let trace_on = crate::trace::is_active();
1356    let start_ink = if trace_on { info.ink_ready()?.0 } else { 0 };
1357    info.pay_for_keccak(input_len)?;
1358    let data = info.read_slice(input_ptr, input_len)?;
1359    let hash = alloy_primitives::keccak256(&data);
1360    info.write_slice(output_ptr, hash.as_slice())?;
1361    if trace_on {
1362        let end_ink = info.ink_ready().map(|i| i.0).unwrap_or(0);
1363        crate::trace::record(
1364            "native_keccak256",
1365            alloy_primitives::Bytes::from(data),
1366            alloy_primitives::Bytes::copy_from_slice(hash.as_slice()),
1367            start_ink,
1368            end_ink,
1369            None,
1370        );
1371    }
1372    Ok(())
1373}
1374
1375// Debug functions
1376
1377/// Log text to console (debug only).
1378pub fn console_log_text<E: EvmApi>(
1379    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1380    ptr: u32,
1381    len: u32,
1382) -> MaybeEscape {
1383    crate::trace::record_leaf("console_log_text", Default::default(), Default::default());
1384    let info = hostio!(&mut env);
1385    let text = info.read_slice(ptr, len)?;
1386    if let Ok(s) = std::str::from_utf8(&text) {
1387        tracing::debug!(target: "stylus", "{s}");
1388    }
1389    Ok(())
1390}
1391
1392/// Log a value to console (debug only).
1393pub fn console_log<E: EvmApi, T: std::fmt::Display>(
1394    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1395    value: T,
1396) -> MaybeEscape {
1397    crate::trace::record_leaf("console_log", Default::default(), Default::default());
1398    let _info = hostio!(&mut env);
1399    tracing::debug!(target: "stylus", "{value}");
1400    Ok(())
1401}
1402
1403/// Log and return a value (debug only).
1404pub fn console_tee<E: EvmApi, T: Copy + std::fmt::Display>(
1405    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
1406    value: T,
1407) -> Result<T, Escape> {
1408    crate::trace::record_leaf("console_tee", Default::default(), Default::default());
1409    let _info = hostio!(&mut env);
1410    tracing::debug!(target: "stylus", "{value}");
1411    Ok(value)
1412}
1413
1414/// No-op host function (debug only).
1415pub fn null_host<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
1416    crate::trace::record_leaf("null_host", Default::default(), Default::default());
1417    let _info = hostio!(&mut env);
1418    Ok(())
1419}
1420
1421/// Start a benchmark measurement (debug only).
1422pub fn start_benchmark<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
1423    crate::trace::record_leaf("start_benchmark", Default::default(), Default::default());
1424    let _info = hostio!(&mut env);
1425    Ok(())
1426}
1427
1428/// End a benchmark measurement (debug only).
1429pub fn end_benchmark<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
1430    crate::trace::record_leaf("end_benchmark", Default::default(), Default::default());
1431    let _info = hostio!(&mut env);
1432    Ok(())
1433}