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