use ethereum_types::{U256, H256, Address}; use vm::{self, CallType}; use wasmi::{self, MemoryRef, RuntimeArgs, RuntimeValue, Error as InterpreterError}; use super::panic_payload; pub struct RuntimeContext { pub address: Address, pub sender: Address, pub origin: Address, pub code_address: Address, pub value: U256, } pub struct Runtime<'a> { gas_counter: u64, gas_limit: u64, ext: &'a mut vm::Ext, context: RuntimeContext, memory: MemoryRef, args: Vec, result: Vec, } /// User trap in native code #[derive(Debug, Clone, PartialEq)] pub enum Error { /// Storage read error StorageReadError, /// Storage update error StorageUpdateError, /// Memory access violation MemoryAccessViolation, /// Native code resulted in suicide Suicide, /// Suicide was requested but coudn't complete SuicideAbort, /// Invalid gas state inside interpreter InvalidGasState, /// Query of the balance resulted in an error BalanceQueryError, /// Failed allocation AllocationFailed, /// Gas limit reached GasLimit, /// Unknown runtime function Unknown, /// Passed string had invalid utf-8 encoding BadUtf8, /// Log event error Log, /// Other error in native code Other, /// Syscall signature mismatch InvalidSyscall, /// Panic with message Panic(String), } impl wasmi::HostError for Error { } impl From for Error { fn from(interpreter_err: InterpreterError) -> Self { match interpreter_err { InterpreterError::Value(_) => Error::InvalidSyscall, InterpreterError::Memory(_) => Error::MemoryAccessViolation, _ => Error::Other, } } } impl ::std::fmt::Display for Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { match *self { Error::StorageReadError => write!(f, "Storage read error"), Error::StorageUpdateError => write!(f, "Storage update error"), Error::MemoryAccessViolation => write!(f, "Memory access violation"), Error::SuicideAbort => write!(f, "Attempt to suicide resulted in an error"), Error::InvalidGasState => write!(f, "Invalid gas state"), Error::BalanceQueryError => write!(f, "Balance query resulted in an error"), Error::Suicide => write!(f, "Suicide result"), Error::Unknown => write!(f, "Unknown runtime function invoked"), Error::AllocationFailed => write!(f, "Memory allocation failed (OOM)"), Error::BadUtf8 => write!(f, "String encoding is bad utf-8 sequence"), Error::GasLimit => write!(f, "Invocation resulted in gas limit violated"), Error::Log => write!(f, "Error occured while logging an event"), Error::InvalidSyscall => write!(f, "Invalid syscall signature encountered at runtime"), Error::Other => write!(f, "Other unspecified error"), Error::Panic(ref msg) => write!(f, "Panic: {}", msg), } } } type Result = ::std::result::Result; impl<'a> Runtime<'a> { /// New runtime for wasm contract with specified params pub fn with_params( ext: &mut vm::Ext, memory: MemoryRef, gas_limit: u64, args: Vec, context: RuntimeContext, ) -> Runtime { Runtime { gas_counter: 0, gas_limit: gas_limit, memory: memory, ext: ext, context: context, args: args, result: Vec::new(), } } /// Loads 256-bit hash from the specifed sandboxed memory pointer fn h256_at(&self, ptr: u32) -> Result { let mut buf = [0u8; 32]; self.memory.get_into(ptr, &mut buf[..])?; Ok(H256::from(&buf[..])) } /// Loads 160-bit hash (Ethereum address) from the specified sandboxed memory pointer fn address_at(&self, ptr: u32) -> Result
{ let mut buf = [0u8; 20]; self.memory.get_into(ptr, &mut buf[..])?; Ok(Address::from(&buf[..])) } /// Loads 256-bit integer represented with bigendian from the specified sandboxed memory pointer fn u256_at(&self, ptr: u32) -> Result { let mut buf = [0u8; 32]; self.memory.get_into(ptr, &mut buf[..])?; Ok(U256::from_big_endian(&buf[..])) } /// Charge specified amount of gas, returning false is gas limit exceeded, true if not. /// Intuition about the return value sense is to aswer the question 'are we allowed to continue?' fn charge_gas(&mut self, amount: u64) -> bool { let prev = self.gas_counter; if prev + amount > self.gas_limit { // exceeds gas false } else { self.gas_counter = prev + amount; true } } /// Charge gas according to closure pub fn charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> u64 { let amount = f(self.ext.schedule()); if !self.charge_gas(amount as u64) { Err(Error::GasLimit) } else { Ok(()) } } /// Adjusted charge of gas which scales actual charge according to the wasm opcode counting coefficient pub fn adjusted_charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> u64 { self.charge(|schedule| f(schedule) * schedule.wasm.opcodes_div as u64 / schedule.wasm.opcodes_mul as u64) } /// Charge gas provided by the closure, and closure also can return overflowing /// flag as None in gas cost. pub fn overflow_charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> Option { let amount = match f(self.ext.schedule()) { Some(amount) => amount, None => { return Err(Error::GasLimit.into()); } }; if !self.charge_gas(amount as u64) { Err(Error::GasLimit.into()) } else { Ok(()) } } /// Same as overflow_charge, but with amount adjusted by wasm opcodes coeff pub fn adjusted_overflow_charge(&mut self, f: F) -> Result<()> where F: FnOnce(&vm::Schedule) -> Option { self.overflow_charge(|schedule| f(schedule) .and_then(|x| x.checked_mul(schedule.wasm.opcodes_div as u64)) .map(|x| x / schedule.wasm.opcodes_mul as u64) ) } /// Read from the storage to wasm memory. pub fn storage_read(&mut self, args: RuntimeArgs) -> Result<()> { let key = self.h256_at(args.nth(0)?)?; let val_ptr: u32 = args.nth(1)?; let val = self.ext.storage_at(&key).map_err(|_| Error::StorageReadError)?; self.adjusted_charge(|schedule| schedule.sload_gas as u64)?; self.memory.set(val_ptr as u32, &*val)?; Ok(()) } /// Write to storage from wasm memory. pub fn storage_write(&mut self, args: RuntimeArgs) -> Result<()> { let key = self.h256_at(args.nth(0)?)?; let val_ptr: u32 = args.nth(1)?; let val = self.h256_at(val_ptr)?; let former_val = self.ext.storage_at(&key).map_err(|_| Error::StorageUpdateError)?; if former_val == H256::zero() && val != H256::zero() { self.adjusted_charge(|schedule| schedule.sstore_set_gas as u64)?; } else { self.adjusted_charge(|schedule| schedule.sstore_reset_gas as u64)?; } self.ext.set_storage(key, val).map_err(|_| Error::StorageUpdateError)?; if former_val != H256::zero() && val == H256::zero() { self.ext.inc_sstore_clears(); } Ok(()) } /// Return currently used schedule. pub fn schedule(&self) -> &vm::Schedule { self.ext.schedule() } /// Return. Syscall takes 2 arguments - pointer in sandboxed memory where result is and /// the length of the result. pub fn ret(&mut self, args: RuntimeArgs) -> Result<()> { let ptr: u32 = args.nth(0)?; let len: u32 = args.nth(1)?; trace!(target: "wasm", "Contract ret: {} bytes @ {}", len, ptr); self.result = self.memory.get(ptr, len as usize)?; Ok(()) } /// Destroy the runtime, returning currently recorded result of the execution. pub fn into_result(self) -> Vec { self.result } /// Query current gas left for execution pub fn gas_left(&self) -> Result { if self.gas_counter > self.gas_limit { return Err(Error::InvalidGasState); } Ok(self.gas_limit - self.gas_counter) } /// Report gas cost with the params passed in wasm stack fn gas(&mut self, args: RuntimeArgs) -> Result<()> { let amount: u32 = args.nth(0)?; if self.charge_gas(amount as u64) { Ok(()) } else { Err(Error::GasLimit.into()) } } /// Query the length of the input bytes fn input_legnth(&mut self) -> RuntimeValue { RuntimeValue::I32(self.args.len() as i32) } /// Write input bytes to the memory location using the passed pointer fn fetch_input(&mut self, args: RuntimeArgs) -> Result<()> { let ptr: u32 = args.nth(0)?; self.memory.set(ptr, &self.args[..])?; Ok(()) } /// User panic. Contract can invoke this when he encounters unrecoverable error. fn panic(&mut self, args: RuntimeArgs) -> Result<()> { let payload_ptr: u32 = args.nth(0)?; let payload_len: u32 = args.nth(1)?; let raw_payload = self.memory.get(payload_ptr, payload_len as usize)?; let payload = panic_payload::decode(&raw_payload); let msg = format!( "{msg}, {file}:{line}:{col}", msg = payload .msg .as_ref() .map(String::as_ref) .unwrap_or(""), file = payload .file .as_ref() .map(String::as_ref) .unwrap_or(""), line = payload.line.unwrap_or(0), col = payload.col.unwrap_or(0) ); trace!(target: "wasm", "Contract custom panic message: {}", msg); Err(Error::Panic(msg).into()) } fn do_call( &mut self, use_val: bool, call_type: CallType, args: RuntimeArgs, ) -> Result { trace!(target: "wasm", "runtime: CALL({:?})", call_type); let gas: u64 = args.nth(0)?; trace!(target: "wasm", " gas: {:?}", gas); let address = self.address_at(args.nth(1)?)?; trace!(target: "wasm", " address: {:?}", address); let vofs = if use_val { 1 } else { 0 }; let val = if use_val { Some(self.u256_at(args.nth(2)?)?) } else { None }; trace!(target: "wasm", " val: {:?}", val); let input_ptr: u32 = args.nth(2 + vofs)?; trace!(target: "wasm", " input_ptr: {:?}", input_ptr); let input_len: u32 = args.nth(3 + vofs)?; trace!(target: "wasm", " input_len: {:?}", input_len); let result_ptr: u32 = args.nth(4 + vofs)?; trace!(target: "wasm", " result_ptr: {:?}", result_ptr); let result_alloc_len: u32 = args.nth(5 + vofs)?; trace!(target: "wasm", " result_len: {:?}", result_alloc_len); if let Some(ref val) = val { let address_balance = self.ext.balance(&self.context.address) .map_err(|_| Error::BalanceQueryError)?; if &address_balance < val { trace!(target: "wasm", "runtime: call failed due to balance check"); return Ok((-1i32).into()); } } self.adjusted_charge(|schedule| schedule.call_gas as u64)?; let mut result = Vec::with_capacity(result_alloc_len as usize); result.resize(result_alloc_len as usize, 0); // todo: optimize to use memory views once it's in let payload = self.memory.get(input_ptr, input_len as usize)?; let adjusted_gas = match gas.checked_mul(self.ext.schedule().wasm.opcodes_div as u64) .map(|x| x / self.ext.schedule().wasm.opcodes_mul as u64) { Some(x) => x, None => { trace!("CALL overflowed gas, call aborted with error returned"); return Ok(RuntimeValue::I32(-1)) }, }; self.charge(|_| adjusted_gas)?; let call_result = self.ext.call( &gas.into(), match call_type { CallType::DelegateCall => &self.context.sender, _ => &self.context.address }, match call_type { CallType::Call | CallType::StaticCall => &address, _ => &self.context.address }, val, &payload, &address, &mut result[..], call_type, ); match call_result { vm::MessageCallResult::Success(gas_left, _) => { // cannot overflow, before making call gas_counter was incremented with gas, and gas_left < gas self.gas_counter = self.gas_counter - gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 / self.ext.schedule().wasm.opcodes_mul as u64; self.memory.set(result_ptr, &result)?; Ok(0i32.into()) }, vm::MessageCallResult::Reverted(gas_left, _) => { // cannot overflow, before making call gas_counter was incremented with gas, and gas_left < gas self.gas_counter = self.gas_counter - gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 / self.ext.schedule().wasm.opcodes_mul as u64; self.memory.set(result_ptr, &result)?; Ok((-1i32).into()) }, vm::MessageCallResult::Failed => { Ok((-1i32).into()) } } } /// Message call. fn ccall(&mut self, args: RuntimeArgs) -> Result { self.do_call(true, CallType::Call, args) } /// Delegate call. fn dcall(&mut self, args: RuntimeArgs) -> Result { self.do_call(false, CallType::DelegateCall, args) } /// STatic call. fn scall(&mut self, args: RuntimeArgs) -> Result { self.do_call(false, CallType::StaticCall, args) } fn return_address_ptr(&mut self, ptr: u32, val: Address) -> Result<()> { self.charge(|schedule| schedule.wasm.static_address as u64)?; self.memory.set(ptr, &*val)?; Ok(()) } fn return_u256_ptr(&mut self, ptr: u32, val: U256) -> Result<()> { let value: H256 = val.into(); self.charge(|schedule| schedule.wasm.static_u256 as u64)?; self.memory.set(ptr, &*value)?; Ok(()) } pub fn value(&mut self, args: RuntimeArgs) -> Result<()> { let val = self.context.value; self.return_u256_ptr(args.nth(0)?, val) } pub fn create(&mut self, args: RuntimeArgs) -> Result { // // method signature: // fn create(endowment: *const u8, code_ptr: *const u8, code_len: u32, result_ptr: *mut u8) -> i32; // trace!(target: "wasm", "runtime: CREATE"); let endowment = self.u256_at(args.nth(0)?)?; trace!(target: "wasm", " val: {:?}", endowment); let code_ptr: u32 = args.nth(1)?; trace!(target: "wasm", " code_ptr: {:?}", code_ptr); let code_len: u32 = args.nth(2)?; trace!(target: "wasm", " code_len: {:?}", code_len); let result_ptr: u32 = args.nth(3)?; trace!(target: "wasm", "result_ptr: {:?}", result_ptr); let code = self.memory.get(code_ptr, code_len as usize)?; self.adjusted_charge(|schedule| schedule.create_gas as u64)?; self.adjusted_charge(|schedule| schedule.create_data_gas as u64 * code.len() as u64)?; let gas_left: U256 = U256::from(self.gas_left()?) * U256::from(self.ext.schedule().wasm.opcodes_mul) / U256::from(self.ext.schedule().wasm.opcodes_div); match self.ext.create(&gas_left, &endowment, &code, vm::CreateContractAddress::FromSenderAndCodeHash) { vm::ContractCreateResult::Created(address, gas_left) => { self.memory.set(result_ptr, &*address)?; self.gas_counter = self.gas_limit - // this cannot overflow, since initial gas is in [0..u64::max) range, // and gas_left cannot be bigger gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 / self.ext.schedule().wasm.opcodes_mul as u64; trace!(target: "wasm", "runtime: create contract success (@{:?})", address); Ok(0i32.into()) }, vm::ContractCreateResult::Failed => { trace!(target: "wasm", "runtime: create contract fail"); Ok((-1i32).into()) }, vm::ContractCreateResult::Reverted(gas_left, _) => { trace!(target: "wasm", "runtime: create contract reverted"); self.gas_counter = self.gas_limit - // this cannot overflow, since initial gas is in [0..u64::max) range, // and gas_left cannot be bigger gas_left.low_u64() * self.ext.schedule().wasm.opcodes_div as u64 / self.ext.schedule().wasm.opcodes_mul as u64; Ok((-1i32).into()) }, } } fn debug(&mut self, args: RuntimeArgs) -> Result<()> { let msg_ptr: u32 = args.nth(0)?; let msg_len: u32 = args.nth(1)?; let msg = String::from_utf8(self.memory.get(msg_ptr, msg_len as usize)?) .map_err(|_| Error::BadUtf8)?; trace!(target: "wasm", "Contract debug message: {}", msg); Ok(()) } /// Pass suicide to state runtime pub fn suicide(&mut self, args: RuntimeArgs) -> Result<()> { let refund_address = self.address_at(args.nth(0)?)?; if self.ext.exists(&refund_address).map_err(|_| Error::SuicideAbort)? { trace!(target: "wasm", "Suicide: refund to existing address {}", refund_address); self.adjusted_charge(|schedule| schedule.suicide_gas as u64)?; } else { trace!(target: "wasm", "Suicide: refund to new address {}", refund_address); self.adjusted_charge(|schedule| schedule.suicide_to_new_account_cost as u64)?; } self.ext.suicide(&refund_address).map_err(|_| Error::SuicideAbort)?; // We send trap to interpreter so it should abort further execution Err(Error::Suicide.into()) } pub fn blockhash(&mut self, args: RuntimeArgs) -> Result<()> { self.adjusted_charge(|schedule| schedule.blockhash_gas as u64)?; let hash = self.ext.blockhash(&U256::from(args.nth::(0)?)); self.memory.set(args.nth(1)?, &*hash)?; Ok(()) } pub fn blocknumber(&mut self) -> Result { Ok(RuntimeValue::from(self.ext.env_info().number)) } pub fn coinbase(&mut self, args: RuntimeArgs) -> Result<()> { let coinbase = self.ext.env_info().author; self.return_address_ptr(args.nth(0)?, coinbase) } pub fn difficulty(&mut self, args: RuntimeArgs) -> Result<()> { let difficulty = self.ext.env_info().difficulty; self.return_u256_ptr(args.nth(0)?, difficulty) } pub fn gaslimit(&mut self, args: RuntimeArgs) -> Result<()> { let gas_limit = self.ext.env_info().gas_limit; self.return_u256_ptr(args.nth(0)?, gas_limit) } pub fn address(&mut self, args: RuntimeArgs) -> Result<()> { let address = self.context.address; self.return_address_ptr(args.nth(0)?, address) } pub fn sender(&mut self, args: RuntimeArgs) -> Result<()> { let sender = self.context.sender; self.return_address_ptr(args.nth(0)?, sender) } pub fn origin(&mut self, args: RuntimeArgs) -> Result<()> { let origin = self.context.origin; self.return_address_ptr(args.nth(0)?, origin) } pub fn timestamp(&mut self) -> Result { let timestamp = self.ext.env_info().timestamp; Ok(RuntimeValue::from(timestamp)) } pub fn elog(&mut self, args: RuntimeArgs) -> Result<()> { // signature is: // pub fn elog(topic_ptr: *const u8, topic_count: u32, data_ptr: *const u8, data_len: u32); let topic_ptr: u32 = args.nth(0)?; let topic_count: u32 = args.nth(1)?; let data_ptr: u32 = args.nth(2)?; let data_len: u32 = args.nth(3)?; if topic_count > 4 { return Err(Error::Log.into()); } self.adjusted_overflow_charge(|schedule| { let topics_gas = schedule.log_gas as u64 + schedule.log_topic_gas as u64 * topic_count as u64; (schedule.log_data_gas as u64) .checked_mul(schedule.log_data_gas as u64) .and_then(|data_gas| data_gas.checked_add(topics_gas)) } )?; let mut topics: Vec = Vec::with_capacity(topic_count as usize); topics.resize(topic_count as usize, H256::zero()); for i in 0..topic_count { let offset = i.checked_mul(32).ok_or(Error::MemoryAccessViolation)? .checked_add(topic_ptr).ok_or(Error::MemoryAccessViolation)?; *topics.get_mut(i as usize) .expect("topics is resized to `topic_count`, i is in 0..topic count iterator, get_mut uses i as an indexer, get_mut cannot fail; qed") = H256::from(&self.memory.get(offset, 32)?[..]); } self.ext.log(topics, &self.memory.get(data_ptr, data_len as usize)?).map_err(|_| Error::Log)?; Ok(()) } } mod ext_impl { use wasmi::{Externals, RuntimeArgs, RuntimeValue, Error}; use env::ids::*; macro_rules! void { { $e: expr } => { { $e?; Ok(None) } } } macro_rules! some { { $e: expr } => { { Ok(Some($e?)) } } } macro_rules! cast { { $e: expr } => { { Ok(Some($e)) } } } impl<'a> Externals for super::Runtime<'a> { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> Result, Error> { match index { STORAGE_WRITE_FUNC => void!(self.storage_write(args)), STORAGE_READ_FUNC => void!(self.storage_read(args)), RET_FUNC => void!(self.ret(args)), GAS_FUNC => void!(self.gas(args)), INPUT_LENGTH_FUNC => cast!(self.input_legnth()), FETCH_INPUT_FUNC => void!(self.fetch_input(args)), PANIC_FUNC => void!(self.panic(args)), DEBUG_FUNC => void!(self.debug(args)), CCALL_FUNC => some!(self.ccall(args)), DCALL_FUNC => some!(self.dcall(args)), SCALL_FUNC => some!(self.scall(args)), VALUE_FUNC => void!(self.value(args)), CREATE_FUNC => some!(self.create(args)), SUICIDE_FUNC => void!(self.suicide(args)), BLOCKHASH_FUNC => void!(self.blockhash(args)), BLOCKNUMBER_FUNC => some!(self.blocknumber()), COINBASE_FUNC => void!(self.coinbase(args)), DIFFICULTY_FUNC => void!(self.difficulty(args)), GASLIMIT_FUNC => void!(self.gaslimit(args)), TIMESTAMP_FUNC => some!(self.timestamp()), ADDRESS_FUNC => void!(self.address(args)), SENDER_FUNC => void!(self.sender(args)), ORIGIN_FUNC => void!(self.origin(args)), ELOG_FUNC => void!(self.elog(args)), _ => panic!("env module doesn't provide function at index {}", index), } } } }