evm: support legacy tx (#109)
* evm: support legacy tx * lint and minor fix * tx data constructor * tx data tests * ante handler tests * fill msg sender * c++
This commit is contained in:
parent
0c423e8fa1
commit
e8f6f7838e
@ -54,7 +54,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Improvements
|
||||
|
||||
* (evm) [tharsis#24](https://github.com/tharsis/ethermint/pull/24)Implement metrics for `MsgEthereumTx`, state transtitions, `BeginBlock` and `EndBlock`.
|
||||
* (evm) [tharsis#66](https://github.com/tharsis/ethermint/issues/66) Support legacy transaction types for signing.
|
||||
* (evm) [tharsis#24](https://github.com/tharsis/ethermint/pull/24) Implement metrics for `MsgEthereumTx`, state transtitions, `BeginBlock` and `EndBlock`.
|
||||
* (deps) [\#602](https://github.com/cosmos/ethermint/pull/856) Bump tendermint version to [v0.39.3](https://github.com/tendermint/tendermint/releases/tag/v0.39.3)
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -48,6 +48,10 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx, got %d", len(tx.GetMsgs()))
|
||||
}
|
||||
|
||||
// get and set account must be called with an infinite gas meter in order to prevent
|
||||
// additional gas from being deducted.
|
||||
infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||
@ -63,18 +67,21 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
|
||||
blockNum := big.NewInt(ctx.BlockHeight())
|
||||
signer := ethtypes.MakeSigner(ethCfg, blockNum)
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, &evmtypes.MsgEthereumTx{})
|
||||
}
|
||||
|
||||
if _, err := signer.Sender(msgEthTx.AsTransaction()); err != nil {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, err.Error())
|
||||
}
|
||||
msg := tx.GetMsgs()[0]
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, &evmtypes.MsgEthereumTx{})
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
sender, err := signer.Sender(msgEthTx.AsTransaction())
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, err.Error())
|
||||
}
|
||||
|
||||
// set up the sender to the transaction field if not already
|
||||
msgEthTx.From = sender.Hex()
|
||||
|
||||
return next(ctx, msgEthTx, simulate)
|
||||
}
|
||||
|
||||
// EthAccountVerificationDecorator validates an account balance checks
|
||||
@ -228,10 +235,6 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
|
||||
// additional gas from being deducted.
|
||||
infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx, got %d", len(tx.GetMsgs()))
|
||||
}
|
||||
|
||||
// reset the refund gas value in the keeper for the current transaction
|
||||
egcd.evmKeeper.ResetRefundTransient(infCtx)
|
||||
|
||||
@ -331,6 +334,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
|
||||
}
|
||||
|
||||
ethCfg := config.EthereumConfig(ctd.evmKeeper.ChainID())
|
||||
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight()))
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
@ -338,7 +342,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", msg)
|
||||
}
|
||||
|
||||
coreMsg, err := msgEthTx.AsMessage()
|
||||
coreMsg, err := msgEthTx.AsMessage(signer)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
@ -409,12 +413,9 @@ func (ald AccessListDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", msg)
|
||||
}
|
||||
|
||||
coreMsg, err := msgEthTx.AsMessage()
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx cannot be expressed as core.Message: %s", err.Error())
|
||||
}
|
||||
sender := common.BytesToAddress(msgEthTx.GetFrom())
|
||||
|
||||
ald.evmKeeper.PrepareAccessList(coreMsg.From(), coreMsg.To(), vm.ActivePrecompiles(rules), coreMsg.AccessList())
|
||||
ald.evmKeeper.PrepareAccessList(sender, msgEthTx.To(), vm.ActivePrecompiles(rules), *msgEthTx.Data.Accesses.ToEthAccessList())
|
||||
}
|
||||
|
||||
// set the original gas meter
|
||||
|
@ -310,13 +310,16 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
|
||||
func (suite AnteTestSuite) TestCanTransferDecorator() {
|
||||
dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper)
|
||||
|
||||
addr, _ := newTestAddrKey()
|
||||
addr, privKey := newTestAddrKey()
|
||||
|
||||
tx := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil)
|
||||
tx2 := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil)
|
||||
tx := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, ðtypes.AccessList{})
|
||||
tx2 := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, ðtypes.AccessList{})
|
||||
|
||||
tx.From = addr.Hex()
|
||||
|
||||
err := tx.Sign(suite.ethSigner, tests.NewSigner(privKey))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tx sdk.Tx
|
||||
@ -376,7 +379,6 @@ func (suite AnteTestSuite) TestAccessListDecorator() {
|
||||
|
||||
tx := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil)
|
||||
tx2 := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, al)
|
||||
tx3 := evmtypes.NewMsgEthereumTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil)
|
||||
|
||||
tx.From = addr.Hex()
|
||||
tx2.From = addr.Hex()
|
||||
@ -388,7 +390,6 @@ func (suite AnteTestSuite) TestAccessListDecorator() {
|
||||
expPass bool
|
||||
}{
|
||||
{"invalid transaction type", &invalidTx{}, func() {}, false},
|
||||
{"AsMessage failed", tx3, func() {}, false},
|
||||
{
|
||||
"success - no access list",
|
||||
tx,
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
"github.com/cosmos/ethermint/types"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
@ -29,7 +29,7 @@ const (
|
||||
|
||||
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
|
||||
func NewMsgEthereumTx(
|
||||
chainID *big.Int, nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||
chainID *big.Int, nonce uint64, to *common.Address, amount *big.Int,
|
||||
gasLimit uint64, gasPrice *big.Int, input []byte, accesses *ethtypes.AccessList,
|
||||
) *MsgEthereumTx {
|
||||
return newMsgEthereumTx(chainID, nonce, to, amount, gasLimit, gasPrice, input, accesses)
|
||||
@ -45,71 +45,38 @@ func NewMsgEthereumTxContract(
|
||||
}
|
||||
|
||||
func newMsgEthereumTx(
|
||||
chainID *big.Int, nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||
chainID *big.Int, nonce uint64, to *common.Address, amount *big.Int,
|
||||
gasLimit uint64, gasPrice *big.Int, input []byte, accesses *ethtypes.AccessList,
|
||||
) *MsgEthereumTx {
|
||||
if len(input) > 0 {
|
||||
input = ethcmn.CopyBytes(input)
|
||||
}
|
||||
|
||||
var toHex string
|
||||
if to != nil {
|
||||
toHex = to.Hex()
|
||||
}
|
||||
|
||||
var chainIDBz []byte
|
||||
if chainID != nil {
|
||||
chainIDBz = chainID.Bytes()
|
||||
}
|
||||
|
||||
txData := &TxData{
|
||||
ChainID: chainIDBz,
|
||||
Nonce: nonce,
|
||||
To: toHex,
|
||||
Input: input,
|
||||
GasLimit: gasLimit,
|
||||
Amount: []byte{},
|
||||
GasPrice: []byte{},
|
||||
Accesses: NewAccessList(accesses),
|
||||
V: []byte{},
|
||||
R: []byte{},
|
||||
S: []byte{},
|
||||
}
|
||||
|
||||
if amount != nil {
|
||||
txData.Amount = amount.Bytes()
|
||||
}
|
||||
if gasPrice != nil {
|
||||
txData.GasPrice = gasPrice.Bytes()
|
||||
}
|
||||
|
||||
txData := newTxData(chainID, nonce, to, amount, gasLimit, gasPrice, input, accesses)
|
||||
return &MsgEthereumTx{Data: txData}
|
||||
}
|
||||
|
||||
// fromEthereumTx populates the message fields from the given ethereum transaction
|
||||
func (msg *MsgEthereumTx) FromEthereumTx(tx *ethtypes.Transaction) {
|
||||
to := ""
|
||||
if tx.To() != nil {
|
||||
to = tx.To().Hex()
|
||||
}
|
||||
|
||||
al := tx.AccessList()
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
|
||||
msg.Data = &TxData{
|
||||
ChainID: tx.ChainId().Bytes(),
|
||||
Nonce: tx.Nonce(),
|
||||
Input: tx.Data(),
|
||||
GasLimit: tx.Gas(),
|
||||
To: to,
|
||||
Amount: tx.Value().Bytes(),
|
||||
GasPrice: tx.GasPrice().Bytes(),
|
||||
Accesses: NewAccessList(&al),
|
||||
V: v.Bytes(),
|
||||
R: r.Bytes(),
|
||||
S: s.Bytes(),
|
||||
}
|
||||
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
if tx.To() != nil {
|
||||
msg.Data.To = tx.To().Hex()
|
||||
}
|
||||
if tx.Value() != nil {
|
||||
msg.Data.Amount = tx.Value().Bytes()
|
||||
}
|
||||
if tx.GasPrice() != nil {
|
||||
msg.Data.GasPrice = tx.GasPrice().Bytes()
|
||||
}
|
||||
if tx.AccessList() != nil {
|
||||
al := tx.AccessList()
|
||||
msg.Data.Accesses = NewAccessList(&al)
|
||||
}
|
||||
|
||||
msg.Data.setSignatureValues(tx.ChainId(), v, r, s)
|
||||
|
||||
msg.Size_ = float64(tx.Size())
|
||||
msg.Hash = tx.Hash().Hex()
|
||||
}
|
||||
@ -123,41 +90,19 @@ func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
|
||||
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
||||
// checks of a Transaction. If returns an error if validation fails.
|
||||
func (msg MsgEthereumTx) ValidateBasic() error {
|
||||
gasPrice := new(big.Int).SetBytes(msg.Data.GasPrice)
|
||||
if gasPrice.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(ErrInvalidGasPrice, "gas price cannot be negative %s", gasPrice)
|
||||
}
|
||||
|
||||
// Amount can be 0
|
||||
amount := new(big.Int).SetBytes(msg.Data.Amount)
|
||||
if amount.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount)
|
||||
}
|
||||
|
||||
if msg.Data.To != "" {
|
||||
if err := types.ValidateAddress(msg.Data.To); err != nil {
|
||||
return sdkerrors.Wrap(err, "invalid to address")
|
||||
}
|
||||
}
|
||||
|
||||
if msg.From != "" {
|
||||
if err := types.ValidateAddress(msg.From); err != nil {
|
||||
return sdkerrors.Wrap(err, "invalid from address")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return msg.Data.Validate()
|
||||
}
|
||||
|
||||
// To returns the recipient address of the transaction. It returns nil if the
|
||||
// transaction is a contract creation.
|
||||
func (msg MsgEthereumTx) To() *ethcmn.Address {
|
||||
if msg.Data.To == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipient := ethcmn.HexToAddress(msg.Data.To)
|
||||
return &recipient
|
||||
func (msg MsgEthereumTx) To() *common.Address {
|
||||
return msg.Data.to()
|
||||
}
|
||||
|
||||
// GetMsgs returns a single MsgEthereumTx as an sdk.Msg.
|
||||
@ -173,10 +118,10 @@ func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
|
||||
v, r, s := msg.RawSignatureValues()
|
||||
|
||||
if msg.From == "" || v == nil || r == nil || s == nil {
|
||||
panic("must use 'Sign' with a chain ID to get the signer")
|
||||
panic("must use 'Sign' to get the signer address")
|
||||
}
|
||||
|
||||
signer := sdk.AccAddress(ethcmn.HexToAddress(msg.From).Bytes())
|
||||
signer := sdk.AccAddress(common.HexToAddress(msg.From).Bytes())
|
||||
return []sdk.AccAddress{signer}
|
||||
}
|
||||
|
||||
@ -221,34 +166,32 @@ func (msg *MsgEthereumTx) Sign(ethSigner ethtypes.Signer, keyringSigner keyring.
|
||||
|
||||
// GetGas implements the GasTx interface. It returns the GasLimit of the transaction.
|
||||
func (msg MsgEthereumTx) GetGas() uint64 {
|
||||
return msg.Data.GasLimit
|
||||
return msg.Data.gas()
|
||||
}
|
||||
|
||||
// Fee returns gasprice * gaslimit.
|
||||
func (msg MsgEthereumTx) Fee() *big.Int {
|
||||
gasPrice := new(big.Int).SetBytes(msg.Data.GasPrice)
|
||||
gasLimit := new(big.Int).SetUint64(msg.Data.GasLimit)
|
||||
gasPrice := msg.Data.gasPrice()
|
||||
gasLimit := new(big.Int).SetUint64(msg.Data.gas())
|
||||
return new(big.Int).Mul(gasPrice, gasLimit)
|
||||
}
|
||||
|
||||
// ChainID returns which chain id this transaction was signed for (if at all)
|
||||
func (msg *MsgEthereumTx) ChainID() *big.Int {
|
||||
return new(big.Int).SetBytes(msg.Data.ChainID)
|
||||
return msg.Data.chainID()
|
||||
}
|
||||
|
||||
// Cost returns amount + gasprice * gaslimit.
|
||||
func (msg MsgEthereumTx) Cost() *big.Int {
|
||||
total := msg.Fee()
|
||||
total.Add(total, new(big.Int).SetBytes(msg.Data.Amount))
|
||||
total.Add(total, msg.Data.amount())
|
||||
return total
|
||||
}
|
||||
|
||||
// RawSignatureValues returns the V, R, S signature values of the transaction.
|
||||
// The return values should not be modified by the caller.
|
||||
func (msg MsgEthereumTx) RawSignatureValues() (v, r, s *big.Int) {
|
||||
return new(big.Int).SetBytes(msg.Data.V),
|
||||
new(big.Int).SetBytes(msg.Data.R),
|
||||
new(big.Int).SetBytes(msg.Data.S)
|
||||
return msg.Data.rawSignatureValues()
|
||||
}
|
||||
|
||||
// GetFrom loads the ethereum sender address from the sigcache and returns an
|
||||
@ -258,7 +201,7 @@ func (msg *MsgEthereumTx) GetFrom() sdk.AccAddress {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ethcmn.HexToAddress(msg.From).Bytes()
|
||||
return common.HexToAddress(msg.From).Bytes()
|
||||
}
|
||||
|
||||
// AsTransaction creates an Ethereum Transaction type from the msg fields
|
||||
@ -266,64 +209,7 @@ func (msg MsgEthereumTx) AsTransaction() *ethtypes.Transaction {
|
||||
return ethtypes.NewTx(msg.Data.AsEthereumData())
|
||||
}
|
||||
|
||||
// AsMessage creates an Ethereum core.Message from the msg fields. This method
|
||||
// fails if the sender address is not defined
|
||||
func (msg MsgEthereumTx) AsMessage() (core.Message, error) {
|
||||
if msg.From == "" {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "'from' address cannot be empty")
|
||||
}
|
||||
|
||||
from := ethcmn.HexToAddress(msg.From)
|
||||
|
||||
var to *ethcmn.Address
|
||||
if msg.Data.To != "" {
|
||||
toAddr := ethcmn.HexToAddress(msg.Data.To)
|
||||
to = &toAddr
|
||||
}
|
||||
|
||||
var accessList ethtypes.AccessList
|
||||
if msg.Data.Accesses != nil {
|
||||
accessList = *msg.Data.Accesses.ToEthAccessList()
|
||||
}
|
||||
|
||||
return ethtypes.NewMessage(
|
||||
from,
|
||||
to,
|
||||
msg.Data.Nonce,
|
||||
new(big.Int).SetBytes(msg.Data.Amount),
|
||||
msg.Data.GasLimit,
|
||||
new(big.Int).SetBytes(msg.Data.GasPrice),
|
||||
msg.Data.Input,
|
||||
accessList,
|
||||
true,
|
||||
), nil
|
||||
}
|
||||
|
||||
// AsEthereumData returns an AccessListTx transaction data from the proto-formatted
|
||||
// TxData defined on the Cosmos EVM.
|
||||
func (data *TxData) AsEthereumData() ethtypes.TxData {
|
||||
var to *ethcmn.Address
|
||||
if data.To != "" {
|
||||
toAddr := ethcmn.HexToAddress(data.To)
|
||||
to = &toAddr
|
||||
}
|
||||
|
||||
var accessList ethtypes.AccessList
|
||||
if data.Accesses != nil {
|
||||
accessList = *data.Accesses.ToEthAccessList()
|
||||
}
|
||||
|
||||
return ðtypes.AccessListTx{
|
||||
ChainID: new(big.Int).SetBytes(data.ChainID),
|
||||
Nonce: data.Nonce,
|
||||
GasPrice: new(big.Int).SetBytes(data.GasPrice),
|
||||
Gas: data.GasLimit,
|
||||
To: to,
|
||||
Value: new(big.Int).SetBytes(data.Amount),
|
||||
Data: data.Input,
|
||||
AccessList: accessList,
|
||||
V: new(big.Int).SetBytes(data.V),
|
||||
R: new(big.Int).SetBytes(data.R),
|
||||
S: new(big.Int).SetBytes(data.S),
|
||||
}
|
||||
// AsMessage creates an Ethereum core.Message from the msg fields
|
||||
func (msg MsgEthereumTx) AsMessage(signer ethtypes.Signer) (core.Message, error) {
|
||||
return msg.AsTransaction().AsMessage(signer)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/cosmos/ethermint/tests"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
@ -65,91 +66,101 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
|
||||
to *ethcmn.Address
|
||||
amount *big.Int
|
||||
gasPrice *big.Int
|
||||
from string
|
||||
accessList *ethtypes.AccessList
|
||||
chainID *big.Int
|
||||
expectPass bool
|
||||
}{
|
||||
{msg: "pass with recipient", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{msg: "pass contract", to: nil, amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{msg: "pass with recipient - Legacy Tx", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{msg: "pass with recipient - AccessList Tx", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(0), accessList: ðtypes.AccessList{}, chainID: big.NewInt(1), expectPass: true},
|
||||
{msg: "pass contract - Legacy Tx", to: nil, amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{msg: "invalid recipient", to: ðcmn.Address{}, amount: big.NewInt(-1), gasPrice: big.NewInt(1000), expectPass: false},
|
||||
// NOTE: these can't be effectively tested because the SetBytes function from big.Int only sets
|
||||
// the absolute value
|
||||
{msg: "nil amount", to: &suite.to, amount: nil, gasPrice: big.NewInt(1000), expectPass: false},
|
||||
{msg: "negative amount", to: &suite.to, amount: big.NewInt(-1), gasPrice: big.NewInt(1000), expectPass: true},
|
||||
{msg: "nil gas price", to: &suite.to, amount: big.NewInt(100), gasPrice: nil, expectPass: false},
|
||||
{msg: "negative gas price", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: true},
|
||||
{msg: "zero gas price", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(0), expectPass: true},
|
||||
{msg: "invalid from address", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(0), from: ethcmn.Address{}.Hex(), expectPass: false},
|
||||
{msg: "chain ID not set on AccessListTx", to: &suite.to, amount: big.NewInt(100), gasPrice: big.NewInt(0), accessList: ðtypes.AccessList{}, chainID: nil, expectPass: false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
msg := NewMsgEthereumTx(suite.chainID, 0, tc.to, tc.amount, 0, tc.gasPrice, nil, nil)
|
||||
msg := NewMsgEthereumTx(tc.chainID, 0, tc.to, tc.amount, 0, tc.gasPrice, nil, tc.accessList)
|
||||
msg.From = tc.from
|
||||
err := msg.ValidateBasic()
|
||||
|
||||
if tc.expectPass {
|
||||
suite.Require().NoError(err, "valid test %d failed: %s", i, tc.msg)
|
||||
suite.Require().NoError(err, "valid test %d failed: %s, %v", i, tc.msg, msg)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test %d passed: %s", i, tc.msg)
|
||||
suite.Require().Error(err, "invalid test %d passed: %s, %v", i, tc.msg, msg.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() {
|
||||
msg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil)
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
tx *MsgEthereumTx
|
||||
ethSigner ethtypes.Signer
|
||||
malleate func()
|
||||
malleate func(tx *MsgEthereumTx)
|
||||
expectPass bool
|
||||
}{
|
||||
{
|
||||
"pass - EIP2930 signer",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), &types.AccessList{}),
|
||||
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||
func() { msg.From = suite.from.Hex() },
|
||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||
true,
|
||||
},
|
||||
// TODO: support legacy txs
|
||||
{
|
||||
"not supported - EIP155 signer",
|
||||
"pass - EIP155 signer",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil),
|
||||
ethtypes.NewEIP155Signer(suite.chainID),
|
||||
func() { msg.From = suite.from.Hex() },
|
||||
false,
|
||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not supported - Homestead signer",
|
||||
"pass - Homestead signer",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil),
|
||||
ethtypes.HomesteadSigner{},
|
||||
func() { msg.From = suite.from.Hex() },
|
||||
false,
|
||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not supported - Frontier signer",
|
||||
"pass - Frontier signer",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil),
|
||||
ethtypes.FrontierSigner{},
|
||||
func() { msg.From = suite.from.Hex() },
|
||||
false,
|
||||
func(tx *MsgEthereumTx) { tx.From = suite.from.Hex() },
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no from address ",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), &types.AccessList{}),
|
||||
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||
func() { msg.From = "" },
|
||||
func(tx *MsgEthereumTx) { tx.From = "" },
|
||||
false,
|
||||
},
|
||||
{
|
||||
"from address ≠ signer address",
|
||||
NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), &types.AccessList{}),
|
||||
ethtypes.NewEIP2930Signer(suite.chainID),
|
||||
func() { msg.From = suite.to.Hex() },
|
||||
func(tx *MsgEthereumTx) { tx.From = suite.to.Hex() },
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tc.malleate()
|
||||
tc.malleate(tc.tx)
|
||||
|
||||
err := msg.Sign(tc.ethSigner, suite.signer)
|
||||
err := tc.tx.Sign(tc.ethSigner, suite.signer)
|
||||
if tc.expectPass {
|
||||
suite.Require().NoError(err, "valid test %d failed: %s", i, tc.msg)
|
||||
|
||||
tx := msg.AsTransaction()
|
||||
signer := ethtypes.NewEIP2930Signer(suite.chainID)
|
||||
tx := tc.tx.AsTransaction()
|
||||
|
||||
sender, err := ethtypes.Sender(signer, tx)
|
||||
sender, err := ethtypes.Sender(tc.ethSigner, tx)
|
||||
suite.Require().NoError(err, tc.msg)
|
||||
suite.Require().Equal(msg.From, sender.Hex(), tc.msg)
|
||||
suite.Require().Equal(tc.tx.From, sender.Hex(), tc.msg)
|
||||
} else {
|
||||
suite.Require().Error(err, "invalid test %d passed: %s", i, tc.msg)
|
||||
}
|
||||
|
223
x/evm/types/tx_data.go
Normal file
223
x/evm/types/tx_data.go
Normal file
@ -0,0 +1,223 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/ethermint/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// var _ ethtypes.TxData = &TxData{}
|
||||
|
||||
func newTxData(
|
||||
chainID *big.Int, nonce uint64, to *common.Address, amount *big.Int,
|
||||
gasLimit uint64, gasPrice *big.Int, input []byte, accesses *ethtypes.AccessList,
|
||||
) *TxData {
|
||||
txData := &TxData{
|
||||
Nonce: nonce,
|
||||
GasLimit: gasLimit,
|
||||
}
|
||||
|
||||
if len(input) > 0 {
|
||||
txData.Input = common.CopyBytes(input)
|
||||
}
|
||||
|
||||
if to != nil {
|
||||
txData.To = to.Hex()
|
||||
}
|
||||
|
||||
if accesses != nil {
|
||||
txData.Accesses = NewAccessList(accesses)
|
||||
|
||||
// NOTE: we don't populate chain id on LegacyTx type
|
||||
if chainID != nil {
|
||||
txData.ChainID = chainID.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if amount != nil {
|
||||
txData.Amount = amount.Bytes()
|
||||
}
|
||||
if gasPrice != nil {
|
||||
txData.GasPrice = gasPrice.Bytes()
|
||||
}
|
||||
return txData
|
||||
}
|
||||
|
||||
func (data *TxData) txType() byte {
|
||||
if data.Accesses == nil {
|
||||
return ethtypes.LegacyTxType
|
||||
}
|
||||
return ethtypes.AccessListTxType
|
||||
}
|
||||
|
||||
func (data *TxData) chainID() *big.Int {
|
||||
if data.txType() == ethtypes.LegacyTxType {
|
||||
v, _, _ := data.rawSignatureValues()
|
||||
return DeriveChainID(v)
|
||||
}
|
||||
|
||||
if data.ChainID == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return new(big.Int).SetBytes(data.ChainID)
|
||||
}
|
||||
|
||||
func (data *TxData) accessList() ethtypes.AccessList {
|
||||
if data.Accesses == nil {
|
||||
return nil
|
||||
}
|
||||
return *data.Accesses.ToEthAccessList()
|
||||
}
|
||||
|
||||
func (data *TxData) data() []byte {
|
||||
return common.CopyBytes(data.Input)
|
||||
}
|
||||
|
||||
func (data *TxData) gas() uint64 {
|
||||
return data.GasLimit
|
||||
}
|
||||
|
||||
func (data *TxData) gasPrice() *big.Int {
|
||||
if data.GasPrice == nil {
|
||||
return nil
|
||||
}
|
||||
return new(big.Int).SetBytes(data.GasPrice)
|
||||
}
|
||||
|
||||
func (data *TxData) amount() *big.Int {
|
||||
if data.Amount == nil {
|
||||
return nil
|
||||
}
|
||||
return new(big.Int).SetBytes(data.Amount)
|
||||
}
|
||||
|
||||
func (data *TxData) nonce() uint64 { return data.Nonce }
|
||||
|
||||
func (data *TxData) to() *common.Address {
|
||||
if data.To == "" {
|
||||
return nil
|
||||
}
|
||||
to := common.HexToAddress(data.To)
|
||||
return &to
|
||||
}
|
||||
|
||||
// AsEthereumData returns an AccessListTx transaction data from the proto-formatted
|
||||
// TxData defined on the Cosmos EVM.
|
||||
func (data *TxData) AsEthereumData() ethtypes.TxData {
|
||||
v, r, s := data.rawSignatureValues()
|
||||
if data.Accesses == nil {
|
||||
return ðtypes.LegacyTx{
|
||||
Nonce: data.nonce(),
|
||||
GasPrice: data.gasPrice(),
|
||||
Gas: data.gas(),
|
||||
To: data.to(),
|
||||
Value: data.amount(),
|
||||
Data: data.data(),
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
}
|
||||
}
|
||||
|
||||
return ðtypes.AccessListTx{
|
||||
ChainID: data.chainID(),
|
||||
Nonce: data.nonce(),
|
||||
GasPrice: data.gasPrice(),
|
||||
Gas: data.gas(),
|
||||
To: data.to(),
|
||||
Value: data.amount(),
|
||||
Data: data.data(),
|
||||
AccessList: data.accessList(),
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
}
|
||||
}
|
||||
|
||||
// rawSignatureValues returns the V, R, S signature values of the transaction.
|
||||
// The return values should not be modified by the caller.
|
||||
func (data *TxData) rawSignatureValues() (v, r, s *big.Int) {
|
||||
if len(data.V) > 0 {
|
||||
v = new(big.Int).SetBytes(data.V)
|
||||
}
|
||||
if len(data.R) > 0 {
|
||||
r = new(big.Int).SetBytes(data.R)
|
||||
}
|
||||
if len(data.S) > 0 {
|
||||
s = new(big.Int).SetBytes(data.S)
|
||||
}
|
||||
return v, r, s
|
||||
}
|
||||
|
||||
func (data *TxData) setSignatureValues(chainID, v, r, s *big.Int) {
|
||||
if v != nil {
|
||||
data.V = v.Bytes()
|
||||
}
|
||||
if r != nil {
|
||||
data.R = r.Bytes()
|
||||
}
|
||||
if s != nil {
|
||||
data.S = s.Bytes()
|
||||
}
|
||||
if data.txType() == ethtypes.AccessListTxType && chainID != nil {
|
||||
data.ChainID = chainID.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs a basic validation of the tx data fields.
|
||||
func (data TxData) Validate() error {
|
||||
gasPrice := data.gasPrice()
|
||||
if gasPrice == nil {
|
||||
return sdkerrors.Wrap(ErrInvalidGasPrice, "cannot be nil")
|
||||
}
|
||||
|
||||
if gasPrice.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(ErrInvalidGasPrice, "gas price cannot be negative %s", gasPrice)
|
||||
}
|
||||
|
||||
amount := data.amount()
|
||||
if amount == nil {
|
||||
return sdkerrors.Wrap(ErrInvalidAmount, "cannot be nil")
|
||||
}
|
||||
|
||||
// Amount can be 0
|
||||
if amount.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(ErrInvalidAmount, "amount cannot be negative %s", amount)
|
||||
}
|
||||
|
||||
if data.To != "" {
|
||||
if err := types.ValidateAddress(data.To); err != nil {
|
||||
return sdkerrors.Wrap(err, "invalid to address")
|
||||
}
|
||||
}
|
||||
|
||||
if data.txType() == ethtypes.AccessListTxType && data.chainID() == nil {
|
||||
return sdkerrors.Wrap(
|
||||
sdkerrors.ErrInvalidChainID,
|
||||
"chain ID must be present on AccessList txs",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeriveChainID derives the chain id from the given v parameter
|
||||
func DeriveChainID(v *big.Int) *big.Int {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.BitLen() <= 64 {
|
||||
v := v.Uint64()
|
||||
if v == 27 || v == 28 {
|
||||
return new(big.Int)
|
||||
}
|
||||
return new(big.Int).SetUint64((v - 35) / 2)
|
||||
}
|
||||
v = new(big.Int).Sub(v, big.NewInt(35))
|
||||
return v.Div(v, big.NewInt(2))
|
||||
}
|
65
x/evm/types/tx_data_test.go
Normal file
65
x/evm/types/tx_data_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/ethermint/tests"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTxData_chainID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
msg string
|
||||
data TxData
|
||||
expChainID *big.Int
|
||||
}{
|
||||
{
|
||||
"access list tx", TxData{Accesses: AccessList{}, ChainID: big.NewInt(1).Bytes()}, big.NewInt(1),
|
||||
},
|
||||
{
|
||||
"access list tx, nil chain ID", TxData{Accesses: AccessList{}}, nil,
|
||||
},
|
||||
{
|
||||
"legacy tx, derived", TxData{}, nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
chainID := tc.data.chainID()
|
||||
require.Equal(t, chainID, tc.expChainID, tc.msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxData_DeriveChainID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
msg string
|
||||
data TxData
|
||||
expChainID *big.Int
|
||||
from common.Address
|
||||
}{
|
||||
{
|
||||
"v = 0", TxData{V: big.NewInt(0).Bytes()}, nil, tests.GenerateAddress(),
|
||||
},
|
||||
{
|
||||
"v = 1", TxData{V: big.NewInt(1).Bytes()}, big.NewInt(9223372036854775791), tests.GenerateAddress(),
|
||||
},
|
||||
{
|
||||
"v = 27", TxData{V: big.NewInt(27).Bytes()}, new(big.Int), tests.GenerateAddress(),
|
||||
},
|
||||
{
|
||||
"v = 28", TxData{V: big.NewInt(28).Bytes()}, new(big.Int), tests.GenerateAddress(),
|
||||
},
|
||||
{
|
||||
"v = nil ", TxData{V: nil}, nil, tests.GenerateAddress(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
v, _, _ := tc.data.rawSignatureValues()
|
||||
|
||||
chainID := DeriveChainID(v)
|
||||
require.Equal(t, tc.expChainID, chainID, tc.msg)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user