diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go new file mode 100644 index 00000000000..74f012839a9 --- /dev/null +++ b/fvm/evm/emulator/config.go @@ -0,0 +1,175 @@ +package emulator + +import ( + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +var ( + FlowEVMTestnetChainID = big.NewInt(666) + FlowEVMMainnetChainID = big.NewInt(777) + BlockLevelGasLimit = uint64(math.MaxUint64) + zero = uint64(0) +) + +// Config sets the required parameters +type Config struct { + // Chain Config + ChainConfig *params.ChainConfig + // EVM config + EVMConfig vm.Config + // block context + BlockContext *vm.BlockContext + // transaction context + TxContext *vm.TxContext + // base unit of gas for direct calls + DirectCallBaseGasUsage uint64 +} + +// DefaultChainConfig is the default chain config which +// considers majority of EVM upgrades (e.g. Shanghai update) already been applied +// this has done through setting the height of these changes +// to zero nad setting the time for some other changes to zero +// For the future changes of EVM, we need to update the EVM go mod version +// and set a proper height for the specific release based on the Flow EVM heights +// so it could gets activated at a desired time. +var DefaultChainConfig = ¶ms.ChainConfig{ + ChainID: FlowEVMTestnetChainID, // default is testnet + + // Fork scheduling based on block heights + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), // already on Byzantium + ConstantinopleBlock: big.NewInt(0), // already on Constantinople + PetersburgBlock: big.NewInt(0), // already on Petersburg + IstanbulBlock: big.NewInt(0), // already on Istanbul + BerlinBlock: big.NewInt(0), // already on Berlin + LondonBlock: big.NewInt(0), // already on London + MuirGlacierBlock: big.NewInt(0), // already on MuirGlacier + + // Fork scheduling based on timestamps + ShanghaiTime: &zero, // already on Shanghai + CancunTime: &zero, // already on Cancun + PragueTime: &zero, // already on Prague +} + +func defaultConfig() *Config { + return &Config{ + ChainConfig: DefaultChainConfig, + EVMConfig: vm.Config{ + NoBaseFee: true, + }, + TxContext: &vm.TxContext{}, + BlockContext: &vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GasLimit: BlockLevelGasLimit, // block gas limit + BaseFee: big.NewInt(0), + GetHash: func(n uint64) common.Hash { // default returns some random hash values + return common.BytesToHash(crypto.Keccak256([]byte(new(big.Int).SetUint64(n).String()))) + }, + }, + } +} + +// NewConfig initializes a new config +func NewConfig(opts ...Option) *Config { + ctx := defaultConfig() + for _, applyOption := range opts { + ctx = applyOption(ctx) + } + return ctx +} + +type Option func(*Config) *Config + +// WithMainnetChainID sets the chain ID to flow evm testnet +func WithTestnetChainID() Option { + return func(c *Config) *Config { + c.ChainConfig.ChainID = FlowEVMTestnetChainID + return c + } +} + +// WithMainnetChainID sets the chain ID to flow evm mainnet +func WithMainnetChainID() Option { + return func(c *Config) *Config { + c.ChainConfig.ChainID = FlowEVMMainnetChainID + return c + } + +} + +// WithOrigin sets the origin of the transaction (signer) +func WithOrigin(origin common.Address) Option { + return func(c *Config) *Config { + c.TxContext.Origin = origin + return c + } +} + +// WithGasPrice sets the gas price for the transaction (usually the one sets by the sender) +func WithGasPrice(gasPrice *big.Int) Option { + return func(c *Config) *Config { + c.TxContext.GasPrice = gasPrice + return c + } +} + +// WithGasLimit sets the gas limit of the transaction +func WithGasLimit(gasLimit uint64) Option { + return func(c *Config) *Config { + c.BlockContext.GasLimit = gasLimit + return c + } +} + +// WithCoinbase sets the coinbase of the block where the fees are collected in +func WithCoinbase(coinbase common.Address) Option { + return func(c *Config) *Config { + c.BlockContext.Coinbase = coinbase + return c + } +} + +// WithBlockNumber sets the block height in the block context +func WithBlockNumber(blockNumber *big.Int) Option { + return func(c *Config) *Config { + c.BlockContext.BlockNumber = blockNumber + return c + } +} + +// WithBlockTime sets the block time in the block context +func WithBlockTime(time uint64) Option { + return func(c *Config) *Config { + c.BlockContext.Time = time + return c + } +} + +// WithGetBlockHashFunction sets the functionality to look up block hash by height +func WithGetBlockHashFunction(getHash vm.GetHashFunc) Option { + return func(c *Config) *Config { + c.BlockContext.GetHash = getHash + return c + } +} + +// WithDirectCallBaseGasUsage sets the base direct call gas usage +func WithDirectCallBaseGasUsage(gas uint64) Option { + return func(c *Config) *Config { + c.DirectCallBaseGasUsage = gas + return c + } +} diff --git a/fvm/evm/emulator/database/database.go b/fvm/evm/emulator/database/database.go index a7e2ae39287..f47e32b7174 100644 --- a/fvm/evm/emulator/database/database.go +++ b/fvm/evm/emulator/database/database.go @@ -395,6 +395,7 @@ func handleError(err error) error { return nil } var atreeFatalError *atree.FatalError + // if is a atree fatal error or fvm fatal error (the second one captures external errors) if stdErrors.As(err, &atreeFatalError) || errors.IsFailure(err) { return types.NewFatalError(err) } diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go new file mode 100644 index 00000000000..9371562ef63 --- /dev/null +++ b/fvm/evm/emulator/emulator.go @@ -0,0 +1,318 @@ +package emulator + +import ( + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethCore "github.com/ethereum/go-ethereum/core" + gethRawDB "github.com/ethereum/go-ethereum/core/rawdb" + gethState "github.com/ethereum/go-ethereum/core/state" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethVM "github.com/ethereum/go-ethereum/core/vm" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +// Emulator handles operations against evm runtime +type Emulator struct { + Database types.Database +} + +var _ types.Emulator = &Emulator{} + +// NewEmulator constructs a new EVM Emulator +func NewEmulator( + db types.Database, +) *Emulator { + return &Emulator{ + Database: db, + } +} + +func newConfig(ctx types.BlockContext) *Config { + return NewConfig( + WithBlockNumber(new(big.Int).SetUint64(ctx.BlockNumber)), + WithCoinbase(ctx.GasFeeCollector.ToCommon()), + WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage), + ) +} + +// NewReadOnlyBlockView constructs a new readonly block view +func (em *Emulator) NewReadOnlyBlockView(ctx types.BlockContext) (types.ReadOnlyBlockView, error) { + execState, err := newState(em.Database) + return &ReadOnlyBlockView{ + state: execState, + }, err +} + +// NewBlockView constructs a new block view (mutable) +func (em *Emulator) NewBlockView(ctx types.BlockContext) (types.BlockView, error) { + cfg := newConfig(ctx) + return &BlockView{ + config: cfg, + database: em.Database, + }, nil +} + +// ReadOnlyBlockView provides a read only view of a block +// could be used multiple times for queries +type ReadOnlyBlockView struct { + state *gethState.StateDB +} + +// BalanceOf returns the balance of the given address +func (bv *ReadOnlyBlockView) BalanceOf(address types.Address) (*big.Int, error) { + return bv.state.GetBalance(address.ToCommon()), nil +} + +// CodeOf returns the code of the given address +func (bv *ReadOnlyBlockView) CodeOf(address types.Address) (types.Code, error) { + return bv.state.GetCode(address.ToCommon()), nil +} + +// NonceOf returns the nonce of the given address +func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) { + return bv.state.GetNonce(address.ToCommon()), nil +} + +// BlockView allows mutation of the evm state as part of a block +// +// TODO: allow multiple calls per block view +// TODO: add block level commit (separation of trie commit to storage) +type BlockView struct { + config *Config + database types.Database +} + +// DirectCall executes a direct call +func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) { + proc, err := bl.newProcedure() + if err != nil { + return nil, err + } + var res *types.Result + switch call.SubType { + case types.DepositCallSubType: + res, err = proc.mintTo(call.To, call.Value) + case types.WithdrawCallSubType: + res, err = proc.withdrawFrom(call.From, call.Value) + default: + res, err = proc.run(call.Message(), types.DirectCallTxType) + } + if err != nil { + return res, err + } + return res, bl.commit(res.StateRootHash) +} + +// RunTransaction runs an evm transaction +func (bl *BlockView) RunTransaction( + tx *gethTypes.Transaction, +) (*types.Result, error) { + var err error + proc, err := bl.newProcedure() + if err != nil { + return nil, err + } + + msg, err := gethCore.TransactionToMessage(tx, GetSigner(bl.config), proc.config.BlockContext.BaseFee) + if err != nil { + // note that this is not a fatal error (e.g. due to bad signature) + // not a valid transaction + return nil, types.NewEVMValidationError(err) + } + + // update tx context origin + proc.evm.TxContext.Origin = msg.From + res, err := proc.run(msg, tx.Type()) + if err != nil { + return res, err + } + + return res, bl.commit(res.StateRootHash) +} + +func (bl *BlockView) newProcedure() (*procedure, error) { + execState, err := newState(bl.database) + if err != nil { + return nil, err + } + cfg := bl.config + return &procedure{ + config: cfg, + evm: gethVM.NewEVM( + *cfg.BlockContext, + *cfg.TxContext, + execState, + cfg.ChainConfig, + cfg.EVMConfig, + ), + state: execState, + }, nil +} + +func (bl *BlockView) commit(rootHash gethCommon.Hash) error { + // commit atree changes back to the backend + err := bl.database.Commit(rootHash) + return handleCommitError(err) +} + +type procedure struct { + config *Config + evm *gethVM.EVM + state *gethState.StateDB +} + +// commit commits the changes to the state. +func (proc *procedure) commit() (gethCommon.Hash, error) { + // commits the changes from the journal into the in memory trie. + // in the future if we want to move this to the block level we could use finalize + // to get the root hash + newRoot, err := proc.state.Commit(true) + if err != nil { + return gethTypes.EmptyRootHash, handleCommitError(err) + } + + // flush the trie to the lower level db + // the reason we have to do this, is the original database + // is designed to keep changes in memory until the state.Commit + // is called then the changes moves into the trie, but the trie + // would stay in memory for faster transaction execution. you + // have to explicitly ask the trie to commit to the underlying storage + err = proc.state.Database().TrieDB().Commit(newRoot, false) + if err != nil { + return gethTypes.EmptyRootHash, handleCommitError(err) + } + return newRoot, nil +} + +func handleCommitError(err error) error { + if err == nil { + return nil + } + // if known types (database errors) don't do anything and return + if types.IsAFatalError(err) || types.IsADatabaseError(err) { + return err + } + + // else is a new fatal error + return types.NewFatalError(err) +} + +func (proc *procedure) mintTo(address types.Address, amount *big.Int) (*types.Result, error) { + var err error + addr := address.ToCommon() + res := &types.Result{ + GasConsumed: proc.config.DirectCallBaseGasUsage, + TxType: types.DirectCallTxType, + } + + // create account if not exist + if !proc.state.Exist(addr) { + proc.state.CreateAccount(addr) + } + + // add balance + proc.state.AddBalance(addr, amount) + + // we don't need to increment any nonce, given the origin doesn't exist + res.StateRootHash, err = proc.commit() + + return res, err +} + +func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*types.Result, error) { + var err error + + addr := address.ToCommon() + res := &types.Result{ + GasConsumed: proc.config.DirectCallBaseGasUsage, + TxType: types.DirectCallTxType, + } + + // check if account exists + // while this method is only called from bridged accounts + // it might be the case that someone creates a bridged account + // and never transfer tokens to and call for withdraw + if !proc.state.Exist(addr) { + return res, types.ErrAccountDoesNotExist + } + + // check the source account balance + // if balance is lower than amount needed for withdrawal, error out + if proc.state.GetBalance(addr).Cmp(amount) < 0 { + return res, types.ErrInsufficientBalance + } + + // sub balance + proc.state.SubBalance(addr, amount) + + // we increment the nonce for source account cause + // withdraw counts as a transaction + nonce := proc.state.GetNonce(addr) + proc.state.SetNonce(addr, nonce+1) + + res.StateRootHash, err = proc.commit() + return res, err +} + +func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, error) { + res := types.Result{ + TxType: txType, + } + + gasPool := (*gethCore.GasPool)(&proc.config.BlockContext.GasLimit) + execResult, err := gethCore.NewStateTransition( + proc.evm, + msg, + gasPool, + ).TransitionDb() + if err != nil { + res.Failed = true + // if the error is a fatal error or a non-fatal database error return it + if types.IsAFatalError(err) || types.IsADatabaseError(err) { + return &res, err + } + // otherwise is a validation error (pre-check failure) + // no state change, wrap the error and return + return &res, types.NewEVMValidationError(err) + } + + // if prechecks are passed, the exec result won't be nil + if execResult != nil { + res.GasConsumed = execResult.UsedGas + if !execResult.Failed() { // collect vm errors + res.ReturnedValue = execResult.ReturnData + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + res.DeployedContractAddress = types.NewAddress(gethCrypto.CreateAddress(msg.From, msg.Nonce)) + } + res.Logs = proc.state.Logs() + } else { + res.Failed = true + err = types.NewEVMExecutionError(execResult.Err) + } + } + var commitErr error + res.StateRootHash, commitErr = proc.commit() + if commitErr != nil { + return &res, commitErr + } + return &res, err +} + +// Ramtin: this is the part of the code that we have to update if we hit performance problems +// the NewDatabase from the RawDB might have to change. +func newState(database types.Database) (*gethState.StateDB, error) { + root, err := database.GetRootHash() + if err != nil { + return nil, err + } + + return gethState.New(root, + gethState.NewDatabase( + gethRawDB.NewDatabase(database), + ), + nil) +} diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go new file mode 100644 index 00000000000..2169b96e630 --- /dev/null +++ b/fvm/evm/emulator/emulator_test.go @@ -0,0 +1,371 @@ +package emulator_test + +import ( + "fmt" + "math" + "math/big" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethParams "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/emulator" + "github.com/onflow/flow-go/fvm/evm/emulator/database" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +var blockNumber = big.NewInt(10) +var defaultCtx = types.NewDefaultBlockContext(blockNumber.Uint64()) + +func RunWithTestDB(t testing.TB, f func(types.Database)) { + testutils.RunWithTestBackend(t, func(backend types.Backend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { + db, err := database.NewDatabase(backend, flowEVMRoot) + require.NoError(t, err) + f(db) + }) + }) +} + +func RunWithNewEmulator(t testing.TB, db types.Database, f func(*emulator.Emulator)) { + env := emulator.NewEmulator(db) + f(env) +} + +func RunWithNewBlockView(t testing.TB, em *emulator.Emulator, f func(blk types.BlockView)) { + blk, err := em.NewBlockView(defaultCtx) + require.NoError(t, err) + f(blk) +} + +func RunWithNewReadOnlyBlockView(t testing.TB, em *emulator.Emulator, f func(blk types.ReadOnlyBlockView)) { + blk, err := em.NewReadOnlyBlockView(defaultCtx) + require.NoError(t, err) + f(blk) +} + +func TestNativeTokenBridging(t *testing.T) { + RunWithTestDB(t, func(db types.Database) { + originalBalance := big.NewInt(10000) + testAccount := types.NewAddressFromString("test") + + t.Run("mint tokens to the first account", func(t *testing.T) { + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance)) + require.NoError(t, err) + require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + }) + }) + }) + t.Run("mint tokens withdraw", func(t *testing.T) { + amount := big.NewInt(1000) + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + retBalance, err := blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, originalBalance, retBalance) + }) + }) + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount)) + require.NoError(t, err) + require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed) + }) + }) + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + retBalance, err := blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, amount.Sub(originalBalance, amount), retBalance) + }) + }) + }) + }) + +} + +func TestContractInteraction(t *testing.T) { + RunWithTestDB(t, func(db types.Database) { + + testContract := testutils.GetTestContract(t) + + testAccount := types.NewAddressFromString("test") + amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) + amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) + + // fund test account + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount, amount)) + require.NoError(t, err) + }) + }) + + var contractAddr types.Address + + t.Run("deploy contract", func(t *testing.T) { + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewDeployCall( + testAccount, + testContract.ByteCode, + math.MaxUint64, + amountToBeTransfered), + ) + require.NoError(t, err) + contractAddr = res.DeployedContractAddress + }) + RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) { + require.NotNil(t, contractAddr) + retCode, err := blk.CodeOf(contractAddr) + require.NoError(t, err) + require.NotEmpty(t, retCode) + + retBalance, err := blk.BalanceOf(contractAddr) + require.NoError(t, err) + require.Equal(t, amountToBeTransfered, retBalance) + + retBalance, err = blk.BalanceOf(testAccount) + require.NoError(t, err) + require.Equal(t, amount.Sub(amount, amountToBeTransfered), retBalance) + }) + }) + }) + + t.Run("call contract", func(t *testing.T) { + num := big.NewInt(10) + + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeStoreCallData(t, num), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000)) + }) + }) + + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeRetrieveCallData(t), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + + ret := new(big.Int).SetBytes(res.ReturnedValue) + require.Equal(t, num, ret) + require.GreaterOrEqual(t, res.GasConsumed, uint64(23_000)) + }) + }) + + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + res, err := blk.DirectCall( + types.NewContractCall( + testAccount, + contractAddr, + testContract.MakeBlockNumberCallData(t), + 1_000_000, + big.NewInt(0), // this should be zero because the contract doesn't have receiver + ), + ) + require.NoError(t, err) + + ret := new(big.Int).SetBytes(res.ReturnedValue) + require.Equal(t, blockNumber, ret) + }) + }) + + }) + + t.Run("test sending transactions (happy case)", func(t *testing.T) { + account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) + fAddr := account.Address() + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + require.NoError(t, err) + }) + }) + + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + ctx.GasFeeCollector = types.NewAddressFromString("coinbase") + coinbaseOrgBalance := gethCommon.Big1 + // small amount of money to create account + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance)) + require.NoError(t, err) + }) + + blk, err := env.NewBlockView(ctx) + require.NoError(t, err) + tx := account.PrepareAndSignTx( + t, + testAccount.ToCommon(), // to + nil, // data + big.NewInt(1000), // amount + gethParams.TxGas, // gas limit + gethCommon.Big1, // gas fee + + ) + _, err = blk.RunTransaction(tx) + require.NoError(t, err) + + // check the balance of coinbase + RunWithNewReadOnlyBlockView(t, env, func(blk2 types.ReadOnlyBlockView) { + bal, err := blk2.BalanceOf(ctx.GasFeeCollector) + require.NoError(t, err) + expected := gethParams.TxGas*gethCommon.Big1.Uint64() + gethCommon.Big1.Uint64() + require.Equal(t, expected, bal.Uint64()) + + nonce, err := blk2.NonceOf(fAddr) + require.NoError(t, err) + require.Equal(t, 1, int(nonce)) + }) + }) + }) + t.Run("test sending transactions (invalid nonce)", func(t *testing.T) { + account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex) + fAddr := account.Address() + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + RunWithNewBlockView(t, env, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(fAddr, amount)) + require.NoError(t, err) + }) + }) + + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + blk, err := env.NewBlockView(ctx) + require.NoError(t, err) + tx := account.SignTx(t, + gethTypes.NewTransaction( + 100, // nonce + testAccount.ToCommon(), // to + big.NewInt(1000), // amount + gethParams.TxGas, // gas limit + gethCommon.Big1, // gas fee + nil, // data + ), + ) + _, err = blk.RunTransaction(tx) + require.Error(t, err) + require.True(t, types.IsEVMValidationError(err)) + }) + }) + + t.Run("test sending transactions (bad signature)", func(t *testing.T) { + RunWithNewEmulator(t, db, func(env *emulator.Emulator) { + ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) + blk, err := env.NewBlockView(ctx) + require.NoError(t, err) + tx := gethTypes.NewTx(&gethTypes.LegacyTx{ + Nonce: 0, + GasPrice: gethCommon.Big1, + Gas: gethParams.TxGas, // gas limit + To: nil, // to + Value: big.NewInt(1000), // amount + Data: nil, // data + V: big.NewInt(1), + R: big.NewInt(2), + S: big.NewInt(3), + }) + _, err = blk.RunTransaction(tx) + require.Error(t, err) + require.True(t, types.IsEVMValidationError(err)) + }) + }) + + }) +} + +func TestTransfers(t *testing.T) { + RunWithTestDB(t, func(db types.Database) { + testAccount1 := types.NewAddressFromString("test1") + testAccount2 := types.NewAddressFromString("test2") + + amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether)) + amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether)) + + RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount)) + require.NoError(t, err) + }) + }) + + RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered)) + require.NoError(t, err) + }) + }) + + RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + RunWithNewReadOnlyBlockView(t, em, func(blk types.ReadOnlyBlockView) { + bal, err := blk.BalanceOf(testAccount2) + require.NoError(t, err) + require.Equal(t, amountToBeTransfered.Uint64(), bal.Uint64()) + + bal, err = blk.BalanceOf(testAccount1) + require.NoError(t, err) + require.Equal(t, new(big.Int).Sub(amount, amountToBeTransfered).Uint64(), bal.Uint64()) + }) + }) + }) +} + +func TestDatabaseErrorHandling(t *testing.T) { + + t.Run("test non-fatal db error handling", func(t *testing.T) { + db := &testutils.TestDatabase{ + GetRootHashFunc: func() (gethCommon.Hash, error) { + return gethTypes.EmptyRootHash, types.NewDatabaseError(fmt.Errorf("some non-fatal error")) + }, + } + + RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, big.NewInt(1))) + require.Error(t, err) + require.True(t, types.IsADatabaseError(err)) + }) + }) + }) + + t.Run("test fatal db error handling", func(t *testing.T) { + db := &testutils.TestDatabase{ + GetRootHashFunc: func() (gethCommon.Hash, error) { + return gethTypes.EmptyRootHash, types.NewFatalError(fmt.Errorf("some non-fatal error")) + }, + } + + RunWithNewEmulator(t, db, func(em *emulator.Emulator) { + RunWithNewBlockView(t, em, func(blk types.BlockView) { + _, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, big.NewInt(1))) + require.Error(t, err) + require.True(t, types.IsAFatalError(err)) + }) + }) + }) +} diff --git a/fvm/evm/emulator/signer.go b/fvm/evm/emulator/signer.go new file mode 100644 index 00000000000..44b2964f843 --- /dev/null +++ b/fvm/evm/emulator/signer.go @@ -0,0 +1,28 @@ +package emulator + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" +) + +var defaultBlockNumberForEVMRules = big.NewInt(1) // anything bigger than 0 + +// GetDefaultSigner returns a signer which is compatible with the default config +func GetDefaultSigner() types.Signer { + cfg := NewConfig(WithBlockNumber(defaultBlockNumberForEVMRules)) + return GetSigner(cfg) +} + +// GetSigner returns a evm signer object that is compatible with the given config +// +// Despite its misleading name, signer encapsulates transaction signature validation functionality and +// does not provide actual signing functionality. +// we kept the same name to be consistent with EVM naming. +func GetSigner(cfg *Config) types.Signer { + return types.MakeSigner( + cfg.ChainConfig, + cfg.BlockContext.BlockNumber, + cfg.BlockContext.Time, + ) +} diff --git a/fvm/evm/testutils/accounts.go b/fvm/evm/testutils/accounts.go new file mode 100644 index 00000000000..237474da400 --- /dev/null +++ b/fvm/evm/testutils/accounts.go @@ -0,0 +1,135 @@ +package testutils + +import ( + "bytes" + "crypto/ecdsa" + "io" + "math/big" + "sync" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + + "github.com/onflow/atree" + + "github.com/onflow/flow-go/fvm/evm/emulator" + "github.com/onflow/flow-go/fvm/evm/emulator/database" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +// address: 658bdf435d810c91414ec09147daa6db62406379 +const EOATestAccount1KeyHex = "9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c" + +type EOATestAccount struct { + address gethCommon.Address + key *ecdsa.PrivateKey + nonce uint64 + signer gethTypes.Signer + lock sync.Mutex +} + +func (a *EOATestAccount) Address() types.Address { + return types.Address(a.address) +} + +func (a *EOATestAccount) PrepareSignAndEncodeTx( + t testing.TB, + to gethCommon.Address, + data []byte, + amount *big.Int, + gasLimit uint64, + gasPrice *big.Int, +) []byte { + tx := a.PrepareAndSignTx(t, to, data, amount, gasLimit, gasPrice) + var b bytes.Buffer + writer := io.Writer(&b) + err := tx.EncodeRLP(writer) + require.NoError(t, err) + return b.Bytes() +} + +func (a *EOATestAccount) PrepareAndSignTx( + t testing.TB, + to gethCommon.Address, + data []byte, + amount *big.Int, + gasLimit uint64, + gasPrice *big.Int, +) *gethTypes.Transaction { + a.lock.Lock() + defer a.lock.Unlock() + + tx := a.signTx( + t, + gethTypes.NewTransaction( + a.nonce, + to, + amount, + gasLimit, + gasPrice, + data, + ), + ) + a.nonce++ + + return tx +} + +func (a *EOATestAccount) SignTx( + t testing.TB, + tx *gethTypes.Transaction, +) *gethTypes.Transaction { + a.lock.Lock() + defer a.lock.Unlock() + + return a.signTx(t, tx) +} + +func (a *EOATestAccount) signTx( + t testing.TB, + tx *gethTypes.Transaction, +) *gethTypes.Transaction { + tx, err := gethTypes.SignTx(tx, a.signer, a.key) + require.NoError(t, err) + return tx +} + +func GetTestEOAAccount(t testing.TB, keyHex string) *EOATestAccount { + key, _ := gethCrypto.HexToECDSA(keyHex) + address := gethCrypto.PubkeyToAddress(key.PublicKey) + signer := emulator.GetDefaultSigner() + return &EOATestAccount{ + address: address, + key: key, + signer: signer, + lock: sync.Mutex{}, + } +} + +func RunWithEOATestAccount(t *testing.T, led atree.Ledger, flowEVMRootAddress flow.Address, f func(*EOATestAccount)) { + account := GetTestEOAAccount(t, EOATestAccount1KeyHex) + + // fund account + db, err := database.NewDatabase(led, flowEVMRootAddress) + require.NoError(t, err) + + e := emulator.NewEmulator(db) + require.NoError(t, err) + + blk, err := e.NewBlockView(types.NewDefaultBlockContext(2)) + require.NoError(t, err) + + _, err = blk.DirectCall( + types.NewDepositCall( + account.Address(), + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1000)), + ), + ) + require.NoError(t, err) + + f(account) +} diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index fbcbf75bba7..477f1dc89fb 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -29,7 +29,7 @@ func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(fl func RunWithTestBackend(t testing.TB, f func(types.Backend)) { tb := &testBackend{ - TestValueStore: getSimpleValueStore(), + TestValueStore: GetSimpleValueStore(), testEventEmitter: getSimpleEventEmitter(), testMeter: getSimpleMeter(), } @@ -48,7 +48,7 @@ func fullKey(owner, key []byte) string { return string(owner) + "~" + string(key) } -func getSimpleValueStore() *TestValueStore { +func GetSimpleValueStore() *TestValueStore { data := make(map[string][]byte) allocator := make(map[string]uint64) diff --git a/fvm/evm/testutils/contract.go b/fvm/evm/testutils/contract.go new file mode 100644 index 00000000000..a4984974455 --- /dev/null +++ b/fvm/evm/testutils/contract.go @@ -0,0 +1,200 @@ +package testutils + +import ( + "encoding/hex" + "math" + "math/big" + "strings" + "testing" + + gethABI "github.com/ethereum/go-ethereum/accounts/abi" + gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/onflow/atree" + + "github.com/onflow/flow-go/fvm/evm/emulator" + "github.com/onflow/flow-go/fvm/evm/emulator/database" + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +type TestContract struct { + Code string + ABI string + ByteCode []byte + DeployedAt types.Address +} + +func (tc *TestContract) MakeStoreCallData(t *testing.T, num *big.Int) []byte { + abi, err := gethABI.JSON(strings.NewReader(tc.ABI)) + require.NoError(t, err) + store, err := abi.Pack("store", num) + require.NoError(t, err) + return store +} + +func (tc *TestContract) MakeRetrieveCallData(t *testing.T) []byte { + abi, err := gethABI.JSON(strings.NewReader(tc.ABI)) + require.NoError(t, err) + retrieve, err := abi.Pack("retrieve") + require.NoError(t, err) + return retrieve +} + +func (tc *TestContract) MakeBlockNumberCallData(t *testing.T) []byte { + abi, err := gethABI.JSON(strings.NewReader(tc.ABI)) + require.NoError(t, err) + blockNum, err := abi.Pack("blockNumber") + require.NoError(t, err) + return blockNum +} + +func (tc *TestContract) SetDeployedAt(deployedAt types.Address) { + tc.DeployedAt = deployedAt +} + +func GetTestContract(t *testing.T) *TestContract { + byteCodes, err := hex.DecodeString("608060405261022c806100136000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632e64cec11461005c57806348b151661461007a57806357e871e7146100985780636057361d146100b657806385df51fd146100d2575b600080fd5b610064610102565b6040516100719190610149565b60405180910390f35b61008261010b565b60405161008f9190610149565b60405180910390f35b6100a0610113565b6040516100ad9190610149565b60405180910390f35b6100d060048036038101906100cb9190610195565b61011b565b005b6100ec60048036038101906100e79190610195565b610125565b6040516100f991906101db565b60405180910390f35b60008054905090565b600042905090565b600043905090565b8060008190555050565b600081409050919050565b6000819050919050565b61014381610130565b82525050565b600060208201905061015e600083018461013a565b92915050565b600080fd5b61017281610130565b811461017d57600080fd5b50565b60008135905061018f81610169565b92915050565b6000602082840312156101ab576101aa610164565b5b60006101b984828501610180565b91505092915050565b6000819050919050565b6101d5816101c2565b82525050565b60006020820190506101f060008301846101cc565b9291505056fea26469706673582212203ee61567a25f0b1848386ae6b8fdbd7733c8a502c83b5ed305b921b7933f4e8164736f6c63430008120033") + require.NoError(t, err) + return &TestContract{ + Code: ` + contract Storage { + uint256 number; + constructor() payable { + } + function store(uint256 num) public { + number = num; + } + function retrieve() public view returns (uint256){ + return number; + } + function blockNumber() public view returns (uint256) { + return block.number; + } + function blockTime() public view returns (uint) { + return block.timestamp; + } + function blockHash(uint num) public view returns (bytes32) { + return blockhash(num); + } + } + `, + + ABI: ` + [ + { + "inputs": [], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "blockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blockTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "num", + "type": "uint256" + } + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] + `, + ByteCode: byteCodes, + } +} + +func RunWithDeployedContract(t *testing.T, led atree.Ledger, flowEVMRootAddress flow.Address, f func(*TestContract)) { + tc := GetTestContract(t) + // deploy contract + db, err := database.NewDatabase(led, flowEVMRootAddress) + require.NoError(t, err) + + e := emulator.NewEmulator(db) + + blk, err := e.NewBlockView(types.NewDefaultBlockContext(2)) + require.NoError(t, err) + + caller := types.NewAddress(gethCommon.Address{}) + _, err = blk.DirectCall( + types.NewDepositCall( + caller, + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1000)), + ), + ) + require.NoError(t, err) + + res, err := blk.DirectCall( + types.NewDeployCall( + caller, + tc.ByteCode, + math.MaxUint64, + big.NewInt(0), + ), + ) + require.NoError(t, err) + + tc.SetDeployedAt(res.DeployedContractAddress) + f(tc) +} diff --git a/fvm/evm/testutils/emulator.go b/fvm/evm/testutils/emulator.go new file mode 100644 index 00000000000..48b3e2218d7 --- /dev/null +++ b/fvm/evm/testutils/emulator.go @@ -0,0 +1,116 @@ +package testutils + +import ( + cryptoRand "crypto/rand" + "math/big" + "math/rand" + "testing" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/types" +) + +type TestEmulator struct { + BalanceOfFunc func(address types.Address) (*big.Int, error) + NonceOfFunc func(address types.Address) (uint64, error) + CodeOfFunc func(address types.Address) (types.Code, error) + DirectCallFunc func(call *types.DirectCall) (*types.Result, error) + RunTransactionFunc func(tx *gethTypes.Transaction) (*types.Result, error) +} + +var _ types.Emulator = &TestEmulator{} + +// NewBlock returns a new block +func (em *TestEmulator) NewBlockView(_ types.BlockContext) (types.BlockView, error) { + return em, nil +} + +// NewBlock returns a new block view +func (em *TestEmulator) NewReadOnlyBlockView(_ types.BlockContext) (types.ReadOnlyBlockView, error) { + return em, nil +} + +// BalanceOf returns the balance of this address +func (em *TestEmulator) BalanceOf(address types.Address) (*big.Int, error) { + if em.BalanceOfFunc == nil { + panic("method not set") + } + return em.BalanceOfFunc(address) +} + +// NonceOfFunc returns the nonce for this address +func (em *TestEmulator) NonceOf(address types.Address) (uint64, error) { + if em.NonceOfFunc == nil { + panic("method not set") + } + return em.NonceOfFunc(address) +} + +// CodeOf returns the code for this address (if smart contract is deployed at this address) +func (em *TestEmulator) CodeOf(address types.Address) (types.Code, error) { + if em.CodeOfFunc == nil { + panic("method not set") + } + return em.CodeOfFunc(address) +} + +// DirectCall executes a direct call +func (em *TestEmulator) DirectCall(call *types.DirectCall) (*types.Result, error) { + if em.DirectCallFunc == nil { + panic("method not set") + } + return em.DirectCallFunc(call) +} + +// RunTransaction runs a transaction and collect gas fees to the coinbase account +func (em *TestEmulator) RunTransaction(tx *gethTypes.Transaction) (*types.Result, error) { + if em.RunTransactionFunc == nil { + panic("method not set") + } + return em.RunTransactionFunc(tx) +} + +func RandomCommonHash(t testing.TB) gethCommon.Hash { + ret := gethCommon.Hash{} + _, err := cryptoRand.Read(ret[:gethCommon.HashLength]) + require.NoError(t, err) + return ret +} + +func RandomAddress(t testing.TB) types.Address { + return types.NewAddress(RandomCommonAddress(t)) +} + +func RandomCommonAddress(t testing.TB) gethCommon.Address { + ret := gethCommon.Address{} + _, err := cryptoRand.Read(ret[:gethCommon.AddressLength]) + require.NoError(t, err) + return ret +} + +func RandomGas(limit int64) uint64 { + return uint64(rand.Int63n(limit) + 1) +} + +func RandomData(t testing.TB) []byte { + // byte size [1, 100] + size := rand.Intn(100) + 1 + ret := make([]byte, size) + _, err := cryptoRand.Read(ret[:]) + require.NoError(t, err) + return ret +} + +func GetRandomLogFixture(t testing.TB) *gethTypes.Log { + return &gethTypes.Log{ + Address: RandomCommonAddress(t), + Topics: []gethCommon.Hash{ + RandomCommonHash(t), + RandomCommonHash(t), + }, + Data: RandomData(t), + } +} diff --git a/fvm/evm/types/address.go b/fvm/evm/types/address.go index 877e77c2571..afcaa72e246 100644 --- a/fvm/evm/types/address.go +++ b/fvm/evm/types/address.go @@ -32,6 +32,9 @@ func NewAddressFromString(str string) Address { return Address(gethCommon.BytesToAddress([]byte(str))) } +// EmptyAddress is an empty evm address +var EmptyAddress = Address(gethCommon.Address{}) + type GasLimit uint64 type Code []byte diff --git a/fvm/evm/types/call.go b/fvm/evm/types/call.go new file mode 100644 index 00000000000..31562fe9ccd --- /dev/null +++ b/fvm/evm/types/call.go @@ -0,0 +1,131 @@ +package types + +import ( + "math/big" + + gethCommon "github.com/ethereum/go-ethereum/common" + gethCore "github.com/ethereum/go-ethereum/core" + gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // tx type 255 is used for direct calls from bridged accounts + DirectCallTxType = byte(255) + + UnknownCallSubType = byte(0) + DepositCallSubType = byte(1) + WithdrawCallSubType = byte(2) + TransferCallSubType = byte(3) + DeployCallSubType = byte(4) + ContractCallSubType = byte(5) + + TransferGasUsage = 21_000 +) + +// DirectCall captures all the data related to a direct call to evm +// direct calls are similar to transactions but they don't have +// signatures and don't need sequence number checks +type DirectCall struct { + Type byte + SubType byte + From Address + To Address + Data []byte + Value *big.Int + GasLimit uint64 +} + +// Encode encodes the direct call it also adds the type +// as the very first byte, similar to how evm encodes types. +func (dc *DirectCall) Encode() ([]byte, error) { + encoded, err := rlp.EncodeToBytes(dc) + return append([]byte{dc.Type}, encoded...), err +} + +// Hash computes the hash of a direct call +func (dc *DirectCall) Hash() (gethCommon.Hash, error) { + encoded, err := dc.Encode() + return gethCrypto.Keccak256Hash(encoded), err +} + +// Message constructs a core.Message from the direct call +func (dc *DirectCall) Message() *gethCore.Message { + var to *gethCommon.Address + if dc.To != EmptyAddress { + ct := dc.To.ToCommon() + to = &ct + } + return &gethCore.Message{ + From: dc.From.ToCommon(), + To: to, + Value: dc.Value, + Data: dc.Data, + GasLimit: dc.GasLimit, + GasPrice: big.NewInt(0), // price is set to zero fo direct calls + GasTipCap: big.NewInt(1), // also known as maxPriorityFeePerGas + GasFeeCap: big.NewInt(2), // also known as maxFeePerGas + // AccessList: tx.AccessList(), // TODO revisit this value, the cost matter but performance might + SkipAccountChecks: true, // this would let us not set the nonce + } +} + +func NewDepositCall(address Address, amount *big.Int) *DirectCall { + return &DirectCall{ + Type: DirectCallTxType, + SubType: DepositCallSubType, + From: EmptyAddress, + To: address, + Data: nil, + Value: amount, + GasLimit: TransferGasUsage, + } +} + +func NewWithdrawCall(address Address, amount *big.Int) *DirectCall { + return &DirectCall{ + Type: DirectCallTxType, + SubType: WithdrawCallSubType, + From: address, + To: EmptyAddress, + Data: nil, + Value: amount, + GasLimit: TransferGasUsage, + } +} + +func NewTransferCall(from Address, to Address, amount *big.Int) *DirectCall { + return &DirectCall{ + Type: DirectCallTxType, + SubType: TransferCallSubType, + From: from, + To: to, + Data: nil, + Value: amount, + GasLimit: TransferGasUsage, + } +} + +func NewDeployCall(caller Address, code Code, gasLimit uint64, value *big.Int) *DirectCall { + return &DirectCall{ + Type: DirectCallTxType, + SubType: DeployCallSubType, + From: caller, + To: EmptyAddress, + Data: code, + Value: value, + GasLimit: gasLimit, + } +} + +func NewContractCall(caller Address, to Address, data Data, gasLimit uint64, value *big.Int) *DirectCall { + return &DirectCall{ + Type: DirectCallTxType, + SubType: ContractCallSubType, + From: caller, + To: to, + Data: data, + Value: value, + GasLimit: gasLimit, + } +} diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index ef14fa91158..1b86e06fe7b 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -37,26 +37,27 @@ func NewDefaultBlockContext(BlockNumber uint64) BlockContext { type ReadOnlyBlockView interface { // BalanceOf returns the balance of this address BalanceOf(address Address) (*big.Int, error) + // NonceOf returns the nonce of this address + NonceOf(address Address) (uint64, error) // CodeOf returns the code for this address (if smart contract is deployed at this address) CodeOf(address Address) (Code, error) } -// BlockView allows evm calls in the context of a block +// BlockView facilitates execution of a transaction or a direct evm call in the context of a block +// Errors returned by the methods are one of the followings: +// - Fatal error +// - Database error (non-fatal) +// - EVM validation error +// - EVM execution error type BlockView interface { - // MintTo mints new tokens to this address - MintTo(address Address, amount *big.Int) (*Result, error) - // WithdrawFrom withdraws tokens from this address - WithdrawFrom(address Address, amount *big.Int) (*Result, error) - // Transfer transfers token between addresses - Transfer(from Address, to Address, value *big.Int) (*Result, error) - // Deploy deploys an smart contract - Deploy(caller Address, code Code, gasLimit uint64, value *big.Int) (*Result, error) - // Call makes a call to a smart contract - Call(caller Address, to Address, data Data, gasLimit uint64, value *big.Int) (*Result, error) - // RunTransaction runs a transaction + // executes a direct call + DirectCall(call *DirectCall) (*Result, error) + + // RunTransaction executes an evm transaction RunTransaction(tx *gethTypes.Transaction) (*Result, error) } +// Emulator emulates an evm-compatible chain type Emulator interface { // constructs a new block view NewReadOnlyBlockView(ctx BlockContext) (ReadOnlyBlockView, error) @@ -65,6 +66,10 @@ type Emulator interface { NewBlockView(ctx BlockContext) (BlockView, error) } +// Database provides what Emulator needs for storing tries and accounts +// Errors returned by the methods are one of the followings: +// - Fatal error +// - Database error (non-fatal) type Database interface { ethdb.KeyValueStore diff --git a/fvm/evm/types/errors.go b/fvm/evm/types/errors.go index 630ecf12d1b..fa008b741a2 100644 --- a/fvm/evm/types/errors.go +++ b/fvm/evm/types/errors.go @@ -6,6 +6,9 @@ import ( ) var ( + // ErrAccountDoesNotExist is returned when evm account doesn't exist + ErrAccountDoesNotExist = errors.New("account does not exist") + // ErrInsufficientBalance is returned when evm account doesn't have enough balance ErrInsufficientBalance = errors.New("insufficient balance") @@ -30,8 +33,8 @@ var ( ErrNotImplemented = NewFatalError(errors.New("a functionality is called that is not implemented")) ) -// EVMExecutionError is a user-related error, -// emitted when a evm transaction execution or a contract call has been failed +// EVMExecutionError is a non-fatal error, returned when execution of +// an evm transaction or direct call has failed. type EVMExecutionError struct { err error } @@ -49,15 +52,43 @@ func (err EVMExecutionError) Unwrap() error { } func (err EVMExecutionError) Error() string { - return fmt.Sprintf("EVM execution failed: %v", err.err) + return fmt.Sprintf("EVM execution error: %v", err.err) } -// IsEVMExecutionError returns true if the error or any wrapped error -// is of type EVM execution error +// IsEVMValidationError returns true if the error or any underlying errors +// is of the type EVM execution error func IsEVMExecutionError(err error) bool { return errors.As(err, &EVMExecutionError{}) } +// EVMValidationError is a non-fatal error, returned when validation steps of an EVM transaction +// or direct call has failed. +type EVMValidationError struct { + err error +} + +// NewEVMValidationError returns a new EVMValidationError +func NewEVMValidationError(rootCause error) EVMValidationError { + return EVMValidationError{ + err: rootCause, + } +} + +// Unwrap unwraps the underlying evm error +func (err EVMValidationError) Unwrap() error { + return err.err +} + +func (err EVMValidationError) Error() string { + return fmt.Sprintf("EVM validation error: %v", err.err) +} + +// IsEVMValidationError returns true if the error or any underlying errors +// is of the type EVM validation error +func IsEVMValidationError(err error) bool { + return errors.As(err, &EVMValidationError{}) +} + // DatabaseError is a non-fatal error, returned when a database operation // has failed (e.g. reaching storage interaction limit) type DatabaseError struct { diff --git a/fvm/evm/types/result.go b/fvm/evm/types/result.go index cf1b69e1e1b..fb6f4087210 100644 --- a/fvm/evm/types/result.go +++ b/fvm/evm/types/result.go @@ -5,9 +5,6 @@ import ( gethTypes "github.com/ethereum/go-ethereum/core/types" ) -// tx type 255 is used for direct calls from bridged accounts -var DirectCallTxType = uint8(255) - // Result captures the result of an interaction with the emulator (direct call or evm tx) // Its more comprehensive than typical evm receipt, usually // the receipt generation requires some extra calculation (e.g. Deployed contract address) diff --git a/go.mod b/go.mod index 35acd27ee64..1a0742b1393 100644 --- a/go.mod +++ b/go.mod @@ -117,6 +117,8 @@ require ( cloud.google.com/go/compute v1.21.0 // indirect cloud.google.com/go/iam v1.1.1 // indirect github.com/DataDog/zstd v1.5.2 // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/VictoriaMetrics/fastcache v1.6.0 // indirect github.com/aws/aws-sdk-go-v2 v1.17.7 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.18 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect @@ -145,6 +147,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cskr/pubsub v1.0.2 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect @@ -169,8 +172,10 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-stack/stack v1.8.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/gopacket v1.1.19 // indirect @@ -179,6 +184,7 @@ require ( github.com/googleapis/gax-go/v2 v2.11.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect github.com/huin/goupnp v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -240,6 +246,7 @@ require ( github.com/multiformats/go-multicodec v0.9.0 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20230711213910-baad011d2b13 // indirect github.com/onflow/flow-nft/lib/go/contracts v1.1.0 // indirect github.com/onflow/sdks v0.5.0 // indirect @@ -262,12 +269,14 @@ require ( github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect github.com/tklauser/go-sysconf v0.3.9 // indirect github.com/tklauser/numcpus v0.3.0 // indirect @@ -294,6 +303,7 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/go.sum b/go.sum index 3ff3d04ef36..57d610f8bd5 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,7 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -120,6 +121,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= @@ -293,6 +295,8 @@ github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQY github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= @@ -441,6 +445,7 @@ github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= @@ -457,6 +462,7 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= @@ -517,6 +523,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= @@ -657,6 +664,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -1302,6 +1311,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -1310,6 +1320,7 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x071HgCF/0v5hQcaE5qqjc2UqN5gCU8h5Mk6uqpOg= github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= @@ -1348,6 +1359,7 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -1500,6 +1512,7 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sethvargo/go-retry v0.2.3 h1:oYlgvIvsju3jNbottWABtbnoLC+GDtLdBHxKWxQm/iU= github.com/sethvargo/go-retry v0.2.3/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= @@ -1603,6 +1616,7 @@ github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnyg github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= @@ -1877,6 +1891,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -2002,6 +2017,7 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2021,6 +2037,7 @@ golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2402,12 +2419,14 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=