laconicd-deprecated/x/evm/keeper/grpc_query_test.go
2023-01-04 16:34:38 +05:30

1417 lines
36 KiB
Go

package keeper_test
import (
"encoding/json"
"fmt"
"math/big"
sdkmath "cosmossdk.io/math"
"github.com/cerc-io/laconicd/tests"
"github.com/cerc-io/laconicd/x/evm/statedb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
ethlogger "github.com/ethereum/go-ethereum/eth/tracers/logger"
ethparams "github.com/ethereum/go-ethereum/params"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cerc-io/laconicd/server/config"
ethermint "github.com/cerc-io/laconicd/types"
"github.com/cerc-io/laconicd/x/evm/types"
)
// Not valid Ethereum address
const invalidAddress = "0x0000"
func (suite *KeeperTestSuite) TestQueryAccount() {
var (
req *types.QueryAccountRequest
expAccount *types.QueryAccountResponse
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"invalid address",
func() {
expAccount = &types.QueryAccountResponse{
Balance: "0",
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).Hex(),
Nonce: 0,
}
req = &types.QueryAccountRequest{
Address: invalidAddress,
}
},
false,
},
{
"success",
func() {
amt := sdk.Coins{ethermint.NewPhotonCoinInt64(100)}
err := suite.app.BankKeeper.MintCoins(suite.ctx, types.ModuleName, amt)
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, suite.address.Bytes(), amt)
suite.Require().NoError(err)
expAccount = &types.QueryAccountResponse{
Balance: "100",
CodeHash: common.BytesToHash(crypto.Keccak256(nil)).Hex(),
Nonce: 0,
}
req = &types.QueryAccountRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Account(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expAccount, res)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryCosmosAccount() {
var (
req *types.QueryCosmosAccountRequest
expAccount *types.QueryCosmosAccountResponse
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"invalid address",
func() {
expAccount = &types.QueryCosmosAccountResponse{
CosmosAddress: sdk.AccAddress(common.Address{}.Bytes()).String(),
}
req = &types.QueryCosmosAccountRequest{
Address: invalidAddress,
}
},
false,
},
{
"success",
func() {
expAccount = &types.QueryCosmosAccountResponse{
CosmosAddress: sdk.AccAddress(suite.address.Bytes()).String(),
Sequence: 0,
AccountNumber: 0,
}
req = &types.QueryCosmosAccountRequest{
Address: suite.address.String(),
}
},
true,
},
{
"success with seq and account number",
func() {
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, suite.address.Bytes())
suite.Require().NoError(acc.SetSequence(10))
suite.Require().NoError(acc.SetAccountNumber(1))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
expAccount = &types.QueryCosmosAccountResponse{
CosmosAddress: sdk.AccAddress(suite.address.Bytes()).String(),
Sequence: 10,
AccountNumber: 1,
}
req = &types.QueryCosmosAccountRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.CosmosAccount(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expAccount, res)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryBalance() {
var (
req *types.QueryBalanceRequest
expBalance string
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"invalid address",
func() {
expBalance = "0"
req = &types.QueryBalanceRequest{
Address: invalidAddress,
}
},
false,
},
{
"success",
func() {
amt := sdk.Coins{ethermint.NewPhotonCoinInt64(100)}
err := suite.app.BankKeeper.MintCoins(suite.ctx, types.ModuleName, amt)
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, suite.address.Bytes(), amt)
suite.Require().NoError(err)
expBalance = "100"
req = &types.QueryBalanceRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Balance(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expBalance, res.Balance)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryStorage() {
var (
req *types.QueryStorageRequest
expValue string
)
testCases := []struct {
msg string
malleate func(vm.StateDB)
expPass bool
}{
{
"invalid address",
func(vm.StateDB) {
req = &types.QueryStorageRequest{
Address: invalidAddress,
}
},
false,
},
{
"success",
func(vmdb vm.StateDB) {
key := common.BytesToHash([]byte("key"))
value := common.BytesToHash([]byte("value"))
expValue = value.String()
vmdb.SetState(suite.address, key, value)
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: key.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
vmdb := suite.StateDB()
tc.malleate(vmdb)
suite.Require().NoError(vmdb.Commit())
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Storage(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expValue, res.Value)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryCode() {
var (
req *types.QueryCodeRequest
expCode []byte
)
testCases := []struct {
msg string
malleate func(vm.StateDB)
expPass bool
}{
{
"invalid address",
func(vm.StateDB) {
req = &types.QueryCodeRequest{
Address: invalidAddress,
}
exp := &types.QueryCodeResponse{}
expCode = exp.Code
},
false,
},
{
"success",
func(vmdb vm.StateDB) {
expCode = []byte("code")
vmdb.SetCode(suite.address, expCode)
req = &types.QueryCodeRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
vmdb := suite.StateDB()
tc.malleate(vmdb)
suite.Require().NoError(vmdb.Commit())
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Code(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expCode, res.Code)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryTxLogs() {
var expLogs []*types.Log
txHash := common.BytesToHash([]byte("tx_hash"))
txIndex := uint(1)
logIndex := uint(1)
testCases := []struct {
msg string
malleate func(vm.StateDB)
}{
{
"empty logs",
func(vm.StateDB) {
expLogs = nil
},
},
{
"success",
func(vmdb vm.StateDB) {
expLogs = []*types.Log{
{
Address: suite.address.String(),
Topics: []string{common.BytesToHash([]byte("topic")).String()},
Data: []byte("data"),
BlockNumber: 1,
TxHash: txHash.String(),
TxIndex: uint64(txIndex),
BlockHash: common.BytesToHash(suite.ctx.HeaderHash()).Hex(),
Index: uint64(logIndex),
Removed: false,
},
}
for _, log := range types.LogsToEthereum(expLogs) {
vmdb.AddLog(log)
}
},
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
vmdb := statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes()), txHash, txIndex, logIndex))
tc.malleate(vmdb)
suite.Require().NoError(vmdb.Commit())
logs := vmdb.Logs()
suite.Require().Equal(expLogs, types.NewLogsFromEth(logs))
})
}
}
func (suite *KeeperTestSuite) TestQueryParams() {
ctx := sdk.WrapSDKContext(suite.ctx)
expParams := types.DefaultParams()
res, err := suite.queryClient.Params(ctx, &types.QueryParamsRequest{})
suite.Require().NoError(err)
suite.Require().Equal(expParams, res.Params)
}
func (suite *KeeperTestSuite) TestQueryValidatorAccount() {
var (
req *types.QueryValidatorAccountRequest
expAccount *types.QueryValidatorAccountResponse
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"invalid address",
func() {
expAccount = &types.QueryValidatorAccountResponse{
AccountAddress: sdk.AccAddress(common.Address{}.Bytes()).String(),
}
req = &types.QueryValidatorAccountRequest{
ConsAddress: "",
}
},
false,
},
{
"success",
func() {
expAccount = &types.QueryValidatorAccountResponse{
AccountAddress: sdk.AccAddress(suite.address.Bytes()).String(),
Sequence: 0,
AccountNumber: 0,
}
req = &types.QueryValidatorAccountRequest{
ConsAddress: suite.consAddress.String(),
}
},
true,
},
{
"success with seq and account number",
func() {
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, suite.address.Bytes())
suite.Require().NoError(acc.SetSequence(10))
suite.Require().NoError(acc.SetAccountNumber(1))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
expAccount = &types.QueryValidatorAccountResponse{
AccountAddress: sdk.AccAddress(suite.address.Bytes()).String(),
Sequence: 10,
AccountNumber: 1,
}
req = &types.QueryValidatorAccountRequest{
ConsAddress: suite.consAddress.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.ValidatorAccount(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expAccount, res)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestEstimateGas() {
gasHelper := hexutil.Uint64(20000)
higherGas := hexutil.Uint64(25000)
hexBigInt := hexutil.Big(*big.NewInt(1))
var (
args interface{}
gasCap uint64
)
testCases := []struct {
msg string
malleate func()
expPass bool
expGas uint64
enableFeemarket bool
}{
// should success, because transfer value is zero
{
"default args - special case for ErrIntrinsicGas on contract creation, raise gas limit",
func() {
args = types.TransactionArgs{}
},
true,
ethparams.TxGasContractCreation,
false,
},
// should success, because transfer value is zero
{
"default args with 'to' address",
func() {
args = types.TransactionArgs{To: &common.Address{}}
},
true,
ethparams.TxGas,
false,
},
// should fail, because the default From address(zero address) don't have fund
{
"not enough balance",
func() {
args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))}
},
false,
0,
false,
},
// should success, enough balance now
{
"enough balance",
func() {
args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))}
}, false, 0, false},
// should success, because gas limit lower than 21000 is ignored
{
"gas exceed allowance",
func() {
args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper}
},
true,
ethparams.TxGas,
false,
},
// should fail, invalid gas cap
{
"gas exceed global allowance",
func() {
args = types.TransactionArgs{To: &common.Address{}}
gasCap = 20000
},
false,
0,
false,
},
// estimate gas of an erc20 contract deployment, the exact gas number is checked with geth
{
"contract deployment",
func() {
ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Require().NoError(err)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args = types.TransactionArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
}
},
true,
1186778,
false,
},
// estimate gas of an erc20 transfer, the exact gas number is checked with geth
{
"erc20 transfer",
func() {
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
transferData, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
suite.Require().NoError(err)
args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)}
},
true,
51880,
false,
},
// repeated tests with enableFeemarket
{
"default args w/ enableFeemarket",
func() {
args = types.TransactionArgs{To: &common.Address{}}
},
true,
ethparams.TxGas,
true,
},
{
"not enough balance w/ enableFeemarket",
func() {
args = types.TransactionArgs{To: &common.Address{}, Value: (*hexutil.Big)(big.NewInt(100))}
},
false,
0,
true,
},
{
"enough balance w/ enableFeemarket",
func() {
args = types.TransactionArgs{To: &common.Address{}, From: &suite.address, Value: (*hexutil.Big)(big.NewInt(100))}
},
false,
0,
true,
},
{
"gas exceed allowance w/ enableFeemarket",
func() {
args = types.TransactionArgs{To: &common.Address{}, Gas: &gasHelper}
},
true,
ethparams.TxGas,
true,
},
{
"gas exceed global allowance w/ enableFeemarket",
func() {
args = types.TransactionArgs{To: &common.Address{}}
gasCap = 20000
},
false,
0,
true,
},
{
"contract deployment w/ enableFeemarket",
func() {
ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Require().NoError(err)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args = types.TransactionArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
}
},
true,
1186778,
true,
},
{
"erc20 transfer w/ enableFeemarket",
func() {
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
transferData, err := types.ERC20Contract.ABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
suite.Require().NoError(err)
args = types.TransactionArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)}
},
true,
51880,
true,
},
{
"contract creation but 'create' param disabled",
func() {
ctorArgs, err := types.ERC20Contract.ABI.Pack("", &suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Require().NoError(err)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args = types.TransactionArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
}
params := suite.app.EvmKeeper.GetParams(suite.ctx)
params.EnableCreate = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
},
false,
0,
false,
},
{
"specified gas in args higher than ethparams.TxGas (21,000)",
func() {
args = types.TransactionArgs{
To: &common.Address{},
Gas: &higherGas,
}
},
true,
ethparams.TxGas,
false,
},
{
"specified gas in args higher than request gasCap",
func() {
gasCap = 22_000
args = types.TransactionArgs{
To: &common.Address{},
Gas: &higherGas,
}
},
true,
ethparams.TxGas,
false,
},
{
"invalid args - specified both gasPrice and maxFeePerGas",
func() {
args = types.TransactionArgs{
To: &common.Address{},
GasPrice: &hexBigInt,
MaxFeePerGas: &hexBigInt,
}
},
false,
0,
false,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.enableFeemarket = tc.enableFeemarket
suite.SetupTest()
gasCap = 25_000_000
tc.malleate()
args, err := json.Marshal(&args)
suite.Require().NoError(err)
req := types.EthCallRequest{
Args: args,
GasCap: gasCap,
ProposerAddress: suite.ctx.BlockHeader().ProposerAddress,
}
rsp, err := suite.queryClient.EstimateGas(sdk.WrapSDKContext(suite.ctx), &req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(int64(tc.expGas), int64(rsp.Gas))
} else {
suite.Require().Error(err)
}
})
}
suite.enableFeemarket = false // reset flag
}
func (suite *KeeperTestSuite) TestTraceTx() {
// TODO deploy contract that triggers internal transactions
var (
txMsg *types.MsgEthereumTx
traceConfig *types.TraceConfig
predecessors []*types.MsgEthereumTx
chainID *sdkmath.Int
)
testCases := []struct {
msg string
malleate func()
expPass bool
traceResponse string
enableFeemarket bool
}{
{
msg: "default trace",
malleate: func() {
traceConfig = nil
predecessors = []*types.MsgEthereumTx{}
},
expPass: true,
traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":",
},
{
msg: "default trace with filtered response",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
}
predecessors = []*types.MsgEthereumTx{}
},
expPass: true,
traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":",
enableFeemarket: false,
},
{
msg: "javascript tracer",
malleate: func() {
traceConfig = &types.TraceConfig{
Tracer: "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}",
}
predecessors = []*types.MsgEthereumTx{}
},
expPass: true,
traceResponse: "[]",
},
{
msg: "default trace with enableFeemarket",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
}
predecessors = []*types.MsgEthereumTx{}
},
expPass: true,
traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":",
enableFeemarket: true,
},
{
msg: "javascript tracer with enableFeemarket",
malleate: func() {
traceConfig = &types.TraceConfig{
Tracer: "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}",
}
predecessors = []*types.MsgEthereumTx{}
},
expPass: true,
traceResponse: "[]",
enableFeemarket: true,
},
{
msg: "default tracer with predecessors",
malleate: func() {
traceConfig = nil
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
suite.Require().NoError(vmdb.Commit())
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
// Generate token transfer transaction
firstTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
txMsg = suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
suite.Commit()
predecessors = append(predecessors, firstTx)
},
expPass: true,
traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":",
enableFeemarket: false,
},
{
msg: "invalid trace config - Negative Limit",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
Limit: -1,
}
},
expPass: false,
},
{
msg: "invalid trace config - Invalid Tracer",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
Tracer: "invalid_tracer",
}
},
expPass: false,
},
{
msg: "invalid trace config - Invalid Timeout",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
Timeout: "wrong_time",
}
},
expPass: false,
},
{
msg: "default tracer with contract creation tx as predecessor but 'create' param disabled",
malleate: func() {
traceConfig = nil
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
suite.Require().NoError(vmdb.Commit())
chainID := suite.app.EvmKeeper.ChainID()
nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)
data := types.ERC20Contract.Bin
contractTx := types.NewTxContract(
chainID,
nonce,
nil, // amount
ethparams.TxGasContractCreation, // gasLimit
nil, // gasPrice
nil, nil,
data, // input
nil, // accesses
)
predecessors = append(predecessors, contractTx)
suite.Commit()
params := suite.app.EvmKeeper.GetParams(suite.ctx)
params.EnableCreate = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
},
expPass: true,
traceResponse: "{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PUSH1\",\"gas\":",
},
{
msg: "invalid chain id",
malleate: func() {
traceConfig = nil
predecessors = []*types.MsgEthereumTx{}
tmp := sdkmath.NewInt(1)
chainID = &tmp
},
expPass: false,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.enableFeemarket = tc.enableFeemarket
suite.SetupTest()
// Deploy contract
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
// Generate token transfer transaction
txMsg = suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
suite.Commit()
tc.malleate()
traceReq := types.QueryTraceTxRequest{
Msg: txMsg,
TraceConfig: traceConfig,
Predecessors: predecessors,
}
if chainID != nil {
traceReq.ChainId = chainID.Int64()
}
res, err := suite.queryClient.TraceTx(sdk.WrapSDKContext(suite.ctx), &traceReq)
if tc.expPass {
suite.Require().NoError(err)
// if data is to big, slice the result
if len(res.Data) > 150 {
suite.Require().Equal(tc.traceResponse, string(res.Data[:150]))
} else {
suite.Require().Equal(tc.traceResponse, string(res.Data))
}
if traceConfig == nil || traceConfig.Tracer == "" {
var result ethlogger.ExecutionResult
suite.Require().NoError(json.Unmarshal(res.Data, &result))
suite.Require().Positive(result.Gas)
}
} else {
suite.Require().Error(err)
}
// Reset for next test case
chainID = nil
})
}
suite.enableFeemarket = false // reset flag
}
func (suite *KeeperTestSuite) TestTraceBlock() {
var (
txs []*types.MsgEthereumTx
traceConfig *types.TraceConfig
chainID *sdkmath.Int
)
testCases := []struct {
msg string
malleate func()
expPass bool
traceResponse string
enableFeemarket bool
}{
{
msg: "default trace",
malleate: func() {
traceConfig = nil
},
expPass: true,
traceResponse: "[{\"result\":{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PU",
},
{
msg: "filtered trace",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
}
},
expPass: true,
traceResponse: "[{\"result\":{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PU",
},
{
msg: "javascript tracer",
malleate: func() {
traceConfig = &types.TraceConfig{
Tracer: "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}",
}
},
expPass: true,
traceResponse: "[{\"result\":[]}]",
},
{
msg: "default trace with enableFeemarket and filtered return",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
}
},
expPass: true,
traceResponse: "[{\"result\":{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PU",
enableFeemarket: true,
},
{
msg: "javascript tracer with enableFeemarket",
malleate: func() {
traceConfig = &types.TraceConfig{
Tracer: "{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"CALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}",
}
},
expPass: true,
traceResponse: "[{\"result\":[]}]",
enableFeemarket: true,
},
{
msg: "tracer with multiple transactions",
malleate: func() {
traceConfig = nil
// increase nonce to avoid address collision
vmdb := suite.StateDB()
vmdb.SetNonce(suite.address, vmdb.GetNonce(suite.address)+1)
suite.Require().NoError(vmdb.Commit())
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
// create multiple transactions in the same block
firstTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
secondTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
suite.Commit()
// overwrite txs to include only the ones on new block
txs = append([]*types.MsgEthereumTx{}, firstTx, secondTx)
},
expPass: true,
traceResponse: "[{\"result\":{\"gas\":34828,\"failed\":false,\"returnValue\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"structLogs\":[{\"pc\":0,\"op\":\"PU",
enableFeemarket: false,
},
{
msg: "invalid trace config - Negative Limit",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
Limit: -1,
}
},
expPass: false,
},
{
msg: "invalid trace config - Invalid Tracer",
malleate: func() {
traceConfig = &types.TraceConfig{
DisableStack: true,
DisableStorage: true,
EnableMemory: false,
Tracer: "invalid_tracer",
}
},
expPass: true,
traceResponse: "[{\"error\":\"rpc error: code = Internal desc = tracer not found\"}]",
},
{
msg: "invalid chain id",
malleate: func() {
traceConfig = nil
tmp := sdkmath.NewInt(1)
chainID = &tmp
},
expPass: true,
traceResponse: "[{\"error\":\"rpc error: code = Internal desc = invalid chain id for signer\"}]",
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
txs = []*types.MsgEthereumTx{}
suite.enableFeemarket = tc.enableFeemarket
suite.SetupTest()
// Deploy contract
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
// Generate token transfer transaction
txMsg := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt())
suite.Commit()
txs = append(txs, txMsg)
tc.malleate()
traceReq := types.QueryTraceBlockRequest{
Txs: txs,
TraceConfig: traceConfig,
}
if chainID != nil {
traceReq.ChainId = chainID.Int64()
}
res, err := suite.queryClient.TraceBlock(sdk.WrapSDKContext(suite.ctx), &traceReq)
if tc.expPass {
suite.Require().NoError(err)
// if data is to big, slice the result
if len(res.Data) > 150 {
suite.Require().Equal(tc.traceResponse, string(res.Data[:150]))
} else {
suite.Require().Equal(tc.traceResponse, string(res.Data))
}
} else {
suite.Require().Error(err)
}
// Reset for next case
chainID = nil
})
}
suite.enableFeemarket = false // reset flag
}
func (suite *KeeperTestSuite) TestNonceInQuery() {
address := tests.GenerateAddress()
suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(suite.ctx, address))
supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt()
// accupy nonce 0
_ = suite.DeployTestContract(suite.T(), address, supply)
// do an EthCall/EstimateGas with nonce 0
ctorArgs, err := types.ERC20Contract.ABI.Pack("", address, supply)
suite.Require().NoError(err)
data := append(types.ERC20Contract.Bin, ctorArgs...)
args, err := json.Marshal(&types.TransactionArgs{
From: &address,
Data: (*hexutil.Bytes)(&data),
})
suite.Require().NoError(err)
proposerAddress := suite.ctx.BlockHeader().ProposerAddress
_, err = suite.queryClient.EstimateGas(sdk.WrapSDKContext(suite.ctx), &types.EthCallRequest{
Args: args,
GasCap: uint64(config.DefaultGasCap),
ProposerAddress: proposerAddress,
})
suite.Require().NoError(err)
_, err = suite.queryClient.EthCall(sdk.WrapSDKContext(suite.ctx), &types.EthCallRequest{
Args: args,
GasCap: uint64(config.DefaultGasCap),
ProposerAddress: proposerAddress,
})
suite.Require().NoError(err)
}
func (suite *KeeperTestSuite) TestQueryBaseFee() {
var (
aux sdkmath.Int
expRes *types.QueryBaseFeeResponse
)
testCases := []struct {
name string
malleate func()
expPass bool
enableFeemarket bool
enableLondonHF bool
}{
{
"pass - default Base Fee",
func() {
initialBaseFee := sdkmath.NewInt(ethparams.InitialBaseFee)
expRes = &types.QueryBaseFeeResponse{BaseFee: &initialBaseFee}
},
true, true, true,
},
{
"pass - non-nil Base Fee",
func() {
baseFee := sdk.OneInt().BigInt()
suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, baseFee)
aux = sdkmath.NewIntFromBigInt(baseFee)
expRes = &types.QueryBaseFeeResponse{BaseFee: &aux}
},
true, true, true,
},
{
"pass - nil Base Fee when london hardfork not activated",
func() {
baseFee := sdk.OneInt().BigInt()
suite.app.FeeMarketKeeper.SetBaseFee(suite.ctx, baseFee)
expRes = &types.QueryBaseFeeResponse{}
},
true, true, false,
},
{
"pass - zero Base Fee when feemarket not activated",
func() {
baseFee := sdk.ZeroInt()
expRes = &types.QueryBaseFeeResponse{BaseFee: &baseFee}
},
true, false, true,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.enableFeemarket = tc.enableFeemarket
suite.enableLondonHF = tc.enableLondonHF
suite.SetupTest()
tc.malleate()
res, err := suite.queryClient.BaseFee(suite.ctx.Context(), &types.QueryBaseFeeRequest{})
if tc.expPass {
suite.Require().NotNil(res)
suite.Require().Equal(expRes, res, tc.name)
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
suite.enableFeemarket = false
suite.enableLondonHF = true
}
func (suite *KeeperTestSuite) TestEthCall() {
var (
req *types.EthCallRequest
)
address := tests.GenerateAddress()
suite.Require().Equal(uint64(0), suite.app.EvmKeeper.GetNonce(suite.ctx, address))
supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt()
hexBigInt := hexutil.Big(*big.NewInt(1))
ctorArgs, err := types.ERC20Contract.ABI.Pack("", address, supply)
suite.Require().NoError(err)
data := append(types.ERC20Contract.Bin, ctorArgs...)
testCases := []struct {
name string
malleate func()
expPass bool
}{
{
"invalid args",
func() {
req = &types.EthCallRequest{Args: []byte("invalid args"), GasCap: uint64(config.DefaultGasCap)}
},
false,
},
{
"invalid args - specified both gasPrice and maxFeePerGas",
func() {
args, err := json.Marshal(&types.TransactionArgs{
From: &address,
Data: (*hexutil.Bytes)(&data),
GasPrice: &hexBigInt,
MaxFeePerGas: &hexBigInt,
})
suite.Require().NoError(err)
req = &types.EthCallRequest{Args: args, GasCap: uint64(config.DefaultGasCap)}
},
false,
},
{
"set param EnableCreate = false",
func() {
args, err := json.Marshal(&types.TransactionArgs{
From: &address,
Data: (*hexutil.Bytes)(&data),
})
suite.Require().NoError(err)
req = &types.EthCallRequest{Args: args, GasCap: uint64(config.DefaultGasCap)}
params := suite.app.EvmKeeper.GetParams(suite.ctx)
params.EnableCreate = false
suite.app.EvmKeeper.SetParams(suite.ctx, params)
},
false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
tc.malleate()
res, err := suite.queryClient.EthCall(suite.ctx, req)
if tc.expPass {
suite.Require().NotNil(res)
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestEmptyRequest() {
k := suite.app.EvmKeeper
testCases := []struct {
name string
queryFunc func() (interface{}, error)
}{
{
"Account method",
func() (interface{}, error) {
return k.Account(suite.ctx, nil)
},
},
{
"CosmosAccount method",
func() (interface{}, error) {
return k.CosmosAccount(suite.ctx, nil)
},
},
{
"ValidatorAccount method",
func() (interface{}, error) {
return k.ValidatorAccount(suite.ctx, nil)
},
},
{
"Balance method",
func() (interface{}, error) {
return k.Balance(suite.ctx, nil)
},
},
{
"Storage method",
func() (interface{}, error) {
return k.Storage(suite.ctx, nil)
},
},
{
"Code method",
func() (interface{}, error) {
return k.Code(suite.ctx, nil)
},
},
{
"EthCall method",
func() (interface{}, error) {
return k.EthCall(suite.ctx, nil)
},
},
{
"EstimateGas method",
func() (interface{}, error) {
return k.EstimateGas(suite.ctx, nil)
},
},
{
"TraceTx method",
func() (interface{}, error) {
return k.TraceTx(suite.ctx, nil)
},
},
{
"TraceBlock method",
func() (interface{}, error) {
return k.TraceBlock(suite.ctx, nil)
},
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
suite.SetupTest()
_, err := tc.queryFunc()
suite.Require().Error(err)
})
}
}