Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract Calls #47

Merged
merged 10 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,12 @@ 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_calls_wasm = $(call get_stylus_test_wasm,calls)
stylus_test_calls_src = $(call get_stylus_test_rust,calls)
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_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_siphash_wasm) $(stylus_test_calls_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 @@ -342,6 +344,10 @@ $(stylus_test_storage_wasm): $(stylus_test_storage_src)
cargo build --manifest-path $< --release --target wasm32-unknown-unknown
@touch -c $@ # cargo might decide to not rebuild the binary

$(stylus_test_calls_wasm): $(stylus_test_calls_src)
cargo build --manifest-path $< --release --target wasm32-unknown-unknown
@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
22 changes: 22 additions & 0 deletions arbitrator/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 14 additions & 19 deletions arbitrator/jit/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,28 @@ pub fn call_user_wasm(env: WasmEnvMut, sp: u32) {
// skip the root since we don't use these
sp.skip_u64();

macro_rules! error {
($msg:expr, $report:expr) => {{
let outs = format!("{:?}", $report.wrap_err(eyre!($msg))).into_bytes();
sp.write_u8(UserOutcomeKind::Failure as u8).skip_space();
sp.write_ptr(heapify(outs));
if pricing.wasm_gas_price != 0 {
sp.write_u64_raw(evm_gas, pricing.wasm_to_evm(wasm_gas));
}
return;
}};
}

// Safety: module came from compile_user_wasm
let instance = unsafe { NativeInstance::deserialize(&module, config.clone()) };

let mut instance = match instance {
Ok(instance) => instance,
Err(error) => error!("failed to instantiate program", error),
Err(error) => panic!("failed to instantiate program {error:?}"),
};
instance.set_gas(wasm_gas);
instance.set_stack(config.depth.max_depth);

let (status, outs) = match instance.run_main(&calldata, &config) {
Err(err) | Ok(UserOutcome::Failure(err)) => error!("failed to execute program", err),
Ok(outcome) => outcome.into_data(),
let status = match instance.run_main(&calldata, &config) {
Err(err) | Ok(UserOutcome::Failure(err)) => {
let outs = format!("{:?}", err.wrap_err(eyre!("failed to execute program")));
sp.write_u8(UserOutcomeKind::Failure as u8).skip_space();
sp.write_ptr(heapify(outs.into_bytes()));
UserOutcomeKind::Failure
}
Ok(outcome) => {
let (status, outs) = outcome.into_data();
sp.write_u8(status as u8).skip_space();
sp.write_ptr(heapify(outs));
status
}
};
if pricing.wasm_gas_price != 0 {
let wasm_gas = match status {
Expand All @@ -83,8 +80,6 @@ pub fn call_user_wasm(env: WasmEnvMut, sp: u32) {
};
sp.write_u64_raw(evm_gas, pricing.wasm_to_evm(wasm_gas));
}
sp.write_u8(status as u8).skip_space();
sp.write_ptr(heapify(outs));
}

/// Reads the length of a rust `Vec`
Expand Down
62 changes: 62 additions & 0 deletions arbitrator/langs/rust/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2023, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE

use super::util::{Bytes20, Bytes32};

#[derive(Copy, Clone)]
#[repr(C)]
struct RustVec {
ptr: *mut u8,
len: usize,
cap: usize,
}

impl Default for RustVec {
fn default() -> Self {
Self {
ptr: std::ptr::null_mut(),
len: 0,
cap: 0,
}
}
}

#[link(wasm_import_module = "forward")]
extern "C" {
fn call_contract(
contract: *const u8,
calldata: *const u8,
calldata_len: usize,
value: *const u8,
gas: u64,
return_data_len: *mut usize,
) -> u8;

fn read_return_data(dest: *mut u8);
}

pub fn call(contract: Bytes20, calldata: &[u8], value: Option<Bytes32>, gas: Option<u64>) -> Result<Vec<u8>, Vec<u8>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be useful to add documentation to this function, specifically regarding what the error result contains. In the case the unsafe call to read_return_data fails, what will be included in outs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea, I've added some documentation :)

If read_return_data fails, then execution will halt before resuming client execution. So from the user's perspective, this isn't something they need to worry about unless they edit the crate to call read_return_data directly. I've still added a comment on that extern just in case though

let mut outs_len = 0;
let value = value.unwrap_or_default();
let gas = gas.unwrap_or(u64::MAX); // will be clamped by 63/64 rule
let status = unsafe {
call_contract(
contract.ptr(),
calldata.as_ptr(),
calldata.len(),
value.ptr(),
gas,
&mut outs_len as *mut _,
)
};
let outs = unsafe {
let mut outs = Vec::with_capacity(outs_len);
read_return_data(outs.as_mut_ptr());
outs.set_len(outs_len);
outs
};
match status {
0 => Ok(outs),
_ => Err(outs),
}
}
1 change: 1 addition & 0 deletions arbitrator/langs/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

pub use util::{Bytes20, Bytes32};

pub mod contract;
pub mod debug;
mod util;

Expand Down
14 changes: 9 additions & 5 deletions arbitrator/langs/rust/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use std::{array::TryFromSliceError, borrow::Borrow, fmt::{self, Debug, Display, Formatter}, ops::{Deref, DerefMut}};

#[derive(Default)]
use std::{
array::TryFromSliceError,
borrow::Borrow,
fmt::{self, Debug, Display, Formatter},
ops::{Deref, DerefMut},
};

#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct Bytes20(pub [u8; 20]);

Expand Down Expand Up @@ -82,7 +87,7 @@ impl Debug for Bytes20 {
}
}

#[derive(Default)]
#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct Bytes32(pub [u8; 32]);

Expand Down Expand Up @@ -163,4 +168,3 @@ impl Debug for Bytes32 {
write!(f, "{}", hex::encode(self))
}
}

2 changes: 1 addition & 1 deletion arbitrator/prover/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ impl<'a> WasmBinary<'a> {
bound.update_module(self)?;
start.update_module(self)?;

let count = config.debug.count_ops.then(|| Counter::new());
let count = config.debug.count_ops.then(Counter::new);
if let Some(count) = &count {
count.update_module(self)?;
}
Expand Down
4 changes: 2 additions & 2 deletions arbitrator/prover/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::{
machine::{Function, InboxIdentifier},
programs::StylusGlobals,
programs::{run::UserOutcomeKind, StylusGlobals},
value::{ArbValueType, FunctionType, IntegerValType},
wavm::{IBinOpType, Instruction, Opcode},
};
Expand Down Expand Up @@ -202,7 +202,7 @@ pub fn get_host_impl(module: &str, name: &str) -> eyre::Result<Function> {
// λ(module, main, args_len) -> status
opcode!(PushErrorGuard);
opcode!(ArbitraryJumpIf, code.len() + 3);
opcode!(I32Const, 1);
opcode!(I32Const, UserOutcomeKind::Failure as u32);
opcode!(Return);

// jumps here in the happy case
Expand Down
2 changes: 2 additions & 0 deletions arbitrator/prover/src/programs/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2022-2023, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE

#![allow(clippy::field_reassign_with_default)]

use eyre::{bail, Result};
use std::fmt::Debug;
use wasmer_types::Bytes;
Expand Down
8 changes: 7 additions & 1 deletion arbitrator/prover/src/programs/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ impl Counter {
}
}

impl Default for Counter {
fn default() -> Self {
Self::new()
}
}

impl<M> Middleware<M> for Counter
where
M: ModuleMod,
Expand Down Expand Up @@ -115,7 +121,7 @@ impl<'a> FuncMiddleware<'a> for FuncCounter<'a> {
for (op, count) in increments {
let opslen = operators.len();
let offset = *operators.entry(op).or_insert(opslen);
let global = *counters.get(offset).ok_or(eyre!("no global"))?;
let global = *counters.get(offset).ok_or_else(|| eyre!("no global"))?;
out.extend(update(global.as_u32(), count));
}

Expand Down
11 changes: 10 additions & 1 deletion arbitrator/prover/src/programs/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::Machine;
use arbutil::operator::OperatorInfo;
use eyre::Result;
use parking_lot::Mutex;
use std::fmt::Debug;
use std::fmt::{Debug, Display};
use wasmer_types::{GlobalIndex, GlobalInit, LocalFunctionIndex, Type};
use wasmparser::{Operator, Type as WpType, TypeOrFuncType};

Expand Down Expand Up @@ -191,6 +191,15 @@ impl Into<u64> for MachineMeter {
}
}

impl Display for MachineMeter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ready(gas) => write!(f, "{gas} gas"),
Self::Exhausted => write!(f, "exhausted"),
}
}
}

/// Note: implementers may panic if uninstrumented
pub trait MeteredMachine {
fn gas_left(&mut self) -> MachineMeter;
Expand Down
16 changes: 15 additions & 1 deletion arbitrator/prover/src/programs/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,23 @@ impl Display for UserOutcome {
OutOfGas => write!(f, "out of gas"),
OutOfStack => write!(f, "out of stack"),
Revert(data) => {
let text = String::from_utf8(data.clone()).unwrap_or(hex::encode(data));
let text = String::from_utf8(data.clone()).unwrap_or_else(|_| hex::encode(data));
write!(f, "revert {text}")
}
}
}
}

impl Display for UserOutcomeKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let as_u8 = *self as u8;
use UserOutcomeKind::*;
match self {
Success => write!(f, "success ({as_u8})"),
Revert => write!(f, "revert ({as_u8})"),
Failure => write!(f, "failure ({as_u8})"),
OutOfGas => write!(f, "out of gas ({as_u8})"),
OutOfStack => write!(f, "out of stack ({as_u8})"),
}
}
}
1 change: 1 addition & 0 deletions arbitrator/stylus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ parking_lot = "0.12.1"
thiserror = "1.0.33"
libc = "0.2.108"
eyre = "0.6.5"
rand = "0.8.5"
fnv = "1.0.7"
sha3 = "0.10.5"
hex = "0.4.3"
Expand Down
Loading