diff --git a/integration/e2e_test.go b/integration/e2e_test.go index 0ccb0d58..a95c7132 100644 --- a/integration/e2e_test.go +++ b/integration/e2e_test.go @@ -5,12 +5,13 @@ import ( _ "embed" "encoding/hex" "fmt" - "github.com/onflow/flow-go-sdk/access/grpc" - "github.com/onflow/flow-go/fvm/evm/types" "math/big" "testing" "time" + "github.com/onflow/flow-go-sdk/access/grpc" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/params" "github.com/onflow/flow-evm-gateway/bootstrap" @@ -580,6 +581,15 @@ func TestE2E_API_DeployEvents(t *testing.T) { time.Sleep(1 * time.Second) + // perform `eth_call` to read the stored value + storedValue, err := rpcTester.call(contractAddress, callRetrieve) + require.NoError(t, err) + assert.Equal( + t, + "0000000000000000000000000000000000000000000000000000000000000539", // 1337 in ABI encoding + hex.EncodeToString(storedValue), + ) + // check if the sender account nonce has been indexed as increased eoaNonce, err = rpcTester.getNonce(fundEOAAddress) require.NoError(t, err) diff --git a/integration/helpers.go b/integration/helpers.go index 3fd31e7c..1acaa092 100644 --- a/integration/helpers.go +++ b/integration/helpers.go @@ -559,6 +559,31 @@ func (r *rpcTest) estimateGas( return gasUsed, nil } +func (r *rpcTest) call( + to common.Address, + data []byte, +) ([]byte, error) { + rpcRes, err := r.request( + "eth_call", + fmt.Sprintf( + `[{"to":"%s","data":"0x%s"}]`, + to.Hex(), + hex.EncodeToString(data), + ), + ) + if err != nil { + return nil, err + } + + var result hexutil.Bytes + err = json.Unmarshal(rpcRes, &result) + if err != nil { + return nil, err + } + + return result, nil +} + func uintHex(x uint64) string { return fmt.Sprintf("0x%x", x) } diff --git a/services/requester/cadence/call.cdc b/services/requester/cadence/call.cdc index 76c62efa..51c390ad 100644 --- a/services/requester/cadence/call.cdc +++ b/services/requester/cadence/call.cdc @@ -1,16 +1,20 @@ import EVM access(all) -fun main(data: [UInt8], contractAddress: [UInt8; 20]): [UInt8] { +fun main(hexEncodedData: String, hexEncodedAddress: String): String { let account = getAuthAccount(Address(0xCOA)) - let coa = account.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow reference to the COA!") + let coa = account.storage.borrow( + from: /storage/evm + ) ?? panic("Could not borrow reference to the COA!") + let addressBytes = hexEncodedAddress.decodeHex().toConstantSized<[UInt8; 20]>()! - return coa.call( - to: EVM.EVMAddress(bytes: contractAddress), - data: data, + let callResult = coa.call( + to: EVM.EVMAddress(bytes: addressBytes), + data: hexEncodedData.decodeHex(), gasLimit: 15000000, // todo make it configurable, max for now value: EVM.Balance(attoflow: 0) - ).data + ) + + return String.encodeHex(callResult.data) } diff --git a/services/requester/cadence/estimate_gas.cdc b/services/requester/cadence/estimate_gas.cdc index f2fd992e..85c84f1c 100644 --- a/services/requester/cadence/estimate_gas.cdc +++ b/services/requester/cadence/estimate_gas.cdc @@ -1,13 +1,13 @@ import EVM access(all) -fun main(encodedTx: [UInt8]): [UInt64; 2] { +fun main(hexEncodedTx: String): [UInt64; 2] { let account = getAuthAccount(Address(0xCOA)) let coa = account.storage.borrow<&EVM.CadenceOwnedAccount>( from: /storage/evm ) ?? panic("Could not borrow reference to the COA!") - let txResult = EVM.run(tx: encodedTx, coinbase: coa.address()) + let txResult = EVM.run(tx: hexEncodedTx.decodeHex(), coinbase: coa.address()) return [txResult.errorCode, txResult.gasUsed] } diff --git a/services/requester/cadence/get_balance.cdc b/services/requester/cadence/get_balance.cdc index 91ca3115..1a60769a 100644 --- a/services/requester/cadence/get_balance.cdc +++ b/services/requester/cadence/get_balance.cdc @@ -1,7 +1,8 @@ import EVM access(all) -fun main(addressBytes: [UInt8; 20]): UInt { +fun main(hexEncodedAddress: String): UInt { + let addressBytes = hexEncodedAddress.decodeHex().toConstantSized<[UInt8; 20]>()! let address = EVM.EVMAddress(bytes: addressBytes) return address.balance().inAttoFLOW() diff --git a/services/requester/cadence/get_nonce.cdc b/services/requester/cadence/get_nonce.cdc index 56f26e06..76d8321f 100644 --- a/services/requester/cadence/get_nonce.cdc +++ b/services/requester/cadence/get_nonce.cdc @@ -1,7 +1,8 @@ import EVM access(all) -fun main(addressBytes: [UInt8; 20]): UInt64 { +fun main(hexEncodedAddress: String): UInt64 { + let addressBytes = hexEncodedAddress.decodeHex().toConstantSized<[UInt8; 20]>()! let address = EVM.EVMAddress(bytes: addressBytes) return address.nonce() diff --git a/services/requester/cadence/run.cdc b/services/requester/cadence/run.cdc index 139d90da..feb85665 100644 --- a/services/requester/cadence/run.cdc +++ b/services/requester/cadence/run.cdc @@ -1,19 +1,20 @@ import EVM -transaction(encodedTx: [UInt8]) { +transaction(hexEncodedTx: String) { let coa: &EVM.CadenceOwnedAccount prepare(signer: auth(Storage) &Account) { - self.coa = signer.storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) - ?? panic("Could not borrow reference to the bridged account!") + self.coa = signer.storage.borrow<&EVM.CadenceOwnedAccount>( + from: /storage/evm + ) ?? panic("Could not borrow reference to the COA!") } execute { - let result = EVM.run(tx: encodedTx, coinbase: self.coa.address()) + let txResult = EVM.run(tx: hexEncodedTx.decodeHex(), coinbase: self.coa.address()) // todo only temporary until we correctly handle failure events assert( - result.status == EVM.Status.successful, - message: "failed to execute evm transaction: ".concat(result.errorCode.toString()) + txResult.status == EVM.Status.successful, + message: "failed to execute evm transaction: ".concat(txResult.errorCode.toString()) ) } } diff --git a/services/requester/requester.go b/services/requester/requester.go index 11f9ea67..e446c239 100644 --- a/services/requester/requester.go +++ b/services/requester/requester.go @@ -4,7 +4,11 @@ import ( "bytes" "context" _ "embed" + "encoding/hex" "fmt" + "math/big" + "strings" + "github.com/ethereum/go-ethereum/common" gethCore "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -19,8 +23,6 @@ import ( flowGo "github.com/onflow/flow-go/model/flow" "github.com/rs/zerolog" "golang.org/x/sync/errgroup" - "math/big" - "strings" ) var ( @@ -41,12 +43,6 @@ var ( //go:embed cadence/get_nonce.cdc getNonceScript []byte - - byteArrayType = cadence.NewVariableSizedArrayType(cadence.UInt8Type) - addressType = cadence.NewConstantSizedArrayType( - common.AddressLength, - cadence.UInt8Type, - ) ) const minFlowBalance = 2 @@ -154,13 +150,14 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash, return common.Hash{}, err } + hexEncodedTx, err := cadence.NewString(hex.EncodeToString(data)) + if err != nil { + return common.Hash{}, err + } + // todo make sure the gas price is not bellow the configured gas price script := e.replaceAddresses(runTxScript) - flowID, err := e.signAndSend( - ctx, - script, - cadenceArrayFromBytes(data), - ) + flowID, err := e.signAndSend(ctx, script, hexEncodedTx) if err != nil { return common.Hash{}, err } @@ -245,12 +242,17 @@ func (e *EVM) signAndSend(ctx context.Context, script []byte, args ...cadence.Va func (e *EVM) GetBalance(ctx context.Context, address common.Address, height uint64) (*big.Int, error) { // todo make sure provided height is used - addr := cadenceArrayFromBytes(address.Bytes()).WithType(addressType) + hexEncodedAddress, err := cadence.NewString( + strings.TrimPrefix(address.Hex(), "0x"), + ) + if err != nil { + return nil, err + } val, err := e.client.ExecuteScriptAtLatestBlock( ctx, e.replaceAddresses(getBalanceScript), - []cadence.Value{addr}, + []cadence.Value{hexEncodedAddress}, ) if err != nil { return nil, err @@ -267,12 +269,17 @@ func (e *EVM) GetBalance(ctx context.Context, address common.Address, height uin } func (e *EVM) GetNonce(ctx context.Context, address common.Address) (uint64, error) { - addr := cadenceArrayFromBytes(address.Bytes()).WithType(addressType) + hexEncodedAddress, err := cadence.NewString( + strings.TrimPrefix(address.Hex(), "0x"), + ) + if err != nil { + return 0, err + } val, err := e.client.ExecuteScriptAtLatestBlock( ctx, e.replaceAddresses(getNonceScript), - []cadence.Value{addr}, + []cadence.Value{hexEncodedAddress}, ) if err != nil { return 0, err @@ -289,31 +296,51 @@ func (e *EVM) GetNonce(ctx context.Context, address common.Address) (uint64, err } func (e *EVM) Call(ctx context.Context, address common.Address, data []byte) ([]byte, error) { + hexEncodedData, err := cadence.NewString(hex.EncodeToString(data)) + if err != nil { + return nil, err + } + // todo make "to" address optional, this can be used for contract deployment simulations - txData := cadenceArrayFromBytes(data).WithType(byteArrayType) - toAddress := cadenceArrayFromBytes(address.Bytes()).WithType(addressType) + hexEncodedAddress, err := cadence.NewString( + strings.TrimPrefix(address.Hex(), "0x"), + ) + if err != nil { + return nil, err + } e.logger.Debug(). Str("address", address.Hex()). Str("data", fmt.Sprintf("%x", data)). Msg("call") - value, err := e.client.ExecuteScriptAtLatestBlock( + scriptResult, err := e.client.ExecuteScriptAtLatestBlock( ctx, e.replaceAddresses(callScript), - []cadence.Value{txData, toAddress}, + []cadence.Value{hexEncodedData, hexEncodedAddress}, ) if err != nil { return nil, fmt.Errorf("failed to execute script: %w", err) } + // sanity check, should never occur + if _, ok := scriptResult.(cadence.String); !ok { + e.logger.Panic().Msg(fmt.Sprintf("failed to convert script result %v to String", scriptResult)) + } + + output := scriptResult.(cadence.String).ToGoValue().(string) + byteOutput, err := hex.DecodeString(output) + if err != nil { + return nil, fmt.Errorf("failed to convert call output: %w", err) + } + e.logger.Info(). Str("address", address.Hex()). Str("data", fmt.Sprintf("%x", data)). - Str("result", value.String()). + Str("result", output). Msg("call executed") - return bytesFromCadenceArray(value) + return byteOutput, nil } func (e *EVM) EstimateGas(ctx context.Context, data []byte) (uint64, error) { @@ -321,12 +348,15 @@ func (e *EVM) EstimateGas(ctx context.Context, data []byte) (uint64, error) { Str("data", fmt.Sprintf("%x", data)). Msg("estimate gas") - txData := cadenceArrayFromBytes(data).WithType(byteArrayType) + hexEncodedTx, err := cadence.NewString(hex.EncodeToString(data)) + if err != nil { + return 0, err + } value, err := e.client.ExecuteScriptAtLatestBlock( ctx, e.replaceAddresses(estimateGasScript), - []cadence.Value{txData}, + []cadence.Value{hexEncodedTx}, ) if err != nil { return 0, fmt.Errorf("failed to execute script: %w", err) @@ -388,29 +418,6 @@ func (e *EVM) replaceAddresses(script []byte) []byte { return []byte(s) } -func cadenceArrayFromBytes(input []byte) cadence.Array { - values := make([]cadence.Value, 0) - for _, element := range input { - values = append(values, cadence.UInt8(element)) - } - - return cadence.NewArray(values) -} - -func bytesFromCadenceArray(value cadence.Value) ([]byte, error) { - arr, ok := value.(cadence.Array) - if !ok { - return nil, fmt.Errorf("cadence value is not of array type, can not conver to byte array") - } - - res := make([]byte, len(arr.Values)) - for i, x := range arr.Values { - res[i] = x.ToGoValue().(byte) - } - - return res, nil -} - // TODO(m-Peter): Consider moving this to flow-go repository func getErrorForCode(errorCode uint64) error { switch evmTypes.ErrorCode(errorCode) {