arb_stylus/
host.rs

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