diff --git a/Makefile b/Makefile index 91536c46..415643d7 100644 --- a/Makefile +++ b/Makefile @@ -8,5 +8,10 @@ check-tidy: go mod tidy git diff --exit-code +.PHONY: generate +generate: + go get -d github.com/vektra/mockery/v2@v2.21.4 + mockery --name=FlowAccessClient --dir=api/mocksiface --structname=MockAccessClient --output=api/mocks + .PHONY: ci ci: check-tidy test diff --git a/api/api.go b/api/api.go index 780237b5..9b51d28d 100644 --- a/api/api.go +++ b/api/api.go @@ -3,6 +3,7 @@ package api import ( "bytes" "context" + _ "embed" "encoding/hex" "fmt" "math/big" @@ -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" @@ -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, } } @@ -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) diff --git a/api/api_test.go b/api/api_test.go index 354b6fef..10474d2e 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -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" ) @@ -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() @@ -704,18 +709,36 @@ 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, @@ -723,7 +746,11 @@ func TestBlockChainAPI(t *testing.T) { ) 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) { diff --git a/api/cadence/scripts/bridged_account_call.cdc b/api/cadence/scripts/bridged_account_call.cdc new file mode 100644 index 00000000..67bd5261 --- /dev/null +++ b/api/cadence/scripts/bridged_account_call.cdc @@ -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(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 +} diff --git a/api/fixtures/eth_json_rpc_requests.json b/api/fixtures/eth_json_rpc_requests.json index 3a5ed297..68c14b42 100644 --- a/api/fixtures/eth_json_rpc_requests.json +++ b/api/fixtures/eth_json_rpc_requests.json @@ -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"]} diff --git a/api/fixtures/eth_json_rpc_responses.json b/api/fixtures/eth_json_rpc_responses.json index b3220ac6..018566cd 100644 --- a/api/fixtures/eth_json_rpc_responses.json +++ b/api/fixtures/eth_json_rpc_responses.json @@ -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":{}} diff --git a/api/flow_client.go b/api/flow_client.go new file mode 100644 index 00000000..f60cd4c7 --- /dev/null +++ b/api/flow_client.go @@ -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 +} diff --git a/api/mocks/FlowAccessClient.go b/api/mocks/FlowAccessClient.go new file mode 100644 index 00000000..ec62e0b1 --- /dev/null +++ b/api/mocks/FlowAccessClient.go @@ -0,0 +1,813 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mocks + +import ( + context "context" + + cadence "github.com/onflow/cadence" + + flow "github.com/onflow/flow-go-sdk" + + mock "github.com/stretchr/testify/mock" +) + +// MockAccessClient is an autogenerated mock type for the FlowAccessClient type +type MockAccessClient struct { + mock.Mock +} + +// Close provides a mock function with given fields: +func (_m *MockAccessClient) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ExecuteScriptAtBlockHeight provides a mock function with given fields: ctx, height, script, arguments +func (_m *MockAccessClient) ExecuteScriptAtBlockHeight(ctx context.Context, height uint64, script []byte, arguments []cadence.Value) (cadence.Value, error) { + ret := _m.Called(ctx, height, script, arguments) + + var r0 cadence.Value + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, []cadence.Value) (cadence.Value, error)); ok { + return rf(ctx, height, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, []cadence.Value) cadence.Value); ok { + r0 = rf(ctx, height, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cadence.Value) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []byte, []cadence.Value) error); ok { + r1 = rf(ctx, height, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteScriptAtBlockID provides a mock function with given fields: ctx, blockID, script, arguments +func (_m *MockAccessClient) ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments []cadence.Value) (cadence.Value, error) { + ret := _m.Called(ctx, blockID, script, arguments) + + var r0 cadence.Value + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, []cadence.Value) (cadence.Value, error)); ok { + return rf(ctx, blockID, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, []cadence.Value) cadence.Value); ok { + r0 = rf(ctx, blockID, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cadence.Value) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, []byte, []cadence.Value) error); ok { + r1 = rf(ctx, blockID, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteScriptAtLatestBlock provides a mock function with given fields: ctx, script, arguments +func (_m *MockAccessClient) ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments []cadence.Value) (cadence.Value, error) { + ret := _m.Called(ctx, script, arguments) + + var r0 cadence.Value + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, []cadence.Value) (cadence.Value, error)); ok { + return rf(ctx, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte, []cadence.Value) cadence.Value); ok { + r0 = rf(ctx, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(cadence.Value) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte, []cadence.Value) error); ok { + r1 = rf(ctx, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAccount provides a mock function with given fields: ctx, address +func (_m *MockAccessClient) GetAccount(ctx context.Context, address flow.Address) (*flow.Account, error) { + ret := _m.Called(ctx, address) + + var r0 *flow.Account + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Address) (*flow.Account, error)); ok { + return rf(ctx, address) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Address) *flow.Account); ok { + r0 = rf(ctx, address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Account) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Address) error); ok { + r1 = rf(ctx, address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAccountAtBlockHeight provides a mock function with given fields: ctx, address, blockHeight +func (_m *MockAccessClient) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, blockHeight uint64) (*flow.Account, error) { + ret := _m.Called(ctx, address, blockHeight) + + var r0 *flow.Account + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) (*flow.Account, error)); ok { + return rf(ctx, address, blockHeight) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) *flow.Account); ok { + r0 = rf(ctx, address, blockHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Account) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Address, uint64) error); ok { + r1 = rf(ctx, address, blockHeight) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAccountAtLatestBlock provides a mock function with given fields: ctx, address +func (_m *MockAccessClient) GetAccountAtLatestBlock(ctx context.Context, address flow.Address) (*flow.Account, error) { + ret := _m.Called(ctx, address) + + var r0 *flow.Account + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Address) (*flow.Account, error)); ok { + return rf(ctx, address) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Address) *flow.Account); ok { + r0 = rf(ctx, address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Account) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Address) error); ok { + r1 = rf(ctx, address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockByHeight provides a mock function with given fields: ctx, height +func (_m *MockAccessClient) GetBlockByHeight(ctx context.Context, height uint64) (*flow.Block, error) { + ret := _m.Called(ctx, height) + + var r0 *flow.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (*flow.Block, error)); ok { + return rf(ctx, height) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) *flow.Block); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockByID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetBlockByID(ctx context.Context, blockID flow.Identifier) (*flow.Block, error) { + ret := _m.Called(ctx, blockID) + + var r0 *flow.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.Block, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.Block); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockHeaderByHeight provides a mock function with given fields: ctx, height +func (_m *MockAccessClient) GetBlockHeaderByHeight(ctx context.Context, height uint64) (*flow.BlockHeader, error) { + ret := _m.Called(ctx, height) + + var r0 *flow.BlockHeader + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (*flow.BlockHeader, error)); ok { + return rf(ctx, height) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) *flow.BlockHeader); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.BlockHeader) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockHeaderByID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetBlockHeaderByID(ctx context.Context, blockID flow.Identifier) (*flow.BlockHeader, error) { + ret := _m.Called(ctx, blockID) + + var r0 *flow.BlockHeader + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.BlockHeader, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.BlockHeader); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.BlockHeader) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetCollection provides a mock function with given fields: ctx, colID +func (_m *MockAccessClient) GetCollection(ctx context.Context, colID flow.Identifier) (*flow.Collection, error) { + ret := _m.Called(ctx, colID) + + var r0 *flow.Collection + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.Collection, error)); ok { + return rf(ctx, colID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.Collection); ok { + r0 = rf(ctx, colID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Collection) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, colID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEventsForBlockIDs provides a mock function with given fields: ctx, eventType, blockIDs +func (_m *MockAccessClient) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) { + ret := _m.Called(ctx, eventType, blockIDs) + + var r0 []flow.BlockEvents + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) ([]flow.BlockEvents, error)); ok { + return rf(ctx, eventType, blockIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) []flow.BlockEvents); ok { + r0 = rf(ctx, eventType, blockIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []flow.Identifier) error); ok { + r1 = rf(ctx, eventType, blockIDs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEventsForHeightRange provides a mock function with given fields: ctx, eventType, startHeight, endHeight +func (_m *MockAccessClient) GetEventsForHeightRange(ctx context.Context, eventType string, startHeight uint64, endHeight uint64) ([]flow.BlockEvents, error) { + ret := _m.Called(ctx, eventType, startHeight, endHeight) + + var r0 []flow.BlockEvents + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) ([]flow.BlockEvents, error)); ok { + return rf(ctx, eventType, startHeight, endHeight) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) []flow.BlockEvents); ok { + r0 = rf(ctx, eventType, startHeight, endHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, eventType, startHeight, endHeight) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionDataByBlockID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetExecutionDataByBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionData, error) { + ret := _m.Called(ctx, blockID) + + var r0 *flow.ExecutionData + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionData, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionData); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.ExecutionData) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionResultForBlockID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) { + ret := _m.Called(ctx, blockID) + + var r0 *flow.ExecutionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionResult, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionResult); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.ExecutionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestBlock provides a mock function with given fields: ctx, isSealed +func (_m *MockAccessClient) GetLatestBlock(ctx context.Context, isSealed bool) (*flow.Block, error) { + ret := _m.Called(ctx, isSealed) + + var r0 *flow.Block + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.Block, error)); ok { + return rf(ctx, isSealed) + } + if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.Block); ok { + r0 = rf(ctx, isSealed) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, bool) error); ok { + r1 = rf(ctx, isSealed) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestBlockHeader provides a mock function with given fields: ctx, isSealed +func (_m *MockAccessClient) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.BlockHeader, error) { + ret := _m.Called(ctx, isSealed) + + var r0 *flow.BlockHeader + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.BlockHeader, error)); ok { + return rf(ctx, isSealed) + } + if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.BlockHeader); ok { + r0 = rf(ctx, isSealed) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.BlockHeader) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, bool) error); ok { + r1 = rf(ctx, isSealed) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestProtocolStateSnapshot provides a mock function with given fields: ctx +func (_m *MockAccessClient) GetLatestProtocolStateSnapshot(ctx context.Context) ([]byte, error) { + ret := _m.Called(ctx) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []byte); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNetworkParameters provides a mock function with given fields: ctx +func (_m *MockAccessClient) GetNetworkParameters(ctx context.Context) (*flow.NetworkParameters, error) { + ret := _m.Called(ctx) + + var r0 *flow.NetworkParameters + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*flow.NetworkParameters, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *flow.NetworkParameters); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.NetworkParameters) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransaction provides a mock function with given fields: ctx, txID +func (_m *MockAccessClient) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.Transaction, error) { + ret := _m.Called(ctx, txID) + + var r0 *flow.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.Transaction, error)); ok { + return rf(ctx, txID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.Transaction); ok { + r0 = rf(ctx, txID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, txID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionResult provides a mock function with given fields: ctx, txID +func (_m *MockAccessClient) GetTransactionResult(ctx context.Context, txID flow.Identifier) (*flow.TransactionResult, error) { + ret := _m.Called(ctx, txID) + + var r0 *flow.TransactionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.TransactionResult, error)); ok { + return rf(ctx, txID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.TransactionResult); ok { + r0 = rf(ctx, txID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.TransactionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, txID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionResultsByBlockID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetTransactionResultsByBlockID(ctx context.Context, blockID flow.Identifier) ([]*flow.TransactionResult, error) { + ret := _m.Called(ctx, blockID) + + var r0 []*flow.TransactionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) ([]*flow.TransactionResult, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) []*flow.TransactionResult); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*flow.TransactionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionsByBlockID provides a mock function with given fields: ctx, blockID +func (_m *MockAccessClient) GetTransactionsByBlockID(ctx context.Context, blockID flow.Identifier) ([]*flow.Transaction, error) { + ret := _m.Called(ctx, blockID) + + var r0 []*flow.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) ([]*flow.Transaction, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) []*flow.Transaction); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*flow.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Ping provides a mock function with given fields: ctx +func (_m *MockAccessClient) Ping(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *MockAccessClient) SendTransaction(ctx context.Context, tx flow.Transaction) error { + ret := _m.Called(ctx, tx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Transaction) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SubscribeEventsByBlockHeight provides a mock function with given fields: ctx, startHeight, filter +func (_m *MockAccessClient) SubscribeEventsByBlockHeight(ctx context.Context, startHeight uint64, filter flow.EventFilter) (<-chan flow.BlockEvents, <-chan error, error) { + ret := _m.Called(ctx, startHeight, filter) + + var r0 <-chan flow.BlockEvents + var r1 <-chan error + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, flow.EventFilter) (<-chan flow.BlockEvents, <-chan error, error)); ok { + return rf(ctx, startHeight, filter) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, flow.EventFilter) <-chan flow.BlockEvents); ok { + r0 = rf(ctx, startHeight, filter) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, flow.EventFilter) <-chan error); ok { + r1 = rf(ctx, startHeight, filter) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, flow.EventFilter) error); ok { + r2 = rf(ctx, startHeight, filter) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// SubscribeEventsByBlockID provides a mock function with given fields: ctx, startBlockID, filter +func (_m *MockAccessClient) SubscribeEventsByBlockID(ctx context.Context, startBlockID flow.Identifier, filter flow.EventFilter) (<-chan flow.BlockEvents, <-chan error, error) { + ret := _m.Called(ctx, startBlockID, filter) + + var r0 <-chan flow.BlockEvents + var r1 <-chan error + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.EventFilter) (<-chan flow.BlockEvents, <-chan error, error)); ok { + return rf(ctx, startBlockID, filter) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.EventFilter) <-chan flow.BlockEvents); ok { + r0 = rf(ctx, startBlockID, filter) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, flow.EventFilter) <-chan error); ok { + r1 = rf(ctx, startBlockID, filter) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, flow.Identifier, flow.EventFilter) error); ok { + r2 = rf(ctx, startBlockID, filter) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// SubscribeExecutionDataByBlockHeight provides a mock function with given fields: ctx, startHeight +func (_m *MockAccessClient) SubscribeExecutionDataByBlockHeight(ctx context.Context, startHeight uint64) (<-chan flow.ExecutionDataStreamResponse, <-chan error, error) { + ret := _m.Called(ctx, startHeight) + + var r0 <-chan flow.ExecutionDataStreamResponse + var r1 <-chan error + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (<-chan flow.ExecutionDataStreamResponse, <-chan error, error)); ok { + return rf(ctx, startHeight) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) <-chan flow.ExecutionDataStreamResponse); ok { + r0 = rf(ctx, startHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan flow.ExecutionDataStreamResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) <-chan error); ok { + r1 = rf(ctx, startHeight) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64) error); ok { + r2 = rf(ctx, startHeight) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// SubscribeExecutionDataByBlockID provides a mock function with given fields: ctx, startBlockID +func (_m *MockAccessClient) SubscribeExecutionDataByBlockID(ctx context.Context, startBlockID flow.Identifier) (<-chan flow.ExecutionDataStreamResponse, <-chan error, error) { + ret := _m.Called(ctx, startBlockID) + + var r0 <-chan flow.ExecutionDataStreamResponse + var r1 <-chan error + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (<-chan flow.ExecutionDataStreamResponse, <-chan error, error)); ok { + return rf(ctx, startBlockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) <-chan flow.ExecutionDataStreamResponse); ok { + r0 = rf(ctx, startBlockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan flow.ExecutionDataStreamResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) <-chan error); ok { + r1 = rf(ctx, startBlockID) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, flow.Identifier) error); ok { + r2 = rf(ctx, startBlockID) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type mockConstructorTestingTNewMockAccessClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockAccessClient creates a new instance of MockAccessClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockAccessClient(t mockConstructorTestingTNewMockAccessClient) *MockAccessClient { + mock := &MockAccessClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/api/mocksiface/mocks.go b/api/mocksiface/mocks.go new file mode 100644 index 00000000..a2fc9677 --- /dev/null +++ b/api/mocksiface/mocks.go @@ -0,0 +1,8 @@ +package mocksiface + +import "github.com/onflow/flow-go-sdk/access" + +// This is a proxy for the real access.Client for mockery to use. +type FlowAccessClient interface { + access.Client +} diff --git a/api/server_test.go b/api/server_test.go index a4f96790..0bf62ef5 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" _ "embed" + "encoding/hex" "io" "net/http" "strings" @@ -12,10 +13,14 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" + "github.com/onflow/cadence" "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/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -25,6 +30,8 @@ var requests string //go:embed fixtures/eth_json_rpc_responses.json var responses string +var mockFlowClient = new(mocks.MockAccessClient) + func TestServerJSONRPCOveHTTPHandler(t *testing.T) { store := storage.NewStore() srv := api.NewHTTPServer(zerolog.Logger{}, rpc.DefaultHTTPTimeouts) @@ -32,7 +39,8 @@ func TestServerJSONRPCOveHTTPHandler(t *testing.T) { ChainID: api.FlowEVMTestnetChainID, Coinbase: common.HexToAddress("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb"), } - supportedAPIs := api.SupportedAPIs(config, store) + blockchainAPI := api.NewBlockChainAPI(config, store, mockFlowClient) + supportedAPIs := api.SupportedAPIs(blockchainAPI) srv.EnableRPC(supportedAPIs) srv.SetListenAddr("localhost", 8545) err := srv.Start() @@ -111,6 +119,34 @@ func TestServerJSONRPCOveHTTPHandler(t *testing.T) { assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n")) }) + + t.Run("eth_call", func(t *testing.T) { + request := `{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","input":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}]}` + expectedResponse := `{"jsonrpc":"2.0","id":1,"result":"0x000000000000000000000000000000000000000000000000000000000000002a"}` + + 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) + + blockchainAPI = api.NewBlockChainAPI(config, store, mockFlowClient) + + resp := rpcRequest(url, request, "origin", "test.com") + defer resp.Body.Close() + + content, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n")) + }) } func TestServerJSONRPCOveWebSocketHandler(t *testing.T) { @@ -120,10 +156,13 @@ func TestServerJSONRPCOveWebSocketHandler(t *testing.T) { ChainID: api.FlowEVMTestnetChainID, Coinbase: common.HexToAddress("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb"), } - supportedAPIs := api.SupportedAPIs(config, store) + flowClient, err := api.NewFlowClient(grpc.EmulatorHost) + require.NoError(t, err) + blockchainAPI := api.NewBlockChainAPI(config, store, flowClient) + supportedAPIs := api.SupportedAPIs(blockchainAPI) srv.EnableWS(supportedAPIs) srv.SetListenAddr("localhost", 8545) - err := srv.Start() + err = srv.Start() defer srv.Stop() if err != nil { panic(err) diff --git a/cmd/server/main.go b/cmd/server/main.go index a9236045..6b105995 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -167,14 +167,19 @@ func runIndexer(ctx context.Context, store *storage.Store, logger zerolog.Logger func runServer(config *api.Config, store *storage.Store, logger zerolog.Logger) { srv := api.NewHTTPServer(logger, rpc.DefaultHTTPTimeouts) - supportedAPIs := api.SupportedAPIs(config, store) + flowClient, err := api.NewFlowClient(grpc.EmulatorHost) + if err != nil { + panic(err) + } + blockchainAPI := api.NewBlockChainAPI(config, store, flowClient) + supportedAPIs := api.SupportedAPIs(blockchainAPI) srv.EnableRPC(supportedAPIs) srv.EnableWS(supportedAPIs) srv.SetListenAddr("localhost", 8545) - err := srv.Start() + err = srv.Start() if err != nil { panic(err) } diff --git a/go.mod b/go.mod index 83f7b3b4..2dab0350 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/status-im/keycard-go v0.2.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect diff --git a/go.sum b/go.sum index 5c848967..2ec2b8f8 100644 --- a/go.sum +++ b/go.sum @@ -330,12 +330,16 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=