Capture traces and write some tests.
This commit is contained in:
		
							parent
							
								
									4ac406da6b
								
							
						
					
					
						commit
						2d222a5920
					
				
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -940,6 +940,7 @@ dependencies = [ | ||||
|  "ethjson 0.1.0", | ||||
|  "evm 0.1.0", | ||||
|  "panic_hook 0.1.0", | ||||
|  "pretty_assertions 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  | ||||
| @ -1142,7 +1142,7 @@ impl Client { | ||||
| 			state_diff: bool, | ||||
| 			transaction: &SignedTransaction, | ||||
| 			options: TransactOptions<T, V>, | ||||
| 		) -> Result<Executed, CallError> where | ||||
| 		) -> Result<Executed<T::Output, V::Output>, CallError> where | ||||
| 			T: trace::Tracer, | ||||
| 			V: trace::VMTracer, | ||||
| 		{ | ||||
|  | ||||
| @ -197,7 +197,7 @@ impl<'a> EvmTestClient<'a> { | ||||
| 		env_info: &client::EnvInfo, | ||||
| 		transaction: transaction::SignedTransaction, | ||||
| 		vm_tracer: T, | ||||
| 	) -> TransactResult { | ||||
| 	) -> TransactResult<T::Output> { | ||||
| 		let initial_gas = transaction.gas; | ||||
| 		// Verify transaction
 | ||||
| 		let is_ok = transaction.verify_basic(true, None, env_info.number >= self.spec.engine.params().eip86_transition); | ||||
| @ -219,6 +219,7 @@ impl<'a> EvmTestClient<'a> { | ||||
| 					state_root: *self.state.root(), | ||||
| 					gas_left: initial_gas - result.receipt.gas_used, | ||||
| 					output: result.output, | ||||
| 					vm_trace: result.vm_trace, | ||||
| 				} | ||||
| 			}, | ||||
| 			Err(error) => TransactResult::Err { | ||||
| @ -230,7 +231,7 @@ impl<'a> EvmTestClient<'a> { | ||||
| } | ||||
| 
 | ||||
| /// A result of applying transaction to the state.
 | ||||
| pub enum TransactResult { | ||||
| pub enum TransactResult<T> { | ||||
| 	/// Successful execution
 | ||||
| 	Ok { | ||||
| 		/// State root
 | ||||
| @ -239,6 +240,8 @@ pub enum TransactResult { | ||||
| 		gas_left: U256, | ||||
| 		/// Output
 | ||||
| 		output: Vec<u8>, | ||||
| 		/// VM Traces
 | ||||
| 		vm_trace: Option<T>, | ||||
| 	}, | ||||
| 	/// Transaction failed to run
 | ||||
| 	Err { | ||||
|  | ||||
| @ -29,7 +29,7 @@ use std::fmt; | ||||
| 
 | ||||
| /// Transaction execution receipt.
 | ||||
| #[derive(Debug, PartialEq, Clone)] | ||||
| pub struct Executed { | ||||
| pub struct Executed<T = FlatTrace, V = VMTrace> { | ||||
| 	/// True if the outer call/create resulted in an exceptional exit.
 | ||||
| 	pub exception: Option<vm::Error>, | ||||
| 
 | ||||
| @ -63,9 +63,9 @@ pub struct Executed { | ||||
| 	/// Transaction output.
 | ||||
| 	pub output: Bytes, | ||||
| 	/// The trace of this transaction.
 | ||||
| 	pub trace: Vec<FlatTrace>, | ||||
| 	pub trace: Vec<T>, | ||||
| 	/// The VM trace of this transaction.
 | ||||
| 	pub vm_trace: Option<VMTrace>, | ||||
| 	pub vm_trace: Option<V>, | ||||
| 	/// The state diff, if we traced it.
 | ||||
| 	pub state_diff: Option<StateDiff>, | ||||
| } | ||||
|  | ||||
| @ -30,7 +30,7 @@ use evm::{CallType, Factory, Finalize, FinalizationResult}; | ||||
| use vm::{self, Ext, CreateContractAddress, ReturnData, CleanDustMode, ActionParams, ActionValue}; | ||||
| use wasm; | ||||
| use externalities::*; | ||||
| use trace::{self, FlatTrace, VMTrace, Tracer, VMTracer}; | ||||
| use trace::{self, Tracer, VMTracer}; | ||||
| use transaction::{Action, SignedTransaction}; | ||||
| use crossbeam; | ||||
| pub use executed::{Executed, ExecutionResult}; | ||||
| @ -214,7 +214,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { | ||||
| 
 | ||||
| 	/// This function should be used to execute transaction.
 | ||||
| 	pub fn transact<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>) | ||||
| 		-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer, | ||||
| 		-> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer, | ||||
| 	{ | ||||
| 		self.transact_with_tracer(t, options.check_nonce, options.output_from_init_contract, options.tracer, options.vm_tracer) | ||||
| 	} | ||||
| @ -223,7 +223,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { | ||||
| 	/// This will ensure the caller has enough balance to execute the desired transaction.
 | ||||
| 	/// Used for extra-block executions for things like consensus contracts and RPCs
 | ||||
| 	pub fn transact_virtual<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>) | ||||
| 		-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer, | ||||
| 		-> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer, | ||||
| 	{ | ||||
| 		let sender = t.sender(); | ||||
| 		let balance = self.state.balance(&sender)?; | ||||
| @ -244,7 +244,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { | ||||
| 		output_from_create: bool, | ||||
| 		mut tracer: T, | ||||
| 		mut vm_tracer: V | ||||
| 	) -> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer { | ||||
| 	) -> Result<Executed<T::Output, V::Output>, ExecutionError> where T: Tracer, V: VMTracer { | ||||
| 		let sender = t.sender(); | ||||
| 		let nonce = self.state.nonce(&sender)?; | ||||
| 
 | ||||
| @ -587,15 +587,15 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Finalizes the transaction (does refunds and suicides).
 | ||||
| 	fn finalize( | ||||
| 	fn finalize<T, V>( | ||||
| 		&mut self, | ||||
| 		t: &SignedTransaction, | ||||
| 		mut substate: Substate, | ||||
| 		result: vm::Result<FinalizationResult>, | ||||
| 		output: Bytes, | ||||
| 		trace: Vec<FlatTrace>, | ||||
| 		vm_trace: Option<VMTrace> | ||||
| 	) -> ExecutionResult { | ||||
| 		trace: Vec<T>, | ||||
| 		vm_trace: Option<V> | ||||
| 	) -> Result<Executed<T, V>, ExecutionError> { | ||||
| 		let schedule = self.machine.schedule(self.info.number); | ||||
| 
 | ||||
| 		// refunds from SSTORE nonzero -> zero
 | ||||
|  | ||||
| @ -62,19 +62,19 @@ pub use self::backend::Backend; | ||||
| pub use self::substate::Substate; | ||||
| 
 | ||||
| /// Used to return information about an `State::apply` operation.
 | ||||
| pub struct ApplyOutcome { | ||||
| pub struct ApplyOutcome<T, V> { | ||||
| 	/// The receipt for the applied transaction.
 | ||||
| 	pub receipt: Receipt, | ||||
| 	/// The output of the applied transaction.
 | ||||
| 	pub output: Bytes, | ||||
| 	/// The trace for the applied transaction, empty if tracing was not produced.
 | ||||
| 	pub trace: Vec<FlatTrace>, | ||||
| 	pub trace: Vec<T>, | ||||
| 	/// The VM trace for the applied transaction, None if tracing was not produced.
 | ||||
| 	pub vm_trace: Option<VMTrace> | ||||
| 	pub vm_trace: Option<V> | ||||
| } | ||||
| 
 | ||||
| /// Result type for the execution ("application") of a transaction.
 | ||||
| pub type ApplyResult = Result<ApplyOutcome, Error>; | ||||
| pub type ApplyResult<T, V> = Result<ApplyOutcome<T, V>, Error>; | ||||
| 
 | ||||
| /// Return type of proof validity check.
 | ||||
| #[derive(Debug, Clone)] | ||||
| @ -668,7 +668,7 @@ impl<B: Backend> State<B> { | ||||
| 
 | ||||
| 	/// Execute a given transaction, producing a receipt and an optional trace.
 | ||||
| 	/// This will change the state accordingly.
 | ||||
| 	pub fn apply(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, tracing: bool) -> ApplyResult { | ||||
| 	pub fn apply(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, tracing: bool) -> ApplyResult<FlatTrace, VMTrace> { | ||||
| 		if tracing { | ||||
| 			let options = TransactOptions::with_tracing(); | ||||
| 			self.apply_with_tracing(env_info, machine, t, options.tracer, options.vm_tracer) | ||||
| @ -687,7 +687,7 @@ impl<B: Backend> State<B> { | ||||
| 		t: &SignedTransaction, | ||||
| 		tracer: T, | ||||
| 		vm_tracer: V, | ||||
| 	) -> ApplyResult where | ||||
| 	) -> ApplyResult<T::Output, V::Output> where | ||||
| 		T: trace::Tracer, | ||||
| 		V: trace::VMTracer, | ||||
| 	{ | ||||
| @ -728,7 +728,7 @@ impl<B: Backend> State<B> { | ||||
| 	// `virt` signals that we are executing outside of a block set and restrictions like
 | ||||
| 	// gas limits and gas costs should be lifted.
 | ||||
| 	fn execute<T, V>(&mut self, env_info: &EnvInfo, machine: &Machine, t: &SignedTransaction, options: TransactOptions<T, V>, virt: bool) | ||||
| 		-> Result<Executed, ExecutionError> where T: trace::Tracer, V: trace::VMTracer, | ||||
| 		-> Result<Executed<T::Output, V::Output>, ExecutionError> where T: trace::Tracer, V: trace::VMTracer, | ||||
| 	{ | ||||
| 		let mut e = Executive::new(self, env_info, machine); | ||||
| 
 | ||||
|  | ||||
| @ -83,6 +83,8 @@ fn should_prefix_address_properly() { | ||||
| } | ||||
| 
 | ||||
| impl Tracer for ExecutiveTracer { | ||||
| 	type Output = FlatTrace; | ||||
| 
 | ||||
| 	fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> { | ||||
| 		Some(Call::from(params.clone())) | ||||
| 	} | ||||
| @ -201,6 +203,8 @@ impl ExecutiveVMTracer { | ||||
| } | ||||
| 
 | ||||
| impl VMTracer for ExecutiveVMTracer { | ||||
| 	type Output = VMTrace; | ||||
| 
 | ||||
| 	fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { true } | ||||
| 
 | ||||
| 	fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) { | ||||
|  | ||||
| @ -48,6 +48,9 @@ use header::BlockNumber; | ||||
| 
 | ||||
| /// This trait is used by executive to build traces.
 | ||||
| pub trait Tracer: Send { | ||||
| 	/// Data returned when draining the Tracer.
 | ||||
| 	type Output; | ||||
| 
 | ||||
| 	/// Prepares call trace for given params. Noop tracer should return None.
 | ||||
| 	fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>; | ||||
| 
 | ||||
| @ -63,7 +66,7 @@ pub trait Tracer: Send { | ||||
| 		call: Option<Call>, | ||||
| 		gas_used: U256, | ||||
| 		output: Option<Bytes>, | ||||
| 		subs: Vec<FlatTrace>, | ||||
| 		subs: Vec<Self::Output>, | ||||
| 	); | ||||
| 
 | ||||
| 	/// Stores trace create info.
 | ||||
| @ -73,14 +76,14 @@ pub trait Tracer: Send { | ||||
| 		gas_used: U256, | ||||
| 		code: Option<Bytes>, | ||||
| 		address: Address, | ||||
| 		subs: Vec<FlatTrace> | ||||
| 		subs: Vec<Self::Output> | ||||
| 	); | ||||
| 
 | ||||
| 	/// Stores failed call trace.
 | ||||
| 	fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<FlatTrace>, error: TraceError); | ||||
| 	fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<Self::Output>, error: TraceError); | ||||
| 
 | ||||
| 	/// Stores failed create trace.
 | ||||
| 	fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<FlatTrace>, error: TraceError); | ||||
| 	fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<Self::Output>, error: TraceError); | ||||
| 
 | ||||
| 	/// Stores suicide info.
 | ||||
| 	fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address); | ||||
| @ -92,12 +95,15 @@ pub trait Tracer: Send { | ||||
| 	fn subtracer(&self) -> Self where Self: Sized; | ||||
| 
 | ||||
| 	/// Consumes self and returns all traces.
 | ||||
| 	fn drain(self) -> Vec<FlatTrace>; | ||||
| 	fn drain(self) -> Vec<Self::Output>; | ||||
| } | ||||
| 
 | ||||
| /// Used by executive to build VM traces.
 | ||||
| pub trait VMTracer: Send { | ||||
| 
 | ||||
| 	/// Data returned when draining the VMTracer.
 | ||||
| 	type Output; | ||||
| 
 | ||||
| 	/// Trace the progression of interpreter to next instruction.
 | ||||
| 	/// If tracer returns `false` it won't be called again.
 | ||||
| 	/// @returns true if `trace_prepare_execute` and `trace_executed` should be called.
 | ||||
| @ -116,7 +122,7 @@ pub trait VMTracer: Send { | ||||
| 	fn done_subtrace(&mut self, sub: Self) where Self: Sized; | ||||
| 
 | ||||
| 	/// Consumes self and returns the VM trace.
 | ||||
| 	fn drain(self) -> Option<VMTrace>; | ||||
| 	fn drain(self) -> Option<Self::Output>; | ||||
| } | ||||
| 
 | ||||
| /// `DbExtras` provides an interface to query extra data which is not stored in tracesdb,
 | ||||
|  | ||||
| @ -27,6 +27,8 @@ use trace::trace::{Call, Create, VMTrace, RewardType}; | ||||
| pub struct NoopTracer; | ||||
| 
 | ||||
| impl Tracer for NoopTracer { | ||||
| 	type Output = FlatTrace; | ||||
| 
 | ||||
| 	fn prepare_trace_call(&self, _: &ActionParams) -> Option<Call> { | ||||
| 		None | ||||
| 	} | ||||
| @ -76,6 +78,8 @@ impl Tracer for NoopTracer { | ||||
| pub struct NoopVMTracer; | ||||
| 
 | ||||
| impl VMTracer for NoopVMTracer { | ||||
| 	type Output = VMTrace; | ||||
| 
 | ||||
| 	fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8) -> bool { false } | ||||
| 
 | ||||
| 	fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {} | ||||
|  | ||||
| @ -22,5 +22,8 @@ evm = { path = "../ethcore/evm" } | ||||
| vm = { path = "../ethcore/vm" } | ||||
| panic_hook = { path = "../panic_hook" } | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions = "0.1" | ||||
| 
 | ||||
| [features] | ||||
| evm-debug = ["ethcore/evm-debug-tests"] | ||||
|  | ||||
| @ -16,11 +16,13 @@ | ||||
| 
 | ||||
| //! JSON VM output.
 | ||||
| 
 | ||||
| use ethcore::trace; | ||||
| use std::collections::HashMap; | ||||
| use bigint::prelude::U256; | ||||
| use std::mem; | ||||
| 
 | ||||
| use bigint::hash::H256; | ||||
| use bigint::prelude::U256; | ||||
| use bytes::ToPretty; | ||||
| use ethcore::trace; | ||||
| 
 | ||||
| use display; | ||||
| use info as vm; | ||||
| @ -39,6 +41,7 @@ pub struct Informant { | ||||
| 	storage: HashMap<H256, H256>, | ||||
| 	traces: Vec<String>, | ||||
| 	subtraces: Vec<String>, | ||||
| 	unmatched: bool, | ||||
| } | ||||
| 
 | ||||
| impl Informant { | ||||
| @ -59,14 +62,6 @@ impl Informant { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Drop for Informant { | ||||
| 	fn drop(&mut self) { | ||||
| 		for trace in &self.traces { | ||||
| 			println!("{}", trace); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl vm::Informant for Informant { | ||||
| 	fn before_test(&self, name: &str, action: &str) { | ||||
| 		println!( | ||||
| @ -80,28 +75,43 @@ impl vm::Informant for Informant { | ||||
| 		self.gas_used = gas; | ||||
| 	} | ||||
| 
 | ||||
| 	fn finish(result: Result<vm::Success, vm::Failure>) { | ||||
| 	fn finish(result: vm::RunResult<Self::Output>) { | ||||
| 		match result { | ||||
| 			Ok(success) => println!( | ||||
| 				"{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", | ||||
| 				output = success.output.to_hex(), | ||||
| 				gas = success.gas_used, | ||||
| 				time = display::as_micros(&success.time), | ||||
| 			), | ||||
| 			Err(failure) => println!( | ||||
| 				"{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", | ||||
| 				error = failure.error, | ||||
| 				gas = failure.gas_used, | ||||
| 				time = display::as_micros(&failure.time), | ||||
| 			), | ||||
| 			Ok(success) => { | ||||
| 				for trace in success.traces.unwrap_or_else(Vec::new) { | ||||
| 					println!("{}", trace); | ||||
| 				} | ||||
| 
 | ||||
| 				println!( | ||||
| 					"{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", | ||||
| 					output = success.output.to_hex(), | ||||
| 					gas = success.gas_used, | ||||
| 					time = display::as_micros(&success.time), | ||||
| 				) | ||||
| 			}, | ||||
| 			Err(failure) => { | ||||
| 				for trace in failure.traces.unwrap_or_else(Vec::new) { | ||||
| 					println!("{}", trace); | ||||
| 				} | ||||
| 
 | ||||
| 				println!( | ||||
| 					"{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", | ||||
| 					error = failure.error, | ||||
| 					gas = failure.gas_used, | ||||
| 					time = display::as_micros(&failure.time), | ||||
| 				) | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl trace::VMTracer for Informant { | ||||
| 	type Output = Vec<String>; | ||||
| 
 | ||||
| 	fn trace_next_instruction(&mut self, pc: usize, instruction: u8) -> bool { | ||||
| 		self.pc = pc; | ||||
| 		self.instruction = instruction; | ||||
| 		self.unmatched = true; | ||||
| 		true | ||||
| 	} | ||||
| 
 | ||||
| @ -128,6 +138,7 @@ impl trace::VMTracer for Informant { | ||||
| 		); | ||||
| 		self.traces.push(trace); | ||||
| 
 | ||||
| 		self.unmatched = false; | ||||
| 		self.gas_used = gas_used; | ||||
| 
 | ||||
| 		let len = self.stack.len(); | ||||
| @ -147,7 +158,7 @@ impl trace::VMTracer for Informant { | ||||
| 
 | ||||
| 
 | ||||
| 		if !self.subtraces.is_empty() { | ||||
| 			self.traces.extend(::std::mem::replace(&mut self.subtraces, vec![])); | ||||
| 			self.traces.extend(mem::replace(&mut self.subtraces, vec![])); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -159,24 +170,21 @@ impl trace::VMTracer for Informant { | ||||
| 		vm | ||||
| 	} | ||||
| 
 | ||||
| 	fn done_subtrace(&mut self, mut sub: Self) { | ||||
| 		let subtraces = ::std::mem::replace(&mut sub.traces, vec![]); | ||||
| 		if sub.depth == 1 { | ||||
| 			self.traces.extend(subtraces); | ||||
| 			// print last line with final state:
 | ||||
| 			sub.gas_cost = 0.into(); | ||||
| 			let gas_used = sub.gas_used; | ||||
| 			trace::VMTracer::trace_executed(&mut sub, gas_used, &[], None, None); | ||||
| 		} else { | ||||
| 			self.subtraces = subtraces; | ||||
| 	fn done_subtrace(&mut self, sub: Self) { | ||||
| 		if let Some(subtraces) = sub.drain() { | ||||
| 			self.subtraces.extend(subtraces); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn drain(self) -> Option<trace::VMTrace> { | ||||
| 		println!("Drain called."); | ||||
| 		for trace in &self.traces { | ||||
| 			println!("{}", trace); | ||||
| 	fn drain(mut self) -> Option<Self::Output> { | ||||
| 		if self.unmatched { | ||||
| 			// print last line with final state:
 | ||||
| 			self.gas_cost = 0.into(); | ||||
| 			let gas_used = self.gas_used; | ||||
| 			self.trace_executed(gas_used, &[], None, None); | ||||
| 		} else if !self.subtraces.is_empty() { | ||||
| 			self.traces.extend(mem::replace(&mut self.subtraces, vec![])); | ||||
| 		} | ||||
| 		None | ||||
| 		Some(self.traces) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -31,7 +31,7 @@ impl vm::Informant for Informant { | ||||
| 		println!("Test: {} ({})", name, action); | ||||
| 	} | ||||
| 
 | ||||
| 	fn finish(result: Result<vm::Success, vm::Failure>) { | ||||
| 	fn finish(result: vm::RunResult<Self::Output>) { | ||||
| 		match result { | ||||
| 			Ok(success) => { | ||||
| 				println!("Output: 0x{}", success.output.to_hex()); | ||||
| @ -47,7 +47,9 @@ impl vm::Informant for Informant { | ||||
| } | ||||
| 
 | ||||
| impl trace::VMTracer for Informant { | ||||
| 	type Output = (); | ||||
| 
 | ||||
| 	fn prepare_subtrace(&self, _code: &[u8]) -> Self where Self: Sized { Default::default() } | ||||
| 	fn done_subtrace(&mut self, _sub: Self) {} | ||||
| 	fn drain(self) -> Option<trace::VMTrace> { None } | ||||
| 	fn drain(self) -> Option<()> { None } | ||||
| } | ||||
|  | ||||
| @ -22,6 +22,7 @@ use bigint::hash::H256; | ||||
| use ethcore::{trace, spec, transaction, pod_state}; | ||||
| use ethcore::client::{self, EvmTestClient, EvmTestError, TransactResult}; | ||||
| use ethjson; | ||||
| use vm::ActionParams; | ||||
| 
 | ||||
| /// VM execution informant
 | ||||
| pub trait Informant: trace::VMTracer { | ||||
| @ -30,27 +31,51 @@ pub trait Informant: trace::VMTracer { | ||||
| 	/// Set initial gas.
 | ||||
| 	fn set_gas(&mut self, _gas: U256) {} | ||||
| 	/// Display final result.
 | ||||
| 	fn finish(result: Result<Success, Failure>); | ||||
| 	fn finish(result: RunResult<Self::Output>); | ||||
| } | ||||
| 
 | ||||
| /// Execution finished correctly
 | ||||
| pub struct Success { | ||||
| #[derive(Debug)] | ||||
| pub struct Success<T> { | ||||
| 	/// Used gas
 | ||||
| 	pub gas_used: U256, | ||||
| 	/// Output as bytes
 | ||||
| 	pub output: Vec<u8>, | ||||
| 	/// Time Taken
 | ||||
| 	pub time: Duration, | ||||
| 	/// Traces
 | ||||
| 	pub traces: Option<T>, | ||||
| } | ||||
| 
 | ||||
| /// Execution failed
 | ||||
| pub struct Failure { | ||||
| #[derive(Debug)] | ||||
| pub struct Failure<T> { | ||||
| 	/// Used gas
 | ||||
| 	pub gas_used: U256, | ||||
| 	/// Internal error
 | ||||
| 	pub error: EvmTestError, | ||||
| 	/// Duration
 | ||||
| 	pub time: Duration, | ||||
| 	/// Traces
 | ||||
| 	pub traces: Option<T>, | ||||
| } | ||||
| 
 | ||||
| /// EVM Execution result
 | ||||
| pub type RunResult<T> = Result<Success<T>, Failure<T>>; | ||||
| 
 | ||||
| /// Execute given `ActionParams` and return the result.
 | ||||
| pub fn run_action<T: Informant>( | ||||
| 	spec: &spec::Spec, | ||||
| 	params: ActionParams, | ||||
| 	mut informant: T, | ||||
| ) -> RunResult<T::Output> { | ||||
| 	informant.set_gas(params.gas); | ||||
| 	run(spec, params.gas, None, |mut client| { | ||||
| 		let result = client | ||||
| 			.call(params, &mut informant) | ||||
| 			.map(|r| (r.gas_left, r.return_data.to_vec())); | ||||
| 		(result, informant.drain()) | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| /// Execute given Transaction and verify resulting state root.
 | ||||
| @ -82,19 +107,19 @@ pub fn run_transaction<T: Informant>( | ||||
| 		let result = client.transact(env_info, transaction, informant); | ||||
| 		match result { | ||||
| 			TransactResult::Ok { state_root, .. } if state_root != post_root => { | ||||
| 				Err(EvmTestError::PostCondition(format!( | ||||
| 				(Err(EvmTestError::PostCondition(format!( | ||||
| 					"State root mismatch (got: {}, expected: {})", | ||||
| 					state_root, | ||||
| 					post_root, | ||||
| 				))) | ||||
| 				))), None) | ||||
| 			}, | ||||
| 			TransactResult::Ok { gas_left, output, .. } => { | ||||
| 				Ok((gas_left, output)) | ||||
| 			TransactResult::Ok { gas_left, output, vm_trace, .. } => { | ||||
| 				(Ok((gas_left, output)), vm_trace) | ||||
| 			}, | ||||
| 			TransactResult::Err { error, .. } => { | ||||
| 				Err(EvmTestError::PostCondition(format!( | ||||
| 				(Err(EvmTestError::PostCondition(format!( | ||||
| 					"Unexpected execution error: {:?}", error | ||||
| 				))) | ||||
| 				))), None) | ||||
| 			}, | ||||
| 		} | ||||
| 	}); | ||||
| @ -103,8 +128,13 @@ pub fn run_transaction<T: Informant>( | ||||
| } | ||||
| 
 | ||||
| /// Execute VM with given `ActionParams`
 | ||||
| pub fn run<'a, F, T>(spec: &'a spec::Spec, initial_gas: U256, pre_state: T, run: F) -> Result<Success, Failure> where | ||||
| 	F: FnOnce(EvmTestClient) -> Result<(U256, Vec<u8>), EvmTestError>, | ||||
| pub fn run<'a, F, T, X>( | ||||
| 	spec: &'a spec::Spec, | ||||
| 	initial_gas: U256, | ||||
| 	pre_state: T, | ||||
| 	run: F, | ||||
| ) -> RunResult<X> where | ||||
| 	F: FnOnce(EvmTestClient) -> (Result<(U256, Vec<u8>), EvmTestError>, Option<X>), | ||||
| 	T: Into<Option<&'a pod_state::PodState>>, | ||||
| { | ||||
| 	let test_client = match pre_state.into() { | ||||
| @ -113,23 +143,135 @@ pub fn run<'a, F, T>(spec: &'a spec::Spec, initial_gas: U256, pre_state: T, run: | ||||
| 	}.map_err(|error| Failure { | ||||
| 		gas_used: 0.into(), | ||||
| 		error, | ||||
| 		time: Duration::from_secs(0) | ||||
| 		time: Duration::from_secs(0), | ||||
| 		traces: None, | ||||
| 	})?; | ||||
| 
 | ||||
| 	let start = Instant::now(); | ||||
| 	let result = run(test_client); | ||||
| 	let duration = start.elapsed(); | ||||
| 	let time = start.elapsed(); | ||||
| 
 | ||||
| 	match result { | ||||
| 		Ok((gas_left, output)) => Ok(Success { | ||||
| 		(Ok((gas_left, output)), traces) => Ok(Success { | ||||
| 			gas_used: initial_gas - gas_left, | ||||
| 			output: output, | ||||
| 			time: duration, | ||||
| 			output, | ||||
| 			time, | ||||
| 			traces, | ||||
| 		}), | ||||
| 		Err(e) => Err(Failure { | ||||
| 		(Err(error), traces) => Err(Failure { | ||||
| 			gas_used: initial_gas, | ||||
| 			error: e, | ||||
| 			time: duration, | ||||
| 			error, | ||||
| 			time, | ||||
| 			traces, | ||||
| 		}), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use std::sync::Arc; | ||||
| 	use rustc_hex::FromHex; | ||||
| 	use super::*; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_trace_failure() { | ||||
| 		run_test( | ||||
| 			"60F8d6", | ||||
| 			0xffff, | ||||
| 			r#" | ||||
| {"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","gasCost":"0x3","memory":"0x","stack":[],"storage":{},"depth":1} | ||||
| {"pc":2,"op":214,"opName":"","gas":"0xfffc","gasCost":"0x0","memory":"0x","stack":["0xf8"],"storage":{},"depth":1} | ||||
| 			"#,
 | ||||
| 		); | ||||
| 
 | ||||
| 		run_test( | ||||
| 			"F8d6", | ||||
| 			0xffff, | ||||
| 			r#" | ||||
| {"pc":0,"op":248,"opName":"","gas":"0xffff","gasCost":"0x0","memory":"0x","stack":[],"storage":{},"depth":1} | ||||
| 			"#,
 | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_trace_create_correctly() { | ||||
| 		run_test( | ||||
| 			"32343434345830f138343438323439f0", | ||||
| 			0xffff, | ||||
| 			r#" | ||||
| {"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1} | ||||
| {"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":1} | ||||
| {"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":5,"op":88,"opName":"PC","gas":"0xfff5","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1} | ||||
| {"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","gasCost":"0x61d0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1} | ||||
| {"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","gasCost":"0x2","memory":"0x","stack":["0x1"],"storage":{},"depth":1} | ||||
| {"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","gasCost":"0x2","memory":"0x","stack":["0x1","0x10"],"storage":{},"depth":1} | ||||
| {"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0"],"storage":{},"depth":1} | ||||
| {"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1} | ||||
| {"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1} | ||||
| {"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","gasCost":"0x9","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","gasCost":"0x9e0c","memory":"0x32343434345830f138343438323439f0","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} | ||||
| {"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":2} | ||||
| {"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":2} | ||||
| {"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":2} | ||||
| {"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":2} | ||||
| {"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2} | ||||
| {"pc":5,"op":88,"opName":"PC","gas":"0x2102","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2} | ||||
| {"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2} | ||||
| {"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","gasCost":"0x0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2} | ||||
| 			"#,
 | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	fn run_test<T: Into<U256>>( | ||||
| 		code: &str, | ||||
| 		gas: T, | ||||
| 		expected: &str, | ||||
| 	) { | ||||
| 		let mut params = ActionParams::default(); | ||||
| 		params.code = Some(Arc::new(code.from_hex().unwrap())); | ||||
| 		params.gas = gas.into(); | ||||
| 
 | ||||
| 		let spec = ::ethcore::ethereum::new_foundation(&::std::env::temp_dir()); | ||||
| 		let informant = ::display::json::Informant::default(); | ||||
| 		let result = run_action(&spec, params, informant); | ||||
| 		let expected = expected.split("\n") | ||||
| 			.map(|x| x.trim()) | ||||
| 			.map(|x| x.to_owned()) | ||||
| 			.filter(|x| !x.is_empty()) | ||||
| 			.collect::<Vec<_>>(); | ||||
| 		match result { | ||||
| 			Ok(Success { traces, .. }) => { | ||||
| 				assert_traces_eq(&traces.unwrap(), &expected); | ||||
| 			}, | ||||
| 			Err(Failure { traces, .. }) => { | ||||
| 				assert_traces_eq(&traces.unwrap(), &expected); | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn assert_traces_eq( | ||||
| 		a: &[String], | ||||
| 		b: &[String], | ||||
| 	) { | ||||
| 		let mut ita = a.iter(); | ||||
| 		let mut itb = b.iter(); | ||||
| 
 | ||||
| 		loop { | ||||
| 			match (ita.next(), itb.next()) { | ||||
| 				(Some(a), Some(b)) => { | ||||
| 					assert_eq!(a, b); | ||||
| 					println!("{}", a); | ||||
| 				}, | ||||
| 				(None, None) => return, | ||||
| 				e => { | ||||
| 					panic!("Traces mismatch: {:?}", e); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -32,6 +32,10 @@ extern crate vm; | ||||
| extern crate evm; | ||||
| extern crate panic_hook; | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| #[macro_use] | ||||
| extern crate pretty_assertions; | ||||
| 
 | ||||
| use std::sync::Arc; | ||||
| use std::{fmt, fs}; | ||||
| use std::path::PathBuf; | ||||
| @ -136,7 +140,7 @@ fn run_state_test(args: Args) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn run_call<T: Informant>(args: Args, mut informant: T) { | ||||
| fn run_call<T: Informant>(args: Args, informant: T) { | ||||
| 	let from = arg(args.from(), "--from"); | ||||
| 	let to = arg(args.to(), "--to"); | ||||
| 	let code = arg(args.code(), "--code"); | ||||
| @ -160,10 +164,7 @@ fn run_call<T: Informant>(args: Args, mut informant: T) { | ||||
| 	params.code = code.map(Arc::new); | ||||
| 	params.data = data; | ||||
| 
 | ||||
| 	informant.set_gas(gas); | ||||
| 	let result = info::run(&spec, gas, None, |mut client| { | ||||
| 		client.call(params, &mut informant).map(|r| (r.gas_left, r.return_data.to_vec())) | ||||
| 	}); | ||||
| 	let result = info::run_action(&spec, params, informant); | ||||
| 	T::finish(result); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -60,9 +60,9 @@ hash = { path = "../util/hash" } | ||||
| hardware-wallet = { path = "../hw" } | ||||
| 
 | ||||
| clippy = { version = "0.0.103", optional = true} | ||||
| pretty_assertions = "0.1" | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions = "0.1" | ||||
| macros = { path = "../util/macros" } | ||||
| ethcore-network = { path = "../util/network" } | ||||
| kvdb-memorydb = { path = "../util/kvdb-memorydb" } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user