diff --git a/programs/far_call.zasm b/programs/far_call.zasm new file mode 100644 index 00000000..9854a0c1 --- /dev/null +++ b/programs/far_call.zasm @@ -0,0 +1,15 @@ + .text + .file "far_call.zasm" + .globl __entry +__entry: +fake_routine: + +.func_begin0: + sstore r0, r0 + far_call r1, r1, @fake_routine + ret + +.func_end0: + + .note.GNU-stack + .rodata diff --git a/src/address_operands.rs b/src/address_operands.rs index 1169879a..36d81a35 100644 --- a/src/address_operands.rs +++ b/src/address_operands.rs @@ -39,17 +39,17 @@ pub fn address_operands_read(vm: &mut VMState, opcode: &Opcode) -> (TaggedValue, // stack-=[src0 + offset] + src1 let (src0, src1) = reg_and_imm_read(vm, opcode); let res = *vm - .current_frame + .current_context_mut() .stack .get_with_offset(src0.value.as_usize()); - vm.current_frame.stack.pop(src0.value); + vm.current_context_mut().stack.pop(src0.value); (res, src1) } ImmMemHandlerFlags::UseStackWithOffset => { // stack[src0 + offset] + src1 let (src0, src1) = reg_and_imm_read(vm, opcode); let res = vm - .current_frame + .current_context_mut() .stack .get_with_offset(src0.value.as_usize()); @@ -58,7 +58,10 @@ pub fn address_operands_read(vm: &mut VMState, opcode: &Opcode) -> (TaggedValue, ImmMemHandlerFlags::UseAbsoluteOnStack => { // stack=[src0 + offset] + src1 let (src0, src1) = reg_and_imm_read(vm, opcode); - let res = vm.current_frame.stack.get_absolute(src0.value.as_usize()); + let res = vm + .current_context_mut() + .stack + .get_absolute(src0.value.as_usize()); (*res, src1) } @@ -66,7 +69,7 @@ pub fn address_operands_read(vm: &mut VMState, opcode: &Opcode) -> (TaggedValue, ImmMemHandlerFlags::UseCodePage => { let (src0, src1) = reg_and_imm_read(vm, opcode); - let res = vm.current_frame.code_page[src0.value.as_usize()]; + let res = vm.current_context_mut().code_page[src0.value.as_usize()]; (TaggedValue::new_raw_integer(res), src1) } } @@ -149,20 +152,22 @@ fn address_operands(vm: &mut VMState, opcode: &Opcode, res: (TaggedValue, Option ImmMemHandlerFlags::UseStackWithPushPop => { // stack+=[src0 + offset] + src1 let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); - vm.current_frame.stack.fill_with_zeros(src0.value + 1); - vm.current_frame.stack.store_with_offset(1, res.0); + vm.current_context_mut() + .stack + .fill_with_zeros(src0.value + 1); + vm.current_context_mut().stack.store_with_offset(1, res.0); } ImmMemHandlerFlags::UseStackWithOffset => { // stack[src0 + offset] + src1 let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); - vm.current_frame + vm.current_context_mut() .stack .store_with_offset(src0.value.as_usize(), res.0); } ImmMemHandlerFlags::UseAbsoluteOnStack => { // stack=[src0 + offset] + src1 let src0 = reg_and_imm_write(vm, OutputOperandPosition::First, opcode); - vm.current_frame + vm.current_context_mut() .stack .store_absolute(src0.value.as_usize(), res.0); } diff --git a/src/call_frame.rs b/src/call_frame.rs new file mode 100644 index 00000000..c2abe096 --- /dev/null +++ b/src/call_frame.rs @@ -0,0 +1,44 @@ +use std::{cell::RefCell, num::Saturating, rc::Rc}; + +use u256::U256; + +use crate::{ + state::Stack, + store::{InMemory, Storage}, +}; + +#[derive(Debug, Clone)] +pub struct CallFrame { + // Max length for this is 1 << 16. Might want to enforce that at some point + pub stack: Stack, + pub heap: Vec, + // Code memory is word addressable even though instructions are 64 bit wide. + // TODO: this is a Vec of opcodes now but it's probably going to switch back to a + // Vec later on, because I believe we have to record memory queries when + // fetching code to execute. Check this + pub code_page: Vec, + pub pc: u64, + /// Storage for the frame using a type that implements the Storage trait. + /// The supported types are InMemory and RocksDB storage. + pub storage: Rc>, + /// Transient storage should be used for temporary storage within a transaction and then discarded. + pub transient_storage: InMemory, + pub gas_left: Saturating, +} +impl CallFrame { + pub fn new( + program_code: Vec, + gas_stipend: u32, + storage: Rc>, + ) -> Self { + Self { + stack: Stack::new(), + heap: vec![], + code_page: program_code, + pc: 0, + gas_left: Saturating(gas_stipend), + storage, + transient_storage: InMemory::default(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b6aaa533..cab37ee1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,22 @@ mod address_operands; +pub mod call_frame; mod op_handlers; mod opcode; mod ptr_operator; pub mod state; -mod store; +pub mod store; pub mod value; -use std::path::PathBuf; +use std::cell::RefCell; +use std::collections::HashMap; +use std::env; +use std::rc::Rc; +use call_frame::CallFrame; use op_handlers::add::_add; use op_handlers::and::_and; use op_handlers::div::_div; +use op_handlers::far_call::far_call; use op_handlers::jump::_jump; use op_handlers::log::{ _storage_read, _storage_write, _transient_storage_read, _transient_storage_write, @@ -24,7 +30,8 @@ use op_handlers::ptr_sub::_ptr_sub; use op_handlers::sub::_sub; use op_handlers::xor::_xor; pub use opcode::Opcode; -use state::{VMState, VMStateBuilder}; +use state::{VMState, VMStateBuilder, DEFAULT_INITIAL_GAS}; +use store::{InMemory, RocksDB}; use u256::U256; use zkevm_opcode_defs::definitions::synthesize_opcode_decoding_tables; use zkevm_opcode_defs::BinopOpcode; @@ -33,31 +40,12 @@ use zkevm_opcode_defs::LogOpcode; use zkevm_opcode_defs::Opcode as Variant; use zkevm_opcode_defs::PtrOpcode; -/// Run a vm program with a clean VM state and with in memory storage. -pub fn run_program_in_memory(bin_path: &str) -> (U256, VMState) { - let vm = VMStateBuilder::default().build(); - run_program_with_custom_state(bin_path, vm) -} - -/// Run a vm program saving the state to a storage file at the given path. -pub fn run_program_with_storage(bin_path: &str, storage_path: String) -> (U256, VMState) { - let vm = VMStateBuilder::default() - .with_storage(PathBuf::from(storage_path)) - .build(); - run_program_with_custom_state(bin_path, vm) -} - -/// Run a vm program from the given path using a custom state. -/// Returns the value stored at storage with key 0 and the final vm state. -pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, VMState) { - let opcode_table = synthesize_opcode_decoding_tables(11, ISAVersion(2)); - +pub fn program_from_file(bin_path: &str) -> Vec { let program = std::fs::read(bin_path).unwrap(); let encoded = String::from_utf8(program.to_vec()).unwrap(); let bin = hex::decode(&encoded[2..]).unwrap(); let mut program_code = vec![]; - for raw_opcode_slice in bin.chunks(32) { let mut raw_opcode_bytes: [u8; 32] = [0; 32]; raw_opcode_bytes.copy_from_slice(&raw_opcode_slice[..32]); @@ -65,9 +53,44 @@ pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, let raw_opcode_u256 = U256::from_big_endian(&raw_opcode_bytes); program_code.push(raw_opcode_u256); } + program_code +} - vm.load_program(program_code); +/// Run a vm program with a clean VM state and with in memory storage. +pub fn run_program_in_memory(bin_path: &str) -> (U256, VMState) { + let mut vm = VMStateBuilder::default().build(); + let program_code = program_from_file(bin_path); + let storage = Rc::new(RefCell::new(InMemory(HashMap::new()))); + vm.push_frame(program_code, DEFAULT_INITIAL_GAS, storage); + run(vm) +} + +/// Run a vm program saving the state to a storage file at the given path. +pub fn run_program_with_storage(bin_path: &str, storage_path: &str) -> (U256, VMState) { + let storage = RocksDB::open(storage_path.into()).unwrap(); + let storage = Rc::new(RefCell::new(storage)); + let frame = CallFrame::new(program_from_file(bin_path), DEFAULT_INITIAL_GAS, storage); + let vm = VMStateBuilder::default().with_frames(vec![frame]).build(); + run(vm) +} +/// Run a vm program with a clean VM state. +pub fn run_program(bin_path: &str) -> (U256, VMState) { + let vm = VMState::new(); + run_program_with_custom_state(bin_path, vm) +} +/// Run a vm program from the given path using a custom state. +/// Returns the value stored at storage with key 0 and the final vm state. +pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, VMState) { + let program = program_from_file(bin_path); + let storage = RocksDB::open(env::temp_dir()).unwrap(); + let storage = Rc::new(RefCell::new(storage)); + vm.push_frame(program, DEFAULT_INITIAL_GAS, storage); + run(vm) +} + +pub fn run(mut vm: VMState) -> (U256, VMState) { + let opcode_table = synthesize_opcode_decoding_tables(11, ISAVersion(2)); loop { let opcode = vm.get_opcode(&opcode_table); @@ -75,7 +98,10 @@ pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, match opcode.variant { // TODO: Properly handle what happens // when the VM runs out of ergs/gas. - _ if vm.gas_left() == 0 => break, + _ if vm.running_frames.len() == 1 && vm.current_context().gas_left.0 == 0 => break, + _ if vm.current_context().gas_left.0 == 0 => { + vm.pop_frame(); + } Variant::Invalid(_) => todo!(), Variant::Nop(_) => todo!(), Variant::Add(_) => { @@ -109,23 +135,26 @@ pub fn run_program_with_custom_state(bin_path: &str, mut vm: VMState) -> (U256, LogOpcode::TransientStorageRead => _transient_storage_read(&mut vm, &opcode), LogOpcode::TransientStorageWrite => _transient_storage_write(&mut vm, &opcode), }, - Variant::FarCall(_) => todo!(), + Variant::FarCall(far_call_variant) => far_call(&mut vm, &far_call_variant), + // TODO: This is not how return works. Fix when we have calls between contracts + // hooked up. + // This is only to keep the context for tests Variant::Ret(_) => { - // TODO: This is not how return works. Fix when we have calls between contracts - // hooked up. - break; + if vm.running_frames.len() > 1 { + vm.pop_frame(); + } else { + break; + } } Variant::UMA(_) => todo!(), } } - vm.current_frame.pc += 1; + vm.current_context_mut().pc += 1; vm.decrease_gas(&opcode); } - let final_storage_value = vm - .current_frame - .storage - .borrow() - .read(&U256::zero()) - .unwrap(); + let final_storage_value = match vm.current_context().storage.borrow().read(&U256::zero()) { + Ok(value) => value, + Err(_) => U256::zero(), + }; (final_storage_value, vm) } diff --git a/src/op_handlers/far_call.rs b/src/op_handlers/far_call.rs new file mode 100644 index 00000000..c35a219e --- /dev/null +++ b/src/op_handlers/far_call.rs @@ -0,0 +1,17 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use zkevm_opcode_defs::FarCallOpcode; + +use crate::{state::VMState, store::InMemory}; + +pub fn far_call(vm: &mut VMState, opcode: &FarCallOpcode) { + match opcode { + FarCallOpcode::Normal => { + let program_code = vm.current_context().code_page.clone(); + let stipend = vm.current_context().gas_left; + let storage = Rc::new(RefCell::new(InMemory(HashMap::new()))); + vm.push_frame(program_code, stipend.0 / 32, storage) + } + _ => todo!(), + } +} diff --git a/src/op_handlers/jump.rs b/src/op_handlers/jump.rs index a2997cea..14dd7b98 100644 --- a/src/op_handlers/jump.rs +++ b/src/op_handlers/jump.rs @@ -8,6 +8,6 @@ pub fn _jump(vm: &mut VMState, opcode: &Opcode) { let (src0, _) = address_operands_read(vm, opcode); let next_pc = (src0.value & U256::from(u64::MAX)) - 1; // we subtract 1 because the pc will be incremented after this function - vm.current_frame.pc = next_pc.as_u64(); + vm.current_context_mut().pc = next_pc.as_u64(); address_operands_store(vm, opcode, TaggedValue::new_raw_integer(next_pc)); } diff --git a/src/op_handlers/log.rs b/src/op_handlers/log.rs index a12e2996..e1681598 100644 --- a/src/op_handlers/log.rs +++ b/src/op_handlers/log.rs @@ -5,7 +5,7 @@ use crate::{state::VMState, store::Storage, value::TaggedValue, Opcode}; pub fn _storage_write(vm: &mut VMState, opcode: &Opcode) { let key = vm.get_register(opcode.src0_index).value; let value = vm.get_register(opcode.src1_index).value; - vm.current_frame + vm.current_context() .storage .borrow_mut() .store(key, value) @@ -15,7 +15,7 @@ pub fn _storage_write(vm: &mut VMState, opcode: &Opcode) { pub fn _storage_read(vm: &mut VMState, opcode: &Opcode) { let key = vm.get_register(opcode.src0_index); let value = vm - .current_frame + .current_context() .storage .borrow() .read(&key.value) @@ -26,7 +26,7 @@ pub fn _storage_read(vm: &mut VMState, opcode: &Opcode) { pub fn _transient_storage_write(vm: &mut VMState, opcode: &Opcode) { let key = vm.get_register(opcode.src0_index).value; let value = vm.get_register(opcode.src1_index).value; - vm.current_frame + vm.current_context_mut() .transient_storage .store(key, value) .unwrap(); @@ -35,7 +35,7 @@ pub fn _transient_storage_write(vm: &mut VMState, opcode: &Opcode) { pub fn _transient_storage_read(vm: &mut VMState, opcode: &Opcode) { let key = vm.get_register(opcode.src0_index).value; let value = vm - .current_frame + .current_context() .transient_storage .read(&key) .unwrap_or(U256::zero()); diff --git a/src/op_handlers/mod.rs b/src/op_handlers/mod.rs index 5be5da13..a1e4aec7 100644 --- a/src/op_handlers/mod.rs +++ b/src/op_handlers/mod.rs @@ -1,6 +1,7 @@ pub mod add; pub mod and; pub mod div; +pub mod far_call; pub mod jump; pub mod log; pub mod mul; diff --git a/src/state.rs b/src/state.rs index aa0f4c54..1e5cf14d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,7 @@ use std::num::Saturating; -use std::path::PathBuf; use std::{cell::RefCell, rc::Rc}; -use crate::store::RocksDB; -use crate::{ - opcode::Predicate, - store::{InMemory, Storage}, - value::TaggedValue, - Opcode, -}; +use crate::{call_frame::CallFrame, opcode::Predicate, store::Storage, value::TaggedValue, Opcode}; use u256::U256; use zkevm_opcode_defs::OpcodeVariant; @@ -17,23 +10,6 @@ pub struct Stack { pub stack: Vec, } -#[derive(Debug, Clone)] -pub struct CallFrame { - // Max length for this is 1 << 16. Might want to enforce that at some point - pub stack: Stack, - pub heap: Vec, - // Code memory is word addressable even though instructions are 64 bit wide. - // TODO: this is a Vec of opcodes now but it's probably going to switch back to a - // Vec later on, because I believe we have to record memory queries when - // fetching code to execute. Check this - pub code_page: Vec, - pub pc: u64, - /// Storage for the frame using a type that implements the Storage trait. - /// The supported types are InMemory and RocksDB storage. - pub storage: Rc>, - /// Transient storage should be used for temporary storage within a transaction and then discarded. - pub transient_storage: InMemory, -} // I'm not really a fan of this, but it saves up time when // adding new fields to the vm state, and makes it easier // to setup certain particular state for the tests . @@ -43,9 +19,12 @@ pub struct VMStateBuilder { pub flag_lt_of: bool, pub flag_gt: bool, pub flag_eq: bool, - pub current_frame: CallFrame, - pub gas_left: u32, + pub running_frames: Vec, } + +// On this specific struct, I prefer to have the actual values +// instead of guessing which ones are the defaults. +#[allow(clippy::derivable_impls)] impl Default for VMStateBuilder { fn default() -> Self { VMStateBuilder { @@ -53,8 +32,7 @@ impl Default for VMStateBuilder { flag_lt_of: false, flag_gt: false, flag_eq: false, - current_frame: CallFrame::new(vec![], Rc::new(RefCell::new(InMemory::default()))), - gas_left: DEFAULT_GAS_LIMIT, + running_frames: vec![], } } } @@ -66,10 +44,11 @@ impl VMStateBuilder { self.registers = registers; self } - pub fn with_current_frame(mut self, frame: CallFrame) -> VMStateBuilder { - self.current_frame = frame; + pub fn with_frames(mut self, frame: Vec) -> VMStateBuilder { + self.running_frames = frame; self } + pub fn eq_flag(mut self, eq: bool) -> VMStateBuilder { self.flag_eq = eq; self @@ -82,23 +61,13 @@ impl VMStateBuilder { self.flag_lt_of = lt_of; self } - pub fn gas_left(mut self, gas_left: u32) -> VMStateBuilder { - self.gas_left = gas_left; - self - } - pub fn with_storage(mut self, storage: PathBuf) -> VMStateBuilder { - let storage = Rc::new(RefCell::new(RocksDB::open(storage).unwrap())); - self.current_frame.storage = storage; - self - } pub fn build(self) -> VMState { VMState { registers: self.registers, - current_frame: self.current_frame, + running_frames: self.running_frames, flag_eq: self.flag_eq, flag_gt: self.flag_gt, flag_lt_of: self.flag_lt_of, - gas_left: Saturating(self.gas_left), } } } @@ -113,39 +82,59 @@ pub struct VMState { pub flag_gt: bool, /// Equal flag pub flag_eq: bool, - pub current_frame: CallFrame, - pub gas_left: Saturating, + pub running_frames: Vec, } impl Default for VMState { fn default() -> Self { - Self { - registers: [TaggedValue::default(); 15], - flag_lt_of: false, - flag_gt: false, - flag_eq: false, - current_frame: CallFrame::new(vec![], Rc::new(RefCell::new(InMemory::default()))), - gas_left: Saturating(DEFAULT_GAS_LIMIT), - } + Self::new() } } + // Arbitrary default, change it if you need to. -const DEFAULT_GAS_LIMIT: u32 = 1 << 16; +pub const DEFAULT_INITIAL_GAS: u32 = 1 << 16; impl VMState { // TODO: The VM will probably not take the program to execute as a parameter later on. - pub fn new(program_code: Vec) -> Self { + pub fn new() -> Self { Self { registers: [TaggedValue::default(); 15], flag_lt_of: false, flag_gt: false, flag_eq: false, - current_frame: CallFrame::new(program_code, Rc::new(RefCell::new(InMemory::default()))), - gas_left: Saturating(DEFAULT_GAS_LIMIT), + running_frames: vec![], + } + } + + pub fn load_program(&mut self, program_code: Vec, storage: Rc>) { + self.current_context_mut().code_page = program_code; + self.current_context_mut().storage = storage; + } + + pub fn push_frame( + &mut self, + program_code: Vec, + gas_stipend: u32, + storage: Rc>, + ) { + if let Some(frame) = self.running_frames.last_mut() { + frame.gas_left -= Saturating(gas_stipend) } + let new_context = CallFrame::new(program_code, gas_stipend, storage); + self.running_frames.push(new_context); + } + pub fn pop_frame(&mut self) { + self.running_frames.pop(); + } + pub fn current_context_mut(&mut self) -> &mut CallFrame { + self.running_frames + .last_mut() + .expect("Fatal: VM has no running contract") } - pub fn load_program(&mut self, program_code: Vec) { - self.current_frame.code_page = program_code; + pub fn current_context(&self) -> &CallFrame { + self.running_frames + .last() + .expect("Fatal: VM has no running contract") } pub fn predicate_holds(&self, condition: &Predicate) -> bool { @@ -178,8 +167,10 @@ impl VMState { } pub fn get_opcode(&self, opcode_table: &[OpcodeVariant]) -> Opcode { - let raw_opcode = self.current_frame.code_page[(self.current_frame.pc / 4) as usize]; - let raw_opcode_64 = match self.current_frame.pc % 4 { + let current_context = self.current_context(); + let pc = current_context.pc; + let raw_opcode = current_context.code_page[(pc / 4) as usize]; + let raw_opcode_64 = match pc % 4 { 3 => (raw_opcode & u64::MAX.into()).as_u64(), 2 => ((raw_opcode >> 64) & u64::MAX.into()).as_u64(), 1 => ((raw_opcode >> 128) & u64::MAX.into()).as_u64(), @@ -190,28 +181,8 @@ impl VMState { Opcode::from_raw_opcode(raw_opcode_64, opcode_table) } - // This is redundant, but eventually this will have - // some complex logic regarding the call frames, - // so I'm future proofing it a little bit. - pub fn gas_left(&self) -> u32 { - self.gas_left.0 - } - pub fn decrease_gas(&mut self, opcode: &Opcode) { - self.gas_left -= opcode.variant.ergs_price(); - } -} - -impl CallFrame { - pub fn new(program_code: Vec, storage: Rc>) -> Self { - Self { - stack: Stack::new(), - heap: vec![], - code_page: program_code, - pc: 0, - storage, - transient_storage: InMemory::default(), - } + self.current_context_mut().gas_left -= opcode.variant.ergs_price(); } } diff --git a/src/store.rs b/src/store.rs index 595e1376..ebdc472c 100644 --- a/src/store.rs +++ b/src/store.rs @@ -18,8 +18,7 @@ pub enum StorageError { /// In-memory storage implementation. #[derive(Debug, Clone, Default)] -pub struct InMemory(HashMap); - +pub struct InMemory(pub HashMap); impl Storage for InMemory { /// Store a key-value pair in the storage. fn store(&mut self, key: U256, value: U256) -> Result<(), StorageError> { diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 0f4510ab..dc605f92 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,8 +1,14 @@ +use era_vm::call_frame::CallFrame; +use era_vm::store::RocksDB; use era_vm::{ - run_program_in_memory, run_program_with_custom_state, run_program_with_storage, + program_from_file, run, run_program, run_program_in_memory, run_program_with_custom_state, + run_program_with_storage, state::VMStateBuilder, value::{FatPointer, TaggedValue}, }; +use std::cell::RefCell; +use std::env; +use std::rc::Rc; use std::time::{SystemTime, UNIX_EPOCH}; use u256::U256; const ARTIFACTS_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/program_artifacts"); @@ -451,7 +457,6 @@ fn test_more_complex_program_with_conditionals() { .lt_of_flag(false) .build(); let (result, _final_vm_state) = run_program_with_custom_state(&bin_path, vm_with_custom_flags); - dbg!(_final_vm_state); assert_eq!(result, U256::from_dec_str("10").unwrap()); } @@ -508,7 +513,7 @@ fn test_jump_asm() { fn test_jump_label() { let bin_path = make_bin_path_asm("jump_label"); let (result, vm_final_state) = run_program_in_memory(&bin_path); - let final_pc = vm_final_state.current_frame.pc; + let final_pc = vm_final_state.current_context().pc; assert_eq!(result, U256::from(42)); // failing to jump into the label will finish program with pc == 2 assert_eq!(final_pc, 6) @@ -561,18 +566,35 @@ fn test_or_conditional_jump() { // the program can save a number 3 into the storage. fn test_runs_out_of_gas_and_stops() { let bin_path = make_bin_path_asm("add_with_costs"); - let vm = VMStateBuilder::new().gas_left(5510).build(); - let (result, _) = run_program_with_custom_state(&bin_path, vm); + let program_code = program_from_file(&bin_path); + let db = RocksDB::open(env::temp_dir()).unwrap(); + let storage = Rc::new(RefCell::new(db)); + let frame = CallFrame::new(program_code, 5510, storage); + let vm = VMStateBuilder::new().with_frames(vec![frame]).build(); + let (result, _) = run(vm); assert_eq!(result, U256::from_dec_str("0").unwrap()); } #[test] fn test_uses_expected_gas() { let bin_path = make_bin_path_asm("add_with_costs"); - let vm = VMStateBuilder::new().gas_left(5600).build(); - let (result, final_vm_state) = run_program_with_custom_state(&bin_path, vm); + let program = program_from_file(&bin_path); + let db = RocksDB::open(env::temp_dir()).unwrap(); + let storage = Rc::new(RefCell::new(db)); + let frame = CallFrame::new(program, 5600, storage); + let vm = VMStateBuilder::new().with_frames(vec![frame]).build(); + let (result, final_vm_state) = run(vm); assert_eq!(result, U256::from_dec_str("3").unwrap()); - assert_eq!(final_vm_state.gas_left(), 0_u32); + assert_eq!(final_vm_state.current_context().gas_left.0, 0_u32); +} + +#[test] +fn test_vm_generates_frames_and_spends_gas() { + let bin_path = make_bin_path_asm("far_call"); + let (_, final_vm_state) = run_program(&bin_path); + let contexts = final_vm_state.running_frames.clone(); + let upper_most_context = contexts.first().unwrap(); + assert_eq!(upper_most_context.gas_left.0, 58145); } #[test] @@ -603,11 +625,11 @@ fn test_tload_with_absent_key() { assert_eq!(result, U256::zero()); } -// TODO: All the tests above should ran with this storage as well. +// TODO: All the tests above should run with this storage as well. #[test] fn test_db_storage_add() { let bin_path = make_bin_path_asm("add"); - let (result, _) = run_program_with_storage(&bin_path, "./tests/test_storage".to_string()); + let (result, _) = run_program_with_storage(&bin_path, "./tests/test_storage"); assert_eq!(result, U256::from_dec_str("3").unwrap()); }