Skip to content

Commit

Permalink
Merge pull request onflow#6380 from onflow/ramtin/evm-fixed-coinbase-…
Browse files Browse the repository at this point in the history
…and-fee-transfers

[Flow EVM] Use a fixed coinbase address for Run and BatchRun and transfer gas fees afterward
  • Loading branch information
ramtinms authored Aug 22, 2024
2 parents 5ffe36f + 40f00dd commit b7a81e5
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 66 deletions.
45 changes: 45 additions & 0 deletions fvm/environment/mock/logger_provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 46 additions & 12 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,25 @@ func TestEVMRun(t *testing.T) {
sc.EVMContract.Address.HexWithPrefix(),
))

coinbaseAddr := types.Address{1, 2, 3}
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())

num := int64(12)
innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
testContract.DeployedAt.ToCommon(),
testContract.MakeCallData(t, "store", big.NewInt(num)),
big.NewInt(0),
uint64(100_000),
big.NewInt(0),
big.NewInt(1),
)

innerTx := cadence.NewArray(
ConvertToCadence(innerTxBytes),
).WithType(stdlib.EVMTransactionBytesCadenceType)

coinbase := cadence.NewArray(
ConvertToCadence(testAccount.Address().Bytes()),
ConvertToCadence(coinbaseAddr.Bytes()),
).WithType(stdlib.EVMAddressBytesCadenceType)

tx := fvm.Transaction(
Expand All @@ -105,22 +109,30 @@ func TestEVMRun(t *testing.T) {
snapshot = snapshot.Append(state)

// assert event fields are correct
require.Len(t, output.Events, 1)
require.Len(t, output.Events, 2)
txEvent := output.Events[0]
txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address)
require.NoError(t, err)

// fee transfer event
feeTransferEvent := output.Events[1]
feeTranferEventPayload := testutils.TxEventToPayload(t, feeTransferEvent, sc.EVMContract.Address)
require.NoError(t, err)
require.Equal(t, uint16(types.ErrCodeNoError), feeTranferEventPayload.ErrorCode)
require.Equal(t, uint16(1), feeTranferEventPayload.Index)
require.Equal(t, uint64(21000), feeTranferEventPayload.GasConsumed)

// commit block
blockEventPayload, snapshot := callEVMHeartBeat(t,
ctx,
vm,
snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(43785), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(64785), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)

txHashes := types.TransactionHashes{txEventPayload.Hash}
txHashes := types.TransactionHashes{txEventPayload.Hash, feeTranferEventPayload.Hash}
require.Equal(t,
txHashes.RootHash(),
blockEventPayload.TransactionHashRoot,
Expand All @@ -131,13 +143,16 @@ func TestEVMRun(t *testing.T) {
require.Equal(t, uint16(types.ErrCodeNoError), txEventPayload.ErrorCode)
require.Equal(t, uint16(0), txEventPayload.Index)
require.Equal(t, blockEventPayload.Height, txEventPayload.BlockHeight)
require.Equal(t, blockEventPayload.TotalGasUsed, txEventPayload.GasConsumed)
require.Equal(t, uint64(43785), blockEventPayload.TotalGasUsed)
require.Equal(t, blockEventPayload.TotalGasUsed-feeTranferEventPayload.GasConsumed, txEventPayload.GasConsumed)
require.Empty(t, txEventPayload.ContractAddress)

// append the state
snapshot = snapshot.Append(state)

// check coinbase balance
coinbaseBalance = getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
require.Equal(t, types.BalanceToBigInt(coinbaseBalance).Uint64(), txEventPayload.GasConsumed)

// query the value
code = []byte(fmt.Sprintf(
`
Expand Down Expand Up @@ -415,6 +430,10 @@ func TestEVMBatchRun(t *testing.T) {
sc.EVMContract.Address.HexWithPrefix(),
))

coinbaseAddr := types.Address{1, 2, 3}
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())

batchCount := 5
var storedValues []int64
txBytes := make([]cadence.Value, batchCount)
Expand All @@ -427,7 +446,7 @@ func TestEVMBatchRun(t *testing.T) {
testContract.MakeCallData(t, "storeWithLog", big.NewInt(num)),
big.NewInt(0),
uint64(100_000),
big.NewInt(0),
big.NewInt(1),
)

// build txs argument
Expand All @@ -437,7 +456,7 @@ func TestEVMBatchRun(t *testing.T) {
}

coinbase := cadence.NewArray(
ConvertToCadence(testAccount.Address().Bytes()),
ConvertToCadence(coinbaseAddr.Bytes()),
).WithType(stdlib.EVMAddressBytesCadenceType)

txs := cadence.NewArray(txBytes).
Expand All @@ -461,10 +480,11 @@ func TestEVMBatchRun(t *testing.T) {
// append the state
snapshot = snapshot.Append(state)

require.Len(t, output.Events, batchCount)
require.Len(t, output.Events, batchCount+1)
txHashes := make(types.TransactionHashes, 0)
totalGasUsed := uint64(0)
for i, event := range output.Events {
if i == batchCount { // last one is block executed
if i == batchCount { // skip last one
continue
}

Expand All @@ -486,16 +506,30 @@ func TestEVMBatchRun(t *testing.T) {
log := logs[0]
last := log.Topics[len(log.Topics)-1] // last topic is the value set in the store method
assert.Equal(t, storedValues[i], last.Big().Int64())
totalGasUsed += event.GasConsumed
}

// last event is fee transfer event
feeTransferEvent := output.Events[batchCount]
feeTranferEventPayload := testutils.TxEventToPayload(t, feeTransferEvent, sc.EVMContract.Address)
require.NoError(t, err)
require.Equal(t, uint16(types.ErrCodeNoError), feeTranferEventPayload.ErrorCode)
require.Equal(t, uint16(batchCount), feeTranferEventPayload.Index)
require.Equal(t, uint64(21000), feeTranferEventPayload.GasConsumed)
txHashes = append(txHashes, feeTranferEventPayload.Hash)

// check coinbase balance (note the gas price is 1)
coinbaseBalance = getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
require.Equal(t, types.BalanceToBigInt(coinbaseBalance).Uint64(), totalGasUsed)

// commit block
blockEventPayload, snapshot := callEVMHeartBeat(t,
ctx,
vm,
snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(155513), blockEventPayload.TotalGasUsed)
require.Equal(t, uint64(176_513), blockEventPayload.TotalGasUsed)
require.Equal(t,
txHashes.RootHash(),
blockEventPayload.TransactionHashRoot,
Expand Down
77 changes: 48 additions & 29 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handler

import (
"fmt"
"math/big"

"github.com/onflow/cadence/runtime/common"
Expand Down Expand Up @@ -134,57 +135,79 @@ func (h *ContractHandler) LastExecutedBlock() *types.Block {
}

// RunOrPanic runs an rlp-encoded evm transaction and
// collects the gas fees and pay it to the coinbase address provided.
func (h *ContractHandler) RunOrPanic(rlpEncodedTx []byte, coinbase types.Address) {
func (h *ContractHandler) RunOrPanic(rlpEncodedTx []byte, gasFeeCollector types.Address) {
// capture open tracing span
defer h.backend.StartChildSpan(trace.FVMEVMRun).End()

res, err := h.run(rlpEncodedTx, coinbase)
panicOnErrorOrInvalidOrFailedState(res, err)
h.runWithGasFeeRefund(gasFeeCollector, func() {
res, err := h.run(rlpEncodedTx)
panicOnErrorOrInvalidOrFailedState(res, err)
})
}

// Run tries to run an rlp-encoded evm transaction and
// collects the gas fees and pay it to the coinbase address provided.
func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) *types.ResultSummary {
// Run tries to run an rlp-encoded evm transaction
// collects the gas fees and pay it to the gasFeeCollector address provided.
func (h *ContractHandler) Run(rlpEncodedTx []byte, gasFeeCollector types.Address) *types.ResultSummary {
// capture open tracing span
defer h.backend.StartChildSpan(trace.FVMEVMRun).End()

res, err := h.run(rlpEncodedTx, coinbase)
panicOnError(err)
var res *types.Result
var err error
h.runWithGasFeeRefund(gasFeeCollector, func() {
// run transaction
res, err = h.run(rlpEncodedTx)
panicOnError(err)

})
// return the result summary
return res.ResultSummary()
}

// BatchRun tries to run batch of rlp-encoded transactions and
// runWithGasFeeRefund runs a method and transfers the balance changes of the
// coinbase address to the provided gas fee collector
func (h *ContractHandler) runWithGasFeeRefund(gasFeeCollector types.Address, f func()) {
// capture coinbase init balance
cb := h.AccountByAddress(types.CoinbaseAddress, true)
initCoinbaseBalance := cb.Balance()
f()
// transfer the gas fees collected to the gas fee collector address
afterBalance := cb.Balance()
diff := new(big.Int).Sub(afterBalance, initCoinbaseBalance)
if diff.Sign() > 0 {
cb.Transfer(gasFeeCollector, diff)
}
if diff.Sign() < 0 { // this should never happen but in case
panic(fvmErrors.NewEVMError(fmt.Errorf("negative balance change on coinbase")))
}
}

// BatchRun tries to run batch of rlp-encoded transactions
// collects the gas fees and pay it to the coinbase address provided.
// All transactions provided in the batch are included in a single block,
// except for invalid transactions
func (h *ContractHandler) BatchRun(
rlpEncodedTxs [][]byte,
coinbase types.Address,
) []*types.ResultSummary {
func (h *ContractHandler) BatchRun(rlpEncodedTxs [][]byte, gasFeeCollector types.Address) []*types.ResultSummary {
// capture open tracing
span := h.backend.StartChildSpan(trace.FVMEVMBatchRun)
span.SetAttributes(attribute.Int("tx_counts", len(rlpEncodedTxs)))
defer span.End()

// batch run transactions and panic if any error
res, err := h.batchRun(rlpEncodedTxs, coinbase)
panicOnError(err)
var results []*types.Result
var err error
h.runWithGasFeeRefund(gasFeeCollector, func() {
// batch run transactions and panic if any error
results, err = h.batchRun(rlpEncodedTxs)
panicOnError(err)
})

// convert results into result summaries
resSummaries := make([]*types.ResultSummary, len(res))
for i, r := range res {
resSummaries := make([]*types.ResultSummary, len(results))
for i, r := range results {
resSummaries[i] = r.ResultSummary()
}
return resSummaries
}

func (h *ContractHandler) batchRun(
rlpEncodedTxs [][]byte,
coinbase types.Address,
) ([]*types.Result, error) {
func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte) ([]*types.Result, error) {
// step 1 - transaction decoding and compute total gas needed
// This is safe to be done before checking the gas
// as it has its own metering
Expand Down Expand Up @@ -215,7 +238,6 @@ func (h *ContractHandler) batchRun(
if err != nil {
return nil, err
}
ctx.GasFeeCollector = coinbase

// step 4 - create a block view
blk, err := h.emulator.NewBlockView(ctx)
Expand Down Expand Up @@ -343,10 +365,7 @@ func (h *ContractHandler) commitBlockProposal() error {
return nil
}

func (h *ContractHandler) run(
rlpEncodedTx []byte,
coinbase types.Address,
) (*types.Result, error) {
func (h *ContractHandler) run(rlpEncodedTx []byte) (*types.Result, error) {
// step 1 - transaction decoding
tx, err := h.decodeTransaction(rlpEncodedTx)
if err != nil {
Expand All @@ -364,7 +383,6 @@ func (h *ContractHandler) run(
if err != nil {
return nil, err
}
ctx.GasFeeCollector = coinbase

// step 4 - create a block view
blk, err := h.emulator.NewBlockView(ctx)
Expand Down Expand Up @@ -535,6 +553,7 @@ func (h *ContractHandler) getBlockContext() (types.BlockContext, error) {
Tracer: h.tracer.TxTracer(),
TxCountSoFar: uint(len(bp.TxHashes)),
TotalGasUsedSoFar: bp.TotalGasUsed,
GasFeeCollector: types.CoinbaseAddress,
}, nil
}

Expand Down
Loading

0 comments on commit b7a81e5

Please sign in to comment.