Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize format for EVM txs & data #124

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading