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:
Federico Kunze
2020-04-23 11:49:25 -04:00
committed by GitHub
co-authored by austinabell noot
parent 4d609b2a22
commit 8ec7edf5bd
24 changed files with 780 additions and 520 deletions
+5 -4
View File
@@ -13,12 +13,13 @@ import (
// BeginBlock sets the Bloom and Hash mappings and resets the Bloom filter and
// the transaction count to 0.
func BeginBlock(k Keeper, ctx sdk.Context, req abci.RequestBeginBlock) {
if req.Header.LastBlockId.GetHash() == nil || req.Header.GetHeight() < 1 {
return
}
// Consider removing this when using evm as module without web3 API
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes())
err := k.SetBlockBloomMapping(ctx, bloom, req.Header.GetHeight()-1)
if err != nil {
panic(err)
}
k.SetBlockBloomMapping(ctx, bloom, req.Header.GetHeight()-1)
k.SetBlockHashMapping(ctx, req.Header.LastBlockId.GetHash(), req.Header.GetHeight()-1)
k.Bloom = big.NewInt(0)
k.TxCount = 0
+6 -5
View File
@@ -19,17 +19,17 @@ func NewHandler(k Keeper) sdk.Handler {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case types.MsgEthereumTx:
return HandleMsgEthereumTx(ctx, k, msg)
return handleMsgEthereumTx(ctx, k, msg)
case types.MsgEthermint:
return HandleMsgEthermint(ctx, k, msg)
return handleMsgEthermint(ctx, k, msg)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
}
}
// HandleMsgEthereumTx handles an Ethereum specific tx
func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*sdk.Result, error) {
// handleMsgEthereumTx handles an Ethereum specific tx
func handleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*sdk.Result, error) {
// parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
@@ -105,7 +105,8 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*s
return returnData.Result, nil
}
func HandleMsgEthermint(ctx sdk.Context, k Keeper, msg types.MsgEthermint) (*sdk.Result, error) {
// handleMsgEthermint handles an sdk.StdTx for an Ethereum state transition
func handleMsgEthermint(ctx sdk.Context, k Keeper, msg types.MsgEthermint) (*sdk.Result, error) {
// parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
+153 -6
View File
@@ -8,18 +8,20 @@ import (
"github.com/stretchr/testify/suite"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/x/evm"
"github.com/cosmos/ethermint/x/evm/keeper"
"github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/secp256k1"
)
type EvmTestSuite struct {
@@ -46,7 +48,150 @@ func TestEvmTestSuite(t *testing.T) {
suite.Run(t, new(EvmTestSuite))
}
func (suite *EvmTestSuite) TestHandler_Logs() {
func (suite *EvmTestSuite) TestHandleMsgEthereumTx() {
privkey, err := crypto.GenerateKey()
suite.Require().NoError(err)
sender := ethcmn.HexToAddress(privkey.PubKey().Address().String())
var (
tx types.MsgEthereumTx
chainID *big.Int
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"passed",
func() {
suite.app.EvmKeeper.SetBalance(suite.ctx, sender, big.NewInt(100))
tx = types.NewMsgEthereumTx(0, &sender, big.NewInt(100), 0, big.NewInt(10000), nil)
// parse context chain ID to big.Int
var ok bool
chainID, ok = new(big.Int).SetString(suite.ctx.ChainID(), 10)
suite.Require().True(ok)
// sign transaction
err = tx.Sign(chainID, privkey.ToECDSA())
suite.Require().NoError(err)
},
true,
},
{
"insufficient balance",
func() {
tx = types.NewMsgEthereumTxContract(0, big.NewInt(100), 0, big.NewInt(10000), nil)
// parse context chain ID to big.Int
var ok bool
chainID, ok = new(big.Int).SetString(suite.ctx.ChainID(), 10)
suite.Require().True(ok)
// sign transaction
err = tx.Sign(chainID, privkey.ToECDSA())
suite.Require().NoError(err)
},
false,
},
{
"tx encoding failed",
func() {
tx = types.NewMsgEthereumTxContract(0, big.NewInt(100), 0, big.NewInt(10000), nil)
},
false,
},
{
"invalid chain ID",
func() {
suite.ctx = suite.ctx.WithChainID("chainID")
},
false,
},
{
"VerifySig failed",
func() {
tx = types.NewMsgEthereumTxContract(0, big.NewInt(100), 0, big.NewInt(10000), nil)
},
false,
},
}
for _, tc := range testCases {
suite.Run("", func() {
suite.SetupTest() // reset
tc.malleate()
res, err := suite.handler(suite.ctx, tx)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
} else {
suite.Require().Error(err)
suite.Require().Nil(res)
}
})
}
}
func (suite *EvmTestSuite) TestMsgEthermint() {
var (
tx types.MsgEthermint
from = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
to = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{
"passed",
func() {
tx = types.NewMsgEthermint(0, &to, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), from)
suite.app.EvmKeeper.SetBalance(suite.ctx, ethcmn.BytesToAddress(from.Bytes()), big.NewInt(100))
},
true,
},
{
"invalid state transition",
func() {
tx = types.NewMsgEthermint(0, &to, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), from)
},
false,
},
{
"invalid chain ID",
func() {
suite.ctx = suite.ctx.WithChainID("chainID")
},
false,
},
}
for _, tc := range testCases {
suite.Run("", func() {
suite.SetupTest() // reset
tc.malleate()
res, err := suite.handler(suite.ctx, tx)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
} else {
suite.Require().Error(err)
suite.Require().Nil(res)
}
})
}
}
func (suite *EvmTestSuite) TestHandlerLogs() {
// Test contract:
// pragma solidity ^0.5.1;
@@ -74,7 +219,8 @@ func (suite *EvmTestSuite) TestHandler_Logs() {
bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029")
tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode)
tx.Sign(big.NewInt(3), priv)
err = tx.Sign(big.NewInt(3), priv.ToECDSA())
suite.Require().NoError(err)
result, err := suite.handler(suite.ctx, tx)
suite.Require().NoError(err, "failed to handle eth tx msg")
@@ -105,7 +251,8 @@ func (suite *EvmTestSuite) TestQueryTxLogs() {
// send contract deployment transaction with an event in the constructor
bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029")
tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode)
tx.Sign(big.NewInt(3), priv)
err = tx.Sign(big.NewInt(3), priv.ToECDSA())
suite.Require().NoError(err)
// result, err := evm.HandleEthTxMsg(suite.ctx, suite.app.EvmKeeper, tx)
// suite.Require().NoError(err, "failed to handle eth tx msg")
+23 -32
View File
@@ -1,7 +1,6 @@
package keeper
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
@@ -50,25 +49,23 @@ func NewKeeper(
// May be removed when using only as module (only required by rpc api)
// ----------------------------------------------------------------------------
// SetBlockHashMapping sets the mapping from block consensus hash to block height
func (k *Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) {
store := ctx.KVStore(k.blockKey)
if !bytes.Equal(hash, []byte{}) {
bz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(hash, bz)
}
}
// GetBlockHashMapping gets block height from block consensus hash
func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64) {
func (k Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (int64, error) {
store := ctx.KVStore(k.blockKey)
bz := store.Get(hash)
if bytes.Equal(bz, []byte{}) {
panic(fmt.Errorf("block with hash %s not found", ethcmn.BytesToHash(hash)))
if len(bz) == 0 {
return 0, fmt.Errorf("block with hash '%s' not found", ethcmn.BytesToHash(hash))
}
height = int64(binary.BigEndian.Uint64(bz))
return height
height := binary.BigEndian.Uint64(bz)
return int64(height), nil
}
// SetBlockHashMapping sets the mapping from block consensus hash to block height
func (k Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) {
store := ctx.KVStore(k.blockKey)
bz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(hash, bz)
}
// ----------------------------------------------------------------------------
@@ -76,28 +73,23 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64
// May be removed when using only as module (only required by rpc api)
// ----------------------------------------------------------------------------
// SetBlockBloomMapping sets the mapping from block height to bloom bits
func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) error {
// GetBlockBloomMapping gets bloombits from block height
func (k Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) (ethtypes.Bloom, error) {
store := ctx.KVStore(k.blockKey)
bz := sdk.Uint64ToBigEndian(uint64(height))
heightBz := sdk.Uint64ToBigEndian(uint64(height))
bz := store.Get(types.BloomKey(heightBz))
if len(bz) == 0 {
return fmt.Errorf("block with bloombits %v not found", bloom)
return ethtypes.Bloom{}, fmt.Errorf("block at height %d not found", height)
}
store.Set(types.BloomKey(bz), bloom.Bytes())
return nil
return ethtypes.BytesToBloom(bz), nil
}
// GetBlockBloomMapping gets bloombits from block height
func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) (ethtypes.Bloom, error) {
// SetBlockBloomMapping sets the mapping from block height to bloom bits
func (k Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) {
store := ctx.KVStore(k.blockKey)
bz := sdk.Uint64ToBigEndian(uint64(height))
if len(bz) == 0 {
return ethtypes.BytesToBloom([]byte{}), fmt.Errorf("block with height %d not found", height)
}
bloom := store.Get(types.BloomKey(bz))
return ethtypes.BytesToBloom(bloom), nil
heightBz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(types.BloomKey(heightBz), bloom.Bytes())
}
// SetTransactionLogs sets the transaction's logs in the KVStore
@@ -107,8 +99,8 @@ func (k *Keeper) SetTransactionLogs(ctx sdk.Context, logs []*ethtypes.Log, hash
if err != nil {
return err
}
store.Set(types.LogsKey(hash), encLogs)
store.Set(types.LogsKey(hash), encLogs)
return nil
}
@@ -135,7 +127,6 @@ func (k *Keeper) CreateGenesisAccount(ctx sdk.Context, account types.GenesisAcco
for _, key := range account.Storage {
csdb.SetState(account.Address, key, account.Storage[key])
}
}
// ----------------------------------------------------------------------------
+15 -5
View File
@@ -18,7 +18,9 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
)
var address = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1")
const addrHex = "0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1"
var address = ethcmn.HexToAddress(addrHex)
type KeeperTestSuite struct {
suite.Suite
@@ -50,12 +52,15 @@ func (suite *KeeperTestSuite) TestDBStorage() {
// Test block hash mapping functionality
suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7)
height, err := suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"))
suite.Require().NoError(err)
suite.Require().Equal(int64(7), height)
suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}, 8)
// Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
err := suite.app.EvmKeeper.SetBlockBloomMapping(suite.ctx, testBloom, 4)
suite.Require().NoError(err, "failed to set block bloom mapping")
suite.app.EvmKeeper.SetBlockBloomMapping(suite.ctx, testBloom, 4)
// Get those state transitions
suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, address).Cmp(big.NewInt(5)), 0)
@@ -63,8 +68,13 @@ func (suite *KeeperTestSuite) TestDBStorage() {
suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3"))
suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, address), []byte{0x1})
suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7))
suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}), int64(8))
height, err = suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"))
suite.Require().NoError(err)
suite.Require().Equal(height, int64(7))
height, err = suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, []byte{0x43, 0x32})
suite.Require().NoError(err)
suite.Require().Equal(height, int64(8))
bloom, err := suite.app.EvmKeeper.GetBlockBloomMapping(suite.ctx, 4)
suite.Require().NoError(err)
suite.Require().Equal(bloom, testBloom)
+18 -3
View File
@@ -7,11 +7,14 @@ import (
"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/utils"
"github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
abci "github.com/tendermint/tendermint/abci/types"
)
@@ -61,8 +64,12 @@ func queryProtocolVersion(keeper Keeper) ([]byte, error) {
func queryBalance(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
addr := ethcmn.HexToAddress(path[1])
balance := keeper.GetBalance(ctx, addr)
balanceStr, err := utils.MarshalBigInt(balance)
if err != nil {
return nil, err
}
res := types.QueryResBalance{Balance: utils.MarshalBigInt(balance)}
res := types.QueryResBalance{Balance: balanceStr}
bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
@@ -120,7 +127,10 @@ func queryNonce(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
blockHash := ethcmn.FromHex(path[1])
blockNumber := keeper.GetBlockHashMapping(ctx, blockHash)
blockNumber, err := keeper.GetBlockHashMapping(ctx, blockHash)
if err != nil {
return []byte{}, err
}
res := types.QueryResBlockNumber{Number: blockNumber}
bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
@@ -182,8 +192,13 @@ func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error)
addr := ethcmn.HexToAddress(path[1])
so := keeper.GetOrNewStateObject(ctx, addr)
balance, err := utils.MarshalBigInt(so.Balance())
if err != nil {
return nil, err
}
res := types.QueryResAccount{
Balance: utils.MarshalBigInt(so.Balance()),
Balance: balance,
CodeHash: so.CodeHash(),
Nonce: so.Nonce(),
}
+52
View File
@@ -0,0 +1,52 @@
package keeper_test
import (
"math/big"
"github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
)
func (suite *KeeperTestSuite) TestQuerier() {
testCases := []struct {
msg string
path []string
malleate func()
expPass bool
}{
{"protocol version", []string{types.QueryProtocolVersion}, func() {}, true},
{"balance", []string{types.QueryBalance, addrHex}, func() {
suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(5))
}, true},
// {"balance", []string{types.QueryBalance, "0x01232"}, func() {}, false},
{"block number", []string{types.QueryBlockNumber, "0x0"}, func() {}, true},
{"storage", []string{types.QueryStorage, "0x0", "0x0"}, func() {}, true},
{"code", []string{types.QueryCode, "0x0"}, func() {}, true},
{"nonce", []string{types.QueryNonce, "0x0"}, func() {}, true},
// {"hash to height", []string{types.QueryHashToHeight, "0x0"}, func() {}, true},
{"tx logs", []string{types.QueryTxLogs, "0x0"}, func() {}, true},
// {"logs bloom", []string{types.QueryLogsBloom, "0x0"}, func() {}, true},
{"logs", []string{types.QueryLogs, "0x0"}, func() {}, true},
{"account", []string{types.QueryAccount, "0x0"}, func() {}, true},
{"unknown request", []string{"other"}, func() {}, false},
}
for i, tc := range testCases {
suite.Run("", func() {
tc := tc
suite.SetupTest() // reset
tc.malleate()
bz, err := suite.querier(suite.ctx, tc.path, abci.RequestQuery{})
if tc.expPass {
suite.Require().NoError(err, "valid test %d failed: %s", i, tc.msg)
suite.Require().NotZero(len(bz))
} else {
suite.Require().Error(err, "invalid test %d passed: %s", i, tc.msg)
}
})
}
}
+1 -1
View File
@@ -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() {
-88
View File
@@ -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
}
-76
View File
@@ -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())
}
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+56
View File
@@ -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)
}
+67
View File
@@ -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
}