Skip to content

Commit

Permalink
Merge pull request #124 from m-Peter/optimize-evm-tx-format
Browse files Browse the repository at this point in the history
Optimize format for EVM txs & data
  • Loading branch information
sideninja authored Mar 4, 2024
2 parents 816b5b7 + 5ee1c4a commit 4a3061b
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 67 deletions.
14 changes: 12 additions & 2 deletions integration/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions integration/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
18 changes: 11 additions & 7 deletions services/requester/cadence/call.cdc
Original file line number Diff line number Diff line change
@@ -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<auth(Storage) &Account>(Address(0xCOA))

let coa = account.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(from: /storage/evm)
?? panic("Could not borrow reference to the COA!")
let coa = account.storage.borrow<auth(EVM.Call) &EVM.CadenceOwnedAccount>(
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)
}
4 changes: 2 additions & 2 deletions services/requester/cadence/estimate_gas.cdc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import EVM

access(all)
fun main(encodedTx: [UInt8]): [UInt64; 2] {
fun main(hexEncodedTx: String): [UInt64; 2] {
let account = getAuthAccount<auth(Storage) &Account>(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]
}
3 changes: 2 additions & 1 deletion services/requester/cadence/get_balance.cdc
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
3 changes: 2 additions & 1 deletion services/requester/cadence/get_nonce.cdc
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
13 changes: 7 additions & 6 deletions services/requester/cadence/run.cdc
Original file line number Diff line number Diff line change
@@ -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())
)
}
}
103 changes: 55 additions & 48 deletions services/requester/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -289,44 +296,67 @@ 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) {
e.logger.Debug().
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)
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 4a3061b

Please sign in to comment.