Skip to content

Commit

Permalink
#1744 handle custom errors in revert
Browse files Browse the repository at this point in the history
  • Loading branch information
olehnikolaiev committed Dec 8, 2023
1 parent 1c975d5 commit c5ff090
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 4 deletions.
41 changes: 39 additions & 2 deletions libdevcore/CommonData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
*/

#include "CommonData.h"
#include <random>

#include "Exceptions.h"

#include <arpa/inet.h>
#include <random>

using namespace std;
using namespace dev;

Expand Down Expand Up @@ -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 );
}
8 changes: 8 additions & 0 deletions libdevcore/CommonData.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 6 additions & 1 deletion libskale/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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( " " ) +
Expand Down
109 changes: 108 additions & 1 deletion test/unittests/libweb3jsonrpc/jsonrpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit c5ff090

Please sign in to comment.