stylus_core/host.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
// Copyright 2024-2025, Offchain Labs, Inc.
// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
//! Defines host environment methods Stylus SDK contracts have access to.
extern crate alloc;
use crate::{
calls::{CallAccess, ValueTransfer},
deploy::DeploymentAccess,
};
use alloc::vec::Vec;
use alloy_primitives::{Address, B256, U256};
use dyn_clone::DynClone;
/// The host trait defines methods a Stylus contract can use to interact
/// with a host environment, such as the EVM. It is a composition
/// of traits with different access to host values and modifications.
/// Stylus contracts in the SDK have access to a host via the HostAccessor trait for safe access
/// to hostios without the need for global invocations. The host trait may be implemented
/// by test frameworks as an easier way of mocking hostio invocations for testing
/// Stylus contracts.
pub trait Host:
CryptographyAccess
+ CalldataAccess
+ UnsafeDeploymentAccess
+ StorageAccess
+ UnsafeCallAccess
+ BlockAccess
+ ChainAccess
+ AccountAccess
+ MemoryAccess
+ MessageAccess
+ MeteringAccess
+ CallAccess
+ DeploymentAccess
+ LogAccess
+ ValueTransfer
+ DynClone
{
}
// Enables cloning of a boxed, host trait object.
dyn_clone::clone_trait_object!(Host);
/// Defines a trait that allows a Stylus contract to access its host safely.
pub trait HostAccess {
/// Provides access to the parametrized host of a contract, giving access
/// to all the desired hostios from the user.
fn vm(&self) -> &dyn Host;
}
/// Defines a trait that can deny access to a contract router method if a message value is
/// passed in while the method is non-payable.
pub trait ValueDenier {
fn deny_value(&self, method_name: &str) -> Result<(), Vec<u8>>;
}
/// Provides access to native cryptography extensions provided by
/// a Stylus contract host, such as keccak256.
pub trait CryptographyAccess {
/// Efficiently computes the [`keccak256`] hash of the given preimage.
/// The semantics are equivalent to that of the EVM's [`SHA3`] opcode.
///
/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3
/// [`SHA3`]: https://www.evm.codes/#20
fn native_keccak256(&self, input: &[u8]) -> B256;
}
/// Provides access to host methods relating to the accessing the calldata
/// of a Stylus contract transaction.
pub trait CalldataAccess {
/// Reads the program calldata. The semantics are equivalent to that of the EVM's
/// [`CALLDATA_COPY`] opcode when requesting the entirety of the current call's calldata.
///
/// [`CALLDATA_COPY`]: https://www.evm.codes/#37
fn read_args(&self, len: usize) -> Vec<u8>;
/// Copies the bytes of the last EVM call or deployment return result. Does not revert if out of
/// bounds, but rather copies the overlapping portion. The semantics are otherwise equivalent
/// to that of the EVM's [`RETURN_DATA_COPY`] opcode.
///
/// Returns the number of bytes written.
///
/// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e
fn read_return_data(&self, offset: usize, size: Option<usize>) -> Vec<u8>;
/// Returns the length of the last EVM call or deployment return result, or `0` if neither have
/// happened during the program's execution. The semantics are equivalent to that of the EVM's
/// [`RETURN_DATA_SIZE`] opcode.
///
/// [`RETURN_DATA_SIZE`]: https://www.evm.codes/#3d
fn return_data_size(&self) -> usize;
/// Writes the final return data. If not called before the program exists, the return data will
/// be 0 bytes long. Note that this hostio does not cause the program to exit, which happens
/// naturally when `user_entrypoint` returns.
fn write_result(&self, data: &[u8]);
}
/// Provides access to programmatic creation of contracts via the host environment's CREATE
/// and CREATE2 opcodes in the EVM.
///
/// # Safety
/// These methods should only be used in advanced cases when lowest-level access
/// to create1 and create2 opcodes is needed. Using the methods by themselves will not protect
/// against reentrancy safety, storage aliasing, or cache flushing. For safe contract deployment,
/// utilize a [`RawDeploy`] struct instead.
pub unsafe trait UnsafeDeploymentAccess {
/// Deploys a new contract using the init code provided, which the EVM executes to construct
/// the code of the newly deployed contract. The init code must be written in EVM bytecode, but
/// the code it deploys can be that of a Stylus program. The code returned will be treated as
/// WASM if it begins with the EOF-inspired header `0xEFF000`. Otherwise the code will be
/// interpreted as that of a traditional EVM-style contract. See [`Deploying Stylus Programs`]
/// for more information on writing init code.
///
/// On success, this hostio returns the address of the newly created account whose address is
/// a function of the sender and nonce. On failure the address will be `0`, `return_data_len`
/// will store the length of the revert data, the bytes of which can be read via the
/// `read_return_data` hostio. The semantics are equivalent to that of the EVM's [`CREATE`]
/// opcode, which notably includes the exact address returned.
///
/// [`Deploying Stylus Programs`]: https://docs.arbitrum.io/stylus/quickstart
/// [`CREATE`]: https://www.evm.codes/#f0
///
/// # Safety
/// This method should only be used in advanced cases when lowest-level access to create1 is required.
/// Safe usage needs to consider reentrancy, storage aliasing, and cache flushing.
/// utilize a [`RawDeploy`] struct instead for safety.
unsafe fn create1(
&self,
code: *const u8,
code_len: usize,
endowment: *const u8,
contract: *mut u8,
revert_data_len: *mut usize,
);
/// Deploys a new contract using the init code provided, which the EVM executes to construct
/// the code of the newly deployed contract. The init code must be written in EVM bytecode, but
/// the code it deploys can be that of a Stylus program. The code returned will be treated as
/// WASM if it begins with the EOF-inspired header `0xEFF000`. Otherwise the code will be
/// interpreted as that of a traditional EVM-style contract. See [`Deploying Stylus Programs`]
/// for more information on writing init code.
///
/// On success, this hostio returns the address of the newly created account whose address is a
/// function of the sender, salt, and init code. On failure the address will be `0`,
/// `return_data_len` will store the length of the revert data, the bytes of which can be read
/// via the `read_return_data` hostio. The semantics are equivalent to that of the EVM's
/// `[CREATE2`] opcode, which notably includes the exact address returned.
///
/// [`Deploying Stylus Programs`]: https://docs.arbitrum.io/stylus/quickstart
/// [`CREATE2`]: https://www.evm.codes/#f5
///
/// # Safety
/// This method should only be used in advanced cases when lowest-level access to create2 is required.
/// Safe usage needs to consider reentrancy, storage aliasing, and cache flushing.
/// utilize a [`RawDeploy`] struct instead for safety.
unsafe fn create2(
&self,
code: *const u8,
code_len: usize,
endowment: *const u8,
salt: *const u8,
contract: *mut u8,
revert_data_len: *mut usize,
);
}
/// Provides access to storage access and mutation via host methods.
pub trait StorageAccess {
/// Reads a 32-byte value from permanent storage. Stylus's storage format is identical to
/// that of the EVM. This means that, under the hood, this hostio is accessing the 32-byte
/// value stored in the EVM state trie at offset `key`, which will be `0` when not previously
/// set. The semantics, then, are equivalent to that of the EVM's [`SLOAD`] opcode.
///
/// Note: the Stylus VM implements storage caching. This means that repeated calls to the same key
/// will cost less than in the EVM.
///
/// [`SLOAD`]: https://www.evm.codes/#54
fn storage_load_bytes32(&self, key: U256) -> B256;
/// Writes a 32-byte value to the permanent storage cache. Stylus's storage format is identical to that
/// of the EVM. This means that, under the hood, this hostio represents storing a 32-byte value into
/// the EVM state trie at offset `key`. Refunds are tabulated exactly as in the EVM. The semantics, then,
/// are equivalent to that of the EVM's [`SSTORE`] opcode.
///
/// Note: because the value is cached, one must call `storage_flush_cache` to persist it.
///
/// [`SSTORE`]: https://www.evm.codes/#55
///
/// # Safety
/// May alias storage.
unsafe fn storage_cache_bytes32(&self, key: U256, value: B256);
/// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested.
/// Analogous to repeated invocations of [`SSTORE`].
///
/// [`SSTORE`]: https://www.evm.codes/#55
fn flush_cache(&self, clear: bool);
}
/// Provides access to calling other contracts using host semantics.
///
/// # Safety
/// These methods should only be used in advanced cases when lowest-level access
/// to call, static_call, and delegate_call methods is required. Using the methods by themselves will not protect
/// against reentrancy safety, storage aliasing, or cache flushing. For safe contract calls,
/// utilize a [`RawCall`] struct instead.
pub unsafe trait UnsafeCallAccess {
/// Calls the contract at the given address with options for passing value and to limit the
/// amount of gas supplied. The return status indicates whether the call succeeded, and is
/// nonzero on failure.
///
/// In both cases `return_data_len` will store the length of the result, the bytes of which can
/// be read via the `read_return_data` hostio. The bytes are not returned directly so that the
/// programmer can potentially save gas by choosing which subset of the return result they'd
/// like to copy.
///
/// The semantics are equivalent to that of the EVM's [`CALL`] opcode, including callvalue
/// stipends and the 63/64 gas rule. This means that supplying the `u64::MAX` gas can be used
/// to send as much as possible.
///
/// [`CALL`]: https://www.evm.codes/#f1
///
/// # Safety
/// This method should only be used in advanced cases when lowest-level access to calls is required.
/// Safe usage needs to consider reentrancy, storage aliasing, and cache flushing.
/// utilize a [`RawCall`] struct instead for safety.
unsafe fn call_contract(
&self,
to: *const u8,
data: *const u8,
data_len: usize,
value: *const u8,
gas: u64,
outs_len: &mut usize,
) -> u8;
/// Static calls the contract at the given address, with the option to limit the amount of gas
/// supplied. The return status indicates whether the call succeeded, and is nonzero on
/// failure.
///
/// In both cases `return_data_len` will store the length of the result, the bytes of which can
/// be read via the `read_return_data` hostio. The bytes are not returned directly so that the
/// programmer can potentially save gas by choosing which subset of the return result they'd
/// like to copy.
///
/// The semantics are equivalent to that of the EVM's [`STATIC_CALL`] opcode, including the
/// 63/64 gas rule. This means that supplying `u64::MAX` gas can be used to send as much as
/// possible.
///
/// [`STATIC_CALL`]: https://www.evm.codes/#FA
///
/// # Safety
/// This method should only be used in advanced cases when lowest-level access to calls is required.
/// Safe usage needs to consider reentrancy, storage aliasing, and cache flushing.
/// utilize a [`RawCall`] struct instead for safety.
unsafe fn static_call_contract(
&self,
to: *const u8,
data: *const u8,
data_len: usize,
gas: u64,
outs_len: &mut usize,
) -> u8;
/// Delegate calls the contract at the given address, with the option to limit the amount of
/// gas supplied. The return status indicates whether the call succeeded, and is nonzero on
/// failure.
///
/// In both cases `return_data_len` will store the length of the result, the bytes of which
/// can be read via the `read_return_data` hostio. The bytes are not returned directly so that
/// the programmer can potentially save gas by choosing which subset of the return result
/// they'd like to copy.
///
/// The semantics are equivalent to that of the EVM's [`DELEGATE_CALL`] opcode, including the
/// 63/64 gas rule. This means that supplying `u64::MAX` gas can be used to send as much as
/// possible.
///
/// [`DELEGATE_CALL`]: https://www.evm.codes/#F4
///
/// # Safety
/// This method should only be used in advanced cases when lowest-level access to calls is required.
/// Safe usage needs to consider reentrancy, storage aliasing, and cache flushing.
/// utilize a [`RawCall`] struct instead for safety.
unsafe fn delegate_call_contract(
&self,
to: *const u8,
data: *const u8,
data_len: usize,
gas: u64,
outs_len: &mut usize,
) -> u8;
}
/// Provides access to host methods relating to the block a transactions
/// to a Stylus contract is included in.
pub trait BlockAccess {
/// Gets the basefee of the current block. The semantics are equivalent to that of the EVM's
/// [`BASEFEE`] opcode.
///
/// [`BASEFEE`]: https://www.evm.codes/#48
fn block_basefee(&self) -> U256;
/// Gets the coinbase of the current block, which on Arbitrum chains is the L1 batch poster's
/// address. This differs from Ethereum where the validator including the transaction
/// determines the coinbase.
fn block_coinbase(&self) -> Address;
/// Gets a bounded estimate of the L1 block number at which the Sequencer sequenced the
/// transaction. See [`Block Numbers and Time`] for more information on how this value is
/// determined.
///
/// [`Block Numbers and Time`]: https://developer.arbitrum.io/time
fn block_number(&self) -> u64;
/// Gets a bounded estimate of the Unix timestamp at which the Sequencer sequenced the
/// transaction. See [`Block Numbers and Time`] for more information on how this value is
/// determined.
///
/// [`Block Numbers and Time`]: https://developer.arbitrum.io/time
fn block_timestamp(&self) -> u64;
/// Gets the gas limit of the current block. The semantics are equivalent to that of the EVM's
/// [`GAS_LIMIT`] opcode. Note that as of the time of this writing, `evm.codes` incorrectly
/// implies that the opcode returns the gas limit of the current transaction. When in doubt,
/// consult [`The Ethereum Yellow Paper`].
///
/// [`GAS_LIMIT`]: https://www.evm.codes/#45
/// [`The Ethereum Yellow Paper`]: https://ethereum.github.io/yellowpaper/paper.pdf
fn block_gas_limit(&self) -> u64;
}
/// Provides access to the chain details of the host environment.
pub trait ChainAccess {
/// Gets the unique chain identifier of the Arbitrum chain. The semantics are equivalent to
/// that of the EVM's [`CHAIN_ID`] opcode.
///
/// [`CHAIN_ID`]: https://www.evm.codes/#46
fn chain_id(&self) -> u64;
}
/// Provides access to account details of addresses of the host environment.
pub trait AccountAccess {
/// Gets the ETH balance in wei of the account at the given address.
/// The semantics are equivalent to that of the EVM's [`BALANCE`] opcode.
///
/// [`BALANCE`]: https://www.evm.codes/#31
fn balance(&self, account: Address) -> U256;
/// Gets the address of the current program. The semantics are equivalent to that of the EVM's
/// [`ADDRESS`] opcode.
///
/// [`ADDRESS`]: https://www.evm.codes/#30
fn contract_address(&self) -> Address;
/// Gets a subset of the code from the account at the given address. The semantics are identical to that
/// of the EVM's [`EXT_CODE_COPY`] opcode, aside from one small detail: the write to the buffer `dest` will
/// stop after the last byte is written. This is unlike the EVM, which right pads with zeros in this scenario.
/// The return value is the number of bytes written, which allows the caller to detect if this has occurred.
///
/// [`EXT_CODE_COPY`]: https://www.evm.codes/#3C
fn code(&self, account: Address) -> Vec<u8>;
/// Gets the size of the code in bytes at the given address. The semantics are equivalent
/// to that of the EVM's [`EXT_CODESIZE`].
///
/// [`EXT_CODESIZE`]: https://www.evm.codes/#3B
fn code_size(&self, account: Address) -> usize;
/// Gets the code hash of the account at the given address. The semantics are equivalent
/// to that of the EVM's [`EXT_CODEHASH`] opcode. Note that the code hash of an account without
/// code will be the empty hash
/// `keccak("") = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
///
/// [`EXT_CODEHASH`]: https://www.evm.codes/#3F
fn code_hash(&self, account: Address) -> B256;
}
/// Provides the ability to pay for memory growth of a Stylus contract.
pub trait MemoryAccess {
/// The `entrypoint!` macro handles importing this hostio, which is required if the
/// program's memory grows. Otherwise compilation through the `ArbWasm` precompile will revert.
/// Internally the Stylus VM forces calls to this hostio whenever new WASM pages are allocated.
/// Calls made voluntarily will unproductively consume gas.
fn pay_for_memory_grow(&self, pages: u16);
}
/// Provides access to transaction details of a Stylus contract.
pub trait MessageAccess {
/// Gets the address of the account that called the program. For normal L2-to-L2 transactions
/// the semantics are equivalent to that of the EVM's [`CALLER`] opcode, including in cases
/// arising from [`DELEGATE_CALL`].
///
/// For L1-to-L2 retryable ticket transactions, the top-level sender's address will be aliased.
/// See [`Retryable Ticket Address Aliasing`] for more information on how this works.
///
/// [`CALLER`]: https://www.evm.codes/#33
/// [`DELEGATE_CALL`]: https://www.evm.codes/#f4
/// [`Retryable Ticket Address Aliasing`]: https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing
fn msg_sender(&self) -> Address;
/// Whether the current call is reentrant.
fn msg_reentrant(&self) -> bool;
/// Get the ETH value in wei sent to the program. The semantics are equivalent to that of the
/// EVM's [`CALLVALUE`] opcode.
///
/// [`CALLVALUE`]: https://www.evm.codes/#34
fn msg_value(&self) -> U256;
/// Gets the top-level sender of the transaction. The semantics are equivalent to that of the
/// EVM's [`ORIGIN`] opcode.
///
/// [`ORIGIN`]: https://www.evm.codes/#32
fn tx_origin(&self) -> Address;
}
/// Provides access to metering values such as EVM gas and Stylus ink used and remaining,
/// as well as details of their prices based on the host environment.
pub trait MeteringAccess {
/// Gets the amount of gas left after paying for the cost of this hostio. The semantics are
/// equivalent to that of the EVM's [`GAS`] opcode.
///
/// [`GAS`]: https://www.evm.codes/#5a
fn evm_gas_left(&self) -> u64;
/// Gets the amount of ink remaining after paying for the cost of this hostio. The semantics
/// are equivalent to that of the EVM's [`GAS`] opcode, except the units are in ink. See
/// [`Ink and Gas`] for more information on Stylus's compute pricing.
///
/// [`GAS`]: https://www.evm.codes/#5a
/// [`Ink and Gas`]: https://docs.arbitrum.io/stylus/concepts/gas-metering
fn evm_ink_left(&self) -> u64;
/// Gets the gas price in wei per gas, which on Arbitrum chains equals the basefee. The
/// semantics are equivalent to that of the EVM's [`GAS_PRICE`] opcode.
///
/// [`GAS_PRICE`]: https://www.evm.codes/#3A
fn tx_gas_price(&self) -> U256;
/// Gets the price of ink in evm gas basis points. See [`Ink and Gas`] for more information on
/// Stylus's compute-pricing model.
///
/// [`Ink and Gas`]: https://docs.arbitrum.io/stylus/concepts/gas-metering
fn tx_ink_price(&self) -> u32;
/// Computes the units of gas per a specified amount of ink.
fn ink_to_gas(&self, ink: u64) -> u64 {
ink / self.tx_ink_price() as u64
}
/// Computes the units of ink per a specified amount of gas.
fn gas_to_ink(&self, gas: u64) -> u64 {
gas.saturating_mul(self.tx_ink_price().into())
}
}
/// Provides access to the ability to emit logs from a Stylus contract.
pub trait LogAccess {
/// Emits an EVM log with the given number of topics and data, the first bytes of which should
/// be the 32-byte-aligned topic data. The semantics are equivalent to that of the EVM's
/// [`LOG0`], [`LOG1`], [`LOG2`], [`LOG3`], and [`LOG4`] opcodes based on the number of topics
/// specified. Requesting more than `4` topics will induce a revert.
///
/// [`LOG0`]: https://www.evm.codes/#a0
/// [`LOG1`]: https://www.evm.codes/#a1
/// [`LOG2`]: https://www.evm.codes/#a2
/// [`LOG3`]: https://www.evm.codes/#a3
/// [`LOG4`]: https://www.evm.codes/#a4
fn emit_log(&self, input: &[u8], num_topics: usize);
/// Emits a raw log from topics and data.
fn raw_log(&self, topics: &[B256], data: &[u8]) -> Result<(), &'static str>;
}