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  }