Skip to content

Commit

Permalink
Merge pull request #81 from OffchainLabs/read-return-data-partial
Browse files Browse the repository at this point in the history
Add partial return data support for stylus contract calls
  • Loading branch information
rachel-bousfield authored Jul 26, 2023
2 parents e17df78 + 627af07 commit 4e89184
Show file tree
Hide file tree
Showing 37 changed files with 954 additions and 145 deletions.
46 changes: 26 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,28 @@ stylus_lang_c = $(wildcard arbitrator/langs/c/*.c arbitrator/langs/c/*.h)
get_stylus_test_wasm = $(stylus_test_dir)/$(1)/$(wasm32_unknown)/$(1).wasm
get_stylus_test_rust = $(wildcard $(stylus_test_dir)/$(1)/*.toml $(stylus_test_dir)/$(1)/src/*.rs) $(stylus_cargo) $(stylus_lang_rust)
get_stylus_test_c = $(wildcard $(stylus_test_dir)/$(1)/*.c $(stylus_test_dir)/$(1)/*.h) $(stylus_lang_c)
stylus_test_keccak_wasm = $(call get_stylus_test_wasm,keccak)
stylus_test_keccak_src = $(call get_stylus_test_rust,keccak)
stylus_test_keccak-100_wasm = $(call get_stylus_test_wasm,keccak-100)
stylus_test_keccak-100_src = $(call get_stylus_test_rust,keccak-100)
stylus_test_fallible_wasm = $(call get_stylus_test_wasm,fallible)
stylus_test_fallible_src = $(call get_stylus_test_rust,fallible)
stylus_test_storage_wasm = $(call get_stylus_test_wasm,storage)
stylus_test_storage_src = $(call get_stylus_test_rust,storage)
stylus_test_multicall_wasm = $(call get_stylus_test_wasm,multicall)
stylus_test_multicall_src = $(call get_stylus_test_rust,multicall)
stylus_test_log_wasm = $(call get_stylus_test_wasm,log)
stylus_test_log_src = $(call get_stylus_test_rust,log)
stylus_test_create_wasm = $(call get_stylus_test_wasm,create)
stylus_test_create_src = $(call get_stylus_test_rust,create)
stylus_test_evm-data_wasm = $(call get_stylus_test_wasm,evm-data)
stylus_test_evm-data_src = $(call get_stylus_test_rust,evm-data)
stylus_test_siphash_wasm = $(stylus_test_dir)/siphash/siphash.wasm
stylus_test_siphash_src = $(call get_stylus_test_c,siphash)

stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_siphash_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_evm-data_wasm)
stylus_test_keccak_wasm = $(call get_stylus_test_wasm,keccak)
stylus_test_keccak_src = $(call get_stylus_test_rust,keccak)
stylus_test_keccak-100_wasm = $(call get_stylus_test_wasm,keccak-100)
stylus_test_keccak-100_src = $(call get_stylus_test_rust,keccak-100)
stylus_test_fallible_wasm = $(call get_stylus_test_wasm,fallible)
stylus_test_fallible_src = $(call get_stylus_test_rust,fallible)
stylus_test_storage_wasm = $(call get_stylus_test_wasm,storage)
stylus_test_storage_src = $(call get_stylus_test_rust,storage)
stylus_test_multicall_wasm = $(call get_stylus_test_wasm,multicall)
stylus_test_multicall_src = $(call get_stylus_test_rust,multicall)
stylus_test_log_wasm = $(call get_stylus_test_wasm,log)
stylus_test_log_src = $(call get_stylus_test_rust,log)
stylus_test_create_wasm = $(call get_stylus_test_wasm,create)
stylus_test_create_src = $(call get_stylus_test_rust,create)
stylus_test_evm-data_wasm = $(call get_stylus_test_wasm,evm-data)
stylus_test_evm-data_src = $(call get_stylus_test_rust,evm-data)
stylus_test_read-return-data_wasm = $(call get_stylus_test_wasm,read-return-data)
stylus_test_read-return-data_src = $(call get_stylus_test_rust,read-return-data)
stylus_test_siphash_wasm = $(stylus_test_dir)/siphash/siphash.wasm
stylus_test_siphash_src = $(call get_stylus_test_c,siphash)

stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_siphash_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm)
stylus_benchmarks = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(stylus_test_wasms)
stylus_files = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(rust_prover_files)

Expand Down Expand Up @@ -380,6 +382,10 @@ $(stylus_test_evm-data_wasm): $(stylus_test_evm-data_src)
cargo build --manifest-path $< --release --config $(stylus_cargo)
@touch -c $@ # cargo might decide to not rebuild the binary

$(stylus_test_read-return-data_wasm): $(stylus_test_read-return-data_src)
cargo build --manifest-path $< --release --config $(stylus_cargo)
@touch -c $@ # cargo might decide to not rebuild the binary

$(stylus_test_siphash_wasm): $(stylus_test_siphash_src)
clang $(filter %.c, $^) -o $@ --target=wasm32 --no-standard-libraries -Wl,--no-entry -Oz

Expand Down
2 changes: 1 addition & 1 deletion arbitrator/arbutil/src/evm/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub trait EvmApi: Send + 'static {

/// Returns the EVM return data.
/// Analogous to `vm.RETURNDATASIZE`.
fn get_return_data(&mut self) -> Vec<u8>;
fn get_return_data(&mut self, offset: u32, size: u32) -> Vec<u8>;

/// Emits an EVM log with the given number of topics and data, the first bytes of which should be the topic data.
/// Returns an error message on failure.
Expand Down
4 changes: 2 additions & 2 deletions arbitrator/arbutil/src/evm/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ impl<T: JsCallIntoGo> EvmApi for JsEvmApi<T> {
(result, len.assert_u32(), cost.assert_u64())
}

fn get_return_data(&mut self) -> Bytes {
let [data] = call!(self, 1, GetReturnData);
fn get_return_data(&mut self, offset: u32, size: u32) -> Bytes {
let [data] = call!(self, 1, GetReturnData, offset, size);
data.assert_bytes()
}

Expand Down
2 changes: 1 addition & 1 deletion arbitrator/arbutil/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub const ORIGIN_GAS: u64 = GAS_QUICK_STEP;
#[repr(C)]
pub struct EvmData {
pub block_basefee: Bytes32,
pub block_chainid: Bytes32,
pub chainid: Bytes32,
pub block_coinbase: Bytes20,
pub block_gas_limit: u64,
pub block_number: Bytes32,
Expand Down
27 changes: 27 additions & 0 deletions arbitrator/arbutil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,30 @@ pub mod wavm;
pub fn heapify<T>(value: T) -> *mut T {
Box::into_raw(Box::new(value))
}

/// Equivalent to &[start..offset], but truncates when out of bounds rather than panicking.
pub fn slice_with_runoff<T>(data: &impl AsRef<[T]>, start: usize, end: usize) -> &[T] {
let data = data.as_ref();
if start >= data.len() || end < start {
return &[];
}
&data[start..end.min(data.len())]
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_limit_vec() {
let testvec = vec![0, 1, 2, 3];
assert_eq!(slice_with_runoff(&testvec, 4, 4), &testvec[0..0]);
assert_eq!(slice_with_runoff(&testvec, 1, 0), &testvec[0..0]);
assert_eq!(slice_with_runoff(&testvec, 0, 0), &testvec[0..0]);
assert_eq!(slice_with_runoff(&testvec, 0, 1), &testvec[0..1]);
assert_eq!(slice_with_runoff(&testvec, 1, 3), &testvec[1..3]);
assert_eq!(slice_with_runoff(&testvec, 0, 4), &testvec[0..4]);
assert_eq!(slice_with_runoff(&testvec, 0, 5), &testvec[0..4]);
assert_eq!(slice_with_runoff(&testvec, 2, usize::MAX), &testvec[2..4]);
}
}
6 changes: 3 additions & 3 deletions arbitrator/jit/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,15 @@ pub fn rust_config_impl(env: WasmEnvMut, sp: u32) {

/// Creates an `EvmData` from its component parts.
/// go side: λ(
/// blockBasefee, blockChainid *[32]byte, blockCoinbase *[20]byte,
/// blockGasLimit u64, blockNumber *[32]byte, blockTimestamp u64, contractAddress, msgSender *[20]byte,
/// blockBasefee, chainid *[32]byte, blockCoinbase *[20]byte, blockGasLimit u64,
/// blockNumber *[32]byte, blockTimestamp u64, contractAddress, msgSender *[20]byte,
/// msgValue, txGasPrice *[32]byte, txOrigin *[20]byte,
///) *EvmData
pub fn evm_data_impl(env: WasmEnvMut, sp: u32) {
let mut sp = GoStack::simple(sp, &env);
let evm_data = EvmData {
block_basefee: sp.read_bytes32().into(),
block_chainid: sp.read_bytes32().into(),
chainid: sp.read_bytes32().into(),
block_coinbase: sp.read_bytes20().into(),
block_gas_limit: sp.read_u64(),
block_number: sp.read_bytes32().into(),
Expand Down
4 changes: 2 additions & 2 deletions arbitrator/langs/c/arbitrum.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extern "C" {
#define USER_HOST import_module("vm_hooks")

extern __attribute__((USER_HOST, import_name("read_args"))) void read_args(const uint8_t * data);
extern __attribute__((USER_HOST, import_name("return_data"))) void return_data(const uint8_t * data, size_t len);
extern __attribute__((USER_HOST, import_name("write_result"))) void write_result(const uint8_t * data, size_t len);
extern __attribute__((USER_HOST, import_name("memory_grow"))) void memory_grow(uint32_t pages);

typedef enum ArbStatus {
Expand All @@ -41,7 +41,7 @@ typedef struct ArbResult {
const uint8_t args[args_len]; \
read_args(args); \
const ArbResult result = user_main(args, args_len); \
return_data(result.output, result.output_len); \
write_result(result.output, result.output_len); \
return result.status; \
}

Expand Down
2 changes: 1 addition & 1 deletion arbitrator/langs/rust
Submodule rust updated 6 files
+4 −4 src/block.rs
+107 −48 src/contract.rs
+3 −3 src/evm.rs
+45 −12 src/hostio.rs
+3 −3 src/lib.rs
+27 −9 src/tx.rs
7 changes: 4 additions & 3 deletions arbitrator/stylus/src/evm_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ pub struct GoEvmApi {
gas: *mut u64,
return_data_len: *mut u32,
) -> EvmApiStatus,
pub get_return_data: unsafe extern "C" fn(id: usize, output: *mut RustVec),
pub get_return_data:
unsafe extern "C" fn(id: usize, output: *mut RustVec, offset: u32, size: u32),
pub emit_log: unsafe extern "C" fn(id: usize, data: *mut RustVec, topics: u32) -> EvmApiStatus,
pub account_balance:
unsafe extern "C" fn(id: usize, address: Bytes20, gas_cost: *mut u64) -> Bytes32, // balance
Expand Down Expand Up @@ -218,9 +219,9 @@ impl EvmApi for GoEvmApi {
(result, return_data_len, call_gas)
}

fn get_return_data(&mut self) -> Vec<u8> {
fn get_return_data(&mut self, offset: u32, size: u32) -> Vec<u8> {
let mut data = RustVec::new(vec![]);
call!(self, get_return_data, ptr!(data));
call!(self, get_return_data, ptr!(data), offset, size);
into_vec!(data)
}

Expand Down
43 changes: 23 additions & 20 deletions arbitrator/stylus/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ pub(crate) fn read_args<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32) -> MaybeEsc
Ok(())
}

pub(crate) fn return_data<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32, len: u32) -> MaybeEscape {
pub(crate) fn write_result<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32, len: u32) -> MaybeEscape {
let mut env = WasmEnv::start(&mut env)?;
env.pay_for_evm_copy(len.into())?;
env.outs = env.read_slice(ptr, len)?;
Ok(())
}

pub(crate) fn account_load_bytes32<E: EvmApi>(
pub(crate) fn storage_load_bytes32<E: EvmApi>(
mut env: WasmEnvMut<E>,
key: u32,
dest: u32,
Expand All @@ -38,7 +38,7 @@ pub(crate) fn account_load_bytes32<E: EvmApi>(
Ok(())
}

pub(crate) fn account_store_bytes32<E: EvmApi>(
pub(crate) fn storage_store_bytes32<E: EvmApi>(
mut env: WasmEnvMut<E>,
key: u32,
value: u32,
Expand All @@ -59,38 +59,38 @@ pub(crate) fn call_contract<E: EvmApi>(
data: u32,
data_len: u32,
value: u32,
ink: u64,
gas: u64,
ret_len: u32,
) -> Result<u8, Escape> {
let value = Some(value);
let call = |api: &mut E, contract, data, gas, value: Option<_>| {
api.contract_call(contract, data, gas, value.unwrap())
};
do_call(env, contract, data, data_len, value, ink, ret_len, call)
do_call(env, contract, data, data_len, value, gas, ret_len, call)
}

pub(crate) fn delegate_call_contract<E: EvmApi>(
env: WasmEnvMut<E>,
contract: u32,
data: u32,
data_len: u32,
ink: u64,
gas: u64,
ret_len: u32,
) -> Result<u8, Escape> {
let call = |api: &mut E, contract, data, gas, _| api.delegate_call(contract, data, gas);
do_call(env, contract, data, data_len, None, ink, ret_len, call)
do_call(env, contract, data, data_len, None, gas, ret_len, call)
}

pub(crate) fn static_call_contract<E: EvmApi>(
env: WasmEnvMut<E>,
contract: u32,
data: u32,
data_len: u32,
ink: u64,
gas: u64,
ret_len: u32,
) -> Result<u8, Escape> {
let call = |api: &mut E, contract, data, gas, _| api.static_call(contract, data, gas);
do_call(env, contract, data, data_len, None, ink, ret_len, call)
do_call(env, contract, data, data_len, None, gas, ret_len, call)
}

pub(crate) fn do_call<F, E>(
Expand All @@ -99,7 +99,7 @@ pub(crate) fn do_call<F, E>(
calldata: u32,
calldata_len: u32,
value: Option<u32>,
mut ink: u64,
mut gas: u64,
return_data_len: u32,
call: F,
) -> Result<u8, Escape>
Expand All @@ -109,9 +109,8 @@ where
{
let mut env = WasmEnv::start(&mut env)?;
env.pay_for_evm_copy(calldata_len.into())?;
ink = ink.min(env.ink_ready()?); // provide no more than what the user has
gas = gas.min(env.gas_left()?); // provide no more than what the user has

let gas = env.pricing().ink_to_gas(ink);
let contract = env.read_bytes20(contract)?;
let input = env.read_slice(calldata, calldata_len)?;
let value = value.map(|x| env.read_bytes32(x)).transpose()?;
Expand Down Expand Up @@ -172,15 +171,19 @@ pub(crate) fn create2<E: EvmApi>(
Ok(())
}

pub(crate) fn read_return_data<E: EvmApi>(mut env: WasmEnvMut<E>, dest: u32) -> MaybeEscape {
pub(crate) fn read_return_data<E: EvmApi>(
mut env: WasmEnvMut<E>,
dest: u32,
offset: u32,
size: u32,
) -> Result<u32, Escape> {
let mut env = WasmEnv::start(&mut env)?;
let len = env.evm_data.return_data_len;
env.pay_for_evm_copy(len.into())?;
env.pay_for_evm_copy(size.into())?;

let data = env.evm_api.get_return_data();
let data = env.evm_api.get_return_data(offset, size);
assert!(data.len() <= size as usize);
env.write_slice(dest, &data)?;
assert_eq!(data.len(), len as usize);
Ok(())
Ok(data.len() as u32)
}

pub(crate) fn return_data_size<E: EvmApi>(mut env: WasmEnvMut<E>) -> Result<u32, Escape> {
Expand Down Expand Up @@ -251,10 +254,10 @@ pub(crate) fn block_basefee<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32) -> Mayb
Ok(())
}

pub(crate) fn block_chainid<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32) -> MaybeEscape {
pub(crate) fn chainid<E: EvmApi>(mut env: WasmEnvMut<E>, ptr: u32) -> MaybeEscape {
let mut env = WasmEnv::start(&mut env)?;
env.buy_gas(evm::CHAINID_GAS)?;
env.write_bytes32(ptr, env.evm_data.block_chainid)?;
env.write_bytes32(ptr, env.evm_data.chainid)?;
Ok(())
}

Expand Down
18 changes: 9 additions & 9 deletions arbitrator/stylus/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ impl<E: EvmApi> NativeInstance<E> {
let mut imports = imports! {
"vm_hooks" => {
"read_args" => func!(host::read_args),
"return_data" => func!(host::return_data),
"account_load_bytes32" => func!(host::account_load_bytes32),
"account_store_bytes32" => func!(host::account_store_bytes32),
"write_result" => func!(host::write_result),
"storage_load_bytes32" => func!(host::storage_load_bytes32),
"storage_store_bytes32" => func!(host::storage_store_bytes32),
"call_contract" => func!(host::call_contract),
"delegate_call_contract" => func!(host::delegate_call_contract),
"static_call_contract" => func!(host::static_call_contract),
Expand All @@ -135,7 +135,7 @@ impl<E: EvmApi> NativeInstance<E> {
"evm_gas_left" => func!(host::evm_gas_left),
"evm_ink_left" => func!(host::evm_ink_left),
"block_basefee" => func!(host::block_basefee),
"block_chainid" => func!(host::block_chainid),
"chainid" => func!(host::chainid),
"block_coinbase" => func!(host::block_coinbase),
"block_gas_limit" => func!(host::block_gas_limit),
"block_number" => func!(host::block_number),
Expand Down Expand Up @@ -311,23 +311,23 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result<Vec<u8>> {
let mut imports = imports! {
"vm_hooks" => {
"read_args" => stub!(|_: u32|),
"return_data" => stub!(|_: u32, _: u32|),
"account_load_bytes32" => stub!(|_: u32, _: u32|),
"account_store_bytes32" => stub!(|_: u32, _: u32|),
"write_result" => stub!(|_: u32, _: u32|),
"storage_load_bytes32" => stub!(|_: u32, _: u32|),
"storage_store_bytes32" => stub!(|_: u32, _: u32|),
"call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u32, _: u64, _: u32|),
"delegate_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|),
"static_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|),
"create1" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32|),
"create2" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32, _: u32|),
"read_return_data" => stub!(|_: u32|),
"read_return_data" => stub!(u32 <- |_: u32, _: u32, _: u32|),
"return_data_size" => stub!(u32 <- ||),
"emit_log" => stub!(|_: u32, _: u32, _: u32|),
"account_balance" => stub!(|_: u32, _: u32|),
"account_codehash" => stub!(|_: u32, _: u32|),
"evm_gas_left" => stub!(u64 <- ||),
"evm_ink_left" => stub!(u64 <- ||),
"block_basefee" => stub!(|_: u32|),
"block_chainid" => stub!(|_: u32|),
"chainid" => stub!(|_: u32|),
"block_coinbase" => stub!(|_: u32|),
"block_gas_limit" => stub!(u64 <- ||),
"block_number" => stub!(|_: u32|),
Expand Down
2 changes: 1 addition & 1 deletion arbitrator/stylus/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl<E: EvmApi> RunProgram for NativeInstance<E> {
self.set_stack(config.max_depth);

let store = &mut self.store;
let mut env = self.env.as_mut(store);
let env = self.env.as_mut(store);
env.args = args.to_owned();
env.outs.clear();
env.config = Some(config);
Expand Down
Loading

0 comments on commit 4e89184

Please sign in to comment.