gitlab.com/flarenetwork/coreth@v0.1.1/core/vm/contracts_stateful_test.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package vm 5 6 import ( 7 "math/big" 8 "testing" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/log" 12 "github.com/stretchr/testify/assert" 13 "gitlab.com/flarenetwork/coreth/core/rawdb" 14 "gitlab.com/flarenetwork/coreth/core/state" 15 "gitlab.com/flarenetwork/coreth/params" 16 ) 17 18 func TestPrecompiledContractSpendsGas(t *testing.T) { 19 unwrapped := &sha256hash{} 20 21 input := []byte{'J', 'E', 'T', 'S'} 22 requiredGas := unwrapped.RequiredGas(input) 23 _, remainingGas, err := RunPrecompiledContract(unwrapped, input, requiredGas) 24 if err != nil { 25 t.Fatalf("Unexpectedly failed to run precompiled contract: %s", err) 26 } 27 28 if remainingGas != 0 { 29 t.Fatalf("Found more remaining gas than expected: %d", remainingGas) 30 } 31 } 32 33 // CanTransfer checks whether there are enough funds in the address' account to make a transfer. 34 // This does not take the necessary gas in to account to make the transfer valid. 35 func CanTransfer(db StateDB, addr common.Address, amount *big.Int) bool { 36 return db.GetBalance(addr).Cmp(amount) >= 0 37 } 38 39 func CanTransferMC(db StateDB, addr common.Address, to common.Address, coinID *common.Hash, amount *big.Int) bool { 40 log.Info("CanTransferMC", "address", addr, "to", to, "coinID", coinID, "amount", amount) 41 if coinID == nil { 42 return true 43 } 44 if db.GetBalanceMultiCoin(addr, *coinID).Cmp(amount) >= 0 { 45 return true 46 } 47 // insufficient balance 48 return false 49 } 50 51 // Transfer subtracts amount from sender and adds amount to recipient using the given Db 52 func Transfer(db StateDB, sender, recipient common.Address, amount *big.Int) { 53 db.SubBalance(sender, amount) 54 db.AddBalance(recipient, amount) 55 } 56 57 // Transfer subtracts amount from sender and adds amount to recipient using the given Db 58 func TransferMultiCoin(db StateDB, sender, recipient common.Address, coinID *common.Hash, amount *big.Int) { 59 if coinID == nil { 60 return 61 } 62 db.SubBalanceMultiCoin(sender, *coinID, amount) 63 db.AddBalanceMultiCoin(recipient, *coinID, amount) 64 } 65 66 func TestPackNativeAssetCallInput(t *testing.T) { 67 addr := common.BytesToAddress([]byte("hello")) 68 assetID := common.BytesToHash([]byte("ScoobyCoin")) 69 assetAmount := big.NewInt(50) 70 callData := []byte{1, 2, 3, 4, 5, 6, 7, 8} 71 72 input := PackNativeAssetCallInput(addr, assetID, assetAmount, callData) 73 74 unpackedAddr, unpackedAssetID, unpackedAssetAmount, unpackedCallData, err := UnpackNativeAssetCallInput(input) 75 assert.NoError(t, err) 76 assert.Equal(t, addr, unpackedAddr, "address") 77 assert.Equal(t, &assetID, unpackedAssetID, "assetID") 78 assert.Equal(t, assetAmount, unpackedAssetAmount, "assetAmount") 79 assert.Equal(t, callData, unpackedCallData, "callData") 80 } 81 82 func TestStatefulPrecompile(t *testing.T) { 83 vmCtx := BlockContext{ 84 BlockNumber: big.NewInt(0), 85 Time: big.NewInt(0), 86 CanTransfer: CanTransfer, 87 CanTransferMC: CanTransferMC, 88 Transfer: Transfer, 89 TransferMultiCoin: TransferMultiCoin, 90 } 91 92 type statefulContractTest struct { 93 setupStateDB func() StateDB 94 from common.Address 95 precompileAddr common.Address 96 input []byte 97 value *big.Int 98 gasInput uint64 99 expectedGasRemaining uint64 100 expectedErr error 101 expectedResult []byte 102 name string 103 stateDBCheck func(*testing.T, StateDB) 104 } 105 106 userAddr1 := common.BytesToAddress([]byte("user1")) 107 userAddr2 := common.BytesToAddress([]byte("user2")) 108 assetID := common.BytesToHash([]byte("ScoobyCoin")) 109 zeroBytes := make([]byte, 32) 110 bigHundred := big.NewInt(100) 111 oneHundredBytes := make([]byte, 32) 112 big0.FillBytes(zeroBytes) 113 bigFifty := big.NewInt(50) 114 fiftyBytes := make([]byte, 32) 115 bigFifty.FillBytes(fiftyBytes) 116 bigHundred.FillBytes(oneHundredBytes) 117 118 tests := []statefulContractTest{ 119 { 120 setupStateDB: func() StateDB { 121 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 122 if err != nil { 123 t.Fatal(err) 124 } 125 // Create account 126 statedb.CreateAccount(userAddr1) 127 // Set balance to pay for gas fee 128 statedb.SetBalance(userAddr1, bigHundred) 129 // Set MultiCoin balance 130 statedb.Finalise(true) 131 return statedb 132 }, 133 from: userAddr1, 134 precompileAddr: nativeAssetBalanceAddr, 135 input: PackNativeAssetBalanceInput(userAddr1, assetID), 136 value: big0, 137 gasInput: params.AssetBalanceApricot, 138 expectedGasRemaining: 0, 139 expectedErr: nil, 140 expectedResult: zeroBytes, 141 name: "native asset balance: uninitialized multicoin balance returns 0", 142 }, 143 { 144 setupStateDB: func() StateDB { 145 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 146 if err != nil { 147 t.Fatal(err) 148 } 149 // Create account 150 statedb.CreateAccount(userAddr1) 151 // Set balance to pay for gas fee 152 statedb.SetBalance(userAddr1, bigHundred) 153 // Initialize multicoin balance and set it back to 0 154 statedb.AddBalanceMultiCoin(userAddr1, assetID, bigHundred) 155 statedb.SubBalanceMultiCoin(userAddr1, assetID, bigHundred) 156 statedb.Finalise(true) 157 return statedb 158 }, 159 from: userAddr1, 160 precompileAddr: nativeAssetBalanceAddr, 161 input: PackNativeAssetBalanceInput(userAddr1, assetID), 162 value: big0, 163 gasInput: params.AssetBalanceApricot, 164 expectedGasRemaining: 0, 165 expectedErr: nil, 166 expectedResult: zeroBytes, 167 name: "native asset balance: initialized multicoin balance returns 0", 168 }, 169 { 170 setupStateDB: func() StateDB { 171 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 172 if err != nil { 173 t.Fatal(err) 174 } 175 // Create account 176 statedb.CreateAccount(userAddr1) 177 // Set balance to pay for gas fee 178 statedb.SetBalance(userAddr1, bigHundred) 179 // Initialize multicoin balance to 100 180 statedb.AddBalanceMultiCoin(userAddr1, assetID, bigHundred) 181 statedb.Finalise(true) 182 return statedb 183 }, 184 from: userAddr1, 185 precompileAddr: nativeAssetBalanceAddr, 186 input: PackNativeAssetBalanceInput(userAddr1, assetID), 187 value: big0, 188 gasInput: params.AssetBalanceApricot, 189 expectedGasRemaining: 0, 190 expectedErr: nil, 191 expectedResult: oneHundredBytes, 192 name: "native asset balance: returns correct non-zero multicoin balance", 193 }, 194 { 195 setupStateDB: func() StateDB { 196 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 197 if err != nil { 198 t.Fatal(err) 199 } 200 return statedb 201 }, 202 from: userAddr1, 203 precompileAddr: nativeAssetBalanceAddr, 204 input: nil, 205 value: big0, 206 gasInput: params.AssetBalanceApricot, 207 expectedGasRemaining: 0, 208 expectedErr: ErrExecutionReverted, 209 expectedResult: nil, 210 name: "native asset balance: invalid input data reverts", 211 }, 212 { 213 setupStateDB: func() StateDB { 214 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 215 if err != nil { 216 t.Fatal(err) 217 } 218 return statedb 219 }, 220 from: userAddr1, 221 precompileAddr: nativeAssetBalanceAddr, 222 input: PackNativeAssetBalanceInput(userAddr1, assetID), 223 value: big0, 224 gasInput: params.AssetBalanceApricot - 1, 225 expectedGasRemaining: 0, 226 expectedErr: ErrOutOfGas, 227 expectedResult: nil, 228 name: "native asset balance: insufficient gas errors", 229 }, 230 { 231 setupStateDB: func() StateDB { 232 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 233 if err != nil { 234 t.Fatal(err) 235 } 236 return statedb 237 }, 238 from: userAddr1, 239 precompileAddr: nativeAssetBalanceAddr, 240 input: PackNativeAssetBalanceInput(userAddr1, assetID), 241 value: bigHundred, 242 gasInput: params.AssetBalanceApricot, 243 expectedGasRemaining: params.AssetBalanceApricot, 244 expectedErr: ErrInsufficientBalance, 245 expectedResult: nil, 246 name: "native asset balance: non-zero value with insufficient funds reverts before running pre-compile", 247 }, 248 { 249 setupStateDB: func() StateDB { 250 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 251 if err != nil { 252 t.Fatal(err) 253 } 254 statedb.SetBalance(userAddr1, bigHundred) 255 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 256 statedb.Finalise(true) 257 return statedb 258 }, 259 from: userAddr1, 260 precompileAddr: nativeAssetCallAddr, 261 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 262 value: big0, 263 gasInput: params.AssetCallApricot + params.CallNewAccountGas, 264 expectedGasRemaining: 0, 265 expectedErr: nil, 266 expectedResult: nil, 267 name: "native asset call: multicoin transfer", 268 stateDBCheck: func(t *testing.T, stateDB StateDB) { 269 user1Balance := stateDB.GetBalance(userAddr1) 270 user2Balance := stateDB.GetBalance(userAddr2) 271 user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID) 272 user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID) 273 274 expectedBalance := big.NewInt(50) 275 assert.Equal(t, bigHundred, user1Balance, "user 1 balance") 276 assert.Equal(t, big0, user2Balance, "user 2 balance") 277 assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance") 278 assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance") 279 }, 280 }, 281 { 282 setupStateDB: func() StateDB { 283 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 284 if err != nil { 285 t.Fatal(err) 286 } 287 statedb.SetBalance(userAddr1, bigHundred) 288 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 289 statedb.Finalise(true) 290 return statedb 291 }, 292 from: userAddr1, 293 precompileAddr: nativeAssetCallAddr, 294 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 295 value: big.NewInt(49), 296 gasInput: params.AssetCallApricot + params.CallNewAccountGas, 297 expectedGasRemaining: 0, 298 expectedErr: nil, 299 expectedResult: nil, 300 name: "native asset call: multicoin transfer with non-zero value", 301 stateDBCheck: func(t *testing.T, stateDB StateDB) { 302 user1Balance := stateDB.GetBalance(userAddr1) 303 user2Balance := stateDB.GetBalance(userAddr2) 304 nativeAssetCallAddrBalance := stateDB.GetBalance(nativeAssetCallAddr) 305 user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID) 306 user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID) 307 expectedBalance := big.NewInt(50) 308 309 assert.Equal(t, big.NewInt(51), user1Balance, "user 1 balance") 310 assert.Equal(t, big0, user2Balance, "user 2 balance") 311 assert.Equal(t, big.NewInt(49), nativeAssetCallAddrBalance, "native asset call addr balance") 312 assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance") 313 assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance") 314 }, 315 }, 316 { 317 setupStateDB: func() StateDB { 318 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 319 if err != nil { 320 t.Fatal(err) 321 } 322 statedb.SetBalance(userAddr1, bigHundred) 323 statedb.SetBalanceMultiCoin(userAddr1, assetID, big.NewInt(50)) 324 statedb.Finalise(true) 325 return statedb 326 }, 327 from: userAddr1, 328 precompileAddr: nativeAssetCallAddr, 329 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(51), nil), 330 value: big.NewInt(50), 331 gasInput: params.AssetCallApricot, 332 expectedGasRemaining: 0, 333 expectedErr: ErrInsufficientBalance, 334 expectedResult: nil, 335 name: "native asset call: insufficient multicoin funds", 336 stateDBCheck: func(t *testing.T, stateDB StateDB) { 337 user1Balance := stateDB.GetBalance(userAddr1) 338 user2Balance := stateDB.GetBalance(userAddr2) 339 user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID) 340 user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID) 341 342 assert.Equal(t, bigHundred, user1Balance, "user 1 balance") 343 assert.Equal(t, big0, user2Balance, "user 2 balance") 344 assert.Equal(t, big.NewInt(51), user1AssetBalance, "user 1 asset balance") 345 assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance") 346 }, 347 }, 348 { 349 setupStateDB: func() StateDB { 350 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 351 if err != nil { 352 t.Fatal(err) 353 } 354 statedb.SetBalance(userAddr1, big.NewInt(50)) 355 statedb.SetBalanceMultiCoin(userAddr1, assetID, big.NewInt(50)) 356 statedb.Finalise(true) 357 return statedb 358 }, 359 from: userAddr1, 360 precompileAddr: nativeAssetCallAddr, 361 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 362 value: big.NewInt(51), 363 gasInput: params.AssetCallApricot, 364 expectedGasRemaining: params.AssetCallApricot, 365 expectedErr: ErrInsufficientBalance, 366 expectedResult: nil, 367 name: "native asset call: insufficient funds", 368 stateDBCheck: func(t *testing.T, stateDB StateDB) { 369 user1Balance := stateDB.GetBalance(userAddr1) 370 user2Balance := stateDB.GetBalance(userAddr2) 371 user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID) 372 user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID) 373 374 assert.Equal(t, big.NewInt(50), user1Balance, "user 1 balance") 375 assert.Equal(t, big0, user2Balance, "user 2 balance") 376 assert.Equal(t, big.NewInt(50), user1AssetBalance, "user 1 asset balance") 377 assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance") 378 }, 379 }, 380 { 381 setupStateDB: func() StateDB { 382 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 383 if err != nil { 384 t.Fatal(err) 385 } 386 statedb.SetBalance(userAddr1, bigHundred) 387 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 388 statedb.Finalise(true) 389 return statedb 390 }, 391 from: userAddr1, 392 precompileAddr: nativeAssetCallAddr, 393 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 394 value: big.NewInt(50), 395 gasInput: params.AssetCallApricot - 1, 396 expectedGasRemaining: 0, 397 expectedErr: ErrOutOfGas, 398 expectedResult: nil, 399 name: "native asset call: insufficient gas for native asset call", 400 }, 401 { 402 setupStateDB: func() StateDB { 403 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 404 if err != nil { 405 t.Fatal(err) 406 } 407 statedb.SetBalance(userAddr1, bigHundred) 408 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 409 statedb.Finalise(true) 410 return statedb 411 }, 412 from: userAddr1, 413 precompileAddr: nativeAssetCallAddr, 414 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 415 value: big.NewInt(50), 416 gasInput: params.AssetCallApricot + params.CallNewAccountGas - 1, 417 expectedGasRemaining: 0, 418 expectedErr: ErrOutOfGas, 419 expectedResult: nil, 420 name: "native asset call: insufficient gas to create new account", 421 stateDBCheck: func(t *testing.T, stateDB StateDB) { 422 user1Balance := stateDB.GetBalance(userAddr1) 423 user2Balance := stateDB.GetBalance(userAddr2) 424 user1AssetBalance := stateDB.GetBalanceMultiCoin(userAddr1, assetID) 425 user2AssetBalance := stateDB.GetBalanceMultiCoin(userAddr2, assetID) 426 427 assert.Equal(t, bigHundred, user1Balance, "user 1 balance") 428 assert.Equal(t, big0, user2Balance, "user 2 balance") 429 assert.Equal(t, bigHundred, user1AssetBalance, "user 1 asset balance") 430 assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance") 431 }, 432 }, 433 { 434 setupStateDB: func() StateDB { 435 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 436 if err != nil { 437 t.Fatal(err) 438 } 439 statedb.SetBalance(userAddr1, bigHundred) 440 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 441 statedb.Finalise(true) 442 return statedb 443 }, 444 from: userAddr1, 445 precompileAddr: nativeAssetCallAddr, 446 input: make([]byte, 24), 447 value: big.NewInt(50), 448 gasInput: params.AssetCallApricot + params.CallNewAccountGas, 449 expectedGasRemaining: params.CallNewAccountGas, 450 expectedErr: ErrExecutionReverted, 451 expectedResult: nil, 452 name: "native asset call: invalid input", 453 }, 454 { 455 setupStateDB: func() StateDB { 456 statedb, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 457 if err != nil { 458 t.Fatal(err) 459 } 460 statedb.SetBalance(userAddr1, bigHundred) 461 statedb.SetBalanceMultiCoin(userAddr1, assetID, bigHundred) 462 statedb.Finalise(true) 463 return statedb 464 }, 465 from: userAddr1, 466 precompileAddr: genesisContractAddr, 467 input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil), 468 value: big0, 469 gasInput: params.AssetCallApricot + params.CallNewAccountGas, 470 expectedGasRemaining: params.AssetCallApricot + params.CallNewAccountGas, 471 expectedErr: ErrExecutionReverted, 472 expectedResult: nil, 473 name: "deprecated contract", 474 }, 475 } 476 for _, test := range tests { 477 t.Run(test.name, func(t *testing.T) { 478 stateDB := test.setupStateDB() 479 // Create EVM with BlockNumber and Time initialized to 0 to enable Apricot Rules. 480 evm := NewEVM(vmCtx, TxContext{}, stateDB, params.TestChainConfig, Config{}) 481 ret, gasRemaining, err := evm.Call(AccountRef(test.from), test.precompileAddr, test.input, test.gasInput, test.value) 482 // Place gas remaining check before error check, so that it is not skipped when there is an error 483 assert.Equal(t, test.expectedGasRemaining, gasRemaining, "unexpected gas remaining") 484 485 if test.expectedErr != nil { 486 assert.Equal(t, test.expectedErr, err, "expected error to match") 487 return 488 } 489 if assert.NoError(t, err, "EVM Call produced unexpected error") { 490 assert.Equal(t, test.expectedResult, ret, "unexpected return value") 491 if test.stateDBCheck != nil { 492 test.stateDBCheck(t, stateDB) 493 } 494 } 495 }) 496 } 497 }