diff --git a/Cargo.lock b/Cargo.lock index 70da9d14a..ffea9b11b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,6 +1049,7 @@ dependencies = [ "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "vm 0.1.0", ] diff --git a/evmbin/Cargo.toml b/evmbin/Cargo.toml index 8e2e1de3d..7d3a41726 100644 --- a/evmbin/Cargo.toml +++ b/evmbin/Cargo.toml @@ -21,6 +21,7 @@ panic_hook = { path = "../util/panic_hook" } rustc-hex = "1.0" serde = "1.0" serde_derive = "1.0" +serde_json = "1.0" vm = { path = "../ethcore/vm" } [dev-dependencies] diff --git a/evmbin/src/display/json.rs b/evmbin/src/display/json.rs index ea27c2854..d2a825fdf 100644 --- a/evmbin/src/display/json.rs +++ b/evmbin/src/display/json.rs @@ -43,31 +43,9 @@ pub struct Informant { unmatched: bool, } -impl Informant { - fn memory(&self) -> String { - format!("\"0x{}\"", self.memory.to_hex()) - } - - fn stack(&self) -> String { - let items = self.stack.iter().map(|i| format!("\"0x{:x}\"", i)).collect::>(); - format!("[{}]", items.join(",")) - } - - fn storage(&self) -> String { - let vals = self.storage.iter() - .map(|(k, v)| format!("\"0x{:?}\": \"0x{:?}\"", k, v)) - .collect::>(); - format!("{{{}}}", vals.join(",")) - } -} - impl vm::Informant for Informant { fn before_test(&mut self, name: &str, action: &str) { - println!( - "{{\"test\":\"{name}\",\"action\":\"{action}\"}}", - name = name, - action = action, - ); + println!("{}", json!({"action": action, "test": name})); } fn set_gas(&mut self, gas: U256) { @@ -81,24 +59,26 @@ impl vm::Informant for Informant { 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), - ) + let success_msg = json!({ + "output": format!("0x{}", success.output.to_hex()), + "gasUsed": format!("{:#x}", success.gas_used), + "time": display::as_micros(&success.time), + }); + + println!("{}", success_msg) }, Err(failure) => { for trace in failure.traces.unwrap_or_else(Vec::new) { println!("{}", trace); } - println!( - "{{\"error\":\"{error}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}", - error = display::escape_newlines(&failure.error), - gas = failure.gas_used, - time = display::as_micros(&failure.time), - ) + let failure_msg = json!({ + "error": &failure.error.to_string(), + "gasUsed": format!("{:#x}", failure.gas_used), + "time": display::as_micros(&failure.time), + }); + + println!("{}", failure_msg) }, } } @@ -123,19 +103,19 @@ impl trace::VMTracer for Informant { fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) { let info = ::evm::Instruction::from_u8(self.instruction).map(|i| i.info()); - let trace = format!( - "{{\"pc\":{pc},\"op\":{op},\"opName\":\"{name}\",\"gas\":\"0x{gas:x}\",\"gasCost\":\"0x{gas_cost:x}\",\"memory\":{memory},\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}", - pc = self.pc, - op = self.instruction, - name = info.map(|i| i.name).unwrap_or(""), - gas = gas_used.saturating_add(self.gas_cost), - gas_cost = self.gas_cost, - memory = self.memory(), - stack = self.stack(), - storage = self.storage(), - depth = self.depth, - ); - self.traces.push(trace); + let trace = json!({ + "pc": self.pc, + "op": self.instruction, + "opName": info.map(|i| i.name).unwrap_or(""), + "gas": format!("{:#x}", gas_used.saturating_add(self.gas_cost)), + "gasCost": format!("{:#x}", self.gas_cost), + "memory": format!("0x{}", self.memory.to_hex()), + "stack": self.stack, + "storage": self.storage, + "depth": self.depth, + }); + + self.traces.push(trace.to_string()); self.unmatched = false; self.gas_used = gas_used; @@ -193,6 +173,23 @@ impl trace::VMTracer for Informant { mod tests { use super::*; use info::tests::run_test; + use serde_json; + + #[derive(Serialize, Deserialize, Debug, PartialEq)] + #[serde(rename_all = "camelCase")] + struct TestTrace { + pc: usize, + #[serde(rename = "op")] + instruction: u8, + op_name: String, + #[serde(rename = "gas")] + gas_used: U256, + gas_cost: U256, + memory: String, + stack: Vec, + storage: HashMap, + depth: usize, + } fn assert_traces_eq( a: &[String], @@ -204,7 +201,10 @@ mod tests { loop { match (ita.next(), itb.next()) { (Some(a), Some(b)) => { - assert_eq!(a, b); + // Compare both without worrying about the order of the fields + let actual: TestTrace = serde_json::from_str(a).unwrap(); + let expected: TestTrace = serde_json::from_str(b).unwrap(); + assert_eq!(actual, expected); println!("{}", a); }, (None, None) => return, @@ -280,7 +280,20 @@ mod tests { {"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} - "#, +"#, + ); + + run_test( + Informant::default(), + &compare_json, + "3260D85554", + 0xffff, + r#" +{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1} +{"pc":1,"op":96,"opName":"PUSH1","gas":"0xfffd","gasCost":"0x3","memory":"0x","stack":["0x0"],"storage":{},"depth":1} +{"pc":3,"op":85,"opName":"SSTORE","gas":"0xfffa","gasCost":"0x1388","memory":"0x","stack":["0x0","0xd8"],"storage":{},"depth":1} +{"pc":4,"op":84,"opName":"SLOAD","gas":"0xec72","gasCost":"0x0","memory":"0x","stack":[],"storage":{"0x00000000000000000000000000000000000000000000000000000000000000d8":"0x0000000000000000000000000000000000000000000000000000000000000000"},"depth":1} +"#, ) } } diff --git a/evmbin/src/display/mod.rs b/evmbin/src/display/mod.rs index 092316c03..a8eb20d9e 100644 --- a/evmbin/src/display/mod.rs +++ b/evmbin/src/display/mod.rs @@ -31,7 +31,3 @@ pub fn format_time(time: &Duration) -> String { pub fn as_micros(time: &Duration) -> u64 { time.as_secs() * 1_000_000 + time.subsec_nanos() as u64 / 1_000 } - -fn escape_newlines(s: D) -> String { - format!("{}", s).replace("\r\n", "\n").replace('\n', "\\n") -} diff --git a/evmbin/src/display/std_json.rs b/evmbin/src/display/std_json.rs index 57b14128b..f2a0963d8 100644 --- a/evmbin/src/display/std_json.rs +++ b/evmbin/src/display/std_json.rs @@ -81,28 +81,14 @@ impl Informant { } } -impl Informant { - fn stack(&self) -> String { - let items = self.stack.iter().map(|i| format!("\"0x{:x}\"", i)).collect::>(); - format!("[{}]", items.join(",")) - } - - fn storage(&self) -> String { - let vals = self.storage.iter() - .map(|(k, v)| format!("\"0x{:?}\": \"0x{:?}\"", k, v)) - .collect::>(); - format!("{{{}}}", vals.join(",")) - } -} - impl vm::Informant for Informant { fn before_test(&mut self, name: &str, action: &str) { - writeln!( - &mut self.out_sink, - "{{\"test\":\"{name}\",\"action\":\"{action}\"}}", - name = name, - action = action, - ).expect("The sink must be writeable."); + let out_data = json!({ + "action": action, + "test": name, + }); + + writeln!(&mut self.out_sink, "{}", out_data).expect("The sink must be writeable."); } fn set_gas(&mut self, _gas: U256) {} @@ -113,26 +99,26 @@ impl vm::Informant for Informant { match result { Ok(success) => { - writeln!( - &mut trace_sink, - "{{\"stateRoot\":\"{:?}\"}}", success.state_root - ).expect("The sink must be writeable."); - writeln!( - &mut out_sink, - "{{\"output\":\"0x{output}\",\"gasUsed\":\"0x{gas:x}\",\"time\":{time}}}", - output = success.output.to_hex(), - gas = success.gas_used, - time = display::as_micros(&success.time), - ).expect("The sink must be writeable."); + let trace_data = json!({"stateRoot": success.state_root}); + writeln!(&mut trace_sink, "{}", trace_data) + .expect("The sink must be writeable."); + + let out_data = json!({ + "output": format!("0x{}", success.output.to_hex()), + "gasUsed": format!("{:#x}", success.gas_used), + "time": display::as_micros(&success.time), + }); + + writeln!(&mut out_sink, "{}", out_data).expect("The sink must be writeable."); }, Err(failure) => { - writeln!( - &mut out_sink, - "{{\"error\":\"{error}\",\"gasUsed\":\"0x{gas:x}\",\"time\":{time}}}", - error = display::escape_newlines(&failure.error), - gas = failure.gas_used, - time = display::as_micros(&failure.time), - ).expect("The sink must be writeable."); + let out_data = json!({ + "error": &failure.error.to_string(), + "gasUsed": format!("{:#x}", failure.gas_used), + "time": display::as_micros(&failure.time), + }); + + writeln!(&mut out_sink, "{}", out_data).expect("The sink must be writeable."); }, } } @@ -144,20 +130,17 @@ impl trace::VMTracer for Informant { fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool { let info = ::evm::Instruction::from_u8(instruction).map(|i| i.info()); self.instruction = instruction; - let storage = self.storage(); - let stack = self.stack(); + let trace_data = json!({ + "pc": pc, + "op": instruction, + "opName": info.map(|i| i.name).unwrap_or(""), + "gas": format!("{:#x}", current_gas), + "stack": self.stack, + "storage": self.storage, + "depth": self.depth, + }); - writeln!( - &mut self.trace_sink, - "{{\"pc\":{pc},\"op\":{op},\"opName\":\"{name}\",\"gas\":\"0x{gas:x}\",\"stack\":{stack},\"storage\":{storage},\"depth\":{depth}}}", - pc = pc, - op = instruction, - name = info.map(|i| i.name).unwrap_or(""), - gas = current_gas, - stack = stack, - storage = storage, - depth = self.depth, - ).expect("The sink must be writeable."); + writeln!(&mut self.trace_sink, "{}", trace_data).expect("The sink must be writeable."); true } @@ -232,8 +215,8 @@ pub mod tests { }, "60F8d6", 0xffff, - r#"{"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","stack":[],"storage":{},"depth":1} -{"pc":2,"op":214,"opName":"","gas":"0xfffc","stack":["0xf8"],"storage":{},"depth":1} + r#"{"depth":1,"gas":"0xffff","op":96,"opName":"PUSH1","pc":0,"stack":[],"storage":{}} +{"depth":1,"gas":"0xfffc","op":214,"opName":"","pc":2,"stack":["0xf8"],"storage":{}} "#, ); @@ -246,7 +229,7 @@ pub mod tests { }, "F8d6", 0xffff, - r#"{"pc":0,"op":248,"opName":"","gas":"0xffff","stack":[],"storage":{},"depth":1} + r#"{"depth":1,"gas":"0xffff","op":248,"opName":"","pc":0,"stack":[],"storage":{}} "#, ); } @@ -262,30 +245,30 @@ pub mod tests { }, "32343434345830f138343438323439f0", 0xffff, - r#"{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","stack":[],"storage":{},"depth":1} -{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","stack":["0x0"],"storage":{},"depth":1} -{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","stack":["0x0","0x0"],"storage":{},"depth":1} -{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","stack":["0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":5,"op":88,"opName":"PC","gas":"0xfff5","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1} -{"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1} -{"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1} -{"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","stack":["0x1"],"storage":{},"depth":1} -{"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","stack":["0x1","0x10"],"storage":{},"depth":1} -{"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","stack":["0x1","0x10","0x0"],"storage":{},"depth":1} -{"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1} -{"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1} -{"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1} -{"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","stack":[],"storage":{},"depth":2} -{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","stack":["0x0"],"storage":{},"depth":2} -{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","stack":["0x0","0x0"],"storage":{},"depth":2} -{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","stack":["0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":5,"op":88,"opName":"PC","gas":"0x2102","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2} -{"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2} -{"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2} + r#"{"depth":1,"gas":"0xffff","op":50,"opName":"ORIGIN","pc":0,"stack":[],"storage":{}} +{"depth":1,"gas":"0xfffd","op":52,"opName":"CALLVALUE","pc":1,"stack":["0x0"],"storage":{}} +{"depth":1,"gas":"0xfffb","op":52,"opName":"CALLVALUE","pc":2,"stack":["0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0xfff9","op":52,"opName":"CALLVALUE","pc":3,"stack":["0x0","0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0xfff7","op":52,"opName":"CALLVALUE","pc":4,"stack":["0x0","0x0","0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0xfff5","op":88,"opName":"PC","pc":5,"stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0xfff3","op":48,"opName":"ADDRESS","pc":6,"stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{}} +{"depth":1,"gas":"0xfff1","op":241,"opName":"CALL","pc":7,"stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{}} +{"depth":1,"gas":"0x9e21","op":56,"opName":"CODESIZE","pc":8,"stack":["0x1"],"storage":{}} +{"depth":1,"gas":"0x9e1f","op":52,"opName":"CALLVALUE","pc":9,"stack":["0x1","0x10"],"storage":{}} +{"depth":1,"gas":"0x9e1d","op":52,"opName":"CALLVALUE","pc":10,"stack":["0x1","0x10","0x0"],"storage":{}} +{"depth":1,"gas":"0x9e1b","op":56,"opName":"CODESIZE","pc":11,"stack":["0x1","0x10","0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0x9e19","op":50,"opName":"ORIGIN","pc":12,"stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{}} +{"depth":1,"gas":"0x9e17","op":52,"opName":"CALLVALUE","pc":13,"stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{}} +{"depth":1,"gas":"0x9e15","op":57,"opName":"CODECOPY","pc":14,"stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{}} +{"depth":1,"gas":"0x9e0c","op":240,"opName":"CREATE","pc":15,"stack":["0x1","0x10","0x0","0x0"],"storage":{}} +{"depth":2,"gas":"0x210c","op":50,"opName":"ORIGIN","pc":0,"stack":[],"storage":{}} +{"depth":2,"gas":"0x210a","op":52,"opName":"CALLVALUE","pc":1,"stack":["0x0"],"storage":{}} +{"depth":2,"gas":"0x2108","op":52,"opName":"CALLVALUE","pc":2,"stack":["0x0","0x0"],"storage":{}} +{"depth":2,"gas":"0x2106","op":52,"opName":"CALLVALUE","pc":3,"stack":["0x0","0x0","0x0"],"storage":{}} +{"depth":2,"gas":"0x2104","op":52,"opName":"CALLVALUE","pc":4,"stack":["0x0","0x0","0x0","0x0"],"storage":{}} +{"depth":2,"gas":"0x2102","op":88,"opName":"PC","pc":5,"stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{}} +{"depth":2,"gas":"0x2100","op":48,"opName":"ADDRESS","pc":6,"stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{}} +{"depth":2,"gas":"0x20fe","op":241,"opName":"CALL","pc":7,"stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{}} "#, ) } diff --git a/evmbin/src/info.rs b/evmbin/src/info.rs index 6483bb1e0..dfb97e760 100644 --- a/evmbin/src/info.rs +++ b/evmbin/src/info.rs @@ -225,16 +225,16 @@ pub mod tests { assert_eq!( &String::from_utf8_lossy(&**res.lock().unwrap()), -r#"{"pc":0,"op":98,"opName":"PUSH3","gas":"0xffff","stack":[],"storage":{},"depth":1} -{"pc":4,"op":96,"opName":"PUSH1","gas":"0xfffc","stack":["0xaaaaaa"],"storage":{},"depth":1} -{"pc":6,"op":96,"opName":"PUSH1","gas":"0xfff9","stack":["0xaaaaaa","0xaa"],"storage":{},"depth":1} -{"pc":8,"op":80,"opName":"POP","gas":"0xfff6","stack":["0xaaaaaa","0xaa","0xaa"],"storage":{},"depth":1} -{"pc":9,"op":96,"opName":"PUSH1","gas":"0xfff4","stack":["0xaaaaaa","0xaa"],"storage":{},"depth":1} -{"pc":11,"op":96,"opName":"PUSH1","gas":"0xfff1","stack":["0xaaaaaa","0xaa","0xaa"],"storage":{},"depth":1} -{"pc":13,"op":96,"opName":"PUSH1","gas":"0xffee","stack":["0xaaaaaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} -{"pc":15,"op":96,"opName":"PUSH1","gas":"0xffeb","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} -{"pc":17,"op":96,"opName":"PUSH1","gas":"0xffe8","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} -{"pc":19,"op":96,"opName":"PUSH1","gas":"0xffe5","stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{},"depth":1} +r#"{"depth":1,"gas":"0xffff","op":98,"opName":"PUSH3","pc":0,"stack":[],"storage":{}} +{"depth":1,"gas":"0xfffc","op":96,"opName":"PUSH1","pc":4,"stack":["0xaaaaaa"],"storage":{}} +{"depth":1,"gas":"0xfff9","op":96,"opName":"PUSH1","pc":6,"stack":["0xaaaaaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xfff6","op":80,"opName":"POP","pc":8,"stack":["0xaaaaaa","0xaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xfff4","op":96,"opName":"PUSH1","pc":9,"stack":["0xaaaaaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xfff1","op":96,"opName":"PUSH1","pc":11,"stack":["0xaaaaaa","0xaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xffee","op":96,"opName":"PUSH1","pc":13,"stack":["0xaaaaaa","0xaa","0xaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xffeb","op":96,"opName":"PUSH1","pc":15,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xffe8","op":96,"opName":"PUSH1","pc":17,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}} +{"depth":1,"gas":"0xffe5","op":96,"opName":"PUSH1","pc":19,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}} "#); } } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index c0f500347..ded5c321c 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -24,6 +24,8 @@ extern crate rustc_hex; extern crate serde; #[macro_use] extern crate serde_derive; +#[macro_use] +extern crate serde_json; extern crate docopt; extern crate ethcore_transaction as transaction; extern crate parity_bytes as bytes;