use crate::{ExecError, ExecResult, Execution, Instance, Module, ModuleError};
pub const GAS_MAX: i64 = i64::MAX;
pub fn run(program: &[u8]) {
counter_start_at_0(program);
counter_start_at_7(program);
counter_multiple_calls(program);
panic_works(program);
exit_works(program);
run_out_of_gas_works(program);
gas_consumption_works(program);
memory_reset_on_instantiate(program);
memory_persistent(program);
counter_in_subcall(program);
from_storage_key_not_found(program);
}
pub enum RunResult {
Ok(Instance),
Exit,
Err(ExecError),
}
pub fn run_loop(
mut execution: Execution,
gas_left: &mut i64,
mut handler: impl FnMut(&mut Execution, &[u8], u64, u64, u64, u64, u64, u64) -> Result<u64, ()>,
) -> RunResult {
let mut a0 = 0u64;
loop {
match execution.run(*gas_left, a0) {
ExecResult::Finished { instance, gas_left: g } => {
*gas_left = g;
return RunResult::Ok(instance);
},
ExecResult::Syscall {
execution: e,
gas_left: g,
syscall_symbol,
a0: sa0,
a1,
a2,
a3,
a4,
a5,
} => {
execution = e;
*gas_left = g;
match handler(&mut execution, syscall_symbol.as_ref(), sa0, a1, a2, a3, a4, a5) {
Ok(result) => a0 = result,
Err(()) => return RunResult::Exit,
}
},
ExecResult::Error { instance: _, error: ExecError::OutOfGas } => {
*gas_left = 0;
return RunResult::Err(ExecError::OutOfGas);
},
ExecResult::Error { instance: _, error } => return RunResult::Err(error),
}
}
}
pub fn make_handler<'a>(
counter: &'a mut u64,
) -> impl FnMut(&mut Execution, &[u8], u64, u64, u64, u64, u64, u64) -> Result<u64, ()> + 'a {
move |execution, syscall_symbol, a0, _a1, _a2, _a3, _a4, _a5| match syscall_symbol {
b"read_counter" => {
let buf = counter.to_le_bytes();
execution.write_memory(a0 as u32, buf.as_ref()).unwrap();
Ok(1)
},
b"increment_counter" => {
let mut buf = [0u8; 8];
execution.read_memory(a0 as u32, buf.as_mut()).unwrap();
*counter += u64::from_le_bytes(buf);
Ok(2u64 << 56)
},
b"exit" => Err(()),
_ => panic!("unknown syscall: {:?}", syscall_symbol),
}
}
fn counter_start_at_0(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"counter").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 8);
}
fn counter_start_at_7(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"counter").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 7;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 15);
}
fn counter_multiple_calls(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"counter").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 7;
let instance = match run_loop(execution, &mut gas_left, make_handler(&mut counter)) {
RunResult::Ok(instance) => instance,
_ => panic!("expected Ok"),
};
assert_eq!(counter, 15);
let execution = instance.prepare(b"counter").unwrap();
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 23);
}
fn panic_works(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"do_panic").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Err(ExecError::Trap)));
assert_eq!(counter, 0);
}
fn exit_works(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"do_exit").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Exit));
assert_eq!(counter, 0);
}
fn run_out_of_gas_works(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"increment_forever").unwrap();
let mut gas_left: i64 = 100_000;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Err(ExecError::OutOfGas)));
assert_eq!(counter, 793);
assert_eq!(gas_left, 0);
}
fn gas_consumption_works(program: &[u8]) {
let gas_limit_0 = GAS_MAX;
let gas_limit_1 = gas_limit_0 / 2;
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"counter").unwrap();
let mut gas_left = gas_limit_0;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
let gas_consumed = gas_limit_0 - gas_left;
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"counter").unwrap();
let mut gas_left = gas_limit_1;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(gas_consumed, gas_limit_1 - gas_left);
}
fn memory_reset_on_instantiate(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"offset").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 3);
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"offset").unwrap();
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 6);
}
fn memory_persistent(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"offset").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let instance = match run_loop(execution, &mut gas_left, make_handler(&mut counter)) {
RunResult::Ok(instance) => instance,
_ => panic!("expected Ok"),
};
assert_eq!(counter, 3);
let execution = instance.prepare(b"offset").unwrap();
let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 7);
}
fn counter_in_subcall(program: &[u8]) {
let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
let execution = instance.prepare(b"do_subcall").unwrap();
let mut gas_left = GAS_MAX;
let mut counter: u64 = 0;
let program = program.to_vec();
let result =
run_loop(execution, &mut gas_left, |execution, syscall_symbol, a0, a1, a2, a3, a4, a5| {
match syscall_symbol {
b"read_counter" | b"increment_counter" | b"exit" => {
make_handler(&mut counter)(execution, syscall_symbol, a0, a1, a2, a3, a4, a5)
},
b"subcall" => {
let sub_instance = Module::from_bytes(program.as_ref(), None)
.unwrap()
.0
.instantiate()
.unwrap();
let sub_execution = sub_instance.prepare(b"counter").unwrap();
let mut sub_gas = GAS_MAX;
let mut sub_counter: u64 = 0;
let result =
run_loop(sub_execution, &mut sub_gas, make_handler(&mut sub_counter));
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(sub_counter, 8);
Ok(0)
},
_ => panic!("unknown syscall: {:?}", syscall_symbol),
}
});
assert!(matches!(result, RunResult::Ok(_)));
assert_eq!(counter, 0);
}
fn from_storage_key_not_found(_program: &[u8]) {
let storage_key = b"::missing::";
assert!(matches!(Module::from_storage_key(storage_key, b""), Err(ModuleError::NotFound)));
}