From b3f873dd7a388369bd5e8aa86b1d2dd317add4cd Mon Sep 17 00:00:00 2001 From: Shashank Trivedi <100513286+lordshashank@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:23:17 +0530 Subject: [PATCH] feat: fvm-bench: decode evm revert messages (#1874) --- tools/contracts/benchmarks/SimpleCoin.bin | 2 +- tools/contracts/benchmarks/SimpleCoin.sol | 17 ++- tools/fvm-bench/src/fevm.rs | 159 ++++++++++++++++++++++ 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/tools/contracts/benchmarks/SimpleCoin.bin b/tools/contracts/benchmarks/SimpleCoin.bin index 55b83cb12..a9b550f3b 100644 --- a/tools/contracts/benchmarks/SimpleCoin.bin +++ b/tools/contracts/benchmarks/SimpleCoin.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea26469706673582212205ede41ff9072784ccc19ac18de0781558d305a8139361fa85dc51a8614e47d8c64736f6c63430008110033 \ No newline at end of file +608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610795806100656000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637bd703e81461005157806390b98a1114610081578063c0626aa2146100b1578063f8b2cb4f146100e1575b600080fd5b61006b600480360381019061006691906103eb565b610111565b6040516100789190610431565b60405180910390f35b61009b60048036038101906100969190610478565b61012f565b6040516100a891906104d3565b60405180910390f35b6100cb60048036038101906100c691906104ee565b61029a565b6040516100d891906104d3565b60405180910390f35b6100fb60048036038101906100f691906103eb565b610340565b6040516101089190610431565b60405180910390f35b6000600261011e83610340565b610128919061054a565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101805760009050610294565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101ce919061058c565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461022391906105c0565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516102879190610431565b60405180910390a3600190505b92915050565b6000600182116102ad576102ac6105f4565b5b60058210156102f45760026040517f442666110000000000000000000000000000000000000000000000000000000081526004016102eb91906106c5565b60405180910390fd5b600a8211610337576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161032e9061073f565b60405180910390fd5b60019050919050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006103b88261038d565b9050919050565b6103c8816103ad565b81146103d357600080fd5b50565b6000813590506103e5816103bf565b92915050565b60006020828403121561040157610400610388565b5b600061040f848285016103d6565b91505092915050565b6000819050919050565b61042b81610418565b82525050565b60006020820190506104466000830184610422565b92915050565b61045581610418565b811461046057600080fd5b50565b6000813590506104728161044c565b92915050565b6000806040838503121561048f5761048e610388565b5b600061049d858286016103d6565b92505060206104ae85828601610463565b9150509250929050565b60008115159050919050565b6104cd816104b8565b82525050565b60006020820190506104e860008301846104c4565b92915050565b60006020828403121561050457610503610388565b5b600061051284828501610463565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061055582610418565b915061056083610418565b925082820261056e81610418565b915082820484148315176105855761058461051b565b5b5092915050565b600061059782610418565b91506105a283610418565b92508282039050818111156105ba576105b961051b565b5b92915050565b60006105cb82610418565b91506105d683610418565b92508282019050808211156105ee576105ed61051b565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b600082825260208201905092915050565b7f4c657373207468616e2066697665000000000000000000000000000000000000600082015250565b600061066a600e83610623565b915061067582610634565b602082019050919050565b6000819050919050565b6000819050919050565b60006106af6106aa6106a584610680565b61068a565b610418565b9050919050565b6106bf81610694565b82525050565b600060408201905081810360008301526106de8161065d565b90506106ed60208301846106b6565b92915050565b7f4c657373205468616e2074656e00000000000000000000000000000000000000600082015250565b6000610729600d83610623565b9150610734826106f3565b602082019050919050565b600060208201905081810360008301526107588161071c565b905091905056fea2646970667358221220a6ed6e1ad429eb3ff6712942fd81d137f4187d47310e3bbddc2af76ee5c585ba64736f6c63430008120033 \ No newline at end of file diff --git a/tools/contracts/benchmarks/SimpleCoin.sol b/tools/contracts/benchmarks/SimpleCoin.sol index 5318b0cb8..afd1adc4b 100644 --- a/tools/contracts/benchmarks/SimpleCoin.sol +++ b/tools/contracts/benchmarks/SimpleCoin.sol @@ -6,14 +6,16 @@ contract SimpleCoin { event Transfer(address indexed _from, address indexed _to, uint256 _value); + error lessThanFive(string err, uint256 code); + constructor() { balances[tx.origin] = 10000; } - function sendCoin(address receiver, uint256 amount) - public - returns (bool sufficient) - { + function sendCoin( + address receiver, + uint256 amount + ) public returns (bool sufficient) { if (balances[msg.sender] < amount) return false; balances[msg.sender] -= amount; balances[receiver] += amount; @@ -21,6 +23,13 @@ contract SimpleCoin { return true; } + function greaterThanTen(uint256 num) public pure returns (bool) { + assert(num > 1); + if (num < 5) revert lessThanFive("Less than five", 2); + require(num > 10, "Less Than ten"); + return true; + } + function getBalanceInEth(address addr) public view returns (uint256) { return getBalance(addr) * 2; } diff --git a/tools/fvm-bench/src/fevm.rs b/tools/fvm-bench/src/fevm.rs index b66d314a0..7be7c2cc2 100644 --- a/tools/fvm-bench/src/fevm.rs +++ b/tools/fvm-bench/src/fevm.rs @@ -7,6 +7,24 @@ use fvm_integration_tests::{tester, testkit}; use fvm_ipld_encoding::BytesDe; use fvm_shared::address::Address; +// Eth ABI (solidity) panic codes. +const PANIC_ERROR_CODES: [(u64, &str); 10] = [ + (0x00, "Panic()"), + (0x01, "Assert()"), + (0x11, "ArithmeticOverflow()"), + (0x12, "DivideByZero()"), + (0x21, "InvalidEnumVariant()"), + (0x22, "InvalidStorageArray()"), + (0x31, "PopEmptyArray()"), + (0x32, "ArrayIndexOutOfBounds()"), + (0x41, "OutOfMemory()"), + (0x51, "CalledUninitializedFunction()"), +]; + +// Function Selectors +const ERROR_FUNCTION_SELECTOR: &[u8] = b"\x08\xc3\x79\xa0"; // Error(string) +const PANIC_FUNCTION_SELECTOR: &[u8] = b"\x4e\x48\x7b\x71"; // Panic(uint256) + fn handle_result(tester: &tester::BasicTester, name: &str, res: &ApplyRet) -> anyhow::Result<()> { let (trace, events) = tester .options @@ -52,6 +70,10 @@ fn handle_result(tester: &tester::BasicTester, name: &str, res: &ApplyRet) -> an if res.msg_receipt.exit_code.is_success() { Ok(()) } else { + if res.msg_receipt.exit_code == 33.into() { + let BytesDe(returnval) = res.msg_receipt.return_data.deserialize().unwrap(); + println!("Revert Reason: {}", parse_eth_revert(&returnval).unwrap()); + } Err(anyhow!("{name} failed")) } } @@ -85,3 +107,140 @@ pub fn run( handle_result(tester, "contract invocation", &invoke_res) } + +// Parses the error message from a revert reason of type Error(string) or Panic(uint256) +// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require +pub fn parse_eth_revert(returnval: &Vec) -> anyhow::Result { + if returnval.is_empty() { + return Err(anyhow!("invalid return value")); + } + if returnval.len() < 4 + 32 { + return Ok(hex::encode(returnval)); + } + match &returnval[0..4] { + PANIC_FUNCTION_SELECTOR => { + let cbytes = &returnval[4..]; + match bytes_to_u64(&cbytes[..32]) { + Ok(panic_code) => { + let error = panic_error_codes(panic_code); + match error { + Some(s) => return Ok(format!("Panic Code: {}, msg: {}", s.0, s.1)), + None => return Err(anyhow!("Returned with panic code({})", panic_code)), + } + } + Err(_) => { + return Ok(hex::encode(returnval)); + } + } + } + ERROR_FUNCTION_SELECTOR => { + let cbytes = &returnval[4..]; + let cbytes_len = cbytes.len() as u64; + if let Ok(offset) = bytes_to_u64(&cbytes[0..32]) { + if cbytes_len >= offset + 32 { + if let Ok(length) = bytes_to_u64(&cbytes[offset as usize..offset as usize + 32]) + { + if cbytes_len >= offset + 32 + length { + let msg = String::from_utf8_lossy( + &cbytes + [offset as usize + 32..offset as usize + 32 + length as usize], + ); + return Ok(msg.to_string()); + } + } + } + } + } + _ => return Ok(hex::encode(returnval)), + }; + Ok(hex::encode(returnval)) +} + +// Converts a byte slice to a u64 +fn bytes_to_u64(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(anyhow::anyhow!("Invalid byte slice length")); + } + let mut buf = [0u8; 8]; + buf.copy_from_slice(&bytes[24..32]); + Ok(u64::from_be_bytes(buf)) +} + +// Returns the panic code and message for a given panic code +fn panic_error_codes(code: u64) -> Option<&'static (u64, &'static str)> { + PANIC_ERROR_CODES.iter().find(|(c, _)| *c == code) +} + +////////////////////// +/////// Tests /////// +///////////////////// + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_eth_revert_empty_returnval() { + let returnval = vec![]; + let result = parse_eth_revert(&returnval); + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), "invalid return value"); + } + + #[test] + fn test_parse_eth_revert_short_returnval() { + let returnval = vec![0x01, 0x02, 0x03]; + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "010203"); + } + + #[test] + fn test_parse_eth_revert_panic_function_selector() { + let returnval = vec![ + 0x4e, 0x48, 0x7b, 0x71, // function selector for "Panic(uint256)" + 0x00, 0x00, 0x00, 0x00, + ]; + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "4e487b7100000000"); + } + + #[test] + fn test_parse_eth_revert_panic_function_selector_with_message() { + // assert error from simplecoin contract + let returnval = + hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "Panic Code: 1, msg: Assert()"); + } + + #[test] + fn test_parse_eth_revert_error_function_selector() { + // "Less Than ten" error from simplecoin contract + let returnval = hex::decode("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d4c657373205468616e2074656e00000000000000000000000000000000000000").unwrap(); + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "Less Than ten"); + } + + #[test] + fn test_parse_eth_revert_error_function_selector_invalid_data() { + // invalid data for error function selector + let returnval = hex::decode("08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000").unwrap(); + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), hex::encode(&returnval)); + } + + #[test] + fn test_parse_eth_revert_custom_error() { + // any other data like custom error, etc. "lessThanFive" custom error of simplecoin contract in this case. + let returnval = hex::decode("4426661100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4c657373207468616e2066697665000000000000000000000000000000000000").unwrap(); + let result = parse_eth_revert(&returnval); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), hex::encode(&returnval)); + } +}