forked from cerc-io/laconicd-deprecated
x/evm: unit tests and fixes (#223)
* evm: move Keeper and Querier to /keeper package * keeper: update keeper_test.go * fix format * evm: use aliased types * bump SDK version to v0.38.1 * app: updates from new version * errors: switch sdk.Error -> error * errors: switch sdk.Error -> error. Continuation * more fixes * update app/ * update keys and client pkgs * build * fix tests * lint * minor changes * changelog * address @austinbell comments * Fix keyring usage in rpc API and CLI * fix keyring * break line * Misc cleanup (#188) * evm: move Begin and EndBlock to abci.go * evm: use expected keeper interfaces * app: use EthermintApp for integration and unit test setup * evm: remove count type; update codec * go mod verify * evm: rename msgs for consistency * evm: events * minor cleanup * lint * ante: update tests * changelog * nolint * evm: update statedb to create ethermint Account instead of BaseAccount * fix importer test * address @austinabell comments * update README * changelog * evm: update codec * rename GasLimit->Gas and Price ->GasPrice * msg cleanup and tests * cleanup TxData * fix marshaling * revert rename * move types * evm/keeper: querier tests * switch MarshalLengthPrefixed -> BinaryBare; remove panics * fix event sender * fix panic * try fix txDecoder error * evm: handler tests * evm: handler MsgEthermint test * fix tests * store logs in keeper after transition (#210) * add some comments * begin log handler test * update TransitionCSDB to return ReturnData * use rlp for result data encode/decode * update tests * implement SetBlockLogs * implement GetBlockLogs * test log set/get * update keeper get/set logs to use hash as key * fix test * move logsKey to csdb * attempt to fix test * attempt to fix test * attempt to fix test * lint * lint * lint * save logs after handling msg * update k.Logs * cleanup * remove unused * fix issues * comment out handler test * address comments * lint * fix handler test * address comments * use amino * lint * address comments * merge * fix encoding bug * minor fix * rpc: error handling * rpc: simulate only returns gasConsumed * rpc: error ineffassign * evm: handler test * go: bump version to 1.14 and SDK version to latest master * rpc: fix simulation return value * breaking changes from SDK * sdk: breaking changes; build * tests: fixes * minor fix * proto: ethermint types attempt * proto: define EthAccount proto type and extend sdk std.Codec * evm: fix panic on handler test * evm: minor state object changes * cleanup * tests: update test-importer * fix evm test * fix pubkey registration * lint * cleanup * more test checks for importer * minor change * codec fixes * rm init func * fix importer test build * fixes * test fixes * fix bloom key * rm unnecesary func * remove comment Co-authored-by: austinabell <austinabell8@gmail.com> Co-authored-by: noot <36753753+noot@users.noreply.github.com>
This commit is contained in:
co-authored by
austinabell
noot
parent
4d609b2a22
commit
8ec7edf5bd
@@ -12,7 +12,7 @@ var ModuleCdc = codec.New()
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil)
|
||||
cdc.RegisterConcrete(MsgEthermint{}, "ethermint/MsgEthermint", nil)
|
||||
cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodableTxData", nil)
|
||||
cdc.RegisterConcrete(TxData{}, "ethermint/TxData", nil)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
"github.com/cosmos/ethermint/types"
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var (
|
||||
_ sdk.Msg = MsgEthermint{}
|
||||
)
|
||||
|
||||
const (
|
||||
// TypeMsgEthermint defines the type string of Ethermint message
|
||||
TypeMsgEthermint = "ethermint"
|
||||
)
|
||||
|
||||
// MsgEthermint implements a cosmos equivalent structure for Ethereum transactions
|
||||
type MsgEthermint struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price sdk.Int `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *sdk.AccAddress `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Amount sdk.Int `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
|
||||
// From address (formerly derived from signature)
|
||||
From sdk.AccAddress `json:"from"`
|
||||
}
|
||||
|
||||
// NewMsgEthermint returns a reference to a new Ethermint transaction
|
||||
func NewMsgEthermint(
|
||||
nonce uint64, to *sdk.AccAddress, amount sdk.Int,
|
||||
gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress,
|
||||
) MsgEthermint {
|
||||
return MsgEthermint{
|
||||
AccountNonce: nonce,
|
||||
Price: gasPrice,
|
||||
GasLimit: gasLimit,
|
||||
Recipient: to,
|
||||
Amount: amount,
|
||||
Payload: payload,
|
||||
From: from,
|
||||
}
|
||||
}
|
||||
|
||||
// Route should return the name of the module
|
||||
func (msg MsgEthermint) Route() string { return RouterKey }
|
||||
|
||||
// Type returns the action of the message
|
||||
func (msg MsgEthermint) Type() string { return TypeMsgEthermint }
|
||||
|
||||
// GetSignBytes encodes the message for signing
|
||||
func (msg MsgEthermint) GetSignBytes() []byte {
|
||||
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
|
||||
}
|
||||
|
||||
// ValidateBasic runs stateless checks on the message
|
||||
func (msg MsgEthermint) ValidateBasic() error {
|
||||
if msg.Price.Sign() != 1 {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidValue, "price must be positive %s", msg.Price)
|
||||
}
|
||||
|
||||
// Amount can be 0
|
||||
if msg.Amount.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidValue, "amount cannot be negative %s", msg.Amount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners defines whose signature is required
|
||||
func (msg MsgEthermint) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
|
||||
// To returns the recipient address of the transaction. It returns nil if the
|
||||
// transaction is a contract creation.
|
||||
func (msg MsgEthermint) To() *ethcmn.Address {
|
||||
if msg.Recipient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr := ethcmn.BytesToAddress(msg.Recipient.Bytes())
|
||||
return &addr
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
func TestMsgEthermint(t *testing.T) {
|
||||
addr := newSdkAddress()
|
||||
fromAddr := newSdkAddress()
|
||||
|
||||
msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
|
||||
require.NotNil(t, msg)
|
||||
require.Equal(t, msg.Recipient, &addr)
|
||||
|
||||
require.Equal(t, msg.Route(), RouterKey)
|
||||
require.Equal(t, msg.Type(), TypeMsgEthermint)
|
||||
}
|
||||
|
||||
func TestMsgEthermintValidation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
nonce uint64
|
||||
to *sdk.AccAddress
|
||||
amount sdk.Int
|
||||
gasLimit uint64
|
||||
gasPrice sdk.Int
|
||||
payload []byte
|
||||
expectPass bool
|
||||
from sdk.AccAddress
|
||||
}{
|
||||
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(100000), expectPass: true},
|
||||
{amount: sdk.NewInt(0), gasPrice: sdk.NewInt(100000), expectPass: true},
|
||||
{amount: sdk.NewInt(-1), gasPrice: sdk.NewInt(100000), expectPass: false},
|
||||
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(-1), expectPass: false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
msg := NewMsgEthermint(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from)
|
||||
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmintEncodingAndDecoding(t *testing.T) {
|
||||
addr := newSdkAddress()
|
||||
fromAddr := newSdkAddress()
|
||||
|
||||
msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
|
||||
|
||||
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var msg2 MsgEthermint
|
||||
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, msg.AccountNonce, msg2.AccountNonce)
|
||||
require.Equal(t, msg.Recipient, msg2.Recipient)
|
||||
require.Equal(t, msg.Amount, msg2.Amount)
|
||||
require.Equal(t, msg.GasLimit, msg2.GasLimit)
|
||||
require.Equal(t, msg.Price, msg2.Price)
|
||||
require.Equal(t, msg.Payload, msg2.Payload)
|
||||
require.Equal(t, msg.From, msg2.From)
|
||||
}
|
||||
|
||||
func newSdkAddress() sdk.AccAddress {
|
||||
tmpKey := secp256k1.GenPrivKey().PubKey()
|
||||
return sdk.AccAddress(tmpKey.Address().Bytes())
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidateGenesis(t *testing.T) {
|
||||
testCases := []struct {
|
||||
msg string
|
||||
genstate GenesisState
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
msg: "pass with defaultState ",
|
||||
genstate: DefaultGenesisState(),
|
||||
expPass: true,
|
||||
},
|
||||
{
|
||||
msg: "empty address",
|
||||
genstate: GenesisState{
|
||||
Accounts: []GenesisAccount{{}},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
{
|
||||
msg: "empty balance",
|
||||
genstate: GenesisState{
|
||||
Accounts: []GenesisAccount{{Balance: nil}},
|
||||
},
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
|
||||
err := ValidateGenesis(tc.genstate)
|
||||
if tc.expPass {
|
||||
require.NoError(t, err, "test (%d) %s", i, tc.msg)
|
||||
} else {
|
||||
require.Error(t, err, "test (%d): %s", i, tc.msg)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+107
-109
@@ -8,7 +8,6 @@ import (
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/ethermint/types"
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ sdk.Msg = MsgEthermint{}
|
||||
_ sdk.Msg = MsgEthereumTx{}
|
||||
_ sdk.Tx = MsgEthereumTx{}
|
||||
)
|
||||
@@ -28,52 +28,104 @@ var big8 = big.NewInt(8)
|
||||
|
||||
// message type and route constants
|
||||
const (
|
||||
// TypeMsgEthereumTx defines the type string of an Ethereum tranasction
|
||||
TypeMsgEthereumTx = "ethereum"
|
||||
// TypeMsgEthermint defines the type string of Ethermint message
|
||||
TypeMsgEthermint = "ethermint"
|
||||
)
|
||||
|
||||
// MsgEthermint implements a cosmos equivalent structure for Ethereum transactions
|
||||
type MsgEthermint struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price sdk.Int `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *sdk.AccAddress `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Amount sdk.Int `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
|
||||
// From address (formerly derived from signature)
|
||||
From sdk.AccAddress `json:"from"`
|
||||
}
|
||||
|
||||
// NewMsgEthermint returns a reference to a new Ethermint transaction
|
||||
func NewMsgEthermint(
|
||||
nonce uint64, to *sdk.AccAddress, amount sdk.Int,
|
||||
gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress,
|
||||
) MsgEthermint {
|
||||
return MsgEthermint{
|
||||
AccountNonce: nonce,
|
||||
Price: gasPrice,
|
||||
GasLimit: gasLimit,
|
||||
Recipient: to,
|
||||
Amount: amount,
|
||||
Payload: payload,
|
||||
From: from,
|
||||
}
|
||||
}
|
||||
|
||||
// Route should return the name of the module
|
||||
func (msg MsgEthermint) Route() string { return RouterKey }
|
||||
|
||||
// Type returns the action of the message
|
||||
func (msg MsgEthermint) Type() string { return TypeMsgEthermint }
|
||||
|
||||
// GetSignBytes encodes the message for signing
|
||||
func (msg MsgEthermint) GetSignBytes() []byte {
|
||||
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
|
||||
}
|
||||
|
||||
// ValidateBasic runs stateless checks on the message
|
||||
func (msg MsgEthermint) ValidateBasic() error {
|
||||
if msg.Price.Sign() != 1 {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidValue, "price must be positive %s", msg.Price)
|
||||
}
|
||||
|
||||
// Amount can be 0
|
||||
if msg.Amount.Sign() == -1 {
|
||||
return sdkerrors.Wrapf(types.ErrInvalidValue, "amount cannot be negative %s", msg.Amount)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSigners defines whose signature is required
|
||||
func (msg MsgEthermint) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.From}
|
||||
}
|
||||
|
||||
// To returns the recipient address of the transaction. It returns nil if the
|
||||
// transaction is a contract creation.
|
||||
func (msg MsgEthermint) To() *ethcmn.Address {
|
||||
if msg.Recipient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
addr := ethcmn.BytesToAddress(msg.Recipient.Bytes())
|
||||
return &addr
|
||||
}
|
||||
|
||||
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
|
||||
type (
|
||||
MsgEthereumTx struct {
|
||||
Data TxData
|
||||
type MsgEthereumTx struct {
|
||||
Data TxData
|
||||
|
||||
// caches
|
||||
size atomic.Value
|
||||
from atomic.Value
|
||||
}
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
from atomic.Value
|
||||
}
|
||||
|
||||
// TxData implements the Ethereum transaction data structure. It is used
|
||||
// solely as intended in Ethereum abiding by the protocol.
|
||||
TxData struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price *big.Int `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Amount *big.Int `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
|
||||
// signature values
|
||||
V *big.Int `json:"v"`
|
||||
R *big.Int `json:"r"`
|
||||
S *big.Int `json:"s"`
|
||||
|
||||
// hash is only used when marshaling to JSON
|
||||
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||
}
|
||||
|
||||
// sigCache is used to cache the derived sender and contains the signer used
|
||||
// to derive it.
|
||||
sigCache struct {
|
||||
signer ethtypes.Signer
|
||||
from ethcmn.Address
|
||||
}
|
||||
)
|
||||
// sigCache is used to cache the derived sender and contains the signer used
|
||||
// to derive it.
|
||||
type sigCache struct {
|
||||
signer ethtypes.Signer
|
||||
from ethcmn.Address
|
||||
}
|
||||
|
||||
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
|
||||
func NewMsgEthereumTx(
|
||||
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||
) MsgEthereumTx {
|
||||
|
||||
return newMsgEthereumTx(nonce, to, amount, gasLimit, gasPrice, payload)
|
||||
}
|
||||
|
||||
@@ -82,7 +134,6 @@ func NewMsgEthereumTx(
|
||||
func NewMsgEthereumTxContract(
|
||||
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||
) MsgEthereumTx {
|
||||
|
||||
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
|
||||
}
|
||||
|
||||
@@ -90,7 +141,6 @@ func newMsgEthereumTx(
|
||||
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||
) MsgEthereumTx {
|
||||
|
||||
if len(payload) > 0 {
|
||||
payload = ethcmn.CopyBytes(payload)
|
||||
}
|
||||
@@ -191,30 +241,34 @@ func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
|
||||
|
||||
// DecodeRLP implements the rlp.Decoder interface.
|
||||
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
|
||||
_, size, _ := s.Kind()
|
||||
|
||||
err := s.Decode(&msg.Data)
|
||||
if err == nil {
|
||||
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
||||
_, size, err := s.Kind()
|
||||
if err != nil {
|
||||
// return error if stream is too large
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
if err := s.Decode(&msg.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
||||
// takes a private key and chainID to sign an Ethereum transaction according to
|
||||
// EIP155 standard. It mutates the transaction as it populates the V, R, S
|
||||
// fields of the Transaction's Signature.
|
||||
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
||||
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) error {
|
||||
txHash := msg.RLPSignBytes(chainID)
|
||||
|
||||
sig, err := ethcrypto.Sign(txHash[:], priv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(sig) != 65 {
|
||||
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
|
||||
return fmt.Errorf("wrong size for signature: got %d, want 65", len(sig))
|
||||
}
|
||||
|
||||
r := new(big.Int).SetBytes(sig[:32])
|
||||
@@ -234,6 +288,7 @@ func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
||||
msg.Data.V = v
|
||||
msg.Data.R = r
|
||||
msg.Data.S = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifySig attempts to verify a Transaction's signature for a given chainID.
|
||||
@@ -291,6 +346,12 @@ func (msg MsgEthereumTx) Cost() *big.Int {
|
||||
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 msg.Data.V, msg.Data.R, msg.Data.S
|
||||
}
|
||||
|
||||
// From loads the ethereum sender address from the sigcache and returns an
|
||||
// sdk.AccAddress from its bytes
|
||||
func (msg *MsgEthereumTx) From() sdk.AccAddress {
|
||||
@@ -320,66 +381,3 @@ func deriveChainID(v *big.Int) *big.Int {
|
||||
v = new(big.Int).Sub(v, big.NewInt(35))
|
||||
return v.Div(v, big.NewInt(2))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Auxiliary
|
||||
|
||||
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
|
||||
// MsgEthereumTx transactions.
|
||||
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
|
||||
return func(txBytes []byte) (sdk.Tx, error) {
|
||||
var tx sdk.Tx
|
||||
|
||||
if len(txBytes) == 0 {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "tx bytes are empty")
|
||||
}
|
||||
|
||||
// sdk.Tx is an interface. The concrete message types
|
||||
// are registered by MakeTxCodec
|
||||
err := cdc.UnmarshalBinaryBare(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, err.Error())
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
|
||||
// recoverEthSig recovers a signature according to the Ethereum specification and
|
||||
// returns the sender or an error.
|
||||
//
|
||||
// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F
|
||||
// nolint: gocritic
|
||||
func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) {
|
||||
if Vb.BitLen() > 8 {
|
||||
return ethcmn.Address{}, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
V := byte(Vb.Uint64() - 27)
|
||||
if !ethcrypto.ValidateSignatureValues(V, R, S, true) {
|
||||
return ethcmn.Address{}, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
// encode the signature in uncompressed format
|
||||
r, s := R.Bytes(), S.Bytes()
|
||||
sig := make([]byte, 65)
|
||||
|
||||
copy(sig[32-len(r):32], r)
|
||||
copy(sig[64-len(s):64], s)
|
||||
sig[64] = V
|
||||
|
||||
// recover the public key from the signature
|
||||
pub, err := ethcrypto.Ecrecover(sigHash[:], sig)
|
||||
if err != nil {
|
||||
return ethcmn.Address{}, err
|
||||
}
|
||||
|
||||
if len(pub) == 0 || pub[0] != 4 {
|
||||
return ethcmn.Address{}, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
var addr ethcmn.Address
|
||||
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
+96
-80
@@ -6,50 +6,51 @@ import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/cosmos/ethermint/crypto"
|
||||
"github.com/cosmos/ethermint/utils"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
func TestMsgEthereumTx(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
func TestMsgEthermint(t *testing.T) {
|
||||
addr := newSdkAddress()
|
||||
fromAddr := newSdkAddress()
|
||||
|
||||
msg1 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
|
||||
require.NotNil(t, msg1)
|
||||
require.Equal(t, *msg1.Data.Recipient, addr)
|
||||
msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
|
||||
require.NotNil(t, msg)
|
||||
require.Equal(t, msg.Recipient, &addr)
|
||||
|
||||
msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
|
||||
require.NotNil(t, msg2)
|
||||
require.Nil(t, msg2.Data.Recipient)
|
||||
|
||||
msg3 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
|
||||
require.Equal(t, msg3.Route(), RouterKey)
|
||||
require.Equal(t, msg3.Type(), TypeMsgEthereumTx)
|
||||
require.Panics(t, func() { msg3.GetSigners() })
|
||||
require.Panics(t, func() { msg3.GetSignBytes() })
|
||||
require.Equal(t, msg.Route(), RouterKey)
|
||||
require.Equal(t, msg.Type(), TypeMsgEthermint)
|
||||
}
|
||||
|
||||
func TestMsgEthereumTxValidation(t *testing.T) {
|
||||
func TestMsgEthermintValidation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
payload []byte
|
||||
amount *big.Int
|
||||
gasPrice *big.Int
|
||||
gasLimit uint64
|
||||
nonce uint64
|
||||
to ethcmn.Address
|
||||
to *sdk.AccAddress
|
||||
amount sdk.Int
|
||||
gasLimit uint64
|
||||
gasPrice sdk.Int
|
||||
payload []byte
|
||||
expectPass bool
|
||||
from sdk.AccAddress
|
||||
}{
|
||||
{amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{amount: big.NewInt(-1), gasPrice: big.NewInt(100000), expectPass: false},
|
||||
{amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: false},
|
||||
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(100000), expectPass: true},
|
||||
{amount: sdk.NewInt(0), gasPrice: sdk.NewInt(100000), expectPass: true},
|
||||
{amount: sdk.NewInt(-1), gasPrice: sdk.NewInt(100000), expectPass: false},
|
||||
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(-1), expectPass: false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
msg := NewMsgEthereumTx(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
|
||||
msg := NewMsgEthermint(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from)
|
||||
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
@@ -59,6 +60,75 @@ func TestMsgEthereumTxValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgEthermintEncodingAndDecoding(t *testing.T) {
|
||||
addr := newSdkAddress()
|
||||
fromAddr := newSdkAddress()
|
||||
|
||||
msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
|
||||
|
||||
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var msg2 MsgEthermint
|
||||
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, msg.AccountNonce, msg2.AccountNonce)
|
||||
require.Equal(t, msg.Recipient, msg2.Recipient)
|
||||
require.Equal(t, msg.Amount, msg2.Amount)
|
||||
require.Equal(t, msg.GasLimit, msg2.GasLimit)
|
||||
require.Equal(t, msg.Price, msg2.Price)
|
||||
require.Equal(t, msg.Payload, msg2.Payload)
|
||||
require.Equal(t, msg.From, msg2.From)
|
||||
}
|
||||
|
||||
func newSdkAddress() sdk.AccAddress {
|
||||
tmpKey := secp256k1.GenPrivKey().PubKey()
|
||||
return sdk.AccAddress(tmpKey.Address().Bytes())
|
||||
}
|
||||
|
||||
func TestMsgEthereumTx(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
|
||||
msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
|
||||
require.NotNil(t, msg)
|
||||
require.Equal(t, *msg.Data.Recipient, addr)
|
||||
require.Equal(t, msg.Route(), RouterKey)
|
||||
require.Equal(t, msg.Type(), TypeMsgEthereumTx)
|
||||
require.NotNil(t, msg.To())
|
||||
require.Equal(t, msg.GetMsgs(), []sdk.Msg{msg})
|
||||
require.Panics(t, func() { msg.GetSigners() })
|
||||
require.Panics(t, func() { msg.GetSignBytes() })
|
||||
|
||||
msg = NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
|
||||
require.NotNil(t, msg)
|
||||
require.Nil(t, msg.Data.Recipient)
|
||||
require.Nil(t, msg.To())
|
||||
}
|
||||
|
||||
func TestMsgEthereumTxValidation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
msg string
|
||||
amount *big.Int
|
||||
gasPrice *big.Int
|
||||
expectPass bool
|
||||
}{
|
||||
{msg: "pass", amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||
{msg: "invalid amount", amount: big.NewInt(-1), gasPrice: big.NewInt(100000), expectPass: false},
|
||||
{msg: "invalid gas price", amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
msg := NewMsgEthereumTx(0, nil, tc.amount, 0, tc.gasPrice, nil)
|
||||
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "valid test %d failed: %s", i, tc.msg)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "invalid test %d passed: %s", i, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
|
||||
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||
chainID := big.NewInt(3)
|
||||
@@ -115,60 +185,6 @@ func TestMsgEthereumTxSig(t *testing.T) {
|
||||
require.Equal(t, ethcmn.Address{}, signer)
|
||||
}
|
||||
|
||||
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
msg := NewMsgEthereumTx(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test"))
|
||||
|
||||
msg.Data.V = big.NewInt(1)
|
||||
msg.Data.R = big.NewInt(2)
|
||||
msg.Data.S = big.NewInt(3)
|
||||
|
||||
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var msg2 MsgEthereumTx
|
||||
|
||||
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, msg.Data, msg2.Data)
|
||||
}
|
||||
|
||||
func TestMarshalAndUnmarshalInt(t *testing.T) {
|
||||
i := big.NewInt(3)
|
||||
m := utils.MarshalBigInt(i)
|
||||
i2, err := utils.UnmarshalBigInt(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, i, i2)
|
||||
}
|
||||
|
||||
func TestMarshalAndUnmarshalData(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
hash := ethcmn.BigToHash(big.NewInt(2))
|
||||
e := EncodableTxData{
|
||||
AccountNonce: 2,
|
||||
Price: utils.MarshalBigInt(big.NewInt(3)),
|
||||
GasLimit: 1,
|
||||
Recipient: &addr,
|
||||
Amount: utils.MarshalBigInt(big.NewInt(4)),
|
||||
Payload: []byte("test"),
|
||||
|
||||
V: utils.MarshalBigInt(big.NewInt(5)),
|
||||
R: utils.MarshalBigInt(big.NewInt(6)),
|
||||
S: utils.MarshalBigInt(big.NewInt(7)),
|
||||
|
||||
Hash: &hash,
|
||||
}
|
||||
str, err := marshalAmino(e)
|
||||
require.NoError(t, err)
|
||||
|
||||
e2 := new(EncodableTxData)
|
||||
|
||||
err = unmarshalAmino(e2, str)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, e, *e2)
|
||||
}
|
||||
|
||||
func TestMarshalAndUnmarshalLogs(t *testing.T) {
|
||||
var cdc = codec.New()
|
||||
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/cosmos/ethermint/utils"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// EncodableTxData implements the Ethereum transaction data structure. It is used
|
||||
// TxData implements the Ethereum transaction data structure. It is used
|
||||
// solely as intended in Ethereum abiding by the protocol.
|
||||
type EncodableTxData struct {
|
||||
type TxData struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price *big.Int `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
||||
Amount *big.Int `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
|
||||
// signature values
|
||||
V *big.Int `json:"v"`
|
||||
R *big.Int `json:"r"`
|
||||
S *big.Int `json:"s"`
|
||||
|
||||
// hash is only used when marshaling to JSON
|
||||
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||
}
|
||||
|
||||
// encodableTxData implements the Ethereum transaction data structure. It is used
|
||||
// solely as intended in Ethereum abiding by the protocol.
|
||||
type encodableTxData struct {
|
||||
AccountNonce uint64 `json:"nonce"`
|
||||
Price string `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
@@ -25,39 +46,53 @@ type EncodableTxData struct {
|
||||
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||
}
|
||||
|
||||
func marshalAmino(td EncodableTxData) (string, error) {
|
||||
bz, err := ModuleCdc.MarshalBinaryBare(td)
|
||||
return string(bz), err
|
||||
}
|
||||
|
||||
func unmarshalAmino(td *EncodableTxData, text string) error {
|
||||
return ModuleCdc.UnmarshalBinaryBare([]byte(text), td)
|
||||
}
|
||||
|
||||
// MarshalAmino defines custom encoding scheme for TxData
|
||||
func (td TxData) MarshalAmino() (string, error) {
|
||||
e := EncodableTxData{
|
||||
AccountNonce: td.AccountNonce,
|
||||
Price: utils.MarshalBigInt(td.Price),
|
||||
GasLimit: td.GasLimit,
|
||||
Recipient: td.Recipient,
|
||||
Amount: utils.MarshalBigInt(td.Amount),
|
||||
Payload: td.Payload,
|
||||
|
||||
V: utils.MarshalBigInt(td.V),
|
||||
R: utils.MarshalBigInt(td.R),
|
||||
S: utils.MarshalBigInt(td.S),
|
||||
|
||||
Hash: td.Hash,
|
||||
func (td TxData) MarshalAmino() ([]byte, error) {
|
||||
gasPrice, err := utils.MarshalBigInt(td.Price)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return marshalAmino(e)
|
||||
amount, err := utils.MarshalBigInt(td.Amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := utils.MarshalBigInt(td.V)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := utils.MarshalBigInt(td.R)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s, err := utils.MarshalBigInt(td.S)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := encodableTxData{
|
||||
AccountNonce: td.AccountNonce,
|
||||
Price: gasPrice,
|
||||
GasLimit: td.GasLimit,
|
||||
Recipient: td.Recipient,
|
||||
Amount: amount,
|
||||
Payload: td.Payload,
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
Hash: td.Hash,
|
||||
}
|
||||
|
||||
return ModuleCdc.MarshalBinaryBare(e)
|
||||
}
|
||||
|
||||
// UnmarshalAmino defines custom decoding scheme for TxData
|
||||
func (td *TxData) UnmarshalAmino(text string) error {
|
||||
e := new(EncodableTxData)
|
||||
err := unmarshalAmino(e, text)
|
||||
func (td *TxData) UnmarshalAmino(data []byte) error {
|
||||
var e encodableTxData
|
||||
err := ModuleCdc.UnmarshalBinaryBare(data, &e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestMarshalAndUnmarshalData(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
hash := ethcmn.BigToHash(big.NewInt(2))
|
||||
|
||||
txData := TxData{
|
||||
AccountNonce: 2,
|
||||
Price: big.NewInt(3),
|
||||
GasLimit: 1,
|
||||
Recipient: &addr,
|
||||
Amount: big.NewInt(4),
|
||||
Payload: []byte("test"),
|
||||
V: big.NewInt(5),
|
||||
R: big.NewInt(6),
|
||||
S: big.NewInt(7),
|
||||
Hash: &hash,
|
||||
}
|
||||
|
||||
bz, err := txData.MarshalAmino()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, bz)
|
||||
|
||||
var txData2 TxData
|
||||
err = txData2.UnmarshalAmino(bz)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, txData, txData2)
|
||||
}
|
||||
|
||||
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||
addr := GenerateEthAddress()
|
||||
msg := NewMsgEthereumTx(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test"))
|
||||
|
||||
msg.Data.V = big.NewInt(1)
|
||||
msg.Data.R = big.NewInt(2)
|
||||
msg.Data.S = big.NewInt(3)
|
||||
|
||||
raw, err := ModuleCdc.MarshalBinaryBare(msg)
|
||||
require.NoError(t, err)
|
||||
|
||||
var msg2 MsgEthereumTx
|
||||
|
||||
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, msg, msg2)
|
||||
}
|
||||
@@ -2,7 +2,11 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/ethermint/crypto"
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
@@ -86,3 +90,66 @@ func DecodeLogs(in []byte) ([]*ethtypes.Log, error) {
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Auxiliary
|
||||
|
||||
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
|
||||
// MsgEthereumTx transactions.
|
||||
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
|
||||
return func(txBytes []byte) (sdk.Tx, error) {
|
||||
var tx sdk.Tx
|
||||
|
||||
if len(txBytes) == 0 {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "tx bytes are empty")
|
||||
}
|
||||
|
||||
// sdk.Tx is an interface. The concrete message types
|
||||
// are registered by MakeTxCodec
|
||||
err := cdc.UnmarshalBinaryBare(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, err.Error())
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
|
||||
// recoverEthSig recovers a signature according to the Ethereum specification and
|
||||
// returns the sender or an error.
|
||||
//
|
||||
// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F
|
||||
// nolint: gocritic
|
||||
func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) {
|
||||
if Vb.BitLen() > 8 {
|
||||
return ethcmn.Address{}, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
V := byte(Vb.Uint64() - 27)
|
||||
if !ethcrypto.ValidateSignatureValues(V, R, S, true) {
|
||||
return ethcmn.Address{}, errors.New("invalid signature")
|
||||
}
|
||||
|
||||
// encode the signature in uncompressed format
|
||||
r, s := R.Bytes(), S.Bytes()
|
||||
sig := make([]byte, 65)
|
||||
|
||||
copy(sig[32-len(r):32], r)
|
||||
copy(sig[64-len(s):64], s)
|
||||
sig[64] = V
|
||||
|
||||
// recover the public key from the signature
|
||||
pub, err := ethcrypto.Ecrecover(sigHash[:], sig)
|
||||
if err != nil {
|
||||
return ethcmn.Address{}, err
|
||||
}
|
||||
|
||||
if len(pub) == 0 || pub[0] != 4 {
|
||||
return ethcmn.Address{}, errors.New("invalid public key")
|
||||
}
|
||||
|
||||
var addr ethcmn.Address
|
||||
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user