mirror of
https://github.com/luxfi/coreth.git
synced 2026-10-30 17:00:51 +00:00
484 lines
19 KiB
Go
484 lines
19 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package nativeasset_test
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/luxfi/coreth/core/extstate"
|
|
. "github.com/luxfi/coreth/nativeasset"
|
|
"github.com/luxfi/coreth/params"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/state"
|
|
"github.com/luxfi/geth/core/tracing"
|
|
ethtypes "github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/core/vm"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
// Force import core to register the VM hooks.
|
|
// This allows testing the precompiles by exercising the EVM.
|
|
_ "github.com/luxfi/coreth/core"
|
|
)
|
|
|
|
// CanTransfer checks whether there are enough funds in the address' account to make a transfer.
|
|
// This does not take the necessary gas in to account to make the transfer valid.
|
|
func CanTransfer(db vm.StateDB, addr common.Address, amount *uint256.Int) bool {
|
|
return db.GetBalance(addr).Cmp(amount) >= 0
|
|
}
|
|
|
|
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
|
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int) {
|
|
db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
|
|
db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
|
|
}
|
|
|
|
func TestPackNativeAssetCallInput(t *testing.T) {
|
|
addr := common.BytesToAddress([]byte("hello"))
|
|
assetID := common.BytesToHash([]byte("ScoobyCoin"))
|
|
assetAmount := big.NewInt(50)
|
|
callData := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
|
|
|
input := PackNativeAssetCallInput(addr, assetID, assetAmount, callData)
|
|
|
|
unpackedAddr, unpackedAssetID, unpackedAssetAmount, unpackedCallData, err := UnpackNativeAssetCallInput(input)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, addr, unpackedAddr, "address")
|
|
assert.Equal(t, assetID, unpackedAssetID, "assetID")
|
|
assert.Equal(t, assetAmount, unpackedAssetAmount, "assetAmount")
|
|
assert.Equal(t, callData, unpackedCallData, "callData")
|
|
}
|
|
|
|
func TestStatefulPrecompile(t *testing.T) {
|
|
vmCtx := vm.BlockContext{
|
|
BlockNumber: big.NewInt(0),
|
|
Time: 0,
|
|
CanTransfer: CanTransfer,
|
|
Transfer: Transfer,
|
|
}
|
|
|
|
type statefulContractTest struct {
|
|
setupStateDB func() *state.StateDB
|
|
from common.Address
|
|
precompileAddr common.Address
|
|
input []byte
|
|
value *uint256.Int
|
|
gasInput uint64
|
|
expectedGasRemaining uint64
|
|
expectedErr error
|
|
expectedResult []byte
|
|
name string
|
|
stateDBCheck func(*testing.T, *state.StateDB)
|
|
}
|
|
|
|
userAddr1 := common.BytesToAddress([]byte("user1"))
|
|
userAddr2 := common.BytesToAddress([]byte("user2"))
|
|
assetID := common.BytesToHash([]byte("ScoobyCoin"))
|
|
zeroBytes := make([]byte, 32)
|
|
big0 := uint256.NewInt(0)
|
|
bigHundred := big.NewInt(100)
|
|
u256Hundred := uint256.NewInt(100)
|
|
oneHundredBytes := make([]byte, 32)
|
|
bigFifty := big.NewInt(50)
|
|
fiftyBytes := make([]byte, 32)
|
|
bigFifty.FillBytes(fiftyBytes)
|
|
bigHundred.FillBytes(oneHundredBytes)
|
|
|
|
tests := []statefulContractTest{
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Create account
|
|
statedb.CreateAccount(userAddr1)
|
|
// Set balance to pay for gas fee
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
// Set MultiCoin balance
|
|
statedb.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: PackNativeAssetBalanceInput(userAddr1, assetID),
|
|
value: big0,
|
|
gasInput: params.AssetBalanceGasCost,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: nil,
|
|
expectedResult: zeroBytes,
|
|
name: "native asset balance: uninitialized multicoin balance returns 0",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Create account
|
|
statedb.CreateAccount(userAddr1)
|
|
// Set balance to pay for gas fee
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
// Initialize multicoin balance and set it back to 0
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.SubBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: PackNativeAssetBalanceInput(userAddr1, assetID),
|
|
value: big0,
|
|
gasInput: params.AssetBalanceGasCost,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: nil,
|
|
expectedResult: zeroBytes,
|
|
name: "native asset balance: initialized multicoin balance returns 0",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Create account
|
|
statedb.CreateAccount(userAddr1)
|
|
// Set balance to pay for gas fee
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
// Initialize multicoin balance to 100
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: PackNativeAssetBalanceInput(userAddr1, assetID),
|
|
value: big0,
|
|
gasInput: params.AssetBalanceGasCost,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: nil,
|
|
expectedResult: oneHundredBytes,
|
|
name: "native asset balance: returns correct non-zero multicoin balance",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: nil,
|
|
value: big0,
|
|
gasInput: params.AssetBalanceGasCost,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: vm.ErrExecutionReverted,
|
|
expectedResult: nil,
|
|
name: "native asset balance: invalid input data reverts",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: PackNativeAssetBalanceInput(userAddr1, assetID),
|
|
value: big0,
|
|
gasInput: params.AssetBalanceGasCost - 1,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: vm.ErrOutOfGas,
|
|
expectedResult: nil,
|
|
name: "native asset balance: insufficient gas errors",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetBalanceAddr,
|
|
input: PackNativeAssetBalanceInput(userAddr1, assetID),
|
|
value: u256Hundred,
|
|
gasInput: params.AssetBalanceGasCost,
|
|
expectedGasRemaining: params.AssetBalanceGasCost,
|
|
expectedErr: vm.ErrInsufficientBalance,
|
|
expectedResult: nil,
|
|
name: "native asset balance: non-zero value with insufficient funds reverts before running pre-compile",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: big0,
|
|
gasInput: params.AssetCallGasCost + params.CallNewAccountGas + 123,
|
|
expectedGasRemaining: 123,
|
|
expectedErr: nil,
|
|
expectedResult: nil,
|
|
name: "native asset call: multicoin transfer",
|
|
stateDBCheck: func(t *testing.T, statedb *state.StateDB) {
|
|
user1Balance := statedb.GetBalance(userAddr1)
|
|
user2Balance := statedb.GetBalance(userAddr2)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
user1AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr1, assetID)
|
|
user2AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr2, assetID)
|
|
|
|
expectedBalance := big.NewInt(50)
|
|
assert.Equal(t, u256Hundred, user1Balance, "user 1 balance")
|
|
assert.Equal(t, big0, user2Balance, "user 2 balance")
|
|
assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance")
|
|
assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance")
|
|
},
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: uint256.NewInt(49),
|
|
gasInput: params.AssetCallGasCost + params.CallNewAccountGas,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: nil,
|
|
expectedResult: nil,
|
|
name: "native asset call: multicoin transfer with non-zero value",
|
|
stateDBCheck: func(t *testing.T, statedb *state.StateDB) {
|
|
user1Balance := statedb.GetBalance(userAddr1)
|
|
user2Balance := statedb.GetBalance(userAddr2)
|
|
nativeAssetCallAddrBalance := statedb.GetBalance(NativeAssetCallAddr)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
user1AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr1, assetID)
|
|
user2AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr2, assetID)
|
|
expectedBalance := big.NewInt(50)
|
|
|
|
assert.Equal(t, uint256.NewInt(51), user1Balance, "user 1 balance")
|
|
assert.Equal(t, big0, user2Balance, "user 2 balance")
|
|
assert.Equal(t, uint256.NewInt(49), nativeAssetCallAddrBalance, "native asset call addr balance")
|
|
assert.Equal(t, expectedBalance, user1AssetBalance, "user 1 asset balance")
|
|
assert.Equal(t, expectedBalance, user2AssetBalance, "user 2 asset balance")
|
|
},
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, big.NewInt(50))
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(51), nil),
|
|
value: uint256.NewInt(50),
|
|
gasInput: params.AssetCallGasCost,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: vm.ErrInsufficientBalance,
|
|
expectedResult: nil,
|
|
name: "native asset call: insufficient multicoin funds",
|
|
stateDBCheck: func(t *testing.T, statedb *state.StateDB) {
|
|
user1Balance := statedb.GetBalance(userAddr1)
|
|
user2Balance := statedb.GetBalance(userAddr2)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
user1AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr1, assetID)
|
|
user2AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr2, assetID)
|
|
|
|
assert.Equal(t, bigHundred, user1Balance, "user 1 balance")
|
|
assert.Equal(t, big0, user2Balance, "user 2 balance")
|
|
assert.Equal(t, big.NewInt(51), user1AssetBalance, "user 1 asset balance")
|
|
assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
|
|
},
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, uint256.NewInt(50), tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, big.NewInt(50))
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: uint256.NewInt(51),
|
|
gasInput: params.AssetCallGasCost,
|
|
expectedGasRemaining: params.AssetCallGasCost,
|
|
expectedErr: vm.ErrInsufficientBalance,
|
|
expectedResult: nil,
|
|
name: "native asset call: insufficient funds",
|
|
stateDBCheck: func(t *testing.T, statedb *state.StateDB) {
|
|
user1Balance := statedb.GetBalance(userAddr1)
|
|
user2Balance := statedb.GetBalance(userAddr2)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
user1AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr1, assetID)
|
|
user2AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr2, assetID)
|
|
|
|
assert.Equal(t, big.NewInt(50), user1Balance, "user 1 balance")
|
|
assert.Equal(t, big0, user2Balance, "user 2 balance")
|
|
assert.Equal(t, big.NewInt(50), user1AssetBalance, "user 1 asset balance")
|
|
assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
|
|
},
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: uint256.NewInt(50),
|
|
gasInput: params.AssetCallGasCost - 1,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: vm.ErrOutOfGas,
|
|
expectedResult: nil,
|
|
name: "native asset call: insufficient gas for native asset call",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: uint256.NewInt(50),
|
|
gasInput: params.AssetCallGasCost + params.CallNewAccountGas - 1,
|
|
expectedGasRemaining: 0,
|
|
expectedErr: vm.ErrOutOfGas,
|
|
expectedResult: nil,
|
|
name: "native asset call: insufficient gas to create new account",
|
|
stateDBCheck: func(t *testing.T, statedb *state.StateDB) {
|
|
user1Balance := statedb.GetBalance(userAddr1)
|
|
user2Balance := statedb.GetBalance(userAddr2)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
user1AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr1, assetID)
|
|
user2AssetBalance := wrappedStateDB.GetBalanceMultiCoin(userAddr2, assetID)
|
|
|
|
assert.Equal(t, bigHundred, user1Balance, "user 1 balance")
|
|
assert.Equal(t, big0, user2Balance, "user 2 balance")
|
|
assert.Equal(t, bigHundred, user1AssetBalance, "user 1 asset balance")
|
|
assert.Equal(t, big0, user2AssetBalance, "user 2 asset balance")
|
|
},
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: NativeAssetCallAddr,
|
|
input: make([]byte, 24),
|
|
value: uint256.NewInt(50),
|
|
gasInput: params.AssetCallGasCost + params.CallNewAccountGas,
|
|
expectedGasRemaining: params.CallNewAccountGas,
|
|
expectedErr: vm.ErrExecutionReverted,
|
|
expectedResult: nil,
|
|
name: "native asset call: invalid input",
|
|
},
|
|
{
|
|
setupStateDB: func() *state.StateDB {
|
|
statedb, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statedb.SetBalance(userAddr1, u256Hundred, tracing.BalanceChangeUnspecified)
|
|
wrappedStateDB := extstate.New(statedb)
|
|
wrappedStateDB.AddBalanceMultiCoin(userAddr1, assetID, bigHundred)
|
|
wrappedStateDB.Finalise(true)
|
|
return statedb
|
|
},
|
|
from: userAddr1,
|
|
precompileAddr: GenesisContractAddr,
|
|
input: PackNativeAssetCallInput(userAddr2, assetID, big.NewInt(50), nil),
|
|
value: big0,
|
|
gasInput: params.AssetCallGasCost + params.CallNewAccountGas,
|
|
expectedGasRemaining: params.AssetCallGasCost + params.CallNewAccountGas,
|
|
expectedErr: vm.ErrExecutionReverted,
|
|
expectedResult: nil,
|
|
name: "deprecated contract",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
stateDB := test.setupStateDB()
|
|
// Create EVM with BlockNumber and Time initialized to 0.
|
|
evm := vm.NewEVM(vmCtx, stateDB, params.TestChainConfig, vm.Config{})
|
|
ret, gasRemaining, err := evm.Call(test.from, test.precompileAddr, test.input, test.gasInput, test.value)
|
|
// Place gas remaining check before error check, so that it is not skipped when there is an error
|
|
assert.Equalf(t, test.expectedGasRemaining, gasRemaining, "unexpected gas remaining (%d of %d)", gasRemaining, test.gasInput)
|
|
|
|
if test.expectedErr != nil {
|
|
assert.Equal(t, test.expectedErr, err, "expected error to match")
|
|
return
|
|
}
|
|
if assert.NoError(t, err, "EVM Call produced unexpected error") {
|
|
assert.Equal(t, test.expectedResult, ret, "unexpected return value")
|
|
if test.stateDBCheck != nil {
|
|
test.stateDBCheck(t, stateDB)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|