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    info.buy_ink(hio::READ_ARGS_BASE_INK)?;
25    let args = info.env.args.clone();
26    info.pay_for_write(args.len() as u32)?;
27    info.write_slice(ptr, &args)?;
28    Ok(())
29}
30
31/// Write the program's result from WASM memory.
32pub fn write_result<E: EvmApi>(
33    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
34    ptr: u32,
35    len: u32,
36) -> MaybeEscape {
37    let mut info = hostio!(&mut env);
38    info.buy_ink(hio::WRITE_RESULT_BASE_INK)?;
39    info.pay_for_read(len)?;
40    info.pay_for_read(len)?; // read from geth
41    let data = info.read_slice(ptr, len)?;
42    info.env.outs = data;
43    Ok(())
44}
45
46/// Exit the program early with a status code.
47pub fn exit_early<E: EvmApi>(_env: FunctionEnvMut<'_, WasmEnv<E>>, status: u32) -> MaybeEscape {
48    Err(Escape::Exit(status))
49}
50
51/// Load a 32-byte storage value.
52pub fn storage_load_bytes32<E: EvmApi>(
53    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
54    key_ptr: u32,
55    dest_ptr: u32,
56) -> MaybeEscape {
57    let mut info = hostio!(&mut env);
58    info.buy_ink(hio::STORAGE_LOAD_BASE_INK)?;
59    let arbos_version = info.env.evm_data.arbos_version;
60    // Preserve wrong behavior for old arbos versions
61    let evm_api_gas = if arbos_version < ARBOS_VERSION_STYLUS_CHARGING_FIXES {
62        Gas(crate::pricing::EVM_API_INK.0)
63    } else {
64        info.pricing().ink_to_gas(crate::pricing::EVM_API_INK)
65    };
66    info.require_gas(
67        evm_gas::COLD_SLOAD_GAS + evm_gas::STORAGE_CACHE_REQUIRED_ACCESS_GAS + evm_api_gas.0,
68    )?;
69    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
70    let (value, gas_cost) = info
71        .env
72        .evm_api
73        .get_bytes32(key, evm_api_gas)
74        .map_err(|e| Escape::Internal(e.to_string()))?;
75    info.buy_gas(gas_cost.0)?;
76    info.write_slice(dest_ptr, value.as_slice())?;
77    Ok(())
78}
79
80/// Cache a 32-byte storage value for later flushing.
81pub fn storage_cache_bytes32<E: EvmApi>(
82    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
83    key_ptr: u32,
84    value_ptr: u32,
85) -> MaybeEscape {
86    let mut info = hostio!(&mut env);
87    info.buy_ink(hio::STORAGE_CACHE_BASE_INK)?;
88    info.require_gas(evm_gas::SSTORE_SENTRY_GAS + evm_gas::STORAGE_CACHE_REQUIRED_ACCESS_GAS)?;
89    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
90    let value = B256::from(info.read_fixed::<32>(value_ptr)?);
91    let gas_cost = info
92        .env
93        .evm_api
94        .cache_bytes32(key, value)
95        .map_err(|e| Escape::Internal(e.to_string()))?;
96    info.buy_gas(gas_cost.0)?;
97    Ok(())
98}
99
100/// Flush the storage cache.
101pub fn storage_flush_cache<E: EvmApi>(
102    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
103    clear: u32,
104) -> MaybeEscape {
105    let mut info = hostio!(&mut env);
106    info.buy_ink(hio::STORAGE_FLUSH_BASE_INK)?;
107    info.require_gas(evm_gas::SSTORE_SENTRY_GAS)?;
108    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
109    let (gas_cost, status) = info
110        .env
111        .evm_api
112        .flush_storage_cache(clear != 0, Gas(gas_left.0 + 1))
113        .map_err(|e| Escape::Internal(e.to_string()))?;
114    if info.env.evm_data.arbos_version >= ARBOS_VERSION_STYLUS_CHARGING_FIXES {
115        info.buy_gas(gas_cost.0)?;
116    }
117    if status != UserOutcomeKind::Success {
118        return Escape::logical("storage flush failed");
119    }
120    Ok(())
121}
122
123/// Load a transient storage value.
124pub fn transient_load_bytes32<E: EvmApi>(
125    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
126    key_ptr: u32,
127    dest_ptr: u32,
128) -> MaybeEscape {
129    let mut info = hostio!(&mut env);
130    info.buy_ink(hio::TRANSIENT_LOAD_BASE_INK)?;
131    info.buy_gas(evm_gas::TLOAD_GAS)?;
132    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
133    let value = info
134        .env
135        .evm_api
136        .get_transient_bytes32(key)
137        .map_err(|e| Escape::Internal(e.to_string()))?;
138    info.write_slice(dest_ptr, value.as_slice())?;
139    Ok(())
140}
141
142/// Store a transient storage value.
143pub fn transient_store_bytes32<E: EvmApi>(
144    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
145    key_ptr: u32,
146    value_ptr: u32,
147) -> MaybeEscape {
148    let mut info = hostio!(&mut env);
149    info.buy_ink(hio::TRANSIENT_STORE_BASE_INK)?;
150    info.buy_gas(evm_gas::TSTORE_GAS)?;
151    let key = B256::from(info.read_fixed::<32>(key_ptr)?);
152    let value = B256::from(info.read_fixed::<32>(value_ptr)?);
153    let status = info
154        .env
155        .evm_api
156        .set_transient_bytes32(key, value)
157        .map_err(|e| Escape::Internal(e.to_string()))?;
158    if status == UserOutcomeKind::Failure {
159        return Escape::logical("transient store failed");
160    }
161    Ok(())
162}
163
164/// Execute a CALL.
165pub fn call_contract<E: EvmApi>(
166    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
167    contract_ptr: u32,
168    calldata_ptr: u32,
169    calldata_len: u32,
170    value_ptr: u32,
171    gas: u64,
172    ret_len_ptr: u32,
173) -> Result<u8, Escape> {
174    let mut info = hostio!(&mut env);
175    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
176    info.pay_for_read(calldata_len)?;
177    info.pay_for_read(calldata_len)?; // read from geth
178    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
179    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
180    let value = U256::from_be_bytes(info.read_fixed::<32>(value_ptr)?);
181    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
182    let gas_req = Gas(gas.min(gas_left.0));
183    let (ret_len, gas_cost, status) = info
184        .env
185        .evm_api
186        .contract_call(contract, &calldata, gas_left, gas_req, value)
187        .map_err(|e| Escape::Internal(e.to_string()))?;
188    info.buy_gas(gas_cost.0)?;
189    info.env.evm_return_data_len = ret_len;
190    info.write_u32(ret_len_ptr, ret_len)?;
191    Ok(status as u8)
192}
193
194/// Execute a DELEGATECALL.
195pub fn delegate_call_contract<E: EvmApi>(
196    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
197    contract_ptr: u32,
198    calldata_ptr: u32,
199    calldata_len: u32,
200    gas: u64,
201    ret_len_ptr: u32,
202) -> Result<u8, Escape> {
203    let mut info = hostio!(&mut env);
204    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
205    info.pay_for_read(calldata_len)?;
206    info.pay_for_read(calldata_len)?; // read from geth
207    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
208    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
209    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
210    let gas_req = Gas(gas.min(gas_left.0));
211    let (ret_len, gas_cost, status) = info
212        .env
213        .evm_api
214        .delegate_call(contract, &calldata, gas_left, gas_req)
215        .map_err(|e| Escape::Internal(e.to_string()))?;
216    info.buy_gas(gas_cost.0)?;
217    info.env.evm_return_data_len = ret_len;
218    info.write_u32(ret_len_ptr, ret_len)?;
219    Ok(status as u8)
220}
221
222/// Execute a STATICCALL.
223pub fn static_call_contract<E: EvmApi>(
224    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
225    contract_ptr: u32,
226    calldata_ptr: u32,
227    calldata_len: u32,
228    gas: u64,
229    ret_len_ptr: u32,
230) -> Result<u8, Escape> {
231    let mut info = hostio!(&mut env);
232    info.buy_ink(hio::CALL_CONTRACT_BASE_INK)?;
233    info.pay_for_read(calldata_len)?;
234    info.pay_for_read(calldata_len)?; // read from geth
235    let contract = Address::from_slice(&info.read_fixed::<20>(contract_ptr)?);
236    let calldata = info.read_slice(calldata_ptr, calldata_len)?;
237    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
238    let gas_req = Gas(gas.min(gas_left.0));
239    let (ret_len, gas_cost, status) = info
240        .env
241        .evm_api
242        .static_call(contract, &calldata, gas_left, gas_req)
243        .map_err(|e| Escape::Internal(e.to_string()))?;
244    info.buy_gas(gas_cost.0)?;
245    info.env.evm_return_data_len = ret_len;
246    info.write_u32(ret_len_ptr, ret_len)?;
247    Ok(status as u8)
248}
249
250/// Deploy a contract via CREATE.
251pub fn create1<E: EvmApi>(
252    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
253    code_ptr: u32,
254    code_len: u32,
255    endowment_ptr: u32,
256    contract_ptr: u32,
257    ret_len_ptr: u32,
258) -> MaybeEscape {
259    let mut info = hostio!(&mut env);
260    info.buy_ink(hio::CREATE1_BASE_INK)?;
261    info.pay_for_read(code_len)?;
262    info.pay_for_read(code_len)?; // read from geth
263    let code = info.read_slice(code_ptr, code_len)?;
264    let endowment = U256::from_be_bytes(info.read_fixed::<32>(endowment_ptr)?);
265    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
266    let (response, ret_len, gas_cost) = info
267        .env
268        .evm_api
269        .create1(code, endowment, gas_left)
270        .map_err(|e| Escape::Internal(e.to_string()))?;
271    let address = match response {
272        crate::evm_api::CreateResponse::Success(addr) => addr,
273        crate::evm_api::CreateResponse::Fail(reason) => {
274            return Err(Escape::Internal(reason));
275        }
276    };
277    info.buy_gas(gas_cost.0)?;
278    info.env.evm_return_data_len = ret_len;
279    info.write_u32(ret_len_ptr, ret_len)?;
280    info.write_slice(contract_ptr, address.as_slice())?;
281    Ok(())
282}
283
284/// Deploy a contract via CREATE2.
285pub fn create2<E: EvmApi>(
286    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
287    code_ptr: u32,
288    code_len: u32,
289    endowment_ptr: u32,
290    salt_ptr: u32,
291    contract_ptr: u32,
292    ret_len_ptr: u32,
293) -> MaybeEscape {
294    let mut info = hostio!(&mut env);
295    info.buy_ink(hio::CREATE2_BASE_INK)?;
296    info.pay_for_read(code_len)?;
297    info.pay_for_read(code_len)?; // read from geth
298    let code = info.read_slice(code_ptr, code_len)?;
299    let endowment = U256::from_be_bytes(info.read_fixed::<32>(endowment_ptr)?);
300    let salt = B256::from(info.read_fixed::<32>(salt_ptr)?);
301    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
302    let (response, ret_len, gas_cost) = info
303        .env
304        .evm_api
305        .create2(code, endowment, salt, gas_left)
306        .map_err(|e| Escape::Internal(e.to_string()))?;
307    let address = match response {
308        crate::evm_api::CreateResponse::Success(addr) => addr,
309        crate::evm_api::CreateResponse::Fail(reason) => {
310            return Err(Escape::Internal(reason));
311        }
312    };
313    info.buy_gas(gas_cost.0)?;
314    info.env.evm_return_data_len = ret_len;
315    info.write_u32(ret_len_ptr, ret_len)?;
316    info.write_slice(contract_ptr, address.as_slice())?;
317    Ok(())
318}
319
320/// Read return data into WASM memory.
321pub fn read_return_data<E: EvmApi>(
322    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
323    dest_ptr: u32,
324    offset: u32,
325    size: u32,
326) -> Result<u32, Escape> {
327    let mut info = hostio!(&mut env);
328    info.buy_ink(hio::READ_RETURN_DATA_BASE_INK)?;
329    let max = info.env.evm_return_data_len.saturating_sub(offset);
330    info.pay_for_write(size.min(max))?;
331    if max == 0 {
332        return Ok(0);
333    }
334    let data = info.env.evm_api.get_return_data();
335    let offset = offset as usize;
336    let size = size as usize;
337    let available = data.len().saturating_sub(offset);
338    let copy_len = available.min(size);
339    if copy_len > 0 {
340        info.write_slice(dest_ptr, &data[offset..offset + copy_len])?;
341    }
342    Ok(copy_len as u32)
343}
344
345/// Get the size of the return data.
346pub fn return_data_size<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
347    let mut info = hostio!(&mut env);
348    info.buy_ink(hio::RETURN_DATA_SIZE_BASE_INK)?;
349    Ok(info.env.evm_return_data_len)
350}
351
352/// Emit a log.
353pub fn emit_log<E: EvmApi>(
354    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
355    data_ptr: u32,
356    data_len: u32,
357    topics: u32,
358) -> MaybeEscape {
359    let mut info = hostio!(&mut env);
360    info.buy_ink(hio::EMIT_LOG_BASE_INK)?;
361    if topics > 4 || data_len < topics * 32 {
362        return Escape::logical("bad topic data");
363    }
364    info.pay_for_read(data_len)?;
365    info.pay_for_evm_log(topics, data_len - topics * 32)?;
366    let data = info.read_slice(data_ptr, data_len)?;
367    info.env
368        .evm_api
369        .emit_log(data, topics)
370        .map_err(|e| Escape::Internal(e.to_string()))?;
371    Ok(())
372}
373
374/// Get an account's balance.
375pub fn account_balance<E: EvmApi>(
376    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
377    addr_ptr: u32,
378    dest_ptr: u32,
379) -> MaybeEscape {
380    let mut info = hostio!(&mut env);
381    info.buy_ink(hio::ACCOUNT_BALANCE_BASE_INK)?;
382    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
383    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
384    let (balance, gas_cost) = info
385        .env
386        .evm_api
387        .account_balance(address)
388        .map_err(|e| Escape::Internal(e.to_string()))?;
389    info.buy_gas(gas_cost.0)?;
390    info.write_slice(dest_ptr, &balance.to_be_bytes::<32>())?;
391    Ok(())
392}
393
394/// Get an account's code.
395pub fn account_code<E: EvmApi>(
396    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
397    addr_ptr: u32,
398    offset: u32,
399    size: u32,
400    dest_ptr: u32,
401) -> Result<u32, Escape> {
402    let mut info = hostio!(&mut env);
403    info.buy_ink(hio::ACCOUNT_CODE_BASE_INK)?;
404    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
405    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
406    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
407    let arbos_version = info.env.evm_data.arbos_version;
408    let (code, gas_cost) = info
409        .env
410        .evm_api
411        .account_code(arbos_version, address, gas_left)
412        .map_err(|e| Escape::Internal(e.to_string()))?;
413    info.buy_gas(gas_cost.0)?;
414    info.pay_for_write(code.len() as u32)?;
415    let offset = offset as usize;
416    let size = size as usize;
417    let available = code.len().saturating_sub(offset);
418    let copy_len = available.min(size);
419    if copy_len > 0 {
420        info.write_slice(dest_ptr, &code[offset..offset + copy_len])?;
421    }
422    Ok(copy_len as u32)
423}
424
425/// Get an account's code size.
426pub fn account_code_size<E: EvmApi>(
427    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
428    addr_ptr: u32,
429) -> Result<u32, Escape> {
430    let mut info = hostio!(&mut env);
431    info.buy_ink(hio::ACCOUNT_CODE_SIZE_BASE_INK)?;
432    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
433    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
434    let gas_left = info.ink_ready().map(|ink| info.pricing().ink_to_gas(ink))?;
435    let arbos_version = info.env.evm_data.arbos_version;
436    let (code, gas_cost) = info
437        .env
438        .evm_api
439        .account_code(arbos_version, address, gas_left)
440        .map_err(|e| Escape::Internal(e.to_string()))?;
441    info.buy_gas(gas_cost.0)?;
442    Ok(code.len() as u32)
443}
444
445/// Get an account's code hash.
446pub fn account_codehash<E: EvmApi>(
447    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
448    addr_ptr: u32,
449    dest_ptr: u32,
450) -> MaybeEscape {
451    let mut info = hostio!(&mut env);
452    info.buy_ink(hio::ACCOUNT_CODE_HASH_BASE_INK)?;
453    info.require_gas(evm_gas::COLD_ACCOUNT_GAS)?;
454    let address = Address::from_slice(&info.read_fixed::<20>(addr_ptr)?);
455    let (hash, gas_cost) = info
456        .env
457        .evm_api
458        .account_codehash(address)
459        .map_err(|e| Escape::Internal(e.to_string()))?;
460    info.buy_gas(gas_cost.0)?;
461    info.write_slice(dest_ptr, hash.as_slice())?;
462    Ok(())
463}
464
465/// Get remaining EVM gas.
466pub fn evm_gas_left<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
467    let mut info = hostio!(&mut env);
468    info.buy_ink(hio::EVM_GAS_LEFT_BASE_INK)?;
469    let ink = info.ink_ready()?;
470    Ok(info.pricing().ink_to_gas(ink).0)
471}
472
473/// Get remaining ink.
474pub fn evm_ink_left<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
475    let mut info = hostio!(&mut env);
476    info.buy_ink(hio::EVM_INK_LEFT_BASE_INK)?;
477    Ok(info.ink_ready()?.0)
478}
479
480/// Write the block base fee.
481pub fn block_basefee<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
482    let mut info = hostio!(&mut env);
483    info.buy_ink(hio::BLOCK_BASEFEE_BASE_INK)?;
484    info.write_slice(ptr, info.env.evm_data.block_basefee.as_slice())?;
485    Ok(())
486}
487
488/// Get the chain ID.
489pub fn chainid<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
490    let mut info = hostio!(&mut env);
491    info.buy_ink(hio::CHAIN_ID_BASE_INK)?;
492    Ok(info.env.evm_data.chain_id)
493}
494
495/// Write the block coinbase address.
496pub fn block_coinbase<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
497    let mut info = hostio!(&mut env);
498    info.buy_ink(hio::BLOCK_COINBASE_BASE_INK)?;
499    info.write_slice(ptr, info.env.evm_data.block_coinbase.as_slice())?;
500    Ok(())
501}
502
503/// Get the block gas limit.
504pub fn block_gas_limit<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
505    let mut info = hostio!(&mut env);
506    info.buy_ink(hio::BLOCK_GAS_LIMIT_BASE_INK)?;
507    Ok(info.env.evm_data.block_gas_limit)
508}
509
510/// Get the block number.
511pub fn block_number<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
512    let mut info = hostio!(&mut env);
513    info.buy_ink(hio::BLOCK_NUMBER_BASE_INK)?;
514    Ok(info.env.evm_data.block_number)
515}
516
517/// Get the block timestamp.
518pub fn block_timestamp<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u64, Escape> {
519    let mut info = hostio!(&mut env);
520    info.buy_ink(hio::BLOCK_TIMESTAMP_BASE_INK)?;
521    Ok(info.env.evm_data.block_timestamp)
522}
523
524/// Write the contract address.
525pub fn contract_address<E: EvmApi>(
526    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
527    ptr: u32,
528) -> MaybeEscape {
529    let mut info = hostio!(&mut env);
530    info.buy_ink(hio::ADDRESS_BASE_INK)?;
531    info.write_slice(ptr, info.env.evm_data.contract_address.as_slice())?;
532    Ok(())
533}
534
535/// 256-bit division.
536pub fn math_div<E: EvmApi>(
537    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
538    a_ptr: u32,
539    b_ptr: u32,
540) -> MaybeEscape {
541    let mut info = hostio!(&mut env);
542    info.buy_ink(hio::MATH_DIV_BASE_INK)?;
543    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
544    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
545    let result = if b.is_zero() { U256::ZERO } else { a / b };
546    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
547    Ok(())
548}
549
550/// 256-bit modulo.
551pub fn math_mod<E: EvmApi>(
552    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
553    a_ptr: u32,
554    b_ptr: u32,
555) -> MaybeEscape {
556    let mut info = hostio!(&mut env);
557    info.buy_ink(hio::MATH_MOD_BASE_INK)?;
558    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
559    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
560    let result = if b.is_zero() { U256::ZERO } else { a % b };
561    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
562    Ok(())
563}
564
565/// 256-bit power.
566pub fn math_pow<E: EvmApi>(
567    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
568    base_ptr: u32,
569    exp_ptr: u32,
570) -> MaybeEscape {
571    let mut info = hostio!(&mut env);
572    info.buy_ink(hio::MATH_POW_BASE_INK)?;
573    let base = U256::from_be_bytes(info.read_fixed::<32>(base_ptr)?);
574    let exp_bytes = info.read_fixed::<32>(exp_ptr)?;
575    info.buy_ink(crate::pricing::pow_price(&exp_bytes))?;
576    let exp = U256::from_be_bytes(exp_bytes);
577    let result = base.pow(exp);
578    info.write_slice(base_ptr, &result.to_be_bytes::<32>())?;
579    Ok(())
580}
581
582/// 256-bit addmod.
583pub fn math_add_mod<E: EvmApi>(
584    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
585    a_ptr: u32,
586    b_ptr: u32,
587    mod_ptr: u32,
588) -> MaybeEscape {
589    let mut info = hostio!(&mut env);
590    info.buy_ink(hio::MATH_ADD_MOD_BASE_INK)?;
591    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
592    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
593    let modulus = U256::from_be_bytes(info.read_fixed::<32>(mod_ptr)?);
594    let result = if modulus.is_zero() {
595        U256::ZERO
596    } else {
597        a.add_mod(b, modulus)
598    };
599    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
600    Ok(())
601}
602
603/// 256-bit mulmod.
604pub fn math_mul_mod<E: EvmApi>(
605    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
606    a_ptr: u32,
607    b_ptr: u32,
608    mod_ptr: u32,
609) -> MaybeEscape {
610    let mut info = hostio!(&mut env);
611    info.buy_ink(hio::MATH_MUL_MOD_BASE_INK)?;
612    let a = U256::from_be_bytes(info.read_fixed::<32>(a_ptr)?);
613    let b = U256::from_be_bytes(info.read_fixed::<32>(b_ptr)?);
614    let modulus = U256::from_be_bytes(info.read_fixed::<32>(mod_ptr)?);
615    let result = if modulus.is_zero() {
616        U256::ZERO
617    } else {
618        a.mul_mod(b, modulus)
619    };
620    info.write_slice(a_ptr, &result.to_be_bytes::<32>())?;
621    Ok(())
622}
623
624/// Get the reentrant counter.
625pub fn msg_reentrant<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
626    let mut info = hostio!(&mut env);
627    info.buy_ink(hio::MSG_REENTRANT_BASE_INK)?;
628    Ok(info.env.evm_data.reentrant)
629}
630
631/// Write the message sender address.
632pub fn msg_sender<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
633    let mut info = hostio!(&mut env);
634    info.buy_ink(hio::MSG_SENDER_BASE_INK)?;
635    info.write_slice(ptr, info.env.evm_data.msg_sender.as_slice())?;
636    Ok(())
637}
638
639/// Write the message value.
640pub fn msg_value<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
641    let mut info = hostio!(&mut env);
642    info.buy_ink(hio::MSG_VALUE_BASE_INK)?;
643    info.write_slice(ptr, info.env.evm_data.msg_value.as_slice())?;
644    Ok(())
645}
646
647/// Write the transaction gas price.
648pub fn tx_gas_price<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
649    let mut info = hostio!(&mut env);
650    info.buy_ink(hio::TX_GAS_PRICE_BASE_INK)?;
651    info.write_slice(ptr, info.env.evm_data.tx_gas_price.as_slice())?;
652    Ok(())
653}
654
655/// Get the ink price.
656pub fn tx_ink_price<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> Result<u32, Escape> {
657    let mut info = hostio!(&mut env);
658    info.buy_ink(hio::TX_INK_PRICE_BASE_INK)?;
659    Ok(info.pricing().ink_price)
660}
661
662/// Write the transaction origin address.
663pub fn tx_origin<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>, ptr: u32) -> MaybeEscape {
664    let mut info = hostio!(&mut env);
665    info.buy_ink(hio::TX_ORIGIN_BASE_INK)?;
666    info.write_slice(ptr, info.env.evm_data.tx_origin.as_slice())?;
667    Ok(())
668}
669
670/// Charge for WASM memory growth.
671pub fn pay_for_memory_grow<E: EvmApi>(
672    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
673    pages: u16,
674) -> MaybeEscape {
675    let mut info = hostio!(&mut env);
676    if pages == 0 {
677        info.buy_ink(hio::PAY_FOR_MEMORY_GROW_BASE_INK)?;
678        return Ok(());
679    }
680    let gas_cost = info
681        .env
682        .evm_api
683        .add_pages(pages)
684        .map_err(|e| Escape::Internal(e.to_string()))?;
685    info.buy_gas(gas_cost.0)?;
686    Ok(())
687}
688
689/// Compute keccak256 hash.
690pub fn native_keccak256<E: EvmApi>(
691    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
692    input_ptr: u32,
693    input_len: u32,
694    output_ptr: u32,
695) -> MaybeEscape {
696    let mut info = hostio!(&mut env);
697    info.pay_for_keccak(input_len)?;
698    let data = info.read_slice(input_ptr, input_len)?;
699    let hash = alloy_primitives::keccak256(&data);
700    info.write_slice(output_ptr, hash.as_slice())?;
701    Ok(())
702}
703
704// Debug functions
705
706/// Log text to console (debug only).
707pub fn console_log_text<E: EvmApi>(
708    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
709    ptr: u32,
710    len: u32,
711) -> MaybeEscape {
712    let info = hostio!(&mut env);
713    let text = info.read_slice(ptr, len)?;
714    if let Ok(s) = std::str::from_utf8(&text) {
715        tracing::debug!(target: "stylus", "{s}");
716    }
717    Ok(())
718}
719
720/// Log a value to console (debug only).
721pub fn console_log<E: EvmApi, T: std::fmt::Display>(
722    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
723    value: T,
724) -> MaybeEscape {
725    let _info = hostio!(&mut env);
726    tracing::debug!(target: "stylus", "{value}");
727    Ok(())
728}
729
730/// Log and return a value (debug only).
731pub fn console_tee<E: EvmApi, T: Copy + std::fmt::Display>(
732    mut env: FunctionEnvMut<'_, WasmEnv<E>>,
733    value: T,
734) -> Result<T, Escape> {
735    let _info = hostio!(&mut env);
736    tracing::debug!(target: "stylus", "{value}");
737    Ok(value)
738}
739
740/// No-op host function (debug only).
741pub fn null_host<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
742    let _info = hostio!(&mut env);
743    Ok(())
744}
745
746/// Start a benchmark measurement (debug only).
747pub fn start_benchmark<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
748    let _info = hostio!(&mut env);
749    Ok(())
750}
751
752/// End a benchmark measurement (debug only).
753pub fn end_benchmark<E: EvmApi>(mut env: FunctionEnvMut<'_, WasmEnv<E>>) -> MaybeEscape {
754    let _info = hostio!(&mut env);
755    Ok(())
756}