diff --git a/x/evm/types/access_list_tx_test.go b/x/evm/types/access_list_tx_test.go index 50806387..7ccef3ee 100644 --- a/x/evm/types/access_list_tx_test.go +++ b/x/evm/types/access_list_tx_test.go @@ -57,6 +57,25 @@ func (suite *TxDataTestSuite) TestAccessListTxGetGasFeeCap() { } } +func (suite *TxDataTestSuite) TestEmptyAccessList() { + testCases := []struct { + name string + tx AccessListTx + }{ + { + "empty access list tx", + AccessListTx{ + Accesses: nil, + }, + }, + } + for _, tc := range testCases { + actual := tc.tx.GetAccessList() + + suite.Require().Nil(actual, tc.name) + } +} + func (suite *TxDataTestSuite) TestAccessListTxCost() { testCases := []struct { name string @@ -81,6 +100,28 @@ func (suite *TxDataTestSuite) TestAccessListTxCost() { } } +func (suite *TxDataTestSuite) TestAccessListEffectiveGasPrice() { + testCases := []struct { + name string + tx AccessListTx + baseFee *big.Int + }{ + { + "non-empty access list tx", + AccessListTx{ + GasPrice: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveGasPrice(tc.baseFee) + + suite.Require().Equal(tc.tx.GetGasPrice(), actual, tc.name) + } +} + func (suite *TxDataTestSuite) TestAccessListTxEffectiveCost() { testCases := []struct { name string diff --git a/x/evm/types/chain_config_test.go b/x/evm/types/chain_config_test.go index ca5cd0ce..89029510 100644 --- a/x/evm/types/chain_config_test.go +++ b/x/evm/types/chain_config_test.go @@ -235,6 +235,69 @@ func TestChainConfigValidate(t *testing.T) { }, true, }, + { + "invalid ArrowGlacierBlock", + ChainConfig{ + HomesteadBlock: newIntPtr(0), + DAOForkBlock: newIntPtr(0), + EIP150Block: newIntPtr(0), + EIP150Hash: defaultEIP150Hash, + EIP155Block: newIntPtr(0), + EIP158Block: newIntPtr(0), + ByzantiumBlock: newIntPtr(0), + ConstantinopleBlock: newIntPtr(0), + PetersburgBlock: newIntPtr(0), + IstanbulBlock: newIntPtr(0), + MuirGlacierBlock: newIntPtr(0), + BerlinBlock: newIntPtr(0), + LondonBlock: newIntPtr(0), + ArrowGlacierBlock: newIntPtr(-1), + }, + true, + }, + { + "invalid GrayGlacierBlock", + ChainConfig{ + HomesteadBlock: newIntPtr(0), + DAOForkBlock: newIntPtr(0), + EIP150Block: newIntPtr(0), + EIP150Hash: defaultEIP150Hash, + EIP155Block: newIntPtr(0), + EIP158Block: newIntPtr(0), + ByzantiumBlock: newIntPtr(0), + ConstantinopleBlock: newIntPtr(0), + PetersburgBlock: newIntPtr(0), + IstanbulBlock: newIntPtr(0), + MuirGlacierBlock: newIntPtr(0), + BerlinBlock: newIntPtr(0), + LondonBlock: newIntPtr(0), + ArrowGlacierBlock: newIntPtr(0), + GrayGlacierBlock: newIntPtr(-1), + }, + true, + }, + { + "invalid MergeNetsplitBlock", + ChainConfig{ + HomesteadBlock: newIntPtr(0), + DAOForkBlock: newIntPtr(0), + EIP150Block: newIntPtr(0), + EIP150Hash: defaultEIP150Hash, + EIP155Block: newIntPtr(0), + EIP158Block: newIntPtr(0), + ByzantiumBlock: newIntPtr(0), + ConstantinopleBlock: newIntPtr(0), + PetersburgBlock: newIntPtr(0), + IstanbulBlock: newIntPtr(0), + MuirGlacierBlock: newIntPtr(0), + BerlinBlock: newIntPtr(0), + LondonBlock: newIntPtr(0), + ArrowGlacierBlock: newIntPtr(0), + GrayGlacierBlock: newIntPtr(0), + MergeNetsplitBlock: newIntPtr(-1), + }, + true, + }, { "invalid fork order - skip HomesteadBlock", ChainConfig{ diff --git a/x/evm/types/dynamic_fee_tx_test.go b/x/evm/types/dynamic_fee_tx_test.go index cc4d847e..a237af14 100644 --- a/x/evm/types/dynamic_fee_tx_test.go +++ b/x/evm/types/dynamic_fee_tx_test.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/evmos/ethermint/tests" "github.com/stretchr/testify/suite" @@ -18,25 +19,33 @@ type TxDataTestSuite struct { sdkInt sdkmath.Int uint64 uint64 + hexUint64 hexutil.Uint64 bigInt *big.Int + hexBigInt hexutil.Big overflowBigInt *big.Int sdkZeroInt sdkmath.Int sdkMinusOneInt sdkmath.Int invalidAddr string addr common.Address hexAddr string + hexDataBytes hexutil.Bytes + hexInputBytes hexutil.Bytes } func (suite *TxDataTestSuite) SetupTest() { suite.sdkInt = sdkmath.NewInt(100) suite.uint64 = suite.sdkInt.Uint64() + suite.hexUint64 = hexutil.Uint64(100) suite.bigInt = big.NewInt(1) + suite.hexBigInt = hexutil.Big(*big.NewInt(1)) suite.overflowBigInt = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(256), nil) suite.sdkZeroInt = sdk.ZeroInt() suite.sdkMinusOneInt = sdkmath.NewInt(-1) suite.invalidAddr = "123456" suite.addr = tests.GenerateAddress() suite.hexAddr = suite.addr.Hex() + suite.hexDataBytes = hexutil.Bytes([]byte("data")) + suite.hexInputBytes = hexutil.Bytes([]byte("input")) } func TestTxDataTestSuite(t *testing.T) { @@ -581,6 +590,84 @@ func (suite *TxDataTestSuite) TestDynamicFeeTxValidate() { } } +func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveGasPrice() { + testCases := []struct { + name string + tx DynamicFeeTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty dynamic fee tx", + DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveGasPrice(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveFee() { + testCases := []struct { + name string + tx DynamicFeeTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty dynamic fee tx", + DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + GasLimit: uint64(1), + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveFee(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *TxDataTestSuite) TestDynamicFeeTxEffectiveCost() { + testCases := []struct { + name string + tx DynamicFeeTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty dynamic fee tx", + DynamicFeeTx{ + GasTipCap: &suite.sdkInt, + GasFeeCap: &suite.sdkInt, + GasLimit: uint64(1), + Amount: &suite.sdkZeroInt, + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveCost(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + func (suite *TxDataTestSuite) TestDynamicFeeTxFeeCost() { tx := &DynamicFeeTx{} suite.Require().Panics(func() { tx.Fee() }, "should panic") diff --git a/x/evm/types/genesis_test.go b/x/evm/types/genesis_test.go index 300f5987..fd8e5762 100644 --- a/x/evm/types/genesis_test.go +++ b/x/evm/types/genesis_test.go @@ -115,6 +115,11 @@ func (suite *GenesisTestSuite) TestValidateGenesis() { genState: &GenesisState{}, expPass: false, }, + { + name: "copied genesis", + genState: NewGenesisState(DefaultGenesisState().Params, DefaultGenesisState().Accounts), + expPass: true, + }, { name: "invalid genesis", genState: &GenesisState{ diff --git a/x/evm/types/legacy_tx_test.go b/x/evm/types/legacy_tx_test.go index 6ebc82a4..0246709c 100644 --- a/x/evm/types/legacy_tx_test.go +++ b/x/evm/types/legacy_tx_test.go @@ -349,9 +349,84 @@ func (suite *TxDataTestSuite) TestLegacyTxValidate() { } } +func (suite *TxDataTestSuite) TestLegacyTxEffectiveGasPrice() { + testCases := []struct { + name string + tx LegacyTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty legacy tx", + LegacyTx{ + GasPrice: &suite.sdkInt, + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveGasPrice(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *TxDataTestSuite) TestLegacyTxEffectiveFee() { + testCases := []struct { + name string + tx LegacyTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty legacy tx", + LegacyTx{ + GasPrice: &suite.sdkInt, + GasLimit: uint64(1), + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveFee(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + +func (suite *TxDataTestSuite) TestLegacyTxEffectiveCost() { + testCases := []struct { + name string + tx LegacyTx + baseFee *big.Int + exp *big.Int + }{ + { + "non-empty legacy tx", + LegacyTx{ + GasPrice: &suite.sdkInt, + GasLimit: uint64(1), + Amount: &suite.sdkZeroInt, + }, + (&suite.sdkInt).BigInt(), + (&suite.sdkInt).BigInt(), + }, + } + + for _, tc := range testCases { + actual := tc.tx.EffectiveCost(tc.baseFee) + + suite.Require().Equal(tc.exp, actual, tc.name) + } +} + func (suite *TxDataTestSuite) TestLegacyTxFeeCost() { tx := &LegacyTx{} - suite.Require().Panics(func() { tx.Fee() }, "should panice") - suite.Require().Panics(func() { tx.Cost() }, "should panice") + suite.Require().Panics(func() { tx.Fee() }, "should panic") + suite.Require().Panics(func() { tx.Cost() }, "should panic") } diff --git a/x/evm/types/logs_test.go b/x/evm/types/logs_test.go index 143c3238..93d797cd 100644 --- a/x/evm/types/logs_test.go +++ b/x/evm/types/logs_test.go @@ -45,6 +45,14 @@ func TestTransactionLogsValidate(t *testing.T) { }, false, }, + { + "nil log", + TransactionLogs{ + Hash: common.BytesToHash([]byte("tx_hash")).String(), + Logs: []*Log{nil}, + }, + false, + }, { "invalid log", TransactionLogs{ @@ -158,3 +166,36 @@ func TestValidateLog(t *testing.T) { } } } + +func TestConversionFunctions(t *testing.T) { + addr := tests.GenerateAddress().String() + + txLogs := TransactionLogs{ + Hash: common.BytesToHash([]byte("tx_hash")).String(), + Logs: []*Log{ + { + Address: addr, + Topics: []string{common.BytesToHash([]byte("topic")).String()}, + Data: []byte("data"), + BlockNumber: 1, + TxHash: common.BytesToHash([]byte("tx_hash")).String(), + TxIndex: 1, + BlockHash: common.BytesToHash([]byte("block_hash")).String(), + Index: 1, + Removed: false, + }, + }, + } + + // convert valid log to eth logs and back (and validate) + conversionLogs := NewTransactionLogsFromEth(common.BytesToHash([]byte("tx_hash")), txLogs.EthLogs()) + conversionErr := conversionLogs.Validate() + + // create new transaction logs as copy of old valid one (and validate) + copyLogs := NewTransactionLogs(common.BytesToHash([]byte("tx_hash")), txLogs.Logs) + copyErr := copyLogs.Validate() + + require.Nil(t, conversionErr) + require.Nil(t, copyErr) +} + diff --git a/x/evm/types/tx_args_test.go b/x/evm/types/tx_args_test.go new file mode 100644 index 00000000..5ebc4d7c --- /dev/null +++ b/x/evm/types/tx_args_test.go @@ -0,0 +1,288 @@ +package types + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + //"github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +func (suite *TxDataTestSuite) TestTxArgsString() { + testCases := []struct { + name string + txArgs TransactionArgs + expectedString string + }{ + { + "empty tx args", + TransactionArgs{}, + "TransactionArgs{From:, To:, Gas:, Nonce:, Data:, Input:, AccessList:}", + }, + { + "tx args with fields", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + Nonce: &suite.hexUint64, + Input: &suite.hexInputBytes, + Data: &suite.hexDataBytes, + AccessList: ðtypes.AccessList{}, + }, + fmt.Sprintf("TransactionArgs{From:%v, To:%v, Gas:%v, Nonce:%v, Data:%v, Input:%v, AccessList:%v}", + &suite.addr, + &suite.addr, + &suite.hexUint64, + &suite.hexUint64, + &suite.hexDataBytes, + &suite.hexInputBytes, + ðtypes.AccessList{}), + }, + } + for _, tc := range testCases { + outputString := tc.txArgs.String() + suite.Require().Equal(outputString, tc.expectedString) + } +} + +func (suite *TxDataTestSuite) TestConvertTxArgsEthTx() { + testCases := []struct { + name string + txArgs TransactionArgs + }{ + { + "empty tx args", + TransactionArgs{}, + }, + { + "no nil args", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: &suite.hexBigInt, + MaxPriorityFeePerGas: &suite.hexBigInt, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + }, + { + "max fee per gas nil, but access list not nil", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: nil, + MaxPriorityFeePerGas: &suite.hexBigInt, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + }, + } + for _, tc := range testCases { + res := tc.txArgs.ToTransaction() + suite.Require().NotNil(res) + } +} + +func (suite *TxDataTestSuite) TestToMessageEVM() { + testCases := []struct { + name string + txArgs TransactionArgs + globalGasCap uint64 + baseFee *big.Int + expError bool + }{ + { + "empty tx args", + TransactionArgs{}, + uint64(0), + nil, + false, + }, + { + "specify gasPrice and (maxFeePerGas or maxPriorityFeePerGas)", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: &suite.hexBigInt, + MaxPriorityFeePerGas: &suite.hexBigInt, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + uint64(0), + nil, + true, + }, + { + "non-1559 execution, zero gas cap", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: nil, + MaxPriorityFeePerGas: nil, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + uint64(0), + nil, + false, + }, + { + "non-1559 execution, nonzero gas cap", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: nil, + MaxPriorityFeePerGas: nil, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + uint64(1), + nil, + false, + }, + { + "1559-type execution, nil gas price", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: nil, + MaxFeePerGas: &suite.hexBigInt, + MaxPriorityFeePerGas: &suite.hexBigInt, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + uint64(1), + suite.bigInt, + false, + }, + { + "1559-type execution, non-nil gas price", + TransactionArgs{ + From: &suite.addr, + To: &suite.addr, + Gas: &suite.hexUint64, + GasPrice: &suite.hexBigInt, + MaxFeePerGas: nil, + MaxPriorityFeePerGas: nil, + Value: &suite.hexBigInt, + Nonce: &suite.hexUint64, + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + AccessList: ðtypes.AccessList{{Address: suite.addr, StorageKeys: []common.Hash{{0}}}}, + ChainID: &suite.hexBigInt, + }, + uint64(1), + suite.bigInt, + false, + }, + } + for _, tc := range testCases { + res, err := tc.txArgs.ToMessage(tc.globalGasCap, tc.baseFee) + + if tc.expError { + suite.Require().NotNil(err) + } else { + suite.Require().Nil(err) + suite.Require().NotNil(res) + } + } +} + +func (suite *TxDataTestSuite) TestGetFrom() { + testCases := []struct { + name string + txArgs TransactionArgs + expAddress common.Address + }{ + { + "empty from field", + TransactionArgs{}, + common.Address{}, + }, + { + "non-empty from field", + TransactionArgs{ + From: &suite.addr, + }, + suite.addr, + }, + } + for _, tc := range testCases { + retrievedAddress := tc.txArgs.GetFrom() + suite.Require().Equal(retrievedAddress, tc.expAddress) + } +} + +func (suite *TxDataTestSuite) TestGetData() { + testCases := []struct { + name string + txArgs TransactionArgs + expectedOutput []byte + }{ + { + "empty input and data fields", + TransactionArgs{ + Data: nil, + Input: nil, + }, + nil, + }, + { + "empty input field, non-empty data field", + TransactionArgs{ + Data: &suite.hexDataBytes, + Input: nil, + }, + []byte("data"), + }, + { + "non-empty input and data fields", + TransactionArgs{ + Data: &suite.hexDataBytes, + Input: &suite.hexInputBytes, + }, + []byte("input"), + }, + } + for _, tc := range testCases { + retrievedData := tc.txArgs.GetData() + suite.Require().Equal(retrievedData, tc.expectedOutput) + } +} diff --git a/x/evm/types/utils_test.go b/x/evm/types/utils_test.go index 5a76e605..ae3f0ce4 100644 --- a/x/evm/types/utils_test.go +++ b/x/evm/types/utils_test.go @@ -14,6 +14,8 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" proto "github.com/gogo/protobuf/proto" + "github.com/evmos/ethermint/tests" + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" @@ -84,3 +86,31 @@ func TestBinSearch(t *testing.T) { require.Error(t, err) require.Equal(t, gas, uint64(0)) } + +func TestTransactionLogsEncodeDecode(t *testing.T) { + addr := tests.GenerateAddress().String() + + txLogs := evmtypes.TransactionLogs{ + Hash: common.BytesToHash([]byte("tx_hash")).String(), + Logs: []*evmtypes.Log{ + { + Address: addr, + Topics: []string{common.BytesToHash([]byte("topic")).String()}, + Data: []byte("data"), + BlockNumber: 1, + TxHash: common.BytesToHash([]byte("tx_hash")).String(), + TxIndex: 1, + BlockHash: common.BytesToHash([]byte("block_hash")).String(), + Index: 1, + Removed: false, + }, + }, + } + + txLogsEncoded, encodeErr := evmtypes.EncodeTransactionLogs(&txLogs) + require.Nil(t, encodeErr) + + txLogsEncodedDecoded, decodeErr := evmtypes.DecodeTransactionLogs(txLogsEncoded) + require.Nil(t, decodeErr) + require.Equal(t, txLogs, txLogsEncodedDecoded) +} \ No newline at end of file