From c5ff0909976d3d816f26eb60041a67b5722ec098 Mon Sep 17 00:00:00 2001 From: Oleh Nikolaiev Date: Fri, 8 Dec 2023 16:10:58 +0000 Subject: [PATCH 1/4] #1744 handle custom errors in revert --- libdevcore/CommonData.cpp | 41 +++++++- libdevcore/CommonData.h | 8 ++ libskale/State.cpp | 7 +- test/unittests/libweb3jsonrpc/jsonrpc.cpp | 109 +++++++++++++++++++++- 4 files changed, 161 insertions(+), 4 deletions(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 16911de42..b0e3a52f6 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -22,10 +22,11 @@ */ #include "CommonData.h" -#include - #include "Exceptions.h" +#include +#include + using namespace std; using namespace dev; @@ -116,3 +117,39 @@ std::string dev::toString( string32 const& _s ) { ret.push_back( _s[i] ); return ret; } + +std::string dev::customErrorMessageToString( const bytes& _b ) { + std::cout << toHex( _b ) << '\n'; + return toHexPrefixed( _b ); +} + +std::string dev::revertMessageToString( const bytes& b ) { + // first 4 bytes are function selector (revert - 0x08c379a0) + // then goes offset - 32 bytes + // then goes string length - 32 bytes + // then goes string data + u256 offset = 4 + 32 + fromBigEndian< u256 >( bytes( b.begin() + 4, b.begin() + 32 + 4 ) ); + // offset for any revert message should be 4 + 32 + 32 = 68 + if ( offset != 68 ) { + BOOST_THROW_EXCEPTION( + std::runtime_error( "Unexpected offset length in revertMessageToString" ) ); + } + u256 length = fromBigEndian< u256 >( + bytes( b.begin() + 4 + 32, b.begin() + offset.convert_to< unsigned >() ) ); + if ( offset + length > b.size() ) { + BOOST_THROW_EXCEPTION( std::runtime_error( + "Unexpected length in revertMessageToString: 4 + offset + length > b.size()" ) ); + } + return asString( bytes( b.begin() + offset.convert_to< unsigned >(), + b.begin() + offset.convert_to< unsigned >() + length.convert_to< unsigned >() ) ); +} + +std::string dev::errorMessageToString( const bytes& b ) { + if ( b.empty() ) { + return ""; + } + // check if the message is a revert message + if ( b.size() > 4 + 32 + 32 && b[0] == 0x08 && b[1] == 0xc3 && b[2] == 0x79 && b[3] == 0xa0 ) + return revertMessageToString( b ); + return customErrorMessageToString( b ); +} diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index fa896be69..388057804 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -111,6 +111,14 @@ inline bytes asBytes( std::string const& _b ) { /// @example asNibbles("A")[0] == 4 && asNibbles("A")[1] == 1 bytes asNibbles( bytesConstRef const& _s ); +// converts byte array containing a custom error message thrown by EVM to string +std::string customErrorMessageToString( const bytes& _b ); + +// converts byte array containing a revert message thrown by EVM to string +std::string revertMessageToString( const bytes& _b ); + +// converts byte array containing an error message thrown by EVM to string +std::string errorMessageToString( const bytes& _b ); // Big-endian to/from host endian conversion functions. diff --git a/libskale/State.cpp b/libskale/State.cpp index 5bf4433d5..317959f13 100644 --- a/libskale/State.cpp +++ b/libskale/State.cpp @@ -1030,7 +1030,12 @@ std::pair< ExecutionResult, TransactionReceipt > State::execute( EnvInfo const& std::string strRevertReason; if ( res.excepted == dev::eth::TransactionException::RevertInstruction ) { - strRevertReason = skutils::eth::call_error_message_2_str( res.output ); + // if ( res.output.size() == 4 ) { + // strRevertReason = dev::toHex( res.output ); + // } else { + // strRevertReason = skutils::eth::call_error_message_2_str( res.output ); + // } + strRevertReason = dev::errorMessageToString( res.output ); if ( strRevertReason.empty() ) strRevertReason = "EVM revert instruction without description message"; std::string strOut = cc::fatal( "Error message from eth_call():" ) + cc::error( " " ) + diff --git a/test/unittests/libweb3jsonrpc/jsonrpc.cpp b/test/unittests/libweb3jsonrpc/jsonrpc.cpp index 12c113e16..5ad72ed55 100644 --- a/test/unittests/libweb3jsonrpc/jsonrpc.cpp +++ b/test/unittests/libweb3jsonrpc/jsonrpc.cpp @@ -2368,7 +2368,114 @@ BOOST_AUTO_TEST_CASE( powTxnGasLimit ) { txPOW2["gasPrice"] = "0xc5002ab03e1e7e196b3d0ffa9801e783fcd48d4c6d972f1389ab63f4e2d0bef0"; // gas 1m txPOW2["value"] = 100; BOOST_REQUIRE_THROW( fixture.rpcClient->eth_sendTransaction( txPOW2 ), jsonrpc::JsonRpcException ); // block gas limit reached - } +} + +BOOST_AUTO_TEST_CASE( revertReason ) { + JsonRpcFixture fixture; + dev::eth::simulateMining( *( fixture.client ), 1000 ); +// // SPDX-License-Identifier: UNLICENSED + +// pragma solidity ^0.8.4; + +// error CustomError(); +// error CustomErrorWithArg(uint256 arg1); +// error CustomErrorWithArgs(uint256 arg1, bytes32 arg2); + +// contract Error { +// function customError() external pure { +// revert CustomError(); +// } + +// function customErrorwithArg(uint256 x) external pure { +// revert CustomErrorWithArg(x); +// } + +// function customErrorwithArgs(uint256 x, bytes32 b) external pure { +// revert CustomErrorWithArgs(x, b); +// } + +// function throwRevertWithoutErrorMessage() external pure { +// revert(); +// } + +// function throwRevertWithErrorMessage() external pure { +// revert("error message"); +// } +// } + std::string bytecode = "608060405234801561001057600080fd5b50610386806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80631ccb2cb41461005c5780633155edc8146100785780634de1fb0f14610094578063dda3a7bd1461009e578063f006fd35146100a8575b600080fd5b610076600480360381019061007191906101f3565b6100b2565b005b610092600480360381019061008d91906101ca565b6100f1565b005b61009c61012e565b005b6100a6610133565b005b6100b0610165565b005b81816040517f0594f0fe0000000000000000000000000000000000000000000000000000000081526004016100e89291906102ab565b60405180910390fd5b806040517fe372fe1e0000000000000000000000000000000000000000000000000000000081526004016101259190610290565b60405180910390fd5b600080fd5b6040517f09caebf300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161019790610270565b60405180910390fd5b6000813590506101af81610322565b92915050565b6000813590506101c481610339565b92915050565b6000602082840312156101dc57600080fd5b60006101ea848285016101b5565b91505092915050565b6000806040838503121561020657600080fd5b6000610214858286016101b5565b9250506020610225858286016101a0565b9150509250929050565b610238816102e5565b82525050565b600061024b600d836102d4565b9150610256826102f9565b602082019050919050565b61026a816102ef565b82525050565b600060208201905081810360008301526102898161023e565b9050919050565b60006020820190506102a56000830184610261565b92915050565b60006040820190506102c06000830185610261565b6102cd602083018461022f565b9392505050565b600082825260208201905092915050565b6000819050919050565b6000819050919050565b7f6572726f72206d65737361676500000000000000000000000000000000000000600082015250565b61032b816102e5565b811461033657600080fd5b50565b610342816102ef565b811461034d57600080fd5b5056fea2646970667358221220dfeb247eec1a9f2092ccf93f00996637cb773621c86b66125e5089370c9ebe2764736f6c63430008040033"; + auto senderAddress = fixture.coinbase.address(); + + Json::Value create; + create["from"] = toJS( senderAddress ); + create["data"] = bytecode; + create["gas"] = "1800000"; + string txHash = fixture.rpcClient->eth_sendTransaction( create ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + + Json::Value receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x1" ) ); + string contractAddress = receipt["contractAddress"].asString(); + + // call customError() + Json::Value txCustomError; + txCustomError["to"] = contractAddress; + txCustomError["data"] = "0xdda3a7bd"; + txCustomError["from"] = senderAddress.hex(); + txCustomError["gasPrice"] = fixture.rpcClient->eth_gasPrice(); + txHash = fixture.rpcClient->eth_sendTransaction( txCustomError ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x0" ) ); + BOOST_REQUIRE( receipt["revertReason"] == "0x09caebf3" ); + + // call customErrorWithArg(1) + Json::Value txCustomErrorWithArg; + txCustomErrorWithArg["to"] = contractAddress; + txCustomErrorWithArg["data"] = "0x3155edc80000000000000000000000000000000000000000000000000000000000000001"; + txCustomErrorWithArg["from"] = senderAddress.hex(); + txCustomErrorWithArg["gasPrice"] = fixture.rpcClient->eth_gasPrice(); + txHash = fixture.rpcClient->eth_sendTransaction( txCustomErrorWithArg ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x0" ) ); + BOOST_REQUIRE( receipt["revertReason"] == "0xe372fe1e0000000000000000000000000000000000000000000000000000000000000001" ); + + // call customErrorWithArgs(1, 0xa449dcaf2bca14e6bd0ac650eed9555008363002b2fc3a4c8422b7a9525a8135) + Json::Value txCustomErrorWithArgs; + txCustomErrorWithArgs["to"] = contractAddress; + txCustomErrorWithArgs["data"] = "0x1ccb2cb40000000000000000000000000000000000000000000000000000000000000001a449dcaf2bca14e6bd0ac650eed9555008363002b2fc3a4c8422b7a9525a8135"; + txCustomErrorWithArgs["from"] = senderAddress.hex(); + txCustomErrorWithArgs["gasPrice"] = fixture.rpcClient->eth_gasPrice(); + txHash = fixture.rpcClient->eth_sendTransaction( txCustomErrorWithArgs ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x0" ) ); + BOOST_REQUIRE( receipt["revertReason"] == "0x0594f0fe0000000000000000000000000000000000000000000000000000000000000001a449dcaf2bca14e6bd0ac650eed9555008363002b2fc3a4c8422b7a9525a8135" ); + + // call to throwRevertWithErrorMessage() + Json::Value txThrowRevertWithErrorMessage; + txThrowRevertWithErrorMessage["to"] = contractAddress; + txThrowRevertWithErrorMessage["data"] = "0xf006fd35"; + txThrowRevertWithErrorMessage["from"] = senderAddress.hex(); + txThrowRevertWithErrorMessage["gasPrice"] = fixture.rpcClient->eth_gasPrice(); + txHash = fixture.rpcClient->eth_sendTransaction( txThrowRevertWithErrorMessage ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x0" ) ); + BOOST_REQUIRE( receipt["revertReason"] == "error message" ); + + // call to throwRevertWithoutErrorMessage() + Json::Value txThrowRevertWithoutErrorMessage; + txThrowRevertWithoutErrorMessage["to"] = contractAddress; + txThrowRevertWithoutErrorMessage["data"] = "0x4de1fb0f"; + txThrowRevertWithoutErrorMessage["from"] = senderAddress.hex(); + txThrowRevertWithoutErrorMessage["gasPrice"] = fixture.rpcClient->eth_gasPrice(); + txHash = fixture.rpcClient->eth_sendTransaction( txThrowRevertWithoutErrorMessage ); + dev::eth::mineTransaction( *( fixture.client ), 1 ); + receipt = fixture.rpcClient->eth_getTransactionReceipt( txHash ); + BOOST_REQUIRE( receipt["status"] == string( "0x0" ) ); + BOOST_REQUIRE( receipt["revertReason"] == "EVM revert instruction without description message" ); +} BOOST_AUTO_TEST_CASE( EIP1898Calls ) { JsonRpcFixture fixture; From 589dfb1251100b495a9742de39454382c58db6f8 Mon Sep 17 00:00:00 2001 From: Oleh Nikolaiev Date: Fri, 8 Dec 2023 16:16:59 +0000 Subject: [PATCH 2/4] #1744 remove commented code --- libskale/State.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libskale/State.cpp b/libskale/State.cpp index 317959f13..230543a61 100644 --- a/libskale/State.cpp +++ b/libskale/State.cpp @@ -1030,11 +1030,6 @@ std::pair< ExecutionResult, TransactionReceipt > State::execute( EnvInfo const& std::string strRevertReason; if ( res.excepted == dev::eth::TransactionException::RevertInstruction ) { - // if ( res.output.size() == 4 ) { - // strRevertReason = dev::toHex( res.output ); - // } else { - // strRevertReason = skutils::eth::call_error_message_2_str( res.output ); - // } strRevertReason = dev::errorMessageToString( res.output ); if ( strRevertReason.empty() ) strRevertReason = "EVM revert instruction without description message"; From 747049f89c6f197adc9c6944d79e66bf1883bde6 Mon Sep 17 00:00:00 2001 From: Oleh Nikolaiev Date: Fri, 8 Dec 2023 16:20:01 +0000 Subject: [PATCH 3/4] #1744 remove extra includes --- libdevcore/CommonData.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index b0e3a52f6..1e7f376a0 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -24,7 +24,6 @@ #include "CommonData.h" #include "Exceptions.h" -#include #include using namespace std; From 9b7b4ebbe5c9616f3287fd136ed077f348463ee7 Mon Sep 17 00:00:00 2001 From: Oleh Nikolaiev Date: Fri, 8 Dec 2023 16:25:25 +0000 Subject: [PATCH 4/4] remove debug output --- libdevcore/CommonData.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 1e7f376a0..6d9d94aab 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -118,7 +118,6 @@ std::string dev::toString( string32 const& _s ) { } std::string dev::customErrorMessageToString( const bytes& _b ) { - std::cout << toHex( _b ) << '\n'; return toHexPrefixed( _b ); }