github.com/onflow/flow-go@v0.33.17/fvm/evm/emulator/emulator_test.go (about)

     1  package emulator_test
     2  
     3  import (
     4  	"math"
     5  	"math/big"
     6  	"testing"
     7  
     8  	gethCommon "github.com/ethereum/go-ethereum/common"
     9  	gethTypes "github.com/ethereum/go-ethereum/core/types"
    10  	gethParams "github.com/ethereum/go-ethereum/params"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/onflow/flow-go/fvm/evm/emulator"
    14  	"github.com/onflow/flow-go/fvm/evm/testutils"
    15  	"github.com/onflow/flow-go/fvm/evm/types"
    16  	"github.com/onflow/flow-go/model/flow"
    17  )
    18  
    19  var blockNumber = big.NewInt(10)
    20  var defaultCtx = types.NewDefaultBlockContext(blockNumber.Uint64())
    21  
    22  func RunWithNewEmulator(t testing.TB, backend *testutils.TestBackend, rootAddr flow.Address, f func(*emulator.Emulator)) {
    23  	env := emulator.NewEmulator(backend, rootAddr)
    24  	f(env)
    25  }
    26  
    27  func RunWithNewBlockView(t testing.TB, em *emulator.Emulator, f func(blk types.BlockView)) {
    28  	blk, err := em.NewBlockView(defaultCtx)
    29  	require.NoError(t, err)
    30  	f(blk)
    31  }
    32  
    33  func RunWithNewReadOnlyBlockView(t testing.TB, em *emulator.Emulator, f func(blk types.ReadOnlyBlockView)) {
    34  	blk, err := em.NewReadOnlyBlockView(defaultCtx)
    35  	require.NoError(t, err)
    36  	f(blk)
    37  }
    38  
    39  func TestNativeTokenBridging(t *testing.T) {
    40  	testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
    41  		testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
    42  			originalBalance := big.NewInt(10000)
    43  			testAccount := types.NewAddressFromString("test")
    44  
    45  			t.Run("mint tokens to the first account", func(t *testing.T) {
    46  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
    47  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
    48  						res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance))
    49  						require.NoError(t, err)
    50  						require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
    51  					})
    52  				})
    53  			})
    54  			t.Run("tokens withdraw", func(t *testing.T) {
    55  				amount := big.NewInt(1000)
    56  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
    57  					RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
    58  						retBalance, err := blk.BalanceOf(testAccount)
    59  						require.NoError(t, err)
    60  						require.Equal(t, originalBalance, retBalance)
    61  					})
    62  				})
    63  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
    64  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
    65  						res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount))
    66  						require.NoError(t, err)
    67  						require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
    68  					})
    69  				})
    70  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
    71  					RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
    72  						retBalance, err := blk.BalanceOf(testAccount)
    73  						require.NoError(t, err)
    74  						require.Equal(t, amount.Sub(originalBalance, amount), retBalance)
    75  					})
    76  				})
    77  			})
    78  		})
    79  	})
    80  }
    81  
    82  func TestContractInteraction(t *testing.T) {
    83  	testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
    84  		testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
    85  
    86  			testContract := testutils.GetStorageTestContract(t)
    87  
    88  			testAccount := types.NewAddressFromString("test")
    89  			amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
    90  			amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))
    91  
    92  			// fund test account
    93  			RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
    94  				RunWithNewBlockView(t, env, func(blk types.BlockView) {
    95  					_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount))
    96  					require.NoError(t, err)
    97  				})
    98  			})
    99  
   100  			var contractAddr types.Address
   101  
   102  			t.Run("deploy contract", func(t *testing.T) {
   103  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   104  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   105  						res, err := blk.DirectCall(
   106  							types.NewDeployCall(
   107  								testAccount,
   108  								testContract.ByteCode,
   109  								math.MaxUint64,
   110  								amountToBeTransfered),
   111  						)
   112  						require.NoError(t, err)
   113  						contractAddr = res.DeployedContractAddress
   114  					})
   115  					RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
   116  						require.NotNil(t, contractAddr)
   117  						retCode, err := blk.CodeOf(contractAddr)
   118  						require.NoError(t, err)
   119  						require.NotEmpty(t, retCode)
   120  
   121  						retBalance, err := blk.BalanceOf(contractAddr)
   122  						require.NoError(t, err)
   123  						require.Equal(t, amountToBeTransfered, retBalance)
   124  
   125  						retBalance, err = blk.BalanceOf(testAccount)
   126  						require.NoError(t, err)
   127  						require.Equal(t, amount.Sub(amount, amountToBeTransfered), retBalance)
   128  					})
   129  				})
   130  			})
   131  
   132  			t.Run("call contract", func(t *testing.T) {
   133  				num := big.NewInt(10)
   134  
   135  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   136  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   137  						res, err := blk.DirectCall(
   138  							types.NewContractCall(
   139  								testAccount,
   140  								contractAddr,
   141  								testContract.MakeCallData(t, "store", num),
   142  								1_000_000,
   143  								big.NewInt(0), // this should be zero because the contract doesn't have receiver
   144  							),
   145  						)
   146  						require.NoError(t, err)
   147  						require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000))
   148  					})
   149  				})
   150  
   151  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   152  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   153  						res, err := blk.DirectCall(
   154  							types.NewContractCall(
   155  								testAccount,
   156  								contractAddr,
   157  								testContract.MakeCallData(t, "retrieve"),
   158  								1_000_000,
   159  								big.NewInt(0), // this should be zero because the contract doesn't have receiver
   160  							),
   161  						)
   162  						require.NoError(t, err)
   163  
   164  						ret := new(big.Int).SetBytes(res.ReturnedValue)
   165  						require.Equal(t, num, ret)
   166  						require.GreaterOrEqual(t, res.GasConsumed, uint64(23_000))
   167  					})
   168  				})
   169  
   170  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   171  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   172  						res, err := blk.DirectCall(
   173  							types.NewContractCall(
   174  								testAccount,
   175  								contractAddr,
   176  								testContract.MakeCallData(t, "blockNumber"),
   177  								1_000_000,
   178  								big.NewInt(0), // this should be zero because the contract doesn't have receiver
   179  							),
   180  						)
   181  						require.NoError(t, err)
   182  
   183  						ret := new(big.Int).SetBytes(res.ReturnedValue)
   184  						require.Equal(t, blockNumber, ret)
   185  					})
   186  				})
   187  
   188  			})
   189  
   190  			t.Run("test sending transactions (happy case)", func(t *testing.T) {
   191  				account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex)
   192  				fAddr := account.Address()
   193  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   194  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   195  						_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount))
   196  						require.NoError(t, err)
   197  					})
   198  				})
   199  
   200  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   201  					ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
   202  					ctx.GasFeeCollector = types.NewAddressFromString("coinbase")
   203  					coinbaseOrgBalance := gethCommon.Big1
   204  					// small amount of money to create account
   205  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   206  						_, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance))
   207  						require.NoError(t, err)
   208  					})
   209  
   210  					blk, err := env.NewBlockView(ctx)
   211  					require.NoError(t, err)
   212  					tx := account.PrepareAndSignTx(
   213  						t,
   214  						testAccount.ToCommon(), // to
   215  						nil,                    // data
   216  						big.NewInt(1000),       // amount
   217  						gethParams.TxGas,       // gas limit
   218  						gethCommon.Big1,        // gas fee
   219  
   220  					)
   221  					_, err = blk.RunTransaction(tx)
   222  					require.NoError(t, err)
   223  
   224  					// check the balance of coinbase
   225  					RunWithNewReadOnlyBlockView(t, env, func(blk2 types.ReadOnlyBlockView) {
   226  						bal, err := blk2.BalanceOf(ctx.GasFeeCollector)
   227  						require.NoError(t, err)
   228  						expected := gethParams.TxGas*gethCommon.Big1.Uint64() + gethCommon.Big1.Uint64()
   229  						require.Equal(t, expected, bal.Uint64())
   230  
   231  						nonce, err := blk2.NonceOf(fAddr)
   232  						require.NoError(t, err)
   233  						require.Equal(t, 1, int(nonce))
   234  					})
   235  				})
   236  			})
   237  			t.Run("test sending transactions (invalid nonce)", func(t *testing.T) {
   238  				account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex)
   239  				fAddr := account.Address()
   240  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   241  					RunWithNewBlockView(t, env, func(blk types.BlockView) {
   242  						_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount))
   243  						require.NoError(t, err)
   244  					})
   245  				})
   246  
   247  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   248  					ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
   249  					blk, err := env.NewBlockView(ctx)
   250  					require.NoError(t, err)
   251  					tx := account.SignTx(t,
   252  						gethTypes.NewTransaction(
   253  							100,                    // nonce
   254  							testAccount.ToCommon(), // to
   255  							big.NewInt(1000),       // amount
   256  							gethParams.TxGas,       // gas limit
   257  							gethCommon.Big1,        // gas fee
   258  							nil,                    // data
   259  						),
   260  					)
   261  					_, err = blk.RunTransaction(tx)
   262  					require.Error(t, err)
   263  					require.True(t, types.IsEVMValidationError(err))
   264  				})
   265  			})
   266  
   267  			t.Run("test sending transactions (bad signature)", func(t *testing.T) {
   268  				RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
   269  					ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
   270  					blk, err := env.NewBlockView(ctx)
   271  					require.NoError(t, err)
   272  					tx := gethTypes.NewTx(&gethTypes.LegacyTx{
   273  						Nonce:    0,
   274  						GasPrice: gethCommon.Big1,
   275  						Gas:      gethParams.TxGas, // gas limit
   276  						To:       nil,              // to
   277  						Value:    big.NewInt(1000), // amount
   278  						Data:     nil,              // data
   279  						V:        big.NewInt(1),
   280  						R:        big.NewInt(2),
   281  						S:        big.NewInt(3),
   282  					})
   283  					_, err = blk.RunTransaction(tx)
   284  					require.Error(t, err)
   285  					require.True(t, types.IsEVMValidationError(err))
   286  				})
   287  			})
   288  
   289  		})
   290  	})
   291  }
   292  
   293  func TestTransfers(t *testing.T) {
   294  	testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
   295  		testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
   296  
   297  			testAccount1 := types.NewAddressFromString("test1")
   298  			testAccount2 := types.NewAddressFromString("test2")
   299  
   300  			amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
   301  			amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))
   302  
   303  			RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
   304  				RunWithNewBlockView(t, em, func(blk types.BlockView) {
   305  					_, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount))
   306  					require.NoError(t, err)
   307  				})
   308  			})
   309  
   310  			RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
   311  				RunWithNewBlockView(t, em, func(blk types.BlockView) {
   312  					_, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered))
   313  					require.NoError(t, err)
   314  				})
   315  			})
   316  
   317  			RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
   318  				RunWithNewReadOnlyBlockView(t, em, func(blk types.ReadOnlyBlockView) {
   319  					bal, err := blk.BalanceOf(testAccount2)
   320  					require.NoError(t, err)
   321  					require.Equal(t, amountToBeTransfered.Uint64(), bal.Uint64())
   322  
   323  					bal, err = blk.BalanceOf(testAccount1)
   324  					require.NoError(t, err)
   325  					require.Equal(t, new(big.Int).Sub(amount, amountToBeTransfered).Uint64(), bal.Uint64())
   326  				})
   327  			})
   328  		})
   329  	})
   330  }
   331  
   332  func TestStorageNoSideEffect(t *testing.T) {
   333  	testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
   334  		testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) {
   335  			var err error
   336  			em := emulator.NewEmulator(backend, flowEVMRoot)
   337  			testAccount := types.NewAddressFromString("test")
   338  
   339  			amount := big.NewInt(10)
   340  			RunWithNewBlockView(t, em, func(blk types.BlockView) {
   341  				_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount))
   342  				require.NoError(t, err)
   343  			})
   344  
   345  			orgSize := backend.TotalStorageSize()
   346  			RunWithNewBlockView(t, em, func(blk types.BlockView) {
   347  				_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount))
   348  				require.NoError(t, err)
   349  			})
   350  			require.Equal(t, orgSize, backend.TotalStorageSize())
   351  		})
   352  	})
   353  }