diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index d45f58d0305..8ecc8ae6cef 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -198,6 +198,7 @@ func (bl *BlockView) RunTransaction( func (bl *BlockView) BatchRunTransactions(txs []*gethTypes.Transaction) ([]*types.Result, error) { batchResults := make([]*types.Result, len(txs)) + // create a new procedure proc, err := bl.newProcedure() if err != nil { return nil, err @@ -233,6 +234,7 @@ func (bl *BlockView) BatchRunTransactions(txs []*gethTypes.Transaction) ([]*type if err != nil { return nil, err } + // all commit errors (StateDB errors) has to be returned if err := proc.commit(false); err != nil { return nil, err @@ -240,6 +242,7 @@ func (bl *BlockView) BatchRunTransactions(txs []*gethTypes.Transaction) ([]*type // this clears state for any subsequent transaction runs proc.state.Reset() + // collect result batchResults[i] = res } @@ -258,10 +261,14 @@ func (bl *BlockView) DryRunTransaction( from gethCommon.Address, ) (*types.Result, error) { var txResult *types.Result + + // create a new procedure proc, err := bl.newProcedure() if err != nil { return nil, err } + + // convert tx into message msg, err := gethCore.TransactionToMessage( tx, GetSigner(bl.config), @@ -398,6 +405,13 @@ func (proc *procedure) withdrawFrom( types.ErrInvalidBalance), nil } + // check balance is not prone to rounding error + if types.BalanceConversionToUFix64ProneToRoundingError(call.Value) { + return types.NewInvalidResult( + call.Transaction(), + types.ErrWithdrawBalanceRounding), nil + } + // create bridge account if not exist bridge := call.To.ToCommon() if !proc.state.Exist(bridge) { @@ -619,6 +633,7 @@ func (proc *procedure) run( // if the block gas limit is set to anything than max // we need to update this code. gasPool := (*gethCore.GasPool)(&proc.config.BlockContext.GasLimit) + // transit the state execResult, err := gethCore.NewStateTransition( proc.evm, diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index 34623024589..1698ce2c1ea 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -1,6 +1,7 @@ package emulator_test import ( + "encoding/hex" "encoding/json" "fmt" "math" @@ -45,10 +46,16 @@ func RunWithNewReadOnlyBlockView(t testing.TB, em *emulator.Emulator, f func(blk f(blk) } +func requireSuccessfulExecution(t testing.TB, err error, res *types.Result) { + require.NoError(t, err) + require.NoError(t, res.VMError) + require.NoError(t, res.ValidationError) +} + func TestNativeTokenBridging(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { - originalBalance := big.NewInt(10000) + originalBalance := types.MakeBigIntInFlow(3) testAccount := types.NewAddressFromString("test") bridgeAccount := types.NewAddressFromString("bridge") testAccountNonce := uint64(0) @@ -58,7 +65,7 @@ func TestNativeTokenBridging(t *testing.T) { RunWithNewBlockView(t, env, func(blk types.BlockView) { call := types.NewDepositCall(bridgeAccount, testAccount, originalBalance, 0) res, err := blk.DirectCall(call) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) require.Equal(t, call.Hash(), res.TxHash) }) @@ -68,16 +75,46 @@ func TestNativeTokenBridging(t *testing.T) { retBalance, err := blk.BalanceOf(testAccount) require.NoError(t, err) require.Equal(t, originalBalance, retBalance) - // check balance of bridgeAccount to be zero + // check balance of bridgeAccount to be zero retBalance, err = blk.BalanceOf(bridgeAccount) require.NoError(t, err) require.Equal(t, big.NewInt(0).Uint64(), retBalance.Uint64()) }) }) }) + t.Run("tokens deposit to an smart contract that doesn't accept native token", func(t *testing.T) { + var testContract types.Address + // deploy contract + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + emptyContractByteCode, err := hex.DecodeString("6080604052348015600e575f80fd5b50603e80601a5f395ff3fe60806040525f80fdfea2646970667358221220093c3754c634ed147652afc2e8c4a2336be5c37cbc733839668aa5a11e713e6e64736f6c634300081a0033") + require.NoError(t, err) + call := types.NewDeployCall( + bridgeAccount, + emptyContractByteCode, + 100_000, + big.NewInt(0), + 1) + res, err := blk.DirectCall(call) + requireSuccessfulExecution(t, err, res) + testContract = *res.DeployedContractAddress + }) + }) + + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + call := types.NewDepositCall(bridgeAccount, testContract, types.MakeBigIntInFlow(1), 0) + res, err := blk.DirectCall(call) + require.NoError(t, err) + require.NoError(t, res.ValidationError) + require.Equal(t, res.VMError, gethVM.ErrExecutionReverted) + }) + }) + }) + t.Run("tokens withdraw", func(t *testing.T) { - amount := big.NewInt(1000) + amount := types.OneFlow() RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { retBalance, err := blk.BalanceOf(testAccount) @@ -92,7 +129,7 @@ func TestNativeTokenBridging(t *testing.T) { RunWithNewBlockView(t, env, func(blk types.BlockView) { call := types.NewWithdrawCall(bridgeAccount, testAccount, amount, testAccountNonce) res, err := blk.DirectCall(call) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) require.Equal(t, call.Hash(), res.TxHash) testAccountNonce += 1 @@ -115,8 +152,33 @@ func TestNativeTokenBridging(t *testing.T) { }) }) }) - // TODO: add test for deploying to an empty contract (error on deposit call) - // and a test case for when not enough balance on account for withdraw + t.Run("tokens withdraw that results in rounding error", func(t *testing.T) { + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + call := types.NewWithdrawCall(bridgeAccount, testAccount, big.NewInt(1000), testAccountNonce) + res, err := blk.DirectCall(call) + require.NoError(t, err) + require.Equal(t, res.ValidationError, types.ErrWithdrawBalanceRounding) + testAccountNonce += 1 + }) + }) + }) + + t.Run("tokens withdraw not having enough balance", func(t *testing.T) { + RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + call := types.NewWithdrawCall(bridgeAccount, testAccount, types.MakeBigIntInFlow(3), testAccountNonce) + res, err := blk.DirectCall(call) + require.NoError(t, err) + require.True(t, + strings.Contains( + res.ValidationError.Error(), + "insufficient funds", + ), + ) + }) + }) + }) }) }) } @@ -155,7 +217,7 @@ func TestContractInteraction(t *testing.T) { amountToBeTransfered, testAccountNonce) res, err := blk.DirectCall(call) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) require.NotNil(t, res.DeployedContractAddress) contractAddr = *res.DeployedContractAddress require.Equal(t, call.Hash(), res.TxHash) @@ -196,7 +258,7 @@ func TestContractInteraction(t *testing.T) { testAccountNonce, ), ) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000)) testAccountNonce += 1 require.Empty(t, res.PrecompiledCalls) @@ -215,7 +277,7 @@ func TestContractInteraction(t *testing.T) { testAccountNonce, ), ) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) testAccountNonce += 1 ret := new(big.Int).SetBytes(res.ReturnedData) @@ -236,7 +298,7 @@ func TestContractInteraction(t *testing.T) { testAccountNonce, ), ) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) testAccountNonce += 1 ret := new(big.Int).SetBytes(res.ReturnedData) @@ -277,6 +339,7 @@ func TestContractInteraction(t *testing.T) { ), ) require.NoError(t, err) + require.NoError(t, res.ValidationError) testAccountNonce += 1 require.Error(t, res.VMError) strings.Contains(string(res.ReturnedData), "Value is too low") @@ -297,7 +360,7 @@ func TestContractInteraction(t *testing.T) { testAccountNonce, ), ) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) testAccountNonce += 1 ret := new(big.Int).SetBytes(res.ReturnedData) @@ -337,9 +400,7 @@ func TestContractInteraction(t *testing.T) { ) res, err := blk.RunTransaction(tx) - require.NoError(t, err) - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, err, res) require.Greater(t, res.GasConsumed, uint64(0)) // check the balance of coinbase @@ -397,8 +458,7 @@ func TestContractInteraction(t *testing.T) { results, err := blk.BatchRunTransactions(txs) require.NoError(t, err) for _, res := range results { - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, nil, res) require.Greater(t, res.GasConsumed, uint64(0)) } @@ -416,7 +476,7 @@ func TestContractInteraction(t *testing.T) { }) }) - t.Run("test runing transactions with dynamic fees (happy case)", func(t *testing.T) { + t.Run("test running transactions with dynamic fees (happy case)", func(t *testing.T) { account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) fAddr := account.Address() RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { @@ -454,9 +514,7 @@ func TestContractInteraction(t *testing.T) { account.SetNonce(account.Nonce() + 1) res, err := blk.RunTransaction(tx) - require.NoError(t, err) - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, err, res) require.Greater(t, res.GasConsumed, uint64(0)) }) }) @@ -621,8 +679,9 @@ func TestSelfdestruct(t *testing.T) { // setup the test with funded account and deploying a selfdestruct contract. RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) { RunWithNewBlockView(t, env, func(blk types.BlockView) { - _, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAddress, startBalance, 0)) + res, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAddress, startBalance, 0)) require.NoError(t, err) + requireSuccessfulExecution(t, err, res) }) RunWithNewBlockView(t, env, func(blk types.BlockView) { @@ -634,7 +693,7 @@ func TestSelfdestruct(t *testing.T) { deployBalance, 0), ) - require.NoError(t, err) + requireSuccessfulExecution(t, err, res) require.NotNil(t, res.DeployedContractAddress) contractAddr = *res.DeployedContractAddress }) @@ -659,8 +718,7 @@ func TestSelfdestruct(t *testing.T) { Value: big.NewInt(0), GasLimit: 100_000, }) - require.NoError(t, err) - require.False(t, res.Failed()) + requireSuccessfulExecution(t, err, res) }) // after calling selfdestruct the balance should be returned to the caller and @@ -944,9 +1002,7 @@ func TestTransactionTracing(t *testing.T) { testAccount.Nonce(), ), ) - require.NoError(t, err) - require.NoError(t, res.ValidationError) - require.NoError(t, res.VMError) + requireSuccessfulExecution(t, err, res) txID = res.TxHash trace, err = tracer.TxTracer().GetResult() require.NoError(t, err) @@ -991,9 +1047,7 @@ func TestTransactionTracing(t *testing.T) { // interact and record trace res, err := blk.RunTransaction(tx) - require.NoError(t, err) - require.NoError(t, res.ValidationError) - require.NoError(t, res.VMError) + requireSuccessfulExecution(t, err, res) txID = res.TxHash trace, err = tracer.TxTracer().GetResult() require.NoError(t, err) @@ -1112,8 +1166,8 @@ func TestTxIndex(t *testing.T) { ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) expectedTxIndex := uint16(1) ctx.TxCountSoFar = 1 - testAccount1 := types.NewAddressFromString("test") - testAccount2 := types.NewAddressFromString("test") + testAccount1 := testutils.RandomAddress(t) + testAccount2 := testutils.RandomAddress(t) blk, err := em.NewBlockView(ctx) require.NoError(t, err) @@ -1144,12 +1198,10 @@ func TestTxIndex(t *testing.T) { types.NewDepositCall( types.EmptyAddress, fAddr, - types.OneFlow, + types.OneFlow(), account.Nonce(), )) - require.NoError(t, err) - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, err, res) require.Equal(t, expectedTxIndex, res.Index) expectedTxIndex += 1 ctx.TxCountSoFar = 3 @@ -1161,15 +1213,13 @@ func TestTxIndex(t *testing.T) { t, testAccount1.ToCommon(), // to nil, // data - big.NewInt(1000), // amount + big.NewInt(0), // amount gethParams.TxGas, // gas limit big.NewInt(0), ) res, err = blk.RunTransaction(tx) - require.NoError(t, err) - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, err, res) require.Equal(t, expectedTxIndex, res.Index) expectedTxIndex += 1 ctx.TxCountSoFar = 4 @@ -1184,7 +1234,7 @@ func TestTxIndex(t *testing.T) { t, testAccount1.ToCommon(), // to nil, // data - big.NewInt(1000), // amount + big.NewInt(0), // amount gethParams.TxGas, // gas limit big.NewInt(0), ) @@ -1192,8 +1242,7 @@ func TestTxIndex(t *testing.T) { results, err := blk.BatchRunTransactions(txs) require.NoError(t, err) for i, res := range results { - require.NoError(t, res.VMError) - require.NoError(t, res.ValidationError) + requireSuccessfulExecution(t, err, res) require.Equal(t, expectedTxIndex+uint16(i), res.Index) } }) diff --git a/fvm/evm/emulator/state/delta.go b/fvm/evm/emulator/state/delta.go index ba600934efc..d3fd4e8e0f7 100644 --- a/fvm/evm/emulator/state/delta.go +++ b/fvm/evm/emulator/state/delta.go @@ -109,8 +109,8 @@ func (d *DeltaView) Exist(addr gethCommon.Address) (bool, error) { // CreateAccount creates a new account for the given address // -// if address already extists (even if destructed), carry over the balance -// and reset the data from the orginal account. +// if address already exists (even if destructed), carry over the balance +// and reset the data from the original account. func (d *DeltaView) CreateAccount(addr gethCommon.Address) error { // if is already created return if d.IsCreated(addr) { @@ -137,7 +137,7 @@ func (d *DeltaView) CreateAccount(addr gethCommon.Address) error { d.nonces[addr] = 0 d.codes[addr] = nil d.codeHashes[addr] = gethTypes.EmptyCodeHash - // carrying over the balance. (legacy behaviour of the Geth stateDB) + // carrying over the balance. (legacy behavior of the Geth stateDB) d.balances[addr] = balance // flag addr as recreated, this flag helps with postponing deletion of slabs diff --git a/fvm/evm/emulator/state/stateDB.go b/fvm/evm/emulator/state/stateDB.go index b09abda7cc9..d2a5f216d85 100644 --- a/fvm/evm/emulator/state/stateDB.go +++ b/fvm/evm/emulator/state/stateDB.go @@ -8,13 +8,12 @@ import ( "github.com/holiman/uint256" "github.com/onflow/atree" - "github.com/onflow/go-ethereum/common" gethCommon "github.com/onflow/go-ethereum/common" - "github.com/onflow/go-ethereum/core/stateless" - "github.com/onflow/go-ethereum/core/tracing" + gethStateless "github.com/onflow/go-ethereum/core/stateless" + gethTracing "github.com/onflow/go-ethereum/core/tracing" gethTypes "github.com/onflow/go-ethereum/core/types" gethParams "github.com/onflow/go-ethereum/params" - "github.com/onflow/go-ethereum/trie/utils" + gethUtils "github.com/onflow/go-ethereum/trie/utils" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" @@ -59,7 +58,7 @@ func NewStateDB(ledger atree.Ledger, root flow.Address) (*StateDB, error) { // // this should also return true for self destructed accounts during the transaction execution. func (db *StateDB) Exist(addr gethCommon.Address) bool { - exist, err := db.lastestView().Exist(addr) + exist, err := db.latestView().Exist(addr) db.handleError(err) return exist } @@ -79,13 +78,13 @@ func (db *StateDB) Empty(addr gethCommon.Address) bool { // CreateAccount creates a new account for the given address // it sets the nonce to zero func (db *StateDB) CreateAccount(addr gethCommon.Address) { - err := db.lastestView().CreateAccount(addr) + err := db.latestView().CreateAccount(addr) db.handleError(err) } // IsCreated returns true if address is recently created (context of a transaction) func (db *StateDB) IsCreated(addr gethCommon.Address) bool { - return db.lastestView().IsCreated(addr) + return db.latestView().IsCreated(addr) } // CreateContract is used whenever a contract is created. This may be preceded @@ -94,12 +93,12 @@ func (db *StateDB) IsCreated(addr gethCommon.Address) bool { // This operation sets the 'newContract'-flag, which is required in order to // correctly handle EIP-6780 'delete-in-same-transaction' logic. func (db *StateDB) CreateContract(addr gethCommon.Address) { - db.lastestView().CreateContract(addr) + db.latestView().CreateContract(addr) } // IsCreated returns true if address is a new contract func (db *StateDB) IsNewContract(addr gethCommon.Address) bool { - return db.lastestView().IsNewContract(addr) + return db.latestView().IsNewContract(addr) } // SelfDestruct flags the address for deletion. @@ -107,7 +106,7 @@ func (db *StateDB) IsNewContract(addr gethCommon.Address) bool { // while this address exists for the rest of transaction, // the balance of this account is return zero after the SelfDestruct call. func (db *StateDB) SelfDestruct(addr gethCommon.Address) { - err := db.lastestView().SelfDestruct(addr) + err := db.latestView().SelfDestruct(addr) db.handleError(err) } @@ -119,9 +118,9 @@ func (db *StateDB) Selfdestruct6780(addr gethCommon.Address) { } } -// HasSelfDestructed returns true if address is flaged with self destruct. +// HasSelfDestructed returns true if address is flagged with self destruct. func (db *StateDB) HasSelfDestructed(addr gethCommon.Address) bool { - destructed, _ := db.lastestView().HasSelfDestructed(addr) + destructed, _ := db.latestView().HasSelfDestructed(addr) return destructed } @@ -129,14 +128,14 @@ func (db *StateDB) HasSelfDestructed(addr gethCommon.Address) bool { func (db *StateDB) SubBalance( addr gethCommon.Address, amount *uint256.Int, - reason tracing.BalanceChangeReason, + reason gethTracing.BalanceChangeReason, ) { // negative amounts are not accepted. if amount.Sign() < 0 { db.handleError(types.ErrInvalidBalance) return } - err := db.lastestView().SubBalance(addr, amount) + err := db.latestView().SubBalance(addr, amount) db.handleError(err) } @@ -144,82 +143,82 @@ func (db *StateDB) SubBalance( func (db *StateDB) AddBalance( addr gethCommon.Address, amount *uint256.Int, - reason tracing.BalanceChangeReason, + reason gethTracing.BalanceChangeReason, ) { // negative amounts are not accepted. if amount.Sign() < 0 { db.handleError(types.ErrInvalidBalance) return } - err := db.lastestView().AddBalance(addr, amount) + err := db.latestView().AddBalance(addr, amount) db.handleError(err) } // GetBalance returns the balance of the given address func (db *StateDB) GetBalance(addr gethCommon.Address) *uint256.Int { - bal, err := db.lastestView().GetBalance(addr) + bal, err := db.latestView().GetBalance(addr) db.handleError(err) return bal } // GetNonce returns the nonce of the given address func (db *StateDB) GetNonce(addr gethCommon.Address) uint64 { - nonce, err := db.lastestView().GetNonce(addr) + nonce, err := db.latestView().GetNonce(addr) db.handleError(err) return nonce } // SetNonce sets the nonce value for the given address func (db *StateDB) SetNonce(addr gethCommon.Address, nonce uint64) { - err := db.lastestView().SetNonce(addr, nonce) + err := db.latestView().SetNonce(addr, nonce) db.handleError(err) } // GetCodeHash returns the code hash of the given address func (db *StateDB) GetCodeHash(addr gethCommon.Address) gethCommon.Hash { - hash, err := db.lastestView().GetCodeHash(addr) + hash, err := db.latestView().GetCodeHash(addr) db.handleError(err) return hash } // GetCode returns the code for the given address func (db *StateDB) GetCode(addr gethCommon.Address) []byte { - code, err := db.lastestView().GetCode(addr) + code, err := db.latestView().GetCode(addr) db.handleError(err) return code } // GetCodeSize returns the size of the code for the given address func (db *StateDB) GetCodeSize(addr gethCommon.Address) int { - codeSize, err := db.lastestView().GetCodeSize(addr) + codeSize, err := db.latestView().GetCodeSize(addr) db.handleError(err) return codeSize } // SetCode sets the code for the given address func (db *StateDB) SetCode(addr gethCommon.Address, code []byte) { - err := db.lastestView().SetCode(addr, code) + err := db.latestView().SetCode(addr, code) db.handleError(err) } // AddRefund adds the amount to the total (gas) refund func (db *StateDB) AddRefund(amount uint64) { - err := db.lastestView().AddRefund(amount) + err := db.latestView().AddRefund(amount) db.handleError(err) } // SubRefund subtracts the amount from the total (gas) refund func (db *StateDB) SubRefund(amount uint64) { - err := db.lastestView().SubRefund(amount) + err := db.latestView().SubRefund(amount) db.handleError(err) } // GetRefund returns the total (gas) refund func (db *StateDB) GetRefund() uint64 { - return db.lastestView().GetRefund() + return db.latestView().GetRefund() } -// GetCommittedState returns the value for the given storage slot considering only the commited state and not +// GetCommittedState returns the value for the given storage slot considering only the committed state and not // changes in the scope of current transaction. func (db *StateDB) GetCommittedState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { value, err := db.baseView.GetState(types.SlotAddress{Address: addr, Key: key}) @@ -229,14 +228,14 @@ func (db *StateDB) GetCommittedState(addr gethCommon.Address, key gethCommon.Has // GetState returns the value for the given storage slot func (db *StateDB) GetState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { - state, err := db.lastestView().GetState(types.SlotAddress{Address: addr, Key: key}) + state, err := db.latestView().GetState(types.SlotAddress{Address: addr, Key: key}) db.handleError(err) return state } // GetStorageRoot returns some sort of root for the given address. // -// Warning! Since StateDB doesn't construct a Merkel tree under the hood, +// Warning! Since StateDB doesn't construct a Merkle tree under the hood, // the behavior of this endpoint is as follow: // - if an account doesn't exist it returns common.Hash{} // - if account is EOA it returns gethCommon.EmptyRootHash @@ -248,59 +247,59 @@ func (db *StateDB) GetState(addr gethCommon.Address, key gethCommon.Hash) gethCo // where the value that is returned is compared to empty values to make sure the storage is empty // This endpoint is added mostly to prevent the case that an smart contract is self-destructed // and a later transaction tries to deploy a contract to the same address. -func (db *StateDB) GetStorageRoot(addr common.Address) common.Hash { - root, err := db.lastestView().GetStorageRoot(addr) +func (db *StateDB) GetStorageRoot(addr gethCommon.Address) gethCommon.Hash { + root, err := db.latestView().GetStorageRoot(addr) db.handleError(err) return root } // SetState sets a value for the given storage slot func (db *StateDB) SetState(addr gethCommon.Address, key gethCommon.Hash, value gethCommon.Hash) { - err := db.lastestView().SetState(types.SlotAddress{Address: addr, Key: key}, value) + err := db.latestView().SetState(types.SlotAddress{Address: addr, Key: key}, value) db.handleError(err) } // GetTransientState returns the value for the given key of the transient storage func (db *StateDB) GetTransientState(addr gethCommon.Address, key gethCommon.Hash) gethCommon.Hash { - return db.lastestView().GetTransientState(types.SlotAddress{Address: addr, Key: key}) + return db.latestView().GetTransientState(types.SlotAddress{Address: addr, Key: key}) } // SetTransientState sets a value for the given key of the transient storage func (db *StateDB) SetTransientState(addr gethCommon.Address, key, value gethCommon.Hash) { - db.lastestView().SetTransientState(types.SlotAddress{Address: addr, Key: key}, value) + db.latestView().SetTransientState(types.SlotAddress{Address: addr, Key: key}, value) } // AddressInAccessList checks if an address is in the access list func (db *StateDB) AddressInAccessList(addr gethCommon.Address) bool { - return db.lastestView().AddressInAccessList(addr) + return db.latestView().AddressInAccessList(addr) } // SlotInAccessList checks if the given (address,slot) is in the access list func (db *StateDB) SlotInAccessList(addr gethCommon.Address, key gethCommon.Hash) (addressOk bool, slotOk bool) { - return db.lastestView().SlotInAccessList(types.SlotAddress{Address: addr, Key: key}) + return db.latestView().SlotInAccessList(types.SlotAddress{Address: addr, Key: key}) } // AddAddressToAccessList adds the given address to the access list. func (db *StateDB) AddAddressToAccessList(addr gethCommon.Address) { - db.lastestView().AddAddressToAccessList(addr) + db.latestView().AddAddressToAccessList(addr) } // AddSlotToAccessList adds the given (address,slot) to the access list. func (db *StateDB) AddSlotToAccessList(addr gethCommon.Address, key gethCommon.Hash) { - db.lastestView().AddSlotToAccessList(types.SlotAddress{Address: addr, Key: key}) + db.latestView().AddSlotToAccessList(types.SlotAddress{Address: addr, Key: key}) } // AddLog appends a lot to the collection of logs func (db *StateDB) AddLog(log *gethTypes.Log) { - db.lastestView().AddLog(log) + db.latestView().AddLog(log) } -// AddPreimage adds a preimage to the collection of preimages +// AddPreimage adds a pre-image to the collection of pre-images func (db *StateDB) AddPreimage(hash gethCommon.Hash, data []byte) { - db.lastestView().AddPreimage(hash, data) + db.latestView().AddPreimage(hash, data) } -// RevertToSnapshot reverts the changes until we reach the given snaptshot +// RevertToSnapshot reverts the changes until we reach the given snapshot func (db *StateDB) RevertToSnapshot(index int) { if index > len(db.views) { db.cachedError = fmt.Errorf("invalid revert") @@ -312,7 +311,7 @@ func (db *StateDB) RevertToSnapshot(index int) { // Snapshot takes an snapshot of the state and returns an int // that can be used later for revert calls. func (db *StateDB) Snapshot() int { - newView := db.lastestView().NewChildView() + newView := db.latestView().NewChildView() db.views = append(db.views, newView) return len(db.views) - 1 } @@ -336,7 +335,7 @@ func (db *StateDB) Logs( return allLogs } -// Preimages returns a set of preimages +// Preimages returns a set of pre-images func (db *StateDB) Preimages() map[gethCommon.Hash][]byte { preImages := make(map[gethCommon.Hash][]byte, 0) for _, view := range db.views { @@ -458,9 +457,9 @@ func (db *StateDB) Finalize() error { return wrapError(err) } -// Prepare is a highlevel logic that sadly is considered to be part of the +// Prepare is a high level logic that sadly is considered to be part of the // stateDB interface and not on the layers above. -// based on parameters that are passed it updates accesslists +// based on parameters that are passed it updates access lists func (db *StateDB) Prepare(rules gethParams.Rules, sender, coinbase gethCommon.Address, dest *gethCommon.Address, precompiles []gethCommon.Address, txAccesses gethTypes.AccessList) { if rules.IsBerlin { db.AddAddressToAccessList(sender) @@ -500,7 +499,7 @@ func (s *StateDB) Error() error { // PointCache is not supported and only needed // when EIP-4762 is enabled in the future versions // (currently planned for after Verkle fork). -func (s *StateDB) PointCache() *utils.PointCache { +func (s *StateDB) PointCache() *gethUtils.PointCache { return nil } @@ -508,11 +507,11 @@ func (s *StateDB) PointCache() *utils.PointCache { // when if witness collection is enabled (EnableWitnessCollection flag). // By definition it should returns a set containing all trie nodes that have been accessed. // The returned map could be nil if the witness is empty. -func (s *StateDB) Witness() *stateless.Witness { +func (s *StateDB) Witness() *gethStateless.Witness { return nil } -func (db *StateDB) lastestView() *DeltaView { +func (db *StateDB) latestView() *DeltaView { return db.views[len(db.views)-1] } diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index b1384a6af40..67c190c13c7 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -955,7 +955,7 @@ func TestEVMAddressDeposit(t *testing.T) { snapshot = snapshot.Append(execSnap) - expectedBalance := types.OneFlowBalance + expectedBalance := types.OneFlowBalance() bal := getEVMAccountBalance(t, ctx, vm, snapshot, addr) require.Equal(t, expectedBalance, bal) @@ -971,7 +971,7 @@ func TestEVMAddressDeposit(t *testing.T) { depEvPayload, err := events.DecodeFLOWTokensDepositedEventPayload(depEv) require.NoError(t, err) - require.Equal(t, types.OneFlow, depEvPayload.BalanceAfterInAttoFlow.Value) + require.Equal(t, types.OneFlow(), depEvPayload.BalanceAfterInAttoFlow.Value) // commit block blockEventPayload, _ := callEVMHeartBeat(t, diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 0e226d64579..c8e0338778c 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -32,16 +32,9 @@ type ContractHandler struct { tracer debug.EVMTracer } -func (h *ContractHandler) FlowTokenAddress() common.Address { - return h.flowTokenAddress -} - -func (h *ContractHandler) EVMContractAddress() common.Address { - return common.Address(h.evmContractAddress) -} - var _ types.ContractHandler = &ContractHandler{} +// NewContractHandler constructs a new ContractHandler func NewContractHandler( flowChainID flow.ChainID, evmContractAddress flow.Address, @@ -54,35 +47,56 @@ func NewContractHandler( tracer debug.EVMTracer, ) *ContractHandler { return &ContractHandler{ - flowChainID: flowChainID, - evmContractAddress: evmContractAddress, - flowTokenAddress: flowTokenAddress, - blockStore: blockStore, - addressAllocator: addressAllocator, - backend: backend, - emulator: emulator, - tracer: tracer, - precompiledContracts: preparePrecompiledContracts(evmContractAddress, randomBeaconAddress, addressAllocator, backend), - } + flowChainID: flowChainID, + evmContractAddress: evmContractAddress, + flowTokenAddress: flowTokenAddress, + blockStore: blockStore, + addressAllocator: addressAllocator, + backend: backend, + emulator: emulator, + tracer: tracer, + precompiledContracts: preparePrecompiledContracts( + evmContractAddress, + randomBeaconAddress, + addressAllocator, + backend, + ), + } +} + +// FlowTokenAddress returns the address where the FlowToken contract is deployed +func (h *ContractHandler) FlowTokenAddress() common.Address { + return h.flowTokenAddress +} + +// EVMContractAddress returns the address where EVM contract is deployed +func (h *ContractHandler) EVMContractAddress() common.Address { + return common.Address(h.evmContractAddress) } // DeployCOA deploys a cadence-owned-account and returns the address func (h *ContractHandler) DeployCOA(uuid uint64) types.Address { + // capture open tracing traces defer h.backend.StartChildSpan(trace.FVMEVMDeployCOA).End() res, err := h.deployCOA(uuid) panicOnErrorOrInvalidOrFailedState(res, err) + return *res.DeployedContractAddress } func (h *ContractHandler) deployCOA(uuid uint64) (*types.Result, error) { - target := h.addressAllocator.AllocateCOAAddress(uuid) + // step 1 - check enough computation is available gaslimit := types.GasLimit(coa.ContractDeploymentRequiredGas) err := h.checkGasLimit(gaslimit) if err != nil { return nil, err } + // step 2 - allocate a new address for the COA + target := h.addressAllocator.AllocateCOAAddress(uuid) + + // step 3 - create a COA deployment call factory := h.addressAllocator.COAFactoryAddress() factoryAccount := h.AccountByAddress(factory, false) factoryNonce := factoryAccount.Nonce() @@ -95,12 +109,15 @@ func (h *ContractHandler) deployCOA(uuid uint64) (*types.Result, error) { factoryNonce, ) - ctx, err := h.getBlockContext() + // step 4 - execute the call + res, err := h.executeAndHandleCall(call, nil, false) if err != nil { return nil, err } + + // step 5 - if successful COA metrics h.backend.SetNumberOfDeployedCOAs(factoryNonce) - return h.executeAndHandleCall(ctx, call, nil, false) + return res, nil } // AccountByAddress returns the account for the given address, @@ -116,20 +133,26 @@ func (h *ContractHandler) LastExecutedBlock() *types.Block { return block } -// RunOrPanic runs an rlpencoded evm transaction and +// 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) { + // capture open tracing span + defer h.backend.StartChildSpan(trace.FVMEVMRun).End() + res, err := h.run(rlpEncodedTx, coinbase) panicOnErrorOrInvalidOrFailedState(res, err) } -// Run tries to run an rlpencoded evm transaction and +// 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 { + // capture open tracing span defer h.backend.StartChildSpan(trace.FVMEVMRun).End() res, err := h.run(rlpEncodedTx, coinbase) panicOnError(err) + + // return the result summary return res.ResultSummary() } @@ -137,46 +160,41 @@ func (h *ContractHandler) Run(rlpEncodedTx []byte, coinbase types.Address) *type // 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, + coinbase 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) - resSummary := make([]*types.ResultSummary, len(res)) + // convert results into result summaries + resSummaries := make([]*types.ResultSummary, len(res)) for i, r := range res { - resSummary[i] = r.ResultSummary() + resSummaries[i] = r.ResultSummary() } - return resSummary + return resSummaries } -func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Address) ([]*types.Result, error) { - // prepare block view used to run the batch - ctx, err := h.getBlockContext() - if err != nil { - return nil, err - } - - ctx.GasFeeCollector = coinbase - blk, err := h.emulator.NewBlockView(ctx) - if err != nil { - return nil, err - } - - bp, err := h.blockStore.BlockProposal() - if err != nil { - return nil, err - } - - // decode all transactions and calculate total gas limit +func (h *ContractHandler) batchRun( + rlpEncodedTxs [][]byte, + coinbase types.Address, +) ([]*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 var totalGasLimit types.GasLimit batchLen := len(rlpEncodedTxs) txs := make([]*gethTypes.Transaction, batchLen) for i, rlpEncodedTx := range rlpEncodedTxs { tx, err := h.decodeTransaction(rlpEncodedTx) + // if any tx fails decoding revert the batch if err != nil { return nil, err } @@ -185,23 +203,38 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres totalGasLimit += types.GasLimit(tx.Gas()) } - // check if all transactions in the batch are below gas limit - err = h.checkGasLimit(totalGasLimit) + // step 2 - check if enough computation is available + // for the whole batch + err := h.checkGasLimit(totalGasLimit) if err != nil { return nil, err } - res, err := blk.BatchRunTransactions(txs) + // step 3 - prepare block context + ctx, err := h.getBlockContext() if err != nil { return nil, err } + ctx.GasFeeCollector = coinbase - // safety check for result - if len(res) == 0 { + // step 4 - create a block view + blk, err := h.emulator.NewBlockView(ctx) + if err != nil { + return nil, err + } + + // step 5 - batch run transactions + res, err := blk.BatchRunTransactions(txs) + if err != nil { + return nil, err + } + if len(res) == 0 { // safety check for result return nil, types.ErrUnexpectedEmptyResult } - // meter all the transaction gas usage and append hashes to the block + var hasAtLeastOneValid bool + // step 6 - meter all the transaction gas usage + // and append hashes to the block for _, r := range res { // meter gas anyway (even for invalid or failed states) err = h.meterGasUsage(r) @@ -211,20 +244,32 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres // include it in a block only if valid (not invalid) if !r.Invalid() { - bp.AppendTransaction(r) + hasAtLeastOneValid = true } } - // if there were no valid transactions skip emitting events - // and update the block proposal - if len(bp.TxHashes) == 0 { + // step 7 - if there were no valid transactions + // skip the rest of steps + if !hasAtLeastOneValid { return res, nil } + // load block proposal + bp, err := h.blockStore.BlockProposal() + if err != nil { + return nil, err + } + + // for valid transactions for i, r := range res { if r.Invalid() { // don't emit events for invalid tx continue } + + // step 8 - update block proposal + bp.AppendTransaction(r) + + // step 9 - emit transaction event err = h.emitEvent(events.NewTransactionEvent( r, rlpEncodedTxs[i], @@ -234,13 +279,14 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres return nil, err } - // step 4 - update metrics + // step 10 - report metrics h.backend.EVMTransactionExecuted( r.GasConsumed, false, r.Failed(), ) + // step 11 - collect traces h.tracer.Collect(r.TxHash) } @@ -253,7 +299,8 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres return res, nil } -// CommitBlockProposal commits the block proposal and add a new block to the EVM blockchain +// CommitBlockProposal commits the block proposal +// and add a new block to the EVM blockchain func (h *ContractHandler) CommitBlockProposal() { panicOnError(h.commitBlockProposal()) } @@ -297,44 +344,46 @@ func (h *ContractHandler) run( return nil, err } - // step 2 - run transaction + // step 2 - check if enough computation is available err = h.checkGasLimit(types.GasLimit(tx.Gas())) if err != nil { return nil, err } + // step 3 - prepare block context ctx, err := h.getBlockContext() if err != nil { return nil, err } ctx.GasFeeCollector = coinbase + + // step 4 - create a block view blk, err := h.emulator.NewBlockView(ctx) if err != nil { return nil, err } + // step 5 - run transaction res, err := blk.RunTransaction(tx) if err != nil { return nil, err } - - // safety check for result - if res == nil { + if res == nil { // safety check for result return nil, types.ErrUnexpectedEmptyResult } - // meter gas anyway (even for invalid or failed states) + // step 6 - meter gas anyway (even for invalid or failed states) err = h.meterGasUsage(res) if err != nil { return nil, err } - // if is invalid tx skip the next steps (forming block, ...) + // step 7 - skip the rest if is invalid tx if res.Invalid() { return res, nil } - // step 3 - update the block proposal + // step 8 - update the block proposal bp, err := h.blockStore.BlockProposal() if err != nil { return nil, err @@ -345,24 +394,26 @@ func (h *ContractHandler) run( return nil, err } - // step 4 - emit events + // step 9 - emit transaction event err = h.emitEvent(events.NewTransactionEvent(res, rlpEncodedTx, bp.Height)) if err != nil { return nil, err } - // step 4 - update metrics + // step 10 - report metrics h.backend.EVMTransactionExecuted( res.GasConsumed, false, res.Failed(), ) - // step 6 - collect traces + // step 11 - collect traces h.tracer.Collect(res.TxHash) + return res, nil } +// DryRun simulates execution of the provided RLP-encoded and unsigned transaction. func (h *ContractHandler) DryRun( rlpEncodedTx []byte, from types.Address, @@ -371,6 +422,7 @@ func (h *ContractHandler) DryRun( res, err := h.dryRun(rlpEncodedTx, from) panicOnError(err) + return res.ResultSummary() } @@ -405,15 +457,15 @@ func (h *ContractHandler) dryRun( if err != nil { return nil, err } - - // saftey check for result - if res == nil { + if res == nil { // safety check for result return nil, types.ErrUnexpectedEmptyResult } return res, nil } +// checkGasLimit checks if enough computation is left in the environment +// before attempting executing a evm operation func (h *ContractHandler) checkGasLimit(limit types.GasLimit) error { // check gas limit against what has been left on the transaction side if !h.backend.ComputationAvailable(environment.ComputationKindEVMGasUsage, uint(limit)) { @@ -480,40 +532,49 @@ func (h *ContractHandler) getBlockContext() (types.BlockContext, error) { } func (h *ContractHandler) executeAndHandleCall( - ctx types.BlockContext, call *types.DirectCall, totalSupplyDiff *big.Int, deductSupplyDiff bool, ) (*types.Result, error) { - // execute the call + // step 1 - check enough computation is available + if err := h.checkGasLimit(types.GasLimit(call.GasLimit)); err != nil { + return nil, err + } + + // step 2 - prepare the block context + ctx, err := h.getBlockContext() + if err != nil { + return nil, err + } + + // step 3 - create block view blk, err := h.emulator.NewBlockView(ctx) if err != nil { return nil, err } + // step 4 - run direct call res, err := blk.DirectCall(call) // check backend errors first if err != nil { return nil, err } - - // safety check for result - if res == nil { + if res == nil { // safety check for result return nil, types.ErrUnexpectedEmptyResult } - // gas meter even invalid or failed status + // step 5 - gas meter even invalid or failed status err = h.meterGasUsage(res) if err != nil { return nil, err } - // if is invalid skip the rest of states + // step 6 - if is invalid skip the rest of states if res.Invalid() { return res, nil } - // update block proposal + // step 7 - update block proposal bp, err := h.blockStore.BlockProposal() if err != nil { return nil, err @@ -522,6 +583,7 @@ func (h *ContractHandler) executeAndHandleCall( // append transaction to the block proposal bp.AppendTransaction(res) + // update total supply if applicable if res.Successful() && totalSupplyDiff != nil { if deductSupplyDiff { bp.TotalSupply = new(big.Int).Sub(bp.TotalSupply, totalSupplyDiff) @@ -539,12 +601,11 @@ func (h *ContractHandler) executeAndHandleCall( return nil, err } - // emit events + // step 8 - emit transaction event encoded, err := call.Encode() if err != nil { return nil, err } - err = h.emitEvent( events.NewTransactionEvent(res, encoded, bp.Height), ) @@ -552,15 +613,16 @@ func (h *ContractHandler) executeAndHandleCall( return nil, err } - // metric + // step 9 - report metrics h.backend.EVMTransactionExecuted( res.GasConsumed, true, res.Failed(), ) - // collect traces + // step 10 - collect traces h.tracer.Collect(res.TxHash) + return res, nil } @@ -593,7 +655,7 @@ func (a *Account) Address() types.Address { // Nonce returns the nonce of this account // // Note: we don't meter any extra computation given reading data -// from the storage already transalates into computation +// from the storage already translates into computation func (a *Account) Nonce() uint64 { nonce, err := a.nonce() panicOnError(err) @@ -617,7 +679,7 @@ func (a *Account) nonce() (uint64, error) { // Balance returns the balance of this account // // Note: we don't meter any extra computation given reading data -// from the storage already transalates into computation +// from the storage already translates into computation func (a *Account) Balance() types.Balance { bal, err := a.balance() panicOnError(err) @@ -642,7 +704,7 @@ func (a *Account) balance() (types.Balance, error) { // Code returns the code of this account // // Note: we don't meter any extra computation given reading data -// from the storage already transalates into computation +// from the storage already translates into computation func (a *Account) Code() types.Code { code, err := a.code() panicOnError(err) @@ -665,7 +727,7 @@ func (a *Account) code() (types.Code, error) { // CodeHash returns the code hash of this account // // Note: we don't meter any extra computation given reading data -// from the storage already transalates into computation +// from the storage already translates into computation func (a *Account) CodeHash() []byte { codeHash, err := a.codeHash() panicOnError(err) @@ -690,26 +752,20 @@ func (a *Account) codeHash() ([]byte, error) { func (a *Account) Deposit(v *types.FLOWTokenVault) { defer a.fch.backend.StartChildSpan(trace.FVMEVMDeposit).End() - res, err := a.deposit(v) - panicOnErrorOrInvalidOrFailedState(res, err) -} - -func (a *Account) deposit(v *types.FLOWTokenVault) (*types.Result, error) { bridge := a.fch.addressAllocator.NativeTokenBridgeAddress() bridgeAccount := a.fch.AccountByAddress(bridge, false) - - call := types.NewDepositCall( - bridge, - a.address, + // Note: its not an authorized call + res, err := a.fch.executeAndHandleCall( + types.NewDepositCall( + bridge, + a.address, + v.Balance(), + bridgeAccount.Nonce(), + ), v.Balance(), - bridgeAccount.Nonce(), + false, ) - ctx, err := a.precheck(false, types.GasLimit(call.GasLimit)) - if err != nil { - return nil, err - } - - return a.fch.executeAndHandleCall(ctx, call, v.Balance(), false) + panicOnErrorOrInvalidOrFailedState(res, err) } // Withdraw deducts the balance from the account and @@ -717,52 +773,34 @@ func (a *Account) deposit(v *types.FLOWTokenVault) (*types.Result, error) { func (a *Account) Withdraw(b types.Balance) *types.FLOWTokenVault { defer a.fch.backend.StartChildSpan(trace.FVMEVMWithdraw).End() - res, err := a.withdraw(b) - panicOnErrorOrInvalidOrFailedState(res, err) - - return types.NewFlowTokenVault(b) -} - -func (a *Account) withdraw(b types.Balance) (*types.Result, error) { - call := types.NewWithdrawCall( - a.fch.addressAllocator.NativeTokenBridgeAddress(), - a.address, + res, err := a.executeAndHandleAuthorizedCall( + types.NewWithdrawCall( + a.fch.addressAllocator.NativeTokenBridgeAddress(), + a.address, + b, + a.Nonce(), + ), b, - a.Nonce(), + true, ) + panicOnErrorOrInvalidOrFailedState(res, err) - ctx, err := a.precheck(true, types.GasLimit(call.GasLimit)) - if err != nil { - return nil, err - } - - // Don't allow withdraw for balances that has rounding error - if types.BalanceConvertionToUFix64ProneToRoundingError(b) { - return nil, types.ErrWithdrawBalanceRounding - } - - return a.fch.executeAndHandleCall(ctx, call, b, true) + return types.NewFlowTokenVault(b) } // Transfer transfers tokens between accounts func (a *Account) Transfer(to types.Address, balance types.Balance) { - res, err := a.transfer(to, balance) - panicOnErrorOrInvalidOrFailedState(res, err) -} - -func (a *Account) transfer(to types.Address, balance types.Balance) (*types.Result, error) { - call := types.NewTransferCall( - a.address, - to, - balance, - a.Nonce(), + res, err := a.executeAndHandleAuthorizedCall( + types.NewTransferCall( + a.address, + to, + balance, + a.Nonce(), + ), + nil, + false, ) - ctx, err := a.precheck(true, types.GasLimit(call.GasLimit)) - if err != nil { - return nil, err - } - - return a.fch.executeAndHandleCall(ctx, call, nil, false) + panicOnErrorOrInvalidOrFailedState(res, err) } // Deploy deploys a contract to the EVM environment @@ -770,27 +808,23 @@ func (a *Account) transfer(to types.Address, balance types.Balance) (*types.Resu // contained in the result summary as data and // the contract data is not controlled by the caller accounts func (a *Account) Deploy(code types.Code, gaslimit types.GasLimit, balance types.Balance) *types.ResultSummary { + // capture open tracing span defer a.fch.backend.StartChildSpan(trace.FVMEVMDeploy).End() - res, err := a.deploy(code, gaslimit, balance) + res, err := a.executeAndHandleAuthorizedCall( + types.NewDeployCall( + a.address, + code, + uint64(gaslimit), + balance, + a.Nonce(), + ), + nil, + false, + ) panicOnError(err) - return res.ResultSummary() -} -func (a *Account) deploy(code types.Code, gaslimit types.GasLimit, balance types.Balance) (*types.Result, error) { - ctx, err := a.precheck(true, gaslimit) - if err != nil { - return nil, err - } - - call := types.NewDeployCall( - a.address, - code, - uint64(gaslimit), - balance, - a.Nonce(), - ) - return a.fch.executeAndHandleCall(ctx, call, nil, false) + return res.ResultSummary() } // Call calls a smart contract function with the given data @@ -798,41 +832,35 @@ func (a *Account) deploy(code types.Code, gaslimit types.GasLimit, balance types // given it doesn't goes beyond what Flow transaction allows. // the balance would be deducted from the OFA account and would be transferred to the target address func (a *Account) Call(to types.Address, data types.Data, gaslimit types.GasLimit, balance types.Balance) *types.ResultSummary { + // capture open tracing span defer a.fch.backend.StartChildSpan(trace.FVMEVMCall).End() - res, err := a.call(to, data, gaslimit, balance) - panicOnError(err) - return res.ResultSummary() -} - -func (a *Account) call(to types.Address, data types.Data, gaslimit types.GasLimit, balance types.Balance) (*types.Result, error) { - ctx, err := a.precheck(true, gaslimit) - if err != nil { - return nil, err - } - call := types.NewContractCall( - a.address, - to, - data, - uint64(gaslimit), - balance, - a.Nonce(), + res, err := a.executeAndHandleAuthorizedCall( + types.NewContractCall( + a.address, + to, + data, + uint64(gaslimit), + balance, + a.Nonce(), + ), + nil, + false, ) + panicOnError(err) - return a.fch.executeAndHandleCall(ctx, call, nil, false) + return res.ResultSummary() } -func (a *Account) precheck(authorized bool, gaslimit types.GasLimit) (types.BlockContext, error) { - // check if account is authorized (i.e. is a COA) - if authorized && !a.isAuthorized { - return types.BlockContext{}, types.ErrUnauthorizedMethodCall - } - err := a.fch.checkGasLimit(gaslimit) - if err != nil { - return types.BlockContext{}, err +func (a *Account) executeAndHandleAuthorizedCall( + call *types.DirectCall, + totalSupplyDiff *big.Int, + deductSupplyDiff bool, +) (*types.Result, error) { + if !a.isAuthorized { + return nil, types.ErrUnauthorizedMethodCall } - - return a.fch.getBlockContext() + return a.fch.executeAndHandleCall(call, totalSupplyDiff, deductSupplyDiff) } func panicOnErrorOrInvalidOrFailedState(res *types.Result, err error) { diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index f11fe40c71d..364255c7376 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -12,7 +12,6 @@ import ( gethCommon "github.com/onflow/go-ethereum/common" gethCore "github.com/onflow/go-ethereum/core" gethTypes "github.com/onflow/go-ethereum/core/types" - "github.com/onflow/go-ethereum/core/vm" gethVM "github.com/onflow/go-ethereum/core/vm" gethParams "github.com/onflow/go-ethereum/params" "github.com/onflow/go-ethereum/rlp" @@ -284,7 +283,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { // do some changes address := testutils.RandomAddress(t) account := handler.AccountByAddress(address, true) - bal := types.OneFlowBalance + bal := types.OneFlowBalance() account.Deposit(types.NewFlowTokenVault(bal)) handler.CommitBlockProposal() @@ -328,7 +327,7 @@ func TestHandler_COA(t *testing.T) { zeroBalance := types.NewBalance(big.NewInt(0)) require.True(t, types.BalancesAreEqual(zeroBalance, foa.Balance())) - balance := types.OneFlowBalance + balance := types.OneFlowBalance() vault := types.NewFlowTokenVault(balance) foa.Deposit(vault) @@ -1114,7 +1113,7 @@ func TestHandler_TransactionRun(t *testing.T) { // mock some calls from := eoa.Address().ToCommon() tr.OnTxStart(nil, tx, from) - tr.OnEnter(0, byte(vm.ADD), from, *tx.To(), tx.Data(), 20, big.NewInt(2)) + tr.OnEnter(0, byte(gethVM.ADD), from, *tx.To(), tx.Data(), 20, big.NewInt(2)) tr.OnExit(0, []byte{0x02}, 200, nil, false) tr.OnTxEnd(result.Receipt(), nil) diff --git a/fvm/evm/types/account.go b/fvm/evm/types/account.go index eb224539909..08b3555420a 100644 --- a/fvm/evm/types/account.go +++ b/fvm/evm/types/account.go @@ -35,7 +35,7 @@ type Account interface { // works only for COAs Withdraw(Balance) *FLOWTokenVault - // Transfer is a utility method on top of call for transfering tokens to another account + // Transfer is a utility method on top of call for transferring tokens to another account Transfer(to Address, balance Balance) // Deploy deploys a contract to the environment @@ -49,7 +49,7 @@ type Account interface { // and the Flow transaction's computation limit. // The fees are deducted from the COA // and are transferred to the target address. - // if no data is provided it would behave as transfering tokens to the + // if no data is provided it would behave as transferring tokens to the // target address // works only for COAs Call(Address, Data, GasLimit, Balance) *ResultSummary diff --git a/fvm/evm/types/address.go b/fvm/evm/types/address.go index 8bae671de00..107a29ddd9e 100644 --- a/fvm/evm/types/address.go +++ b/fvm/evm/types/address.go @@ -15,13 +15,13 @@ import ( // FlowEVMSpecialAddressPrefixLen captures the number of prefix bytes with constant values for special accounts (extended precompiles and COAs). // -// The prefix length should insure a high-enough level of security against finding a preimage using the hash +// The prefix length should insure a high-enough level of security against finding a pre-image using the hash // function used for EVM addresses generation (Keccak256). This is required to avoid finding an EVM address // that is also a valid FlowEVM address. // The target (minimal) security in this case is the security level provided by EVM addresses. // Since EVM addresses are 160-bits long, they offer only 80 bits of security (collision resistance // offers the lowest level). -// A preimage resistance of 80 bits requires the prefix to be at least 80-bits long (i.e 10 bytes). +// A pre-image resistance of 80 bits requires the prefix to be at least 80-bits long (i.e 10 bytes). // When used as a prefix in EVM addresses (20-bytes long), a prefix length of 12 bytes // leaves a variable part of 8 bytes (64 bits). const FlowEVMSpecialAddressPrefixLen = 12 diff --git a/fvm/evm/types/balance.go b/fvm/evm/types/balance.go index e31d8966609..2dfdfc53ca6 100644 --- a/fvm/evm/types/balance.go +++ b/fvm/evm/types/balance.go @@ -16,17 +16,15 @@ var ( UFixToAttoConversionMultiplier = new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(UFixedToAttoConversionScale)), nil) OneFlowInUFix64 = cadence.UFix64(uint64(math.Pow(10, float64(UFixedScale)))) - OneFlow = new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(AttoScale)), nil) - OneFlowBalance = Balance(OneFlow) EmptyBalance = Balance(new(big.Int)) ) // Balance represents the balance of an address -// in the evm environment (Flow EVM), balances are kept in attoflow (1e-18 flow); +// in the evm environment (Flow EVM), balances are kept in atto-flow (1e-18 flow); // the smallest denomination of FLOW token (similar to how Wei is used to store Eth) // But A Cadence FLOW Vault uses a Cadence.UFix64 to store values in Flow, which means // 1e-8 is the smallest value that can be stored on the vault. -// The balance here considers the highest precision (attoflow) but utility +// The balance here considers the highest precision (atto-flow) but utility // function has been provided for conversion from/to UFix64 to prevent accidental // conversion errors and dealing with rounding errors. type Balance *big.Int @@ -36,7 +34,7 @@ func BalancesAreEqual(bal1, bal2 Balance) bool { return (*big.Int)(bal1).Cmp(bal2) == 0 } -// NewBalanceconstructs a new balance from an atto flow value +// NewBalance constructs a new balance from an atto-flow value func NewBalance(inp *big.Int) Balance { return Balance(inp) } @@ -87,12 +85,12 @@ func ConvertBalanceToUFix64(bal Balance) (value cadence.UFix64, roundedOff bool, // this should never happen err = fmt.Errorf("balance can't be casted to a uint64") } - return cadence.UFix64(converted.Uint64()), BalanceConvertionToUFix64ProneToRoundingError(bal), err + return cadence.UFix64(converted.Uint64()), BalanceConversionToUFix64ProneToRoundingError(bal), err } -// BalanceConvertionToUFix64ProneToRoundingError returns true +// BalanceConversionToUFix64ProneToRoundingError returns true // if casting to UFix64 could result in rounding error -func BalanceConvertionToUFix64ProneToRoundingError(bal Balance) bool { +func BalanceConversionToUFix64ProneToRoundingError(bal Balance) bool { return new(big.Int).Mod(bal, UFixToAttoConversionMultiplier).BitLen() != 0 } @@ -111,5 +109,20 @@ func AddBalance(bal1 Balance, bal2 Balance) (Balance, error) { // MakeABalanceInFlow makes a balance object that has `amount` Flow Token in it func MakeABalanceInFlow(amount uint64) Balance { - return NewBalance(new(big.Int).Mul(OneFlowBalance, new(big.Int).SetUint64(amount))) + return NewBalance(MakeBigIntInFlow(amount)) +} + +// MakeBigIntInFlow makes big int containing `amount` of Flow +func MakeBigIntInFlow(amount uint64) *big.Int { + return new(big.Int).Mul(OneFlowBalance(), new(big.Int).SetUint64(amount)) +} + +// OneFlow creates a big int including one flow +func OneFlow() *big.Int { + return new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(AttoScale)), nil) +} + +// OneFlowBalance creates a new balance including one flow +func OneFlowBalance() Balance { + return Balance(OneFlow()) } diff --git a/fvm/evm/types/balance_test.go b/fvm/evm/types/balance_test.go index 05604a62a3a..d01b086c3a1 100644 --- a/fvm/evm/types/balance_test.go +++ b/fvm/evm/types/balance_test.go @@ -13,7 +13,7 @@ import ( func TestBalance(t *testing.T) { // test attoflow to flow - bal := types.OneFlowBalance + bal := types.OneFlowBalance() require.Equal(t, bal, types.NewBalanceFromUFix64(types.OneFlowInUFix64)) // 100.0002 Flow @@ -23,7 +23,7 @@ func TestBalance(t *testing.T) { bb := types.NewBalanceFromUFix64(u) require.Equal(t, "100000200000000000000", types.BalanceToBigInt(bb).String()) - require.False(t, types.BalanceConvertionToUFix64ProneToRoundingError(bb)) + require.False(t, types.BalanceConversionToUFix64ProneToRoundingError(bb)) bret, roundedOff, err := types.ConvertBalanceToUFix64(bb) require.NoError(t, err) require.Equal(t, u, bret) @@ -32,7 +32,7 @@ func TestBalance(t *testing.T) { // rounded off flag bal = types.NewBalance(big.NewInt(1)) require.NoError(t, err) - require.True(t, types.BalanceConvertionToUFix64ProneToRoundingError(bal)) + require.True(t, types.BalanceConversionToUFix64ProneToRoundingError(bal)) bret, roundedOff, err = types.ConvertBalanceToUFix64(bal) require.NoError(t, err) require.Equal(t, cadence.UFix64(0), bret) diff --git a/fvm/evm/types/block.go b/fvm/evm/types/block.go index 395cc4f0685..6baacfb4761 100644 --- a/fvm/evm/types/block.go +++ b/fvm/evm/types/block.go @@ -98,7 +98,7 @@ var GenesisBlock = &Block{ var GenesisBlockHash, _ = GenesisBlock.Hash() // BlockProposal is a EVM block proposal -// holding all the iterim data of block before commitment +// holding all the interim data of block before commitment type BlockProposal struct { Block diff --git a/fvm/evm/types/call.go b/fvm/evm/types/call.go index 16451c5071f..fbcfdea8bc0 100644 --- a/fvm/evm/types/call.go +++ b/fvm/evm/types/call.go @@ -104,8 +104,8 @@ func (dc *DirectCall) Message() *gethCore.Message { // Transaction constructs a geth.Transaction from the direct call func (dc *DirectCall) Transaction() *gethTypes.Transaction { - // Since a direct call doesn't have a valid siganture - // and we need to somehow include the From feild for the purpose + // Since a direct call doesn't have a valid signature + // and we need to somehow include the From field for the purpose // of hash calculation. we define the canonical format by // using the FROM bytes to set the bytes for the R part of the tx (big endian), // S captures the subtype of transaction and V is set to DirectCallTxType (255). diff --git a/fvm/evm/types/errors.go b/fvm/evm/types/errors.go index 0bf79b01364..420da1be689 100644 --- a/fvm/evm/types/errors.go +++ b/fvm/evm/types/errors.go @@ -143,7 +143,7 @@ func IsAStateError(err error) bool { } // FatalError is used for any error that is not user related and something -// unusual has happend. Usually we stop the node when this happens +// unusual has happened. Usually we stop the node when this happens // given it might have a non-deterministic root. type FatalError struct { err error diff --git a/fvm/evm/types/proof.go b/fvm/evm/types/proof.go index 8ba9ffebfe8..c0d915af70e 100644 --- a/fvm/evm/types/proof.go +++ b/fvm/evm/types/proof.go @@ -131,8 +131,8 @@ func (proof *COAOwnershipProofInContext) ToCadenceValues() []cadence.Value { // COAOwnershipProof is a proof that a flow account // controls a COA resource. To do so, the flow // account (Address is address of this account) -// provides signatures (with proper total weights) over an arbitary data input -// set by proof requester. KeyIndicies captures, +// provides signatures (with proper total weights) over an arbitrary data input +// set by proof requester. KeyIndices captures, // which account keys has been used for signatures. // Beside signatures, it provides the CapabilityPath // where the resource EVMAddress capability is stored. @@ -153,8 +153,8 @@ func COAOwnershipProofSignatureCountFromEncoded(data []byte) (int, error) { if err != nil { return 0, err } - // first encoded item is KeyIndicies - // so reading number of elements in the key indicies + // first encoded item is KeyIndices + // so reading number of elements in the key indices // should return the count without the need to fully decode KeyIndices, _, err := cadenceRLP.DecodeList(encodedItems[0], 0) return len(KeyIndices), err diff --git a/fvm/evm/types/tokenVault_test.go b/fvm/evm/types/tokenVault_test.go index 2dfefcf672b..02a57ecfe07 100644 --- a/fvm/evm/types/tokenVault_test.go +++ b/fvm/evm/types/tokenVault_test.go @@ -9,16 +9,15 @@ import ( ) func TestVault(t *testing.T) { - vault1 := types.NewFlowTokenVault(types.MakeABalanceInFlow(3)) - vault2, err := vault1.Withdraw(types.OneFlowBalance) + vault2, err := vault1.Withdraw(types.OneFlowBalance()) require.NoError(t, err) require.Equal(t, types.MakeABalanceInFlow(2), vault1.Balance()) - require.Equal(t, types.OneFlowBalance, vault2.Balance()) + require.Equal(t, types.OneFlowBalance(), vault2.Balance()) - toBeDeposited := types.NewFlowTokenVault(types.OneFlowBalance) + toBeDeposited := types.NewFlowTokenVault(types.OneFlowBalance()) err = vault1.Deposit(toBeDeposited) require.NoError(t, err) require.Equal(t, types.MakeABalanceInFlow(3), vault1.Balance()) diff --git a/integration/benchmark/load/evm_batch_load.go b/integration/benchmark/load/evm_batch_load.go index 81ef2c7b2c0..1cbaa7800ae 100644 --- a/integration/benchmark/load/evm_batch_load.go +++ b/integration/benchmark/load/evm_batch_load.go @@ -376,7 +376,7 @@ func (l *EVMBatchTransferLoad) transferTransaction( gasPrice := big.NewInt(0) oneFlow := cadence.UFix64(100_000_000) - amount := new(big.Int).Div(EVMBatchTypes.OneFlowBalance, big.NewInt(int64(oneFlow))) + amount := new(big.Int).Div(EVMBatchTypes.OneFlowBalance(), big.NewInt(int64(oneFlow))) batchCount := int(l.TransfersPerTransaction) diff --git a/integration/benchmark/load/evm_load.go b/integration/benchmark/load/evm_load.go index 571e1d67510..241c315bea6 100644 --- a/integration/benchmark/load/evm_load.go +++ b/integration/benchmark/load/evm_load.go @@ -383,7 +383,7 @@ func (l *EVMTransferLoad) transferTransaction( gasPrice := big.NewInt(0) oneFlow := cadence.UFix64(100_000_000) - amount := new(big.Int).Div(evmTypes.OneFlowBalance, big.NewInt(int64(oneFlow))) + amount := new(big.Int).Div(evmTypes.OneFlowBalance(), big.NewInt(int64(oneFlow))) evmTx := types.NewTx(&types.LegacyTx{Nonce: nonce, To: &to, Value: amount, Gas: params.TxGas, GasPrice: gasPrice, Data: nil}) signed, err := types.SignTx(evmTx, emulator.GetDefaultSigner(), eoa.pk)