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