diff --git a/src/tracing/js/bindings.rs b/src/tracing/js/bindings.rs index e473079c..9e350d26 100644 --- a/src/tracing/js/bindings.rs +++ b/src/tracing/js/bindings.rs @@ -672,9 +672,11 @@ pub(crate) struct JsEvmContext { /// Number, block number pub(crate) block: u64, pub(crate) output: Bytes, - /// Number, block number + /// Number, block timestamp pub(crate) time: String, pub(crate) transaction_ctx: TransactionContext, + /// returns information about the error if one occurred, otherwise returns undefined + pub(crate) error: Option, } impl JsEvmContext { @@ -693,6 +695,7 @@ impl JsEvmContext { output, time, transaction_ctx, + error, } = self; let obj = JsObject::default(); @@ -724,6 +727,9 @@ impl JsEvmContext { if let Some(tx_hash) = transaction_ctx.tx_hash { obj.set(js_string!("txHash"), to_byte_array(tx_hash.0, ctx)?, false, ctx)?; } + if let Some(error) = error { + obj.set(js_string!("error"), js_string!(error), false, ctx)?; + } Ok(obj) } diff --git a/src/tracing/js/mod.rs b/src/tracing/js/mod.rs index 5d3a81ca..1958f0a2 100644 --- a/src/tracing/js/mod.rs +++ b/src/tracing/js/mod.rs @@ -236,6 +236,7 @@ impl JsInspector { let gas_used = result.gas_used(); let mut to = None; let mut output_bytes = None; + let mut error = None; match result { ExecutionResult::Success { output, .. } => match output { Output::Call(out) => { @@ -247,17 +248,21 @@ impl JsInspector { } }, ExecutionResult::Revert { output, .. } => { + error = Some("execution reverted".to_string()); output_bytes = Some(output); } - ExecutionResult::Halt { .. } => {} + ExecutionResult::Halt { reason, .. } => { + error = Some(format!("execution halted: {:?}", reason)); + } }; + if let TransactTo::Call(target) = env.tx.transact_to { + to = Some(target); + } + let ctx = JsEvmContext { r#type: match env.tx.transact_to { - TransactTo::Call(target) => { - to = Some(target); - "CALL" - } + TransactTo::Call(_) => "CALL", TransactTo::Create => "CREATE", } .to_string(), @@ -273,6 +278,7 @@ impl JsInspector { time: env.block.timestamp.to_string(), intrinsic_gas: 0, transaction_ctx: self.transaction_context, + error, }; let ctx = ctx.into_js_object(&mut self.ctx)?; let db = db.into_js_object(&mut self.ctx)?; diff --git a/tests/it/geth.rs b/tests/it/geth.rs index ce9cfb08..854453fc 100644 --- a/tests/it/geth.rs +++ b/tests/it/geth.rs @@ -16,6 +16,9 @@ use revm::{ }; use revm_inspectors::tracing::{MuxInspector, TracingInspector, TracingInspectorConfig}; +#[cfg(feature = "js-tracer")] +use revm_inspectors::tracing::js::JsInspector; + #[test] fn test_geth_calltracer_logs() { /* @@ -335,3 +338,104 @@ fn test_geth_inspector_reset() { 1000000 ); } + +#[test] +#[cfg(feature = "js-tracer")] +fn test_geth_jstracer_revert() { + /* + pragma solidity ^0.8.13; + + contract Foo { + event Log(address indexed addr, uint256 value); + + function foo() external { + emit Log(msg.sender, 0); + } + + function bar() external { + emit Log(msg.sender, 0); + require(false, "barbarbar"); + } + } + */ + + let code = hex!("608060405261023e806100115f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063c298557814610038578063febb0f7e14610042575b5f80fd5b61004061004c565b005b61004a61009c565b005b3373ffffffffffffffffffffffffffffffffffffffff167ff950957d2407bed19dc99b718b46b4ce6090c05589006dfb86fd22c34865b23e5f6040516100929190610177565b60405180910390a2565b3373ffffffffffffffffffffffffffffffffffffffff167ff950957d2407bed19dc99b718b46b4ce6090c05589006dfb86fd22c34865b23e5f6040516100e29190610177565b60405180910390a25f61012a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610121906101ea565b60405180910390fd5b565b5f819050919050565b5f819050919050565b5f819050919050565b5f61016161015c6101578461012c565b61013e565b610135565b9050919050565b61017181610147565b82525050565b5f60208201905061018a5f830184610168565b92915050565b5f82825260208201905092915050565b7f62617262617262617200000000000000000000000000000000000000000000005f82015250565b5f6101d4600983610190565b91506101df826101a0565b602082019050919050565b5f6020820190508181035f830152610201816101c8565b905091905056fea2646970667358221220e058dc2c4bd629d62405850cc8e08e6bfad0eea187260784445dfe8f3ee0bea564736f6c634300081a0033"); + let deployer = Address::ZERO; + + let mut db = CacheDB::new(EmptyDB::default()); + + let cfg = CfgEnvWithHandlerCfg::new(CfgEnv::default(), HandlerCfg::new(SpecId::CANCUN)); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg.clone(), + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Create, + data: code.into(), + ..Default::default() + }, + ); + + let mut insp = TracingInspector::new(TracingInspectorConfig::default_geth()); + + // Create contract + let (res, _) = inspect(&mut db, env, &mut insp).unwrap(); + let addr = match res.result { + ExecutionResult::Success { output, .. } => match output { + Output::Create(_, addr) => addr.unwrap(), + _ => panic!("Create failed"), + }, + _ => panic!("Execution failed: {:?}", res.result), + }; + db.commit(res.state); + + let code = r#" +{ + fault: function() {}, + result: function(ctx) { return { error: !!ctx.error }; }, +}"#; + + // test with normal operation + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg.clone(), + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Call(addr), + data: hex!("c2985578").into(), // call foo + ..Default::default() + }, + ); + let mut insp = JsInspector::new(code.to_string(), serde_json::Value::Null).unwrap(); + let (res, _) = inspect(&mut db, env.clone(), &mut insp).unwrap(); + assert!(res.result.is_success()); + + let result = insp.json_result(res, &env, &db).unwrap(); + + // sucessful operation + assert!(!result["error"].as_bool().unwrap()); + + // test with reverted operation + let env = EnvWithHandlerCfg::new_with_cfg_env( + cfg, + BlockEnv::default(), + TxEnv { + caller: deployer, + gas_limit: 1000000, + transact_to: TransactTo::Call(addr), + data: hex!("febb0f7e").into(), // call bar + ..Default::default() + }, + ); + let mut insp = JsInspector::new(code.to_string(), serde_json::Value::Null).unwrap(); + let (res, _) = inspect(&mut db, env.clone(), &mut insp).unwrap(); + assert!(!res.result.is_success()); + + let result = insp.json_result(res, &env, &db).unwrap(); + + // reverted operation + assert!(result["error"].as_bool().unwrap()); +}