Skip to content

Commit

Permalink
Merge pull request #24 from m-Peter/eth-call-endpoint
Browse files Browse the repository at this point in the history
Implement the `eth_call` JSON-RPC endpoint
  • Loading branch information
m-Peter authored Jan 31, 2024
2 parents 3e5f8ab + 1b20bb5 commit cb7c919
Show file tree
Hide file tree
Showing 13 changed files with 1,017 additions and 27 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ check-tidy:
go mod tidy
git diff --exit-code

.PHONY: generate
generate:
go get -d github.com/vektra/mockery/[email protected]
mockery --name=FlowAccessClient --dir=api/mocksiface --structname=MockAccessClient --output=api/mocks

.PHONY: ci
ci: check-tidy test
71 changes: 63 additions & 8 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"bytes"
"context"
_ "embed"
"encoding/hex"
"fmt"
"math/big"
Expand All @@ -15,7 +16,9 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/onflow/cadence"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go-sdk/access"
)

const EthNamespace = "eth"
Expand All @@ -27,24 +30,33 @@ var (
FlowEVMMainnetChainID = big.NewInt(777)
)

func SupportedAPIs(config *Config, store *storage.Store) []rpc.API {
//go:embed cadence/scripts/bridged_account_call.cdc
var bridgedAccountCall []byte

func SupportedAPIs(blockChainAPI *BlockChainAPI) []rpc.API {
return []rpc.API{
{
Namespace: EthNamespace,
Service: NewBlockChainAPI(config, store),
Service: blockChainAPI,
},
}
}

type BlockChainAPI struct {
config *Config
Store *storage.Store
config *Config
Store *storage.Store
FlowClient access.Client
}

func NewBlockChainAPI(config *Config, store *storage.Store) *BlockChainAPI {
func NewBlockChainAPI(
config *Config,
store *storage.Store,
flowClient access.Client,
) *BlockChainAPI {
return &BlockChainAPI{
config: config,
Store: store,
config: config,
Store: store,
FlowClient: flowClient,
}
}

Expand Down Expand Up @@ -665,7 +677,50 @@ func (s *BlockChainAPI) Call(
overrides *StateOverride,
blockOverrides *BlockOverrides,
) (hexutil.Bytes, error) {
return hexutil.Bytes{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, nil
decodedTx, err := hex.DecodeString(args.Input.String()[2:])
if err != nil {
return hexutil.Bytes{}, err
}
cdcBytes := make([]cadence.Value, 0)
for _, bt := range decodedTx {
cdcBytes = append(cdcBytes, cadence.UInt8(bt))
}
encodedTx := cadence.NewArray(
cdcBytes,
).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type))

decodedTo, err := hex.DecodeString(args.To.String()[2:])
if err != nil {
return hexutil.Bytes{}, err
}
toBytes := make([]cadence.Value, 0)
for _, bt := range decodedTo {
toBytes = append(toBytes, cadence.UInt8(bt))
}
encodedTo := cadence.NewArray(
toBytes,
).WithType(cadence.NewConstantSizedArrayType(20, cadence.TheUInt8Type))

value, err := s.FlowClient.ExecuteScriptAtLatestBlock(
ctx,
bridgedAccountCall,
[]cadence.Value{encodedTx, encodedTo},
)
if err != nil {
return hexutil.Bytes{}, err
}

cadenceArray, ok := value.(cadence.Array)
if !ok {
return hexutil.Bytes{}, fmt.Errorf("script doesn't return byte array as it should")
}

resultValue := make([]byte, len(cadenceArray.Values))
for i, x := range cadenceArray.Values {
resultValue[i] = x.ToGoValue().(byte)
}

return hexutil.Bytes(resultValue), nil
}

// eth_estimateGas (usually runs the call and checks how much gas might be used)
Expand Down
51 changes: 39 additions & 12 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import (
"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime/stdlib"
"github.com/onflow/flow-evm-gateway/api"
"github.com/onflow/flow-evm-gateway/api/mocks"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go-sdk/access/grpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

Expand All @@ -36,7 +39,9 @@ func TestBlockChainAPI(t *testing.T) {
ChainID: api.FlowEVMTestnetChainID,
Coinbase: common.HexToAddress("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb"),
}
blockchainAPI := api.NewBlockChainAPI(config, store)
flowClient, err := api.NewFlowClient(grpc.EmulatorHost)
require.NoError(t, err)
blockchainAPI := api.NewBlockChainAPI(config, store, flowClient)

t.Run("ChainId", func(t *testing.T) {
chainID := blockchainAPI.ChainId()
Expand Down Expand Up @@ -704,26 +709,48 @@ func TestBlockChainAPI(t *testing.T) {
})

t.Run("Call", func(t *testing.T) {
key1, _ := crypto.GenerateKey()
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
from := Account{key: key1, addr: addr1}
key2, _ := crypto.GenerateKey()
addr2 := crypto.PubkeyToAddress(key1.PublicKey)
to := Account{key: key2, addr: addr2}
result, err := blockchainAPI.Call(
mockFlowClient := new(mocks.MockAccessClient)
blockchainAPI = api.NewBlockChainAPI(config, store, mockFlowClient)

from := common.HexToAddress("0x658bdf435d810c91414ec09147daa6db62406379")
to := common.HexToAddress("0x99466ed2e37b892a2ee3e9cd55a98b68f5735db2")
gas := hexutil.Uint64(21500)
gasPrice := hexutil.Big(*big.NewInt(1350000))
value := hexutil.Big(*big.NewInt(0))
input := hexutil.Bytes("0xc6888fa10000000000000000000000000000000000000000000000000000000000000006")

result, err := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000002a")
require.NoError(t, err)
toBytes := make([]cadence.Value, 0)
for _, bt := range result {
toBytes = append(toBytes, cadence.UInt8(bt))
}
returnValue := cadence.NewArray(
toBytes,
).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type))
mockFlowClient.On("ExecuteScriptAtLatestBlock", mock.Anything, mock.Anything, mock.Anything).Return(returnValue, nil)

returnedValue, err := blockchainAPI.Call(
context.Background(),
api.TransactionArgs{
From: &from.addr,
To: &to.addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
From: &from,
To: &to,
Gas: &gas,
GasPrice: &gasPrice,
Value: &value,
Input: &input,
},
nil,
nil,
nil,
)
require.NoError(t, err)

assert.Equal(t, hexutil.Bytes{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, result)
assert.Equal(
t,
hexutil.Bytes{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a},
returnedValue,
)
})

t.Run("EstimateGas", func(t *testing.T) {
Expand Down
20 changes: 20 additions & 0 deletions api/cadence/scripts/bridged_account_call.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// TODO(m-Peter): Use proper address for each network
import EVM from 0xf8d6e0586b0a20c7

access(all)
fun main(data: [UInt8], contractAddress: [UInt8; 20]): [UInt8] {
// TODO(m-Peter): Pass Flow address of bridged account as script argument
let account = getAuthAccount<auth(Storage) &Account>(Address(0xf8d6e0586b0a20c7))
let bridgedAccount = account.storage.borrow<&EVM.BridgedAccount>(
from: /storage/evm
) ?? panic("Could not borrow reference to the bridged account!")

let evmResult = bridgedAccount.call(
to: EVM.EVMAddress(bytes: contractAddress),
data: data,
gasLimit: 300000, // TODO(m-Peter): Maybe also pass this as script argument
value: EVM.Balance(flow: 0.0)
)

return evmResult
}
1 change: 0 additions & 1 deletion api/fixtures/eth_json_rpc_requests.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
{"jsonrpc":"2.0","id":1,"method":"eth_newBlockFilter","params":[]}
{"jsonrpc":"2.0","id":1,"method":"eth_newPendingTransactionFilter","params":[]}
{"jsonrpc":"2.0","id":1,"method":"eth_accounts","params":[]}
{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","input":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}]}
{"jsonrpc":"2.0","id":1,"method":"eth_estimateGas","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","input":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}]}
{"jsonrpc":"2.0","id":1,"method":"eth_getUncleByBlockHashAndIndex","params":["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "0x45"]}
{"jsonrpc":"2.0","id":1,"method":"eth_getUncleByBlockNumberAndIndex","params":["0xe8", "0x45"]}
Expand Down
1 change: 0 additions & 1 deletion api/fixtures/eth_json_rpc_responses.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
{"jsonrpc":"2.0","id":1,"result":"block_filter"}
{"jsonrpc":"2.0","id":1,"result":"pending_tx_filter"}
{"jsonrpc":"2.0","id":1,"result":[]}
{"jsonrpc":"2.0","id":1,"result":"0x00010203040506070809"}
{"jsonrpc":"2.0","id":1,"result":"0x69"}
{"jsonrpc":"2.0","id":1,"result":{}}
{"jsonrpc":"2.0","id":1,"result":{}}
Expand Down
15 changes: 15 additions & 0 deletions api/flow_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package api

import (
"github.com/onflow/flow-go-sdk/access"
"github.com/onflow/flow-go-sdk/access/grpc"
)

func NewFlowClient(host string) (access.Client, error) {
client, err := grpc.NewClient(host)
if err != nil {
return nil, err
}

return client, nil
}
Loading

0 comments on commit cb7c919

Please sign in to comment.