Skip to content
This repository was archived by the owner on Nov 26, 2024. It is now read-only.

account_code and account_code_size host I/Os #191

Merged
merged 8 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion arbitrator/arbutil/src/evm/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub enum EvmApiMethod {
GetReturnData,
EmitLog,
AccountBalance,
AccountCode,
AccountCodeSize,
AccountCodeHash,
AddPages,
}
Expand Down Expand Up @@ -125,9 +127,23 @@ pub trait EvmApi: Send + 'static {
/// Analogous to `vm.BALANCE`.
fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64);

/// Returns the code and the access cost in gas.
/// Analogous to `vm.EXTCODECOPY`.
fn account_code(
&mut self,
address: Bytes20,
offset: u32,
size: u32,
gas_left: u64,
) -> (Vec<u8>, u64);

/// Returns the code size and the access cost in gas.
/// Analogous to `vm.EXTCODESIZE`.
fn account_code_size(&mut self, address: Bytes20, gas_left: u64) -> (u32, u64);

/// Gets the hash of the given address's code.
/// Returns the hash and the access cost in gas.
/// Analogous to `vm.CODEHASH`.
/// Analogous to `vm.EXTCODEHASH`.
fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64);

/// Determines the cost in gas of allocating additional wasm pages.
Expand Down
16 changes: 16 additions & 0 deletions arbitrator/arbutil/src/evm/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,22 @@ impl<T: JsCallIntoGo> EvmApi for JsEvmApi<T> {
(value.assert_bytes32(), cost.assert_u64())
}

fn account_code(
&mut self,
address: Bytes20,
offset: u32,
size: u32,
gas_left: u64,
) -> (Vec<u8>, u64) {
let [value, cost] = call!(self, 2, AccountCode, address, offset, size, gas_left);
(value.assert_bytes(), cost.assert_u64())
}

fn account_code_size(&mut self, address: Bytes20, gas_left: u64) -> (u32, u64) {
let [value, cost] = call!(self, 2, AccountCodeSize, address, gas_left);
(value.assert_u32(), cost.assert_u64())
}

fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64) {
let [value, cost] = call!(self, 2, AccountCodeHash, address);
(value.assert_bytes32(), cost.assert_u64())
Expand Down
6 changes: 6 additions & 0 deletions arbitrator/arbutil/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ pub mod user;
// params.SstoreSentryGasEIP2200
pub const SSTORE_SENTRY_GAS: u64 = 2300;

// params.ColdAccountAccessCostEIP2929
pub const COLD_ACCOUNT_GAS: u64 = 2600;

// params.ColdSloadCostEIP2929
pub const COLD_SLOAD_GAS: u64 = 2100;

// params.LogGas and params.LogDataGas
pub const LOG_TOPIC_GAS: u64 = 375;
pub const LOG_DATA_GAS: u64 = 8;
Expand Down
2 changes: 1 addition & 1 deletion arbitrator/langs/bf
Submodule bf updated 1 files
+2 −2 README.md
2 changes: 1 addition & 1 deletion arbitrator/langs/c
Submodule c updated 1 files
+23 −2 include/hostio.h
2 changes: 1 addition & 1 deletion arbitrator/prover/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ impl<'a> WasmBinary<'a> {
let asm_estimate = 5 * 1024 * 1024;

// TODO: determine safe value
let init_gas = 2048;
let init_gas = 4096;

let [ink_left, ink_status] = meter.globals();
let depth_left = depth.globals();
Expand Down
37 changes: 37 additions & 0 deletions arbitrator/stylus/src/evm_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ pub struct GoEvmApi {
unsafe extern "C" fn(id: usize, data: *mut RustBytes, topics: u32) -> EvmApiStatus,
pub account_balance:
unsafe extern "C" fn(id: usize, address: Bytes20, gas_cost: *mut u64) -> Bytes32, // balance
pub account_code: unsafe extern "C" fn(
id: usize,
code: *mut RustBytes,
address: Bytes20,
offset: u32,
size: u32,
gas_cost: *mut u64,
),
pub account_code_size:
unsafe extern "C" fn(id: usize, address: Bytes20, gas_cost: *mut u64) -> u32, // code size
pub account_codehash:
unsafe extern "C" fn(id: usize, address: Bytes20, gas_cost: *mut u64) -> Bytes32, // codehash
pub add_pages: unsafe extern "C" fn(id: usize, pages: u16) -> u64, // gas cost
Expand Down Expand Up @@ -250,6 +260,33 @@ impl EvmApi for GoEvmApi {
(value, cost)
}

fn account_code(
&mut self,
address: Bytes20,
offset: u32,
size: u32,
gas_left: u64,
) -> (Vec<u8>, u64) {
let mut data = RustBytes::new(vec![]);
let mut cost = gas_left; // pass amount left
call!(
self,
account_code,
ptr!(data),
address,
offset,
size,
ptr!(cost)
);
(into_vec!(data), cost)
}

fn account_code_size(&mut self, address: Bytes20, gas_left: u64) -> (u32, u64) {
let mut cost = gas_left; // pass amount left
let size = call!(self, account_code_size, address, ptr!(cost));
(size, cost)
}

fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64) {
let mut cost = 0;
let value = call!(self, account_codehash, address, ptr!(cost));
Expand Down
17 changes: 17 additions & 0 deletions arbitrator/stylus/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,23 @@ pub(crate) fn account_balance<E: EvmApi>(
hostio!(env, account_balance(address, ptr))
}

pub(crate) fn account_code<E: EvmApi>(
mut env: WasmEnvMut<E>,
address: u32,
offset: u32,
size: u32,
code: u32,
) -> Result<u32, Escape> {
hostio!(env, account_code(address, offset, size, code))
}

pub(crate) fn account_code_size<E: EvmApi>(
mut env: WasmEnvMut<E>,
address: u32,
) -> Result<u32, Escape> {
hostio!(env, account_code_size(address))
}

pub(crate) fn account_codehash<E: EvmApi>(
mut env: WasmEnvMut<E>,
address: u32,
Expand Down
4 changes: 4 additions & 0 deletions arbitrator/stylus/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ impl<E: EvmApi> NativeInstance<E> {
"return_data_size" => func!(host::return_data_size),
"emit_log" => func!(host::emit_log),
"account_balance" => func!(host::account_balance),
"account_code" => func!(host::account_code),
"account_codehash" => func!(host::account_codehash),
"account_code_size" => func!(host::account_code_size),
"evm_gas_left" => func!(host::evm_gas_left),
"evm_ink_left" => func!(host::evm_ink_left),
"block_basefee" => func!(host::block_basefee),
Expand Down Expand Up @@ -342,7 +344,9 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result<Vec<u8>> {
"return_data_size" => stub!(u32 <- ||),
"emit_log" => stub!(|_: u32, _: u32, _: u32|),
"account_balance" => stub!(|_: u32, _: u32|),
"account_code" => stub!(u32 <- |_: u32, _: u32, _: u32, _: u32|),
"account_codehash" => stub!(|_: u32, _: u32|),
"account_code_size" => stub!(u32 <- |_: u32|),
"evm_gas_left" => stub!(u64 <- ||),
"evm_ink_left" => stub!(u64 <- ||),
"block_basefee" => stub!(|_: u32|),
Expand Down
14 changes: 14 additions & 0 deletions arbitrator/stylus/src/test/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ impl EvmApi for TestEvmApi {
unimplemented!()
}

fn account_code(
&mut self,
_address: Bytes20,
_offset: u32,
_size: u32,
_gas_left: u64,
) -> (Vec<u8>, u64) {
unimplemented!()
}

fn account_code_size(&mut self, _address: Bytes20, _gas_left: u64) -> (u32, u64) {
unimplemented!()
}

fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) {
unimplemented!()
}
Expand Down
2 changes: 1 addition & 1 deletion arbitrator/stylus/tests/evm-data/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
stylus-sdk = { path = "../../../langs/rust/stylus-sdk" }
stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["debug"] }
hex = "0.4.3"

[profile.release]
Expand Down
12 changes: 12 additions & 0 deletions arbitrator/stylus/tests/evm-data/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#![no_main]

extern crate alloc;
use alloc::vec::Vec;

use stylus_sdk::{
alloy_primitives::{Address, B256, U256},
block,
Expand All @@ -24,6 +27,14 @@ fn user_main(input: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
let eth_precompile_codehash = eth_precompile_addr.codehash();
let arb_precompile_codehash = arb_test_addr.codehash();
let contract_codehash = contract_addr.codehash();

let code = contract_addr.code();
assert_eq!(code.len(), contract_addr.code_size());
assert_eq!(arb_test_addr.code_size(), 1);
assert_eq!(arb_test_addr.code(), [0xfe]);
assert_eq!(eth_precompile_addr.code_size(), 0);
assert_eq!(eth_precompile_addr.code(), []);

let basefee = block::basefee();
let chainid = block::chainid();
let coinbase = block::coinbase();
Expand Down Expand Up @@ -64,6 +75,7 @@ fn user_main(input: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
output.extend(contract_codehash);
output.extend(arb_precompile_codehash);
output.extend(eth_precompile_codehash);
output.extend(code);

output.extend(ink_price.to_be_bytes());
output.extend(gas_left_before.to_be_bytes());
Expand Down
4 changes: 3 additions & 1 deletion arbitrator/wasm-libraries/forward/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{fs::File, io::Write, path::PathBuf};
use structopt::StructOpt;

/// order matters!
const HOSTIOS: [[&str; 3]; 31] = [
const HOSTIOS: [[&str; 3]; 33] = [
["read_args", "i32", ""],
["write_result", "i32 i32", ""],
["storage_load_bytes32", "i32 i32", ""],
Expand All @@ -20,6 +20,8 @@ const HOSTIOS: [[&str; 3]; 31] = [
["return_data_size", "", "i32"],
["emit_log", "i32 i32 i32", ""],
["account_balance", "i32 i32", ""],
["account_code", "i32 i32 i32 i32", "i32"],
["account_code_size", "i32", "i32"],
["account_codehash", "i32 i32", ""],
["evm_gas_left", "", "i64"],
["evm_ink_left", "", "i64"],
Expand Down
60 changes: 57 additions & 3 deletions arbitrator/wasm-libraries/user-host-trait/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2023, Offchain Labs, Inc.
// Copyright 2022-2024, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

use arbutil::{
Expand Down Expand Up @@ -103,6 +103,7 @@ pub trait UserHost: GasMeteredMachine {
/// [`SLOAD`]: https://www.evm.codes/#54
fn storage_load_bytes32(&mut self, key: u32, dest: u32) -> Result<(), Self::Err> {
self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?;
self.require_gas(evm::COLD_SLOAD_GAS)?;
let key = self.read_bytes32(key)?;

let (value, gas_cost) = self.evm_api().get_bytes32(key);
Expand All @@ -116,6 +117,9 @@ pub trait UserHost: GasMeteredMachine {
/// the EVM state trie at offset `key`. Furthermore, refunds are tabulated exactly as in the
/// EVM. The semantics, then, are equivalent to that of the EVM's [`SSTORE`] opcode.
///
/// Note: we require the [`SSTORE`] sentry per EVM rules. The `gas_cost` returned by the EVM API
/// may exceed this amount, but that's ok because the predominant cost is due to state bloat concerns.
///
/// [`SSTORE`]: https://www.evm.codes/#55
fn storage_store_bytes32(&mut self, key: u32, value: u32) -> Result<(), Self::Err> {
self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?;
Expand Down Expand Up @@ -394,10 +398,15 @@ pub trait UserHost: GasMeteredMachine {
/// 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(&mut self, dest: u32, offset: u32, size: u32) -> Result<u32, Self::Err> {
self.buy_ink(HOSTIO_INK + EVM_API_INK)?;
self.pay_for_write(size)?;

// pay for only as many bytes as could possibly be written
let max = self.evm_return_data_len().saturating_sub(offset);
self.pay_for_write(size.min(max))?;

let data = self.evm_api().get_return_data(offset, size);
assert!(data.len() <= size as usize);
Expand Down Expand Up @@ -453,6 +462,7 @@ pub trait UserHost: GasMeteredMachine {
/// [`BALANCE`]: https://www.evm.codes/#31
fn account_balance(&mut self, address: u32, ptr: u32) -> Result<(), Self::Err> {
self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?;
self.require_gas(evm::COLD_ACCOUNT_GAS)?;
let address = self.read_bytes20(address)?;

let (balance, gas_cost) = self.evm_api().account_balance(address);
Expand All @@ -461,6 +471,49 @@ pub trait UserHost: GasMeteredMachine {
trace!("account_balance", self, address, balance)
}

/// 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 occured.
///
/// [`EXT_CODE_COPY`]: https://www.evm.codes/#3C
fn account_code(
&mut self,
address: u32,
offset: u32,
size: u32,
dest: u32,
) -> Result<u32, Self::Err> {
self.buy_ink(HOSTIO_INK + EVM_API_INK)?;
self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go
let address = self.read_bytes20(address)?;
let gas = self.gas_left()?;

// we pass `gas` to check if there's enough before loading from the db
let (code, gas_cost) = self.evm_api().account_code(address, offset, size, gas);
self.buy_gas(gas_cost)?;

self.pay_for_write(code.len() as u32)?;
self.write_slice(dest, &code)?;

trace!("account_code", self, address, be!(size), code.len() as u32)
}

/// 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 account_code_size(&mut self, address: u32) -> Result<u32, Self::Err> {
self.buy_ink(HOSTIO_INK + EVM_API_INK)?;
self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go
let address = self.read_bytes20(address)?;
let gas = self.gas_left()?;

let (len, gas_cost) = self.evm_api().account_code_size(address, gas);
self.buy_gas(gas_cost)?;
trace!("account_code_size", self, address, &[], len)
}

/// 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
Expand All @@ -469,6 +522,7 @@ pub trait UserHost: GasMeteredMachine {
/// [`EXT_CODEHASH`]: https://www.evm.codes/#3F
fn account_codehash(&mut self, address: u32, ptr: u32) -> Result<(), Self::Err> {
self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?;
self.require_gas(evm::COLD_ACCOUNT_GAS)?;
let address = self.read_bytes20(address)?;

let (hash, gas_cost) = self.evm_api().account_codehash(address);
Expand Down Expand Up @@ -661,7 +715,7 @@ pub trait UserHost: GasMeteredMachine {
self.buy_ink(HOSTIO_INK)?;
return Ok(());
}
let gas_cost = self.evm_api().add_pages(pages);
let gas_cost = self.evm_api().add_pages(pages); // no sentry needed since the work happens after the hostio
self.buy_gas(gas_cost)?;
trace!("pay_for_memory_grow", self, be!(pages), &[])
}
Expand Down
Loading