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 }