diff --git a/standaloneproxy/proxy.go b/standaloneproxy/proxy.go index 75183c9..f92d761 100644 --- a/standaloneproxy/proxy.go +++ b/standaloneproxy/proxy.go @@ -10,6 +10,7 @@ import ( "io" "net" "os" + "strconv" "sync" "syscall" "time" @@ -91,9 +92,6 @@ func (l *StandaloneProxy) Pre(ctx context.Context, name string, _ *endpoint.Endp return ctx, true, nil case "eth_estimateGas": - if len(args) != 2 { - return ctx, true, errors.New("invalid params") - } tx, ok := args[0].(engine.TransactionForCall) if !ok { return ctx, true, errors.New("invalid params") @@ -103,7 +101,8 @@ func (l *StandaloneProxy) Pre(ctx context.Context, name string, _ *endpoint.Endp return ctx, true, errors.New("invalid params") } if blockNumberOrHash == nil { - return ctx, true, errors.New("string 'latest', 'earliest' or integer block number is required") + latest := common.LatestBlockNumber + blockNumberOrHash = &common.BlockNumberOrHash{BlockNumber: &latest} } res, err := l.client.EstimateGas(tx, blockNumberOrHash.BlockNumber) if err != nil { @@ -195,41 +194,61 @@ func (rc *rpcClient) TraceTransaction(hash common.H256) (*response.CallFrame, er } func (rc *rpcClient) EstimateGas(tx engine.TransactionForCall, number *common.BN64) (*common.Uint256, error) { - req, err := buildRequest("eth_estimateGas", tx, number) + var blockParam interface{} = number + switch *number { + case common.EarliestBlockNumber: + blockParam = "earliest" + case common.LatestBlockNumber: + blockParam = "latest" + case common.PendingBlockNumber: + blockParam = "pending" + } + + req, err := buildRequest("eth_estimateGas", tx, blockParam) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to build request: %w", err) } res, err := rc.request(req) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to send request: %w", err) } + return parseEstimateGasResponse(res) +} + +func parseEstimateGasResponse(res []byte) (*common.Uint256, error) { result, resultType, _, err := jsonparser.Get(res, "result") - if err != nil { - return nil, err - } - switch resultType { - case jsonparser.NotExist: - rpcErr, rpcErrType, _, err := jsonparser.Get(res, "error", "message") - switch { - case err != nil: - return nil, err - case rpcErrType == jsonparser.NotExist: - return nil, errors.New("internal rpc error") - default: - return nil, fmt.Errorf("%s", rpcErr) + if err == nil && resultType == jsonparser.Number { + val, err := strconv.ParseInt(string(result), 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse result as integer: %w", err) } + hexStr := fmt.Sprintf("0x%x", val) - case jsonparser.String: resp := new(common.Uint256) - err := json.Unmarshal(result, resp) - return resp, err + if err := resp.UnmarshalJSON([]byte(hexStr)); err != nil { + return nil, fmt.Errorf("failed to unmarshal result: %w", err) + } + return resp, nil + } - default: - return nil, errors.New("failed to parse unexpected response") + return handleEstimateGasError(res) +} + +func handleEstimateGasError(res []byte) (*common.Uint256, error) { + rpcErrData, _, _, rpcErrDataParseErr := jsonparser.Get(res, "error", "data") + if rpcErrDataParseErr == nil && len(rpcErrData) > 0 { + return nil, fmt.Errorf("engine error: %s", rpcErrData) } + + rpcErrMsg, _, _, rpcErrMsgParseErr := jsonparser.Get(res, "error", "message") + if rpcErrMsgParseErr == nil && len(rpcErrMsg) > 0 { + return nil, fmt.Errorf("engine error: %s", rpcErrMsg) + } + + return nil, errors.New("engine error: unknown error occurred") } func (rc *rpcClient) reconnect() error { diff --git a/standaloneproxy/proxy_test.go b/standaloneproxy/proxy_test.go index fc96e70..62e4124 100644 --- a/standaloneproxy/proxy_test.go +++ b/standaloneproxy/proxy_test.go @@ -1,8 +1,10 @@ package standaloneproxy import ( + "errors" "testing" + "github.com/aurora-is-near/relayer2-base/types/common" "github.com/aurora-is-near/relayer2-base/types/primitives" "github.com/stretchr/testify/require" ) @@ -44,3 +46,86 @@ func TestBuildRequest(t *testing.T) { }) } } + +func TestParseEstimateGasResponseSuccess(t *testing.T) { + testCases := []struct { + name string + resp []byte + want common.Uint256 + }{ + { + name: "success", + resp: []byte(`{"id":1,"jsonrpc":"2.0","result":21000}`), + want: common.IntToUint256(21000), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseEstimateGasResponse(tc.resp) + require.NoError(t, err) + require.EqualValues(t, tc.want, *got) + }) + } +} + +func TestParseEstimateGasResponseError(t *testing.T) { + testCases := []struct { + name string + resp []byte + expectedError error + }{ + { + name: "Error with message", + resp: []byte(`{"id":1,"jsonrpc":"2.0","error":{"code":-32000,"message":"Internal error"}}`), + expectedError: errors.New("engine error: Internal error"), + }, + { + name: "Error with message and data", + resp: []byte(`{"id":1,"jsonrpc":"2.0","error":{"code":-32000,"message":"Internal error", "data": "StateMissing"}}`), + expectedError: errors.New("engine error: StateMissing"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseEstimateGasResponse(tc.resp) + require.Nil(t, got) + require.EqualValues(t, tc.expectedError, err) + }) + } +} + +func TestHandleEstimateGasError(t *testing.T) { + testCases := []struct { + name string + input []byte + expectedError error + }{ + { + name: "Error with message", + input: []byte(`{"error":{"code":-32000,"data":"StateMissing","message":"Internal error"}}`), + expectedError: errors.New("engine error: StateMissing"), + }, + { + name: "Error without data", + input: []byte(`{"error":{"code":-32000,"message":"Internal error"}}`), + expectedError: errors.New("engine error: Internal error"), + }, + { + name: "Broken JSON", + input: []byte(`{"error"}}`), + expectedError: errors.New("engine error: unknown error occurred"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := handleEstimateGasError(tc.input) + + if err == nil || err.Error() != tc.expectedError.Error() { + t.Errorf("Test %s failed. Got: %v, want: %v", tc.name, err, tc.expectedError) + } + }) + } +}