Skip to content

Commit

Permalink
Return Revert result as Data (#1566)
Browse files Browse the repository at this point in the history
* Return Revet result as Data

This allows returning custom revert errors from solidity, they are ABI encoded functions that can represent rich and gas efficient revert messages.
  • Loading branch information
vcastellm committed Jun 2, 2023
1 parent 3b827b8 commit 7e66cd5
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 13 deletions.
25 changes: 22 additions & 3 deletions jsonrpc/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ func (e *ObjectError) Error() string {
return string(data)
}

func (e *ObjectError) MarshalJSON() ([]byte, error) {
var ds string

data, ok := e.Data.([]byte)
if ok && len(data) > 0 {
ds = "0x" + string(data)
}

return json.Marshal(&struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data,omitempty"`
}{
Code: e.Code,
Message: e.Message,
Data: ds,
})
}

const (
pending = "pending"
latest = "latest"
Expand Down Expand Up @@ -190,8 +209,8 @@ func (b *BlockNumber) UnmarshalJSON(buffer []byte) error {
}

// NewRPCErrorResponse is used to create a custom error response
func NewRPCErrorResponse(id interface{}, errCode int, err string, jsonrpcver string) Response {
errObject := &ObjectError{errCode, err, nil}
func NewRPCErrorResponse(id interface{}, errCode int, err string, data []byte, jsonrpcver string) Response {
errObject := &ObjectError{errCode, err, data}

response := &ErrorResponse{
JSONRPC: jsonrpcver,
Expand All @@ -209,7 +228,7 @@ func NewRPCResponse(id interface{}, jsonrpcver string, reply []byte, err Error)
case nil:
response = &SuccessResponse{JSONRPC: jsonrpcver, ID: id, Result: reply}
default:
response = NewRPCErrorResponse(id, err.ErrorCode(), err.Error(), jsonrpcver)
response = NewRPCErrorResponse(id, err.ErrorCode(), err.Error(), reply, jsonrpcver)
}

return response
Expand Down
23 changes: 16 additions & 7 deletions jsonrpc/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func (d *Dispatcher) Handle(reqBody []byte) ([]byte, error) {
for _, req := range requests {
var response, err = d.handleReq(req)
if err != nil {
errorResponse := NewRPCResponse(req.ID, "2.0", nil, err)
errorResponse := NewRPCResponse(req.ID, "2.0", response, err)
responses = append(responses, errorResponse)

continue
Expand Down Expand Up @@ -371,17 +371,26 @@ func (d *Dispatcher) handleReq(req Request) ([]byte, Error) {
}
}

var (
data []byte
err error
ok bool
)

output := fd.fv.Call(inArgs)
if err := getError(output[1]); err != nil {
d.logInternalError(req.Method, err)

return nil, NewInvalidRequestError(err.Error())
}
if res := output[0].Interface(); res != nil {
data, ok = res.([]byte)

var (
data []byte
err error
)
if !ok {
return nil, NewInternalError(err.Error())
}
}

return data, NewInvalidRequestError(err.Error())
}

if res := output[0].Interface(); res != nil {
data, err = json.Marshal(res)
Expand Down
34 changes: 33 additions & 1 deletion jsonrpc/eth_blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/0xPolygon/polygon-edge/blockchain"
"github.com/0xPolygon/polygon-edge/helper/hex"
"github.com/0xPolygon/polygon-edge/helper/progress"
"github.com/0xPolygon/polygon-edge/state/runtime"
"github.com/0xPolygon/polygon-edge/types"
Expand Down Expand Up @@ -361,6 +362,33 @@ func TestEth_Call(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, res)
})

t.Run("returns error and result as data of a reverted transaction execution", func(t *testing.T) {
t.Parallel()

returnValue := []byte("Reverted()")

store := newMockBlockStore()
store.add(newTestBlock(100, hash1))
store.ethCallError = runtime.ErrExecutionReverted
store.returnValue = returnValue
eth := newTestEthEndpoint(store)
contractCall := &txnArgs{
From: &addr0,
To: &addr1,
Gas: argUintPtr(100000),
GasPrice: argBytesPtr([]byte{0x64}),
Value: argBytesPtr([]byte{0x64}),
Data: nil,
Nonce: argUintPtr(0),
}

res, err := eth.Call(contractCall, BlockNumberOrHash{}, nil)
assert.Error(t, err)
assert.NotNil(t, res)
bres := res.([]byte) //nolint:forcetypeassert
assert.Equal(t, []byte(hex.EncodeToString(returnValue)), bres)
})
}

type testStore interface {
Expand All @@ -376,6 +404,7 @@ type mockBlockStore struct {
isSyncing bool
averageGasPrice int64
ethCallError error
returnValue []byte
}

func newMockBlockStore() *mockBlockStore {
Expand Down Expand Up @@ -546,7 +575,10 @@ func (m *mockBlockStore) GetAvgGasPrice() *big.Int {
}

func (m *mockBlockStore) ApplyTxn(header *types.Header, txn *types.Transaction, overrides types.StateOverride) (*runtime.ExecutionResult, error) {
return &runtime.ExecutionResult{Err: m.ethCallError}, nil
return &runtime.ExecutionResult{
Err: m.ethCallError,
ReturnValue: m.returnValue,
}, nil
}

func (m *mockBlockStore) SubscribeEvents() blockchain.Subscription {
Expand Down
3 changes: 2 additions & 1 deletion jsonrpc/eth_endpoint.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jsonrpc

import (
"encoding/hex"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -475,7 +476,7 @@ func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *stateOve

// Check if an EVM revert happened
if result.Reverted() {
return nil, constructErrorFromRevert(result)
return []byte(hex.EncodeToString(result.ReturnValue)), constructErrorFromRevert(result)
}

if result.Failed() {
Expand Down
2 changes: 1 addition & 1 deletion tests/tests
Submodule tests updated from 52cb3b to 7497b1

0 comments on commit 7e66cd5

Please sign in to comment.