upgrade changes cleanup (#236)

* changes from update version

* app changes

* cmd changes

* build and send tx

* fix tests

* eth_rpc fixes

* lint

* add WithEventManager to handler ctx

* changelog

* go mod verify and tidy
This commit is contained in:
Federico Kunze 2020-04-01 15:49:21 -03:00 committed by GitHub
parent 1627ed81bf
commit da9157e406
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1631 additions and 1235 deletions

View File

@ -40,3 +40,13 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements ### Improvements
* (x/evm) [\#181](https://github.com/ChainSafe/ethermint/issues/181) Updated EVM module to the recommended module structure. [@fedekunze](https://github.com/fedekunze) * (x/evm) [\#181](https://github.com/ChainSafe/ethermint/issues/181) Updated EVM module to the recommended module structure. [@fedekunze](https://github.com/fedekunze)
* (app) [\#188](https://github.com/ChainSafe/ethermint/issues/186) Misc cleanup [@fedekunze](https://github.com/fedekunze):
* (`x/evm`) Rename `EthereumTxMsg` --> `MsgEthereumTx` and `EmintMsg` --> `MsgEthermint` for consistency with SDK standards
* Updated integration and unit tests to use `EthermintApp` as testing suite
* Use expected keeper interface for `AccountKeeper`
* Replaced `count` type in keeper with `int`
* Add SDK events for transactions
* [\#236](https://github.com/ChainSafe/ethermint/pull/236) Changes from upgrade [@fedekunze](https://github.com/fedekunze)
* (app/ante) Moved `AnteHandler` implementation to `app/ante`
* (keys) Marked `ExportEthKeyCommand` as **UNSAFE**
* (x/evm) Moved `BeginBlock` and `EndBlock` to `x/evm/abci.go`

View File

@ -1,4 +1,4 @@
package app package ante
import ( import (
"fmt" "fmt"
@ -46,15 +46,15 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle
ante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(ak), ante.NewValidateSigCountDecorator(ak),
ante.NewDeductFeeDecorator(ak, sk), ante.NewDeductFeeDecorator(ak, sk),
ante.NewSigGasConsumeDecorator(ak, consumeSigGas), ante.NewSigGasConsumeDecorator(ak, sigGasConsumer),
ante.NewSigVerificationDecorator(ak), ante.NewSigVerificationDecorator(ak),
ante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator ante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator
) )
return stdAnte(ctx, tx, sim) return stdAnte(ctx, tx, sim)
case *evmtypes.EthereumTxMsg: case evmtypes.MsgEthereumTx:
return ethAnteHandler(ctx, ak, sk, castTx, sim) return ethAnteHandler(ctx, ak, sk, &castTx, sim)
default: default:
return ctx, sdk.ErrInternal(fmt.Sprintf("transaction type invalid: %T", tx)) return ctx, sdk.ErrInternal(fmt.Sprintf("transaction type invalid: %T", tx))
@ -62,7 +62,9 @@ func NewAnteHandler(ak auth.AccountKeeper, sk types.SupplyKeeper) sdk.AnteHandle
} }
} }
func consumeSigGas( // sigGasConsumer overrides the DefaultSigVerificationGasConsumer from the x/auth
// module on the SDK. It doesn't allow ed25519 nor multisig thresholds.
func sigGasConsumer(
meter sdk.GasMeter, sig []byte, pubkey tmcrypto.PubKey, params types.Params, meter sdk.GasMeter, sig []byte, pubkey tmcrypto.PubKey, params types.Params,
) error { ) error {
switch pubkey.(type) { switch pubkey.(type) {
@ -89,7 +91,7 @@ func consumeSigGas(
// prevent spam and DoS attacks. // prevent spam and DoS attacks.
func ethAnteHandler( func ethAnteHandler(
ctx sdk.Context, ak auth.AccountKeeper, sk types.SupplyKeeper, ctx sdk.Context, ak auth.AccountKeeper, sk types.SupplyKeeper,
ethTxMsg *evmtypes.EthereumTxMsg, sim bool, ethTxMsg *evmtypes.MsgEthereumTx, sim bool,
) (newCtx sdk.Context, err error) { ) (newCtx sdk.Context, err error) {
var senderAddr sdk.AccAddress var senderAddr sdk.AccAddress
@ -120,7 +122,9 @@ func ethAnteHandler(
if r := recover(); r != nil { if r := recover(); r != nil {
switch rType := r.(type) { switch rType := r.(type) {
case sdk.ErrorOutOfGas: case sdk.ErrorOutOfGas:
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor) log := fmt.Sprintf("out of gas in location: %v; gasUsed: %d",
rType.Descriptor, ctx.GasMeter().GasConsumed(),
)
err = sdk.ErrOutOfGas(log) err = sdk.ErrOutOfGas(log)
default: default:
panic(r) panic(r)
@ -139,9 +143,9 @@ func ethAnteHandler(
// Cost calculates the fees paid to validators based on gas limit and price // Cost calculates the fees paid to validators based on gas limit and price
cost := new(big.Int).Mul(ethTxMsg.Data.Price, new(big.Int).SetUint64(ethTxMsg.Data.GasLimit)) cost := new(big.Int).Mul(ethTxMsg.Data.Price, new(big.Int).SetUint64(ethTxMsg.Data.GasLimit))
feeAmt := sdk.Coins{ feeAmt := sdk.NewCoins(
sdk.NewCoin(emint.DenomDefault, sdk.NewIntFromBigInt(cost)), sdk.NewCoin(emint.DenomDefault, sdk.NewIntFromBigInt(cost)),
} )
err = auth.DeductFees(sk, ctx, senderAcc, feeAmt) err = auth.DeductFees(sk, ctx, senderAcc, feeAmt)
if err != nil { if err != nil {
@ -166,7 +170,7 @@ func ethAnteHandler(
} }
func validateEthTxCheckTx( func validateEthTxCheckTx(
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx,
) (sdk.AccAddress, error) { ) (sdk.AccAddress, error) {
// Validate sufficient fees have been provided that meet a minimum threshold // Validate sufficient fees have been provided that meet a minimum threshold
// defined by the proposer (for mempool purposes during CheckTx). // defined by the proposer (for mempool purposes during CheckTx).
@ -193,7 +197,7 @@ func validateEthTxCheckTx(
} }
// Validates signature and returns sender address // Validates signature and returns sender address
func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) (sdk.AccAddress, error) { func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) (sdk.AccAddress, error) {
// parse the chainID from a string to a base-10 integer // parse the chainID from a string to a base-10 integer
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok { if !ok {
@ -214,7 +218,7 @@ func validateSignature(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) (sdk.A
// that the transaction uses before the transaction is executed. The gas is a // that the transaction uses before the transaction is executed. The gas is a
// constant value of 21000 plus any cost inccured by additional bytes of data // constant value of 21000 plus any cost inccured by additional bytes of data
// supplied with the transaction. // supplied with the transaction.
func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error { func validateIntrinsicGas(ethTxMsg *evmtypes.MsgEthereumTx) error {
gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true) gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, true)
if err != nil { if err != nil {
return sdk.ErrInternal(fmt.Sprintf("failed to compute intrinsic gas cost: %s", err)) return sdk.ErrInternal(fmt.Sprintf("failed to compute intrinsic gas cost: %s", err))
@ -222,7 +226,7 @@ func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error {
if ethTxMsg.Data.GasLimit < gas { if ethTxMsg.Data.GasLimit < gas {
return sdk.ErrInternal( return sdk.ErrInternal(
fmt.Sprintf("intrinsic gas too low; %d < %d", ethTxMsg.Data.GasLimit, gas), fmt.Sprintf("intrinsic gas too low: %d < %d", ethTxMsg.Data.GasLimit, gas),
) )
} }
@ -232,7 +236,7 @@ func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) error {
// validateAccount validates the account nonce and that the account has enough // validateAccount validates the account nonce and that the account has enough
// funds to cover the tx cost. // funds to cover the tx cost.
func validateAccount( func validateAccount(
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, signer sdk.AccAddress, ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress,
) error { ) error {
acc := ak.GetAccount(ctx, signer) acc := ak.GetAccount(ctx, signer)
@ -241,8 +245,9 @@ func validateAccount(
if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 { if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 {
return sdk.ErrInternal( return sdk.ErrInternal(
fmt.Sprintf( fmt.Sprintf(
"invalid account number for height zero; got %d, expected 0", acc.GetAccountNumber(), "invalid account number for height zero (got %d)", acc.GetAccountNumber(),
)) ),
)
} }
// Validate nonce is correct // Validate nonce is correct
@ -262,7 +267,7 @@ func validateAccount(
} }
func checkNonce( func checkNonce(
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, signer sdk.AccAddress, ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.MsgEthereumTx, signer sdk.AccAddress,
) error { ) error {
acc := ak.GetAccount(ctx, signer) acc := ak.GetAccount(ctx, signer)
// Validate the transaction nonce is valid (equivalent to the sender accounts // Validate the transaction nonce is valid (equivalent to the sender accounts
@ -270,7 +275,8 @@ func checkNonce(
seq := acc.GetSequence() seq := acc.GetSequence()
if ethTxMsg.Data.AccountNonce != seq { if ethTxMsg.Data.AccountNonce != seq {
return sdk.ErrInvalidSequence( return sdk.ErrInvalidSequence(
fmt.Sprintf("invalid nonce; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq)) fmt.Sprintf("invalid nonce; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq),
)
} }
return nil return nil
@ -281,7 +287,7 @@ func checkNonce(
// proposer. // proposer.
// //
// NOTE: This should only be ran during a CheckTx mode. // NOTE: This should only be ran during a CheckTx mode.
func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) error { func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.MsgEthereumTx) error {
// fee = GP * GL // fee = GP * GL
fee := sdk.NewDecCoinFromCoin(sdk.NewInt64Coin(emint.DenomDefault, ethTxMsg.Fee().Int64())) fee := sdk.NewDecCoinFromCoin(sdk.NewInt64Coin(emint.DenomDefault, ethTxMsg.Fee().Int64()))
@ -297,7 +303,9 @@ func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxM
if !ctx.MinGasPrices().IsZero() && !allGTE { if !ctx.MinGasPrices().IsZero() && !allGTE {
// reject the transaction that does not meet the minimum fee // reject the transaction that does not meet the minimum fee
return sdk.ErrInsufficientFee( return sdk.ErrInsufficientFee(
fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices()), fmt.Sprintf(
"insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices(),
),
) )
} }

301
app/ante/ante_test.go Normal file
View File

@ -0,0 +1,301 @@
package ante_test
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/app/ante"
"github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
func requireValidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool,
) {
_, err := anteHandler(ctx, tx, sim)
require.NoError(t, err)
}
func requireInvalidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context,
tx sdk.Tx, sim bool,
) {
_, err := anteHandler(ctx, tx, sim)
require.Error(t, err)
}
func (suite *AnteTestSuite) TestValidEthTx() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc1.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc1)
acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2)
err = acc2.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc2)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestValidTx() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc1.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc1)
acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2)
err = acc2.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc2)
// require a valid SDK tx to pass
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{priv1, priv2}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestSDKInvalidSigs() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
addr3, priv3 := newTestAddrKey()
acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc1.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc1)
acc2 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2)
err = acc2.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc2)
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
// require validation failure with no signers
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
// require validation failure with invalid number of signers
msgs = []sdk.Msg{msg1}
privKeys = []tmcrypto.PrivKey{priv1}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
// require validation failure with an invalid signer
msg2 := newTestMsg(addr1, addr3)
msgs = []sdk.Msg{msg1, msg2}
privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestSDKInvalidAcc() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
acc1 := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc1.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc1)
fee := newTestStdFee()
msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{priv1}
// require validation failure with invalid account number
accNums := []uint64{1}
accSeqs := []uint64{acc1.GetSequence()}
tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
// require validation failure with invalid sequence (nonce)
accNums = []uint64{acc1.GetAccountNumber()}
accSeqs = []uint64{1}
tx = newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInvalidSig() {
suite.ctx = suite.ctx.WithBlockHeight(1)
_, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
ctx := suite.ctx.WithChainID("4")
requireInvalidTx(suite.T(), suite.anteHandler, ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInvalidNonce() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc.SetSequence(10)
suite.Require().NoError(err)
err = acc.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInsufficientBalance() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInvalidIntrinsicGas() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
gasLimit := uint64(1000)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, gasLimit, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInvalidMempoolFees() {
// setup app with checkTx = true
suite.app = app.Setup(true)
suite.ctx = suite.app.BaseApp.NewContext(true, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.SupplyKeeper)
suite.ctx = suite.ctx.WithMinGasPrices(sdk.NewDecCoins(sdk.NewCoins(sdk.NewCoin(types.DenomDefault, sdk.NewInt(500000)))))
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("payload"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestEthInvalidChainID() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
err := acc.SetCoins(newTestCoins())
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewMsgEthereumTx(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(suite.ctx, ethMsg, priv1)
ctx := suite.ctx.WithChainID("bad-chain-id")
requireInvalidTx(suite.T(), suite.anteHandler, ctx, tx, false)
}

104
app/ante/utils_test.go Normal file
View File

@ -0,0 +1,104 @@
package ante_test
import (
"fmt"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/app"
ante "github.com/cosmos/ethermint/app/ante"
"github.com/cosmos/ethermint/crypto"
emint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
)
type AnteTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.EthermintApp
anteHandler sdk.AnteHandler
}
func (suite *AnteTestSuite) SetupTest() {
checkTx := false
suite.app = app.Setup(checkTx)
suite.app.Codec().RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.SupplyKeeper)
}
func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
return sdk.NewTestMsg(addrs...)
}
func newTestCoins() sdk.Coins {
return sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 500000000))
}
func newTestStdFee() auth.StdFee {
return auth.NewStdFee(220000, sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 150)))
}
// GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
privkey, _ := crypto.GenerateKey()
addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey)
return sdk.AccAddress(addr.Bytes()), privkey
}
func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
accNums []uint64, seqs []uint64, fee auth.StdFee,
) sdk.Tx {
sigs := make([]auth.StdSignature, len(privs))
for i, priv := range privs {
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
sig, err := priv.Sign(signBytes)
if err != nil {
panic(err)
}
sigs[i] = auth.StdSignature{
PubKey: priv.PubKey(),
Signature: sig,
}
}
return auth.NewStdTx(msgs, fee, sigs, "")
}
func newTestEthTx(ctx sdk.Context, msg evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx {
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
}
privkey, ok := priv.(crypto.PrivKeySecp256k1)
if !ok {
panic(fmt.Sprintf("invalid private key type: %T", priv))
}
msg.Sign(chainID, privkey.ToECDSA())
return msg
}

View File

@ -1,308 +0,0 @@
package app
import (
"math/big"
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
tmcrypto "github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
func requireValidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool,
) {
_, err := anteHandler(ctx, tx, sim)
require.True(t, err == nil)
}
func requireInvalidTx(
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context,
tx sdk.Tx, sim bool, code sdk.CodeType,
) {
_, err := anteHandler(ctx, tx, sim)
// require.Equal(t, code, err, fmt.Sprintf("invalid result: %v", err))
require.Error(t, err)
if code == sdk.CodeOutOfGas {
_, ok := tx.(auth.StdTx)
require.True(t, ok, "tx must be in form auth.StdTx")
}
}
func TestValidEthTx(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc1.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
// nolint:errcheck
acc2.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc2)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
requireValidTx(t, input.anteHandler, input.ctx, tx, false)
}
func TestValidTx(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc1.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
// nolint:errcheck
acc2.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc2)
// require a valid SDK tx to pass
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{priv1, priv2}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireValidTx(t, input.anteHandler, input.ctx, tx, false)
}
func TestSDKInvalidSigs(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, priv2 := newTestAddrKey()
addr3, priv3 := newTestAddrKey()
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc1.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc1)
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
// nolint:errcheck
acc2.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc2)
fee := newTestStdFee()
msg1 := newTestMsg(addr1, addr2)
// require validation failure with no signers
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeNoSignatures)
// require validation failure with invalid number of signers
msgs = []sdk.Msg{msg1}
privKeys = []tmcrypto.PrivKey{priv1}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with an invalid signer
msg2 := newTestMsg(addr1, addr3)
msgs = []sdk.Msg{msg1, msg2}
privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnknownAddress)
}
func TestSDKInvalidAcc(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc1.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc1)
fee := newTestStdFee()
msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1}
privKeys := []tmcrypto.PrivKey{priv1}
// require validation failure with invalid account number
accNums := []uint64{1}
accSeqs := []uint64{acc1.GetSequence()}
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
// require validation failure with invalid sequence (nonce)
accNums = []uint64{acc1.GetAccountNumber()}
accSeqs = []uint64{1}
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
}
func TestEthInvalidSig(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
_, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
ctx := input.ctx.WithChainID("4")
requireInvalidTx(t, input.anteHandler, ctx, tx, false, sdk.CodeUnauthorized)
}
func TestEthInvalidNonce(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc.SetCoins(newTestCoins())
// nolint:errcheck
acc.SetSequence(10)
input.accKeeper.SetAccount(input.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInvalidSequence)
}
func TestEthInsufficientBalance(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
input.accKeeper.SetAccount(input.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFunds)
}
func TestEthInvalidIntrinsicGas(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
gasLimit := uint64(1000)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, gasLimit, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInternal)
}
func TestEthInvalidMempoolFees(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
input.ctx = input.ctx.WithMinGasPrices(sdk.DecCoins{sdk.NewDecCoin(types.DenomDefault, sdk.NewInt(500000))})
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFee)
}
func TestEthInvalidChainID(t *testing.T) {
input := newTestSetup()
input.ctx = input.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
addr2, _ := newTestAddrKey()
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
// nolint:errcheck
acc.SetCoins(newTestCoins())
input.accKeeper.SetAccount(input.ctx, acc)
// require a valid Ethereum tx to pass
to := ethcmn.BytesToAddress(addr2.Bytes())
amt := big.NewInt(32)
gas := big.NewInt(20)
ethMsg := evmtypes.NewEthereumTxMsg(0, &to, amt, 22000, gas, []byte("test"))
tx := newTestEthTx(input.ctx, ethMsg, priv1)
ctx := input.ctx.WithChainID("bad-chain-id")
requireInvalidTx(t, input.anteHandler, ctx, tx, false, types.CodeInvalidChainID)
}

View File

@ -1,15 +1,13 @@
package app package app
import ( import (
"encoding/json" "io"
"os" "os"
emintcrypto "github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/x/evm"
bam "github.com/cosmos/cosmos-sdk/baseapp" bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/version"
@ -26,12 +24,14 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/supply" "github.com/cosmos/cosmos-sdk/x/supply"
"github.com/cosmos/ethermint/app/ante"
emintcrypto "github.com/cosmos/ethermint/crypto"
eminttypes "github.com/cosmos/ethermint/types" eminttypes "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
tmlog "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
) )
@ -44,21 +44,23 @@ var (
// DefaultNodeHome sets the folder where the applcation data and configuration will be stored // DefaultNodeHome sets the folder where the applcation data and configuration will be stored
DefaultNodeHome = os.ExpandEnv("$HOME/.emintd") DefaultNodeHome = os.ExpandEnv("$HOME/.emintd")
// ModuleBasics is the module BasicManager is in charge of setting up basic, // ModuleBasics defines the module BasicManager is in charge of setting up basic,
// non-dependant module elements, such as codec registration // non-dependant module elements, such as codec registration
// and genesis verification. // and genesis verification.
ModuleBasics = module.NewBasicManager( ModuleBasics = module.NewBasicManager(
genutil.AppModuleBasic{},
auth.AppModuleBasic{}, auth.AppModuleBasic{},
supply.AppModuleBasic{},
genutil.AppModuleBasic{},
bank.AppModuleBasic{}, bank.AppModuleBasic{},
staking.AppModuleBasic{}, staking.AppModuleBasic{},
mint.AppModuleBasic{}, mint.AppModuleBasic{},
distr.AppModuleBasic{}, distr.AppModuleBasic{},
gov.NewAppModuleBasic(paramsclient.ProposalHandler, distr.ProposalHandler), gov.NewAppModuleBasic(
paramsclient.ProposalHandler, distr.ProposalHandler,
),
params.AppModuleBasic{}, params.AppModuleBasic{},
crisis.AppModuleBasic{}, crisis.AppModuleBasic{},
slashing.AppModuleBasic{}, slashing.AppModuleBasic{},
supply.AppModuleBasic{},
evm.AppModuleBasic{}, evm.AppModuleBasic{},
) )
@ -71,6 +73,11 @@ var (
staking.NotBondedPoolName: {supply.Burner, supply.Staking}, staking.NotBondedPoolName: {supply.Burner, supply.Staking},
gov.ModuleName: {supply.Burner}, gov.ModuleName: {supply.Burner},
} }
// module accounts that are allowed to receive tokens
allowedReceivingModAcc = map[string]bool{
distr.ModuleName: true,
}
) )
// MakeCodec generates the necessary codecs for Amino // MakeCodec generates the necessary codecs for Amino
@ -100,18 +107,21 @@ type EthermintApp struct {
keys map[string]*sdk.KVStoreKey keys map[string]*sdk.KVStoreKey
tkeys map[string]*sdk.TransientStoreKey tkeys map[string]*sdk.TransientStoreKey
// subspaces
subspaces map[string]params.Subspace
// keepers // keepers
accountKeeper auth.AccountKeeper AccountKeeper auth.AccountKeeper
bankKeeper bank.Keeper BankKeeper bank.Keeper
supplyKeeper supply.Keeper SupplyKeeper supply.Keeper
stakingKeeper staking.Keeper StakingKeeper staking.Keeper
slashingKeeper slashing.Keeper SlashingKeeper slashing.Keeper
mintKeeper mint.Keeper MintKeeper mint.Keeper
distrKeeper distr.Keeper DistrKeeper distr.Keeper
govKeeper gov.Keeper GovKeeper gov.Keeper
crisisKeeper crisis.Keeper CrisisKeeper crisis.Keeper
paramsKeeper params.Keeper ParamsKeeper params.Keeper
evmKeeper evm.Keeper EvmKeeper evm.Keeper
// the module manager // the module manager
mm *module.Manager mm *module.Manager
@ -124,18 +134,25 @@ type EthermintApp struct {
// in a sovereign zone and as an application running with a shared security model. // in a sovereign zone and as an application running with a shared security model.
// For now, it will support only running as a sovereign application. // For now, it will support only running as a sovereign application.
func NewEthermintApp( func NewEthermintApp(
logger tmlog.Logger, db dbm.DB, loadLatest bool, logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp)) *EthermintApp { invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp),
) *EthermintApp {
cdc := MakeCodec() cdc := MakeCodec()
bApp := bam.NewBaseApp(appName, logger, db, evmtypes.TxDecoder(cdc), baseAppOptions...) // use custom Ethermint transaction decoder
bApp := bam.NewBaseApp(appName, logger, db, evm.TxDecoder(cdc), baseAppOptions...)
bApp.SetCommitMultiStoreTracer(traceStore)
bApp.SetAppVersion(version.Version) bApp.SetAppVersion(version.Version)
keys := sdk.NewKVStoreKeys(bam.MainStoreKey, auth.StoreKey, staking.StoreKey, keys := sdk.NewKVStoreKeys(
bam.MainStoreKey, auth.StoreKey, staking.StoreKey,
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
gov.StoreKey, params.StoreKey, evmtypes.EvmStoreKey, evmtypes.EvmCodeKey) gov.StoreKey, params.StoreKey, evm.CodeKey, evm.StoreKey,
blockKey := sdk.NewKVStoreKey(evmtypes.EvmBlockKey) )
tkeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) blockKey := sdk.NewKVStoreKey(evm.BlockKey)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
app := &EthermintApp{ app := &EthermintApp{
BaseApp: bApp, BaseApp: bApp,
@ -143,89 +160,118 @@ func NewEthermintApp(
invCheckPeriod: invCheckPeriod, invCheckPeriod: invCheckPeriod,
keys: keys, keys: keys,
tkeys: tkeys, tkeys: tkeys,
subspaces: make(map[string]params.Subspace),
} }
// init params keeper and subspaces // init params keeper and subspaces
app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) app.ParamsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace)
authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) app.subspaces[auth.ModuleName] = app.ParamsKeeper.Subspace(auth.DefaultParamspace)
bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) app.subspaces[bank.ModuleName] = app.ParamsKeeper.Subspace(bank.DefaultParamspace)
stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) app.subspaces[staking.ModuleName] = app.ParamsKeeper.Subspace(staking.DefaultParamspace)
mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) app.subspaces[mint.ModuleName] = app.ParamsKeeper.Subspace(mint.DefaultParamspace)
distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) app.subspaces[distr.ModuleName] = app.ParamsKeeper.Subspace(distr.DefaultParamspace)
slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) app.subspaces[slashing.ModuleName] = app.ParamsKeeper.Subspace(slashing.DefaultParamspace)
govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) app.subspaces[gov.ModuleName] = app.ParamsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) app.subspaces[crisis.ModuleName] = app.ParamsKeeper.Subspace(crisis.DefaultParamspace)
// add keepers // use custom Ethermint account for contracts
app.accountKeeper = auth.NewAccountKeeper(app.cdc, keys[auth.StoreKey], authSubspace, eminttypes.ProtoBaseAccount) app.AccountKeeper = auth.NewAccountKeeper(
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper, bankSubspace, bank.DefaultCodespace, app.ModuleAccountAddrs()) app.cdc, keys[auth.StoreKey], app.subspaces[auth.ModuleName], eminttypes.ProtoBaseAccount,
app.supplyKeeper = supply.NewKeeper(app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, maccPerms) )
stakingKeeper := staking.NewKeeper(app.cdc, keys[staking.StoreKey], app.BankKeeper = bank.NewBaseKeeper(
app.supplyKeeper, stakingSubspace, staking.DefaultCodespace) app.AccountKeeper, app.subspaces[bank.ModuleName], bank.DefaultCodespace, app.ModuleAccountAddrs(),
app.mintKeeper = mint.NewKeeper(app.cdc, keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.supplyKeeper, auth.FeeCollectorName) )
app.distrKeeper = distr.NewKeeper(app.cdc, keys[distr.StoreKey], distrSubspace, &stakingKeeper, app.SupplyKeeper = supply.NewKeeper(
app.supplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs()) app.cdc, keys[supply.StoreKey], app.AccountKeeper, app.BankKeeper, maccPerms,
app.slashingKeeper = slashing.NewKeeper(app.cdc, keys[slashing.StoreKey], &stakingKeeper, )
slashingSubspace, slashing.DefaultCodespace) stakingKeeper := staking.NewKeeper(
app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.supplyKeeper, auth.FeeCollectorName) app.cdc, keys[staking.StoreKey], app.SupplyKeeper, app.subspaces[staking.ModuleName],
app.evmKeeper = evm.NewKeeper(app.accountKeeper, keys[evmtypes.EvmStoreKey], keys[evmtypes.EvmCodeKey], blockKey, cdc) staking.DefaultCodespace,
)
app.MintKeeper = mint.NewKeeper(
app.cdc, keys[mint.StoreKey], app.subspaces[mint.ModuleName], &stakingKeeper,
app.SupplyKeeper, auth.FeeCollectorName,
)
app.DistrKeeper = distr.NewKeeper(
app.cdc, keys[distr.StoreKey], app.subspaces[distr.ModuleName], &stakingKeeper,
app.SupplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, app.ModuleAccountAddrs(),
)
app.SlashingKeeper = slashing.NewKeeper(
app.cdc, keys[slashing.StoreKey], &stakingKeeper, app.subspaces[slashing.ModuleName],
slashing.DefaultCodespace,
)
app.CrisisKeeper = crisis.NewKeeper(
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
)
app.EvmKeeper = evm.NewKeeper(
app.cdc, blockKey, keys[evm.CodeKey], keys[evm.StoreKey], app.AccountKeeper,
)
// register the proposal types // register the proposal types
govRouter := gov.NewRouter() govRouter := gov.NewRouter()
govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler). govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler).
AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)). AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)) AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper))
app.govKeeper = gov.NewKeeper(app.cdc, keys[gov.StoreKey], govSubspace, app.GovKeeper = gov.NewKeeper(
app.supplyKeeper, &stakingKeeper, gov.DefaultCodespace, govRouter) app.cdc, keys[gov.StoreKey], app.subspaces[gov.ModuleName], app.SupplyKeeper,
&stakingKeeper, gov.DefaultCodespace, govRouter,
)
// register the staking hooks // register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
app.stakingKeeper = *stakingKeeper.SetHooks( app.StakingKeeper = *stakingKeeper.SetHooks(
staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()), staking.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
) )
// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
app.mm = module.NewManager( app.mm = module.NewManager(
genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), genutil.NewAppModule(app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx),
auth.NewAppModule(app.accountKeeper), auth.NewAppModule(app.AccountKeeper),
bank.NewAppModule(app.bankKeeper, app.accountKeeper), bank.NewAppModule(app.BankKeeper, app.AccountKeeper),
crisis.NewAppModule(&app.crisisKeeper), crisis.NewAppModule(&app.CrisisKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper), supply.NewAppModule(app.SupplyKeeper, app.AccountKeeper),
distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper), gov.NewAppModule(app.GovKeeper, app.AccountKeeper, app.SupplyKeeper),
gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), mint.NewAppModule(app.MintKeeper),
mint.NewAppModule(app.mintKeeper), slashing.NewAppModule(app.SlashingKeeper, app.AccountKeeper, app.StakingKeeper),
slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper), distr.NewAppModule(app.DistrKeeper, app.AccountKeeper, app.SupplyKeeper, app.StakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), staking.NewAppModule(app.StakingKeeper, app.AccountKeeper, app.SupplyKeeper),
evm.NewAppModule(app.evmKeeper), evm.NewAppModule(app.EvmKeeper),
) )
// During begin block slashing happens after distr.BeginBlocker so that // During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the // there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant. // CanWithdrawInvariant invariant.
app.mm.SetOrderBeginBlockers(evmtypes.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName) app.mm.SetOrderBeginBlockers(
evm.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName,
app.mm.SetOrderEndBlockers(evmtypes.ModuleName, crisis.ModuleName, gov.ModuleName, staking.ModuleName) )
app.mm.SetOrderEndBlockers(
evm.ModuleName, crisis.ModuleName, gov.ModuleName, staking.ModuleName,
)
// NOTE: The genutils module must occur after staking so that pools are // NOTE: The genutils module must occur after staking so that pools are
// properly initialized with tokens from genesis accounts. // properly initialized with tokens from genesis accounts.
app.mm.SetOrderInitGenesis( app.mm.SetOrderInitGenesis(
distr.ModuleName, staking.ModuleName, auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
auth.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, evmtypes.ModuleName, crisis.ModuleName, genutil.ModuleName, evm.ModuleName,
) )
app.mm.RegisterInvariants(&app.crisisKeeper) app.mm.RegisterInvariants(&app.CrisisKeeper)
app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) app.mm.RegisterRoutes(app.Router(), app.QueryRouter())
// initialize stores // initialize stores
app.MountKVStores(keys) app.MountKVStores(keys)
app.MountTransientStores(tkeys) app.MountTransientStores(tkeys)
// Mount block hash mapping key as DB (no need for historical queries) // Mount block hash mapping key as DB (no need for historical queries)
// TODO: why does this need to be always StoreTypeDB?
app.MountStore(blockKey, sdk.StoreTypeDB) app.MountStore(blockKey, sdk.StoreTypeDB)
// initialize BaseApp // initialize BaseApp
app.SetInitChainer(app.InitChainer) app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker) app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.supplyKeeper)) app.SetAnteHandler(ante.NewAnteHandler(app.AccountKeeper, app.SupplyKeeper))
app.SetEndBlocker(app.EndBlocker) app.SetEndBlocker(app.EndBlocker)
if loadLatest { if loadLatest {
@ -234,12 +280,12 @@ func NewEthermintApp(
cmn.Exit(err.Error()) cmn.Exit(err.Error())
} }
} }
return app return app
} }
// GenesisState is the state of the blockchain is represented here as a map of raw json // Name returns the name of the App
// messages key'd by a identifier string. func (app *EthermintApp) Name() string { return app.BaseApp.Name() }
type GenesisState map[string]json.RawMessage
// BeginBlocker updates every begin block // BeginBlocker updates every begin block
func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
@ -253,7 +299,7 @@ func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a
// InitChainer updates at chain initialization // InitChainer updates at chain initialization
func (app *EthermintApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { func (app *EthermintApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
var genesisState GenesisState var genesisState simapp.GenesisState
app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState) app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState)
return app.mm.InitGenesis(ctx, genesisState) return app.mm.InitGenesis(ctx, genesisState)
} }
@ -267,8 +313,42 @@ func (app *EthermintApp) LoadHeight(height int64) error {
func (app *EthermintApp) ModuleAccountAddrs() map[string]bool { func (app *EthermintApp) ModuleAccountAddrs() map[string]bool {
modAccAddrs := make(map[string]bool) modAccAddrs := make(map[string]bool)
for acc := range maccPerms { for acc := range maccPerms {
modAccAddrs[app.supplyKeeper.GetModuleAddress(acc).String()] = true modAccAddrs[supply.NewModuleAddress(acc).String()] = true
} }
return modAccAddrs return modAccAddrs
} }
// BlacklistedAccAddrs returns all the app's module account addresses black listed for receiving tokens.
func (app *EthermintApp) BlacklistedAccAddrs() map[string]bool {
blacklistedAddrs := make(map[string]bool)
for acc := range maccPerms {
blacklistedAddrs[supply.NewModuleAddress(acc).String()] = !allowedReceivingModAcc[acc]
}
return blacklistedAddrs
}
// GetKey returns the KVStoreKey for the provided store key.
//
// NOTE: This is solely to be used for testing purposes.
func (app *EthermintApp) GetKey(storeKey string) *sdk.KVStoreKey {
return app.keys[storeKey]
}
// Codec returns Ethermint's codec.
//
// NOTE: This is solely to be used for testing purposes as it may be desirable
// for modules to register their own custom testing types.
func (app *EthermintApp) Codec() *codec.Codec {
return app.cdc
}
// GetMaccPerms returns a copy of the module account permissions
func GetMaccPerms() map[string][]string {
dupMaccPerms := make(map[string][]string)
for k, v := range maccPerms {
dupMaccPerms[k] = v
}
return dupMaccPerms
}

View File

@ -15,7 +15,7 @@ import (
func TestEthermintAppExport(t *testing.T) { func TestEthermintAppExport(t *testing.T) {
db := dbm.NewMemDB() db := dbm.NewMemDB()
app := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, true, 0) app := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
genesisState := ModuleBasics.DefaultGenesis() genesisState := ModuleBasics.DefaultGenesis()
stateBytes, err := codec.MarshalJSONIndent(app.cdc, genesisState) stateBytes, err := codec.MarshalJSONIndent(app.cdc, genesisState)
@ -31,7 +31,7 @@ func TestEthermintAppExport(t *testing.T) {
app.Commit() app.Commit()
// Making a new app object with the db, so that initchain hasn't been called // Making a new app object with the db, so that initchain hasn't been called
app2 := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, true, 0) app2 := NewEthermintApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
_, _, err = app2.ExportAppStateAndValidators(false, []string{}) _, _, err = app2.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err, "ExportAppStateAndValidators should not have an error") require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
} }

View File

@ -35,7 +35,7 @@ func (app *EthermintApp) ExportAppStateAndValidators(
} }
// Write validators to staking module to be used by TM node // Write validators to staking module to be used by TM node
validators = staking.WriteValidators(ctx, app.stakingKeeper) validators = staking.WriteValidators(ctx, app.StakingKeeper)
return appState, validators, nil return appState, validators, nil
} }
@ -61,49 +61,48 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList
} }
/* Just to be safe, assert the invariants on current state. */ /* Just to be safe, assert the invariants on current state. */
app.crisisKeeper.AssertInvariants(ctx) app.CrisisKeeper.AssertInvariants(ctx)
/* Handle fee distribution state. */ /* Handle fee distribution state. */
// withdraw all validator commission // withdraw all validator commission
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { app.StakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) {
_, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
return false return false
}) })
// withdraw all delegator rewards // withdraw all delegator rewards
dels := app.stakingKeeper.GetAllDelegations(ctx) dels := app.StakingKeeper.GetAllDelegations(ctx)
for _, delegation := range dels { for _, delegation := range dels {
_, _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress)
} }
// clear validator slash events // clear validator slash events
app.distrKeeper.DeleteAllValidatorSlashEvents(ctx) app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx)
// clear validator historical rewards // clear validator historical rewards
app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx) app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
// set context height to zero // set context height to zero
height := ctx.BlockHeight() height := ctx.BlockHeight()
ctx = ctx.WithBlockHeight(0) ctx = ctx.WithBlockHeight(0)
// reinitialize all validators // reinitialize all validators
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) { app.StakingKeeper.IterateValidators(ctx, func(_ int64, val exported.ValidatorI) (stop bool) {
// donate any unwithdrawn outstanding reward fraction tokens to the community pool // donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) scraps := app.DistrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator())
feePool := app.distrKeeper.GetFeePool(ctx) feePool := app.DistrKeeper.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(scraps) feePool.CommunityPool = feePool.CommunityPool.Add(scraps)
app.distrKeeper.SetFeePool(ctx, feePool) app.DistrKeeper.SetFeePool(ctx, feePool)
app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator())
return false return false
}) })
// reinitialize all delegations // reinitialize all delegations
for _, del := range dels { for _, del := range dels {
app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress) app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress)
app.distrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress) app.DistrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress)
} }
// reset context height // reset context height
@ -112,20 +111,20 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList
/* Handle staking state. */ /* Handle staking state. */
// iterate through redelegations, reset creation height // iterate through redelegations, reset creation height
app.stakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) { app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red staking.Redelegation) (stop bool) {
for i := range red.Entries { for i := range red.Entries {
red.Entries[i].CreationHeight = 0 red.Entries[i].CreationHeight = 0
} }
app.stakingKeeper.SetRedelegation(ctx, red) app.StakingKeeper.SetRedelegation(ctx, red)
return false return false
}) })
// iterate through unbonding delegations, reset creation height // iterate through unbonding delegations, reset creation height
app.stakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) { app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) (stop bool) {
for i := range ubd.Entries { for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0 ubd.Entries[i].CreationHeight = 0
} }
app.stakingKeeper.SetUnbondingDelegation(ctx, ubd) app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
return false return false
}) })
@ -137,7 +136,7 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList
for ; iter.Valid(); iter.Next() { for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(iter.Key()[1:]) addr := sdk.ValAddress(iter.Key()[1:])
validator, found := app.stakingKeeper.GetValidator(ctx, addr) validator, found := app.StakingKeeper.GetValidator(ctx, addr)
if !found { if !found {
panic("expected validator, not found") panic("expected validator, not found")
} }
@ -147,22 +146,22 @@ func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList
validator.Jailed = true validator.Jailed = true
} }
app.stakingKeeper.SetValidator(ctx, validator) app.StakingKeeper.SetValidator(ctx, validator)
counter++ counter++
} }
iter.Close() iter.Close()
_ = app.stakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) _ = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
/* Handle slashing state. */ /* Handle slashing state. */
// reset start height on signing infos // reset start height on signing infos
app.slashingKeeper.IterateValidatorSigningInfos( app.SlashingKeeper.IterateValidatorSigningInfos(
ctx, ctx,
func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) {
info.StartHeight = 0 info.StartHeight = 0
app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info)
return false return false
}, },
) )

34
app/test_helpers.go Normal file
View File

@ -0,0 +1,34 @@
package app
import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
)
// Setup initializes a new EthermintApp. A Nop logger is set in EthermintApp.
func Setup(isCheckTx bool) *EthermintApp {
db := dbm.NewMemDB()
app := NewEthermintApp(log.NewNopLogger(), db, nil, true, 0)
if !isCheckTx {
// init chain must be called to stop deliverState from being nil
genesisState := simapp.NewDefaultGenesisState()
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)
if err != nil {
panic(err)
}
// Initialize the chain
app.InitChain(
abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{},
AppStateBytes: stateBytes,
},
)
}
return app
}

View File

@ -1,138 +0,0 @@
package app
import (
"fmt"
"math/big"
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/ethermint/crypto"
emint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
abci "github.com/tendermint/tendermint/abci/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
)
type testSetup struct {
ctx sdk.Context
cdc *codec.Codec
accKeeper auth.AccountKeeper
supplyKeeper types.SupplyKeeper
anteHandler sdk.AnteHandler
}
func newTestSetup() testSetup {
db := dbm.NewMemDB()
authCapKey := sdk.NewKVStoreKey("authCapKey")
keySupply := sdk.NewKVStoreKey("keySupply")
keyParams := sdk.NewKVStoreKey("params")
tkeyParams := sdk.NewTransientStoreKey("transient_params")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeIAVL, db)
if err := ms.LoadLatestVersion(); err != nil {
cmn.Exit(err.Error())
}
cdc := MakeCodec()
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
// Set params keeper and subspaces
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace)
ctx := sdk.NewContext(
ms,
abci.Header{ChainID: "3", Time: time.Now().UTC()},
true,
log.NewNopLogger(),
)
// Add keepers
accKeeper := auth.NewAccountKeeper(cdc, authCapKey, authSubspace, auth.ProtoBaseAccount)
accKeeper.SetParams(ctx, types.DefaultParams())
supplyKeeper := mock.NewDummySupplyKeeper(accKeeper)
anteHandler := NewAnteHandler(accKeeper, supplyKeeper)
return testSetup{
ctx: ctx,
cdc: cdc,
accKeeper: accKeeper,
supplyKeeper: supplyKeeper,
anteHandler: anteHandler,
}
}
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
return sdk.NewTestMsg(addrs...)
}
func newTestCoins() sdk.Coins {
return sdk.Coins{sdk.NewInt64Coin(emint.DenomDefault, 500000000)}
}
func newTestStdFee() auth.StdFee {
return auth.NewStdFee(220000, sdk.NewCoins(sdk.NewInt64Coin(emint.DenomDefault, 150)))
}
// GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
privkey, _ := crypto.GenerateKey()
addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey)
return sdk.AccAddress(addr.Bytes()), privkey
}
func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
accNums []uint64, seqs []uint64, fee auth.StdFee,
) sdk.Tx {
sigs := make([]auth.StdSignature, len(privs))
for i, priv := range privs {
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
sig, err := priv.Sign(signBytes)
if err != nil {
panic(err)
}
sigs[i] = auth.StdSignature{
PubKey: priv.PubKey(),
Signature: sig,
}
}
return auth.NewStdTx(msgs, fee, sigs, "")
}
func newTestEthTx(ctx sdk.Context, msg *evmtypes.EthereumTxMsg, priv tmcrypto.PrivKey) sdk.Tx {
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
}
privkey, ok := priv.(crypto.PrivKeySecp256k1)
if !ok {
panic(fmt.Sprintf("invalid private key type: %T", priv))
}
msg.Sign(chainID, privkey.ToECDSA())
return msg
}

View File

@ -14,14 +14,15 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input" "github.com/cosmos/cosmos-sdk/client/input"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys" clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
emintcrypto "github.com/cosmos/ethermint/crypto" emintcrypto "github.com/cosmos/ethermint/crypto"
) )
func exportEthKeyCommand() *cobra.Command { func unsafeExportEthKeyCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "export-eth-key <name>", Use: "unsafe-export-eth-key [name]",
Short: "Export an Ethereum private key", Short: "**UNSAFE** Export an Ethereum private key",
Long: `Export an Ethereum private key unencrypted to use in dev tooling **UNSAFE**`, Long: `**UNSAFE** Export an Ethereum private key unencrypted to use in dev tooling`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: runExportCmd, RunE: runExportCmd,
} }
@ -29,12 +30,13 @@ func exportEthKeyCommand() *cobra.Command {
} }
func runExportCmd(cmd *cobra.Command, args []string) error { func runExportCmd(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
kb, err := clientkeys.NewKeyringFromHomeFlag(cmd.InOrStdin()) kb, err := clientkeys.NewKeyringFromHomeFlag(cmd.InOrStdin())
if err != nil { if err != nil {
return err return err
} }
buf := bufio.NewReader(cmd.InOrStdin())
decryptPassword := "" decryptPassword := ""
conf := true conf := true
keyringBackend := viper.GetString(flags.FlagKeyringBackend) keyringBackend := viper.GetString(flags.FlagKeyringBackend)
@ -42,11 +44,11 @@ func runExportCmd(cmd *cobra.Command, args []string) error {
case flags.KeyringBackendFile: case flags.KeyringBackendFile:
decryptPassword, err = input.GetPassword( decryptPassword, err = input.GetPassword(
"**WARNING this is an unsafe way to export your unencrypted private key**\nEnter key password:", "**WARNING this is an unsafe way to export your unencrypted private key**\nEnter key password:",
buf) inBuf)
case flags.KeyringBackendOS: case flags.KeyringBackendOS:
conf, err = input.GetConfirmation( conf, err = input.GetConfirmation(
"**WARNING** this is an unsafe way to export your unencrypted private key, are you sure?", "**WARNING** this is an unsafe way to export your unencrypted private key, are you sure?",
buf) inBuf)
} }
if err != nil || !conf { if err != nil || !conf {
return err return err

View File

@ -4,12 +4,13 @@ import (
"bufio" "bufio"
"io" "io"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys" clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys"
emintCrypto "github.com/cosmos/ethermint/crypto" emintCrypto "github.com/cosmos/ethermint/crypto"
tmcrypto "github.com/tendermint/tendermint/crypto"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -46,7 +47,7 @@ func keyCommands() *cobra.Command {
clientkeys.ParseKeyStringCommand(), clientkeys.ParseKeyStringCommand(),
clientkeys.MigrateCommand(), clientkeys.MigrateCommand(),
flags.LineBreak, flags.LineBreak,
exportEthKeyCommand(), unsafeExportEthKeyCommand(),
) )
return cmd return cmd
} }

View File

@ -1,34 +1,38 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path" "path"
emintapp "github.com/cosmos/ethermint/app" "github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/ethermint/app"
emintcrypto "github.com/cosmos/ethermint/crypto" emintcrypto "github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/rpc" "github.com/cosmos/ethermint/rpc"
"github.com/tendermint/go-amino" "github.com/tendermint/go-amino"
tmamino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys" clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
sdkrpc "github.com/cosmos/cosmos-sdk/client/rpc" clientrpc "github.com/cosmos/cosmos-sdk/client/rpc"
cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys" cryptokeys "github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
"github.com/cosmos/cosmos-sdk/x/bank"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tmamino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/libs/cli"
) )
func main() { func main() {
// Configure cobra to sort commands
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
cdc := emintapp.MakeCodec() cdc := app.MakeCodec()
tmamino.RegisterKeyType(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName) tmamino.RegisterKeyType(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName)
tmamino.RegisterKeyType(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName) tmamino.RegisterKeyType(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName)
@ -45,7 +49,7 @@ func main() {
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "emintcli", Use: "emintcli",
Short: "Ethermint Client", Short: "Command line interface for interacting with emintd",
} }
// Add --chain-id to persistent flags and mark it required // Add --chain-id to persistent flags and mark it required
@ -56,20 +60,24 @@ func main() {
// Construct Root Command // Construct Root Command
rootCmd.AddCommand( rootCmd.AddCommand(
sdkrpc.StatusCommand(), clientrpc.StatusCommand(),
client.ConfigCmd(emintapp.DefaultCLIHome), client.ConfigCmd(app.DefaultCLIHome),
queryCmd(cdc), queryCmd(cdc),
txCmd(cdc), txCmd(cdc),
rpc.EmintServeCmd(cdc), rpc.EmintServeCmd(cdc),
client.LineBreak, client.LineBreak,
keyCommands(), keyCommands(),
client.LineBreak, client.LineBreak,
version.Cmd,
client.NewCompletionCmd(rootCmd, true),
) )
executor := cli.PrepareMainCmd(rootCmd, "EM", emintapp.DefaultCLIHome) // Add flags and prefix all env exposed with EM
executor := cli.PrepareMainCmd(rootCmd, "EM", app.DefaultCLIHome)
err := executor.Execute() err := executor.Execute()
if err != nil { if err != nil {
panic(err) panic(fmt.Errorf("failed executing CLI command: %w", err))
} }
} }
@ -89,7 +97,7 @@ func queryCmd(cdc *amino.Codec) *cobra.Command {
) )
// add modules' query commands // add modules' query commands
emintapp.ModuleBasics.AddQueryCommands(queryCmd, cdc) app.ModuleBasics.AddQueryCommands(queryCmd, cdc)
return queryCmd return queryCmd
} }
@ -104,14 +112,27 @@ func txCmd(cdc *amino.Codec) *cobra.Command {
bankcmd.SendTxCmd(cdc), bankcmd.SendTxCmd(cdc),
client.LineBreak, client.LineBreak,
authcmd.GetSignCommand(cdc), authcmd.GetSignCommand(cdc),
authcmd.GetMultiSignCommand(cdc),
client.LineBreak, client.LineBreak,
authcmd.GetBroadcastCommand(cdc), authcmd.GetBroadcastCommand(cdc),
authcmd.GetEncodeCommand(cdc), authcmd.GetEncodeCommand(cdc),
authcmd.GetDecodeCommand(cdc),
client.LineBreak, client.LineBreak,
) )
// add modules' tx commands // add modules' tx commands
emintapp.ModuleBasics.AddTxCommands(txCmd, cdc) app.ModuleBasics.AddTxCommands(txCmd, cdc)
// remove auth and bank commands as they're mounted under the root tx command
var cmdsToRemove []*cobra.Command
for _, cmd := range txCmd.Commands() {
if cmd.Use == auth.ModuleName || cmd.Use == bank.ModuleName {
cmdsToRemove = append(cmdsToRemove, cmd)
}
}
txCmd.RemoveCommand(cmdsToRemove...)
return txCmd return txCmd
} }

View File

@ -1,4 +1,4 @@
package genaccounts package main
import ( import (
"bufio" "bufio"
@ -19,6 +19,8 @@ import (
authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting" authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/genutil"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
ethermint "github.com/cosmos/ethermint/types" ethermint "github.com/cosmos/ethermint/types"
) )
@ -96,7 +98,10 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa
return errors.New("invalid vesting parameters; must supply start and end time or end time") return errors.New("invalid vesting parameters; must supply start and end time or end time")
} }
} else { } else {
genAccount = ethermint.Account{BaseAccount: baseAccount} genAccount = ethermint.Account{
BaseAccount: baseAccount,
CodeHash: ethcrypto.Keccak256(nil),
}
} }
if err := genAccount.Validate(); err != nil { if err := genAccount.Validate(); err != nil {

View File

@ -23,17 +23,20 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/cosmos/ethermint/app" "github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/client/genaccounts"
emintcrypto "github.com/cosmos/ethermint/crypto" emintcrypto "github.com/cosmos/ethermint/crypto"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmamino "github.com/tendermint/tendermint/crypto/encoding/amino" tmamino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/cli"
tmlog "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
) )
const flagInvCheckPeriod = "inv-check-period"
var invCheckPeriod uint
func main() { func main() {
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
@ -65,12 +68,14 @@ func main() {
withChainIDValidation(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome)), withChainIDValidation(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome)),
genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, app.DefaultNodeHome), genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, app.DefaultNodeHome),
genutilcli.GenTxCmd( genutilcli.GenTxCmd(
ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, auth.GenesisAccountIterator{}, app.DefaultNodeHome, app.DefaultCLIHome, ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, auth.GenesisAccountIterator{},
app.DefaultNodeHome, app.DefaultCLIHome,
), ),
genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics), genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics),
// AddGenesisAccountCmd allows users to add accounts to the genesis file // AddGenesisAccountCmd allows users to add accounts to the genesis file
genaccounts.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome),
client.NewCompletionCmd(rootCmd, true),
) )
// Tendermint node base commands // Tendermint node base commands
@ -78,23 +83,25 @@ func main() {
// prepare and add flags // prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "EM", app.DefaultNodeHome) executor := cli.PrepareBaseCmd(rootCmd, "EM", app.DefaultNodeHome)
rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod,
0, "Assert registered invariants every N blocks")
err := executor.Execute() err := executor.Execute()
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
func newApp(logger tmlog.Logger, db dbm.DB, traceStore io.Writer) abci.Application { func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewEthermintApp(logger, db, true, 0, return app.NewEthermintApp(logger, db, traceStore, true, 0,
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning")))) baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))))
} }
func exportAppStateAndTMValidators( func exportAppStateAndTMValidators(
logger tmlog.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string, logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
) (json.RawMessage, []tmtypes.GenesisValidator, error) { ) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 { if height != -1 {
emintApp := app.NewEthermintApp(logger, db, true, 0) emintApp := app.NewEthermintApp(logger, db, traceStore, true, 0)
err := emintApp.LoadHeight(height) err := emintApp.LoadHeight(height)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -102,7 +109,7 @@ func exportAppStateAndTMValidators(
return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }
emintApp := app.NewEthermintApp(logger, db, true, 0) emintApp := app.NewEthermintApp(logger, db, traceStore, true, 0)
return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) return emintApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }
@ -119,8 +126,7 @@ func withChainIDValidation(baseCmd *cobra.Command) *cobra.Command {
// Verify that the chain-id entered is a base 10 integer // Verify that the chain-id entered is a base 10 integer
_, ok := new(big.Int).SetString(chainIDFlag, 10) _, ok := new(big.Int).SetString(chainIDFlag, 10)
if !ok { if !ok {
return fmt.Errorf( return fmt.Errorf("invalid chainID: %s, must be base-10 integer format", chainIDFlag)
fmt.Sprintf("invalid chainID: %s, must be base-10 integer format", chainIDFlag))
} }
return baseRunE(cmd, args) return baseRunE(cmd, args)

2
go.mod
View File

@ -53,3 +53,5 @@ require (
gopkg.in/urfave/cli.v1 v1.20.0 // indirect gopkg.in/urfave/cli.v1 v1.20.0 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
) )
replace github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.34.4-0.20191213112149-d7b0f4b9b4fb

2
go.sum
View File

@ -314,8 +314,6 @@ github.com/spf13/cobra v0.0.1 h1:zZh3X5aZbdnoj+4XkaBxKfhO4ot82icYdhhREIAXIj8=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=

View File

@ -44,14 +44,11 @@ var (
flagBlockchain string flagBlockchain string
flagCPUProfile string flagCPUProfile string
// miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0") genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
// paramsKey = sdk.NewKVStoreKey("params")
// tParamsKey = sdk.NewTransientStoreKey("transient_params")
accKey = sdk.NewKVStoreKey("acc") accKey = sdk.NewKVStoreKey("acc")
storageKey = sdk.NewKVStoreKey(evmtypes.EvmStoreKey) storageKey = sdk.NewKVStoreKey(evmtypes.StoreKey)
codeKey = sdk.NewKVStoreKey(evmtypes.EvmCodeKey) codeKey = sdk.NewKVStoreKey(evmtypes.CodeKey)
logger = tmlog.NewNopLogger() logger = tmlog.NewNopLogger()
@ -104,7 +101,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
ms := cms.CacheMultiStore() ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger) ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
stateDB := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey) stateDB := evmtypes.NewCommitStateDB(ctx, codeKey, storageKey, ak)
// sort the addresses and insertion of key/value pairs matters // sort the addresses and insertion of key/value pairs matters
genAddrs := make([]string, len(genBlock.Alloc)) genAddrs := make([]string, len(genBlock.Alloc))
@ -270,7 +267,7 @@ func TestImportBlocks(t *testing.T) {
} }
func createStateDB(ctx sdk.Context, ak auth.AccountKeeper) *evmtypes.CommitStateDB { func createStateDB(ctx sdk.Context, ak auth.AccountKeeper) *evmtypes.CommitStateDB {
stateDB := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey) stateDB := evmtypes.NewCommitStateDB(ctx, codeKey, storageKey, ak)
return stateDB return stateDB
} }
@ -346,12 +343,18 @@ func applyTransaction(config *ethparams.ChainConfig, bc ethcore.ChainContext, au
return nil, 0, err return nil, 0, err
} }
// Update the state with pending changes // Update the state with pending changes
var root []byte var intRoot ethcmn.Hash
if config.IsByzantium(header.Number) { if config.IsByzantium(header.Number) {
statedb.Finalise(true) err = statedb.Finalise(true)
} else { } else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() intRoot, err = statedb.IntermediateRoot(config.IsEIP158(header.Number))
} }
if err != nil {
return nil, gas, err
}
root := intRoot.Bytes()
*usedGas += gas *usedGas += gas
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
@ -364,7 +367,7 @@ func applyTransaction(config *ethparams.ChainConfig, bc ethcore.ChainContext, au
receipt.ContractAddress = ethcrypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) receipt.ContractAddress = ethcrypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
} }
// Set the receipt logs and create a bloom for filtering // Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Logs, err = statedb.GetLogs(tx.Hash())
receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt}) receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt})
receipt.BlockHash = statedb.BlockHash() receipt.BlockHash = statedb.BlockHash()
receipt.BlockNumber = header.Number receipt.BlockNumber = header.Number

View File

@ -2,13 +2,15 @@ package rpc
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"log" "log"
"math/big" "math/big"
"strconv" "strconv"
"sync" "sync"
"github.com/cosmos/cosmos-sdk/client/keys" "github.com/spf13/viper"
emintcrypto "github.com/cosmos/ethermint/crypto" emintcrypto "github.com/cosmos/ethermint/crypto"
params "github.com/cosmos/ethermint/rpc/args" params "github.com/cosmos/ethermint/rpc/args"
emint "github.com/cosmos/ethermint/types" emint "github.com/cosmos/ethermint/types"
@ -29,10 +31,10 @@ import (
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils" authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/spf13/viper"
) )
// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. // PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
@ -210,7 +212,7 @@ func (e *PublicEthAPI) getBlockTransactionCountByNumber(number int64) *hexutil.U
return nil return nil
} }
n := hexutil.Uint(block.Block.NumTxs) n := hexutil.Uint(len(block.Block.Txs))
return &n return &n
} }
@ -277,8 +279,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err
// parse the chainID from a string to a base-10 integer // parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(chainID, 10) intChainID, ok := new(big.Int).SetString(chainID, 10)
if !ok { if !ok {
return common.Hash{}, fmt.Errorf( return common.Hash{}, fmt.Errorf("invalid chainID: %s, must be integer format", chainID)
fmt.Sprintf("invalid chainID: %s, must be integer format", chainID))
} }
// Sign transaction // Sign transaction
@ -306,7 +307,7 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err
// SendRawTransaction send a raw Ethereum transaction. // SendRawTransaction send a raw Ethereum transaction.
func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
tx := new(types.EthereumTxMsg) tx := new(types.MsgEthereumTx)
// RLP decode raw transaction bytes // RLP decode raw transaction bytes
if err := rlp.DecodeBytes(data, tx); err != nil { if err := rlp.DecodeBytes(data, tx); err != nil {
@ -351,9 +352,12 @@ func (e *PublicEthAPI) Call(args CallArgs, blockNr rpc.BlockNumber, overrides *m
return []byte{}, err return []byte{}, err
} }
_, _, ret, err := types.DecodeReturnData(result.Data) data, err := types.DecodeResultData(result.Data)
if err != nil {
return []byte{}, err
}
return (hexutil.Bytes)(ret), err return (hexutil.Bytes)(data.Ret), nil
} }
// account indicates the overriding fields of account during the execution of // account indicates the overriding fields of account during the execution of
@ -370,8 +374,9 @@ type account struct {
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
} }
// DoCall performs a simulated call operation through the evm // DoCall performs a simulated call operation through the evm. It returns the
func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int) (sdk.Result, error) { // estimated gas used on the operation or an error if fails.
func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int) (*sdk.Result, error) {
// Set height for historical queries // Set height for historical queries
ctx := e.cliCtx ctx := e.cliCtx
if blockNr.Int64() != 0 { if blockNr.Int64() != 0 {
@ -425,7 +430,7 @@ func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasC
} }
// Create new call message // Create new call message
msg := types.NewEmintMsg(0, &toAddr, sdk.NewIntFromBigInt(value), gas, msg := types.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas,
sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes())) sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes()))
// Generate tx to be used to simulate (signature isn't needed) // Generate tx to be used to simulate (signature isn't needed)
@ -436,24 +441,26 @@ func (e *PublicEthAPI) doCall(args CallArgs, blockNr rpc.BlockNumber, globalGasC
txEncoder := authutils.GetTxEncoder(ctx.Codec) txEncoder := authutils.GetTxEncoder(ctx.Codec)
txBytes, err := txEncoder(tx) txBytes, err := txEncoder(tx)
if err != nil { if err != nil {
return sdk.Result{}, err return nil, err
} }
// Transaction simulation through query // Transaction simulation through query
res, _, err := ctx.QueryWithData("app/simulate", txBytes) res, _, err := ctx.QueryWithData("app/simulate", txBytes)
if err != nil { if err != nil {
return sdk.Result{}, err return nil, err
} }
var simResult sdk.Result var simResult sdk.Result
if err = e.cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res, &simResult); err != nil { if err = e.cliCtx.Codec.UnmarshalBinaryLengthPrefixed(res, &simResult); err != nil {
return sdk.Result{}, err return nil, err
} }
return simResult, nil return &simResult, nil
} }
// EstimateGas estimates gas usage for the given smart contract call. // EstimateGas returns an estimate of gas usage for the given smart contract call.
// It adds 1,000 gas to the returned value instead of using the gas adjustment
// param from the SDK.
func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) { func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) {
result, err := e.doCall(args, 0, big.NewInt(emint.DefaultRPCGasLimit)) result, err := e.doCall(args, 0, big.NewInt(emint.DefaultRPCGasLimit))
if err != nil { if err != nil {
@ -482,31 +489,37 @@ func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[
return e.getEthBlockByNumber(value, fullTx) return e.getEthBlockByNumber(value, fullTx)
} }
func (e *PublicEthAPI) getEthBlockByNumber(value int64, fullTx bool) (map[string]interface{}, error) { func (e *PublicEthAPI) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) {
// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014) // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
var blkNumPtr *int64 var blkNumPtr *int64
if value != 0 { if height != 0 {
blkNumPtr = &value blkNumPtr = &height
} }
block, err := e.cliCtx.Client.Block(blkNumPtr) block, err := e.cliCtx.Client.Block(blkNumPtr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
header := block.BlockMeta.Header header := block.Block.Header
gasLimit, err := e.getGasLimit() gasLimit, err := e.getGasLimit()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var gasUsed *big.Int var (
var transactions []interface{} gasUsed *big.Int
transactions []interface{}
)
if fullTx { if fullTx {
// Populate full transaction data // Populate full transaction data
transactions, gasUsed = convertTransactionsToRPC(e.cliCtx, block.Block.Txs, transactions, gasUsed, err = convertTransactionsToRPC(
common.BytesToHash(header.Hash()), uint64(header.Height)) e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height),
)
if err != nil {
return nil, err
}
} else { } else {
// TODO: Gas used not saved and cannot be calculated by hashes // TODO: Gas used not saved and cannot be calculated by hashes
// Return slice of transaction hashes // Return slice of transaction hashes
@ -553,19 +566,24 @@ func formatBlock(
} }
} }
func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int) { func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int, error) {
transactions := make([]interface{}, len(txs)) transactions := make([]interface{}, len(txs))
gasUsed := big.NewInt(0) gasUsed := big.NewInt(0)
for i, tx := range txs { for i, tx := range txs {
ethTx, err := bytesToEthTx(cliCtx, tx) ethTx, err := bytesToEthTx(cliCtx, tx)
if err != nil { if err != nil {
continue return nil, nil, err
} }
// TODO: Remove gas usage calculation if saving gasUsed per block // TODO: Remove gas usage calculation if saving gasUsed per block
gasUsed.Add(gasUsed, ethTx.Fee()) gasUsed.Add(gasUsed, ethTx.Fee())
transactions[i] = newRPCTransaction(ethTx, blockHash, &height, uint64(i)) transactions[i], err = newRPCTransaction(*ethTx, blockHash, &height, uint64(i))
if err != nil {
return nil, nil, err
} }
return transactions, gasUsed }
return transactions, gasUsed, nil
} }
// Transaction represents a transaction returned to RPC clients. // Transaction represents a transaction returned to RPC clients.
@ -586,23 +604,30 @@ type Transaction struct {
S *hexutil.Big `json:"s"` S *hexutil.Big `json:"s"`
} }
func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*types.EthereumTxMsg, error) { func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*types.MsgEthereumTx, error) {
var stdTx sdk.Tx var stdTx sdk.Tx
err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(bz, &stdTx) err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(bz, &stdTx)
ethTx, ok := stdTx.(*types.EthereumTxMsg) if err != nil {
if !ok || err != nil { return nil, err
}
ethTx, ok := stdTx.(types.MsgEthereumTx)
if !ok {
return nil, fmt.Errorf("invalid transaction type, must be an amino encoded Ethereum transaction") return nil, fmt.Errorf("invalid transaction type, must be an amino encoded Ethereum transaction")
} }
return ethTx, nil return &ethTx, nil
} }
// newRPCTransaction returns a transaction that will serialize to the RPC // newRPCTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available). // representation, with the given location metadata set (if available).
func newRPCTransaction(tx *types.EthereumTxMsg, blockHash common.Hash, blockNumber *uint64, index uint64) *Transaction { func newRPCTransaction(tx types.MsgEthereumTx, blockHash common.Hash, blockNumber *uint64, index uint64) (*Transaction, error) {
// Verify signature and retrieve sender address // Verify signature and retrieve sender address
from, _ := tx.VerifySig(tx.ChainID()) from, err := tx.VerifySig(tx.ChainID())
if err != nil {
return nil, err
}
result := &Transaction{ result := Transaction{
From: from, From: from,
Gas: hexutil.Uint64(tx.Data.GasLimit), Gas: hexutil.Uint64(tx.Data.GasLimit),
GasPrice: (*hexutil.Big)(tx.Data.Price), GasPrice: (*hexutil.Big)(tx.Data.Price),
@ -615,12 +640,14 @@ func newRPCTransaction(tx *types.EthereumTxMsg, blockHash common.Hash, blockNumb
R: (*hexutil.Big)(tx.Data.R), R: (*hexutil.Big)(tx.Data.R),
S: (*hexutil.Big)(tx.Data.S), S: (*hexutil.Big)(tx.Data.S),
} }
if blockHash != (common.Hash{}) { if blockHash != (common.Hash{}) {
result.BlockHash = &blockHash result.BlockHash = &blockHash
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(*blockNumber)) result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(*blockNumber))
result.TransactionIndex = (*hexutil.Uint64)(&index) result.TransactionIndex = (*hexutil.Uint64)(&index)
} }
return result
return &result, nil
} }
// GetTransactionByHash returns the transaction identified by hash. // GetTransactionByHash returns the transaction identified by hash.
@ -636,7 +663,7 @@ func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*Transaction, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
blockHash := common.BytesToHash(block.BlockMeta.Header.Hash()) blockHash := common.BytesToHash(block.Block.Header.Hash())
ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx) ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx)
if err != nil { if err != nil {
@ -644,7 +671,7 @@ func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*Transaction, err
} }
height := uint64(tx.Height) height := uint64(tx.Height)
return newRPCTransaction(ethTx, blockHash, &height, uint64(tx.Index)), nil return newRPCTransaction(*ethTx, blockHash, &height, uint64(tx.Index))
} }
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. // GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
@ -670,7 +697,7 @@ func (e *PublicEthAPI) getTransactionByBlockNumberAndIndex(number int64, idx hex
if err != nil { if err != nil {
return nil, err return nil, err
} }
header := block.BlockMeta.Header header := block.Block.Header
txs := block.Block.Txs txs := block.Block.Txs
if uint64(idx) >= uint64(len(txs)) { if uint64(idx) >= uint64(len(txs)) {
@ -682,8 +709,7 @@ func (e *PublicEthAPI) getTransactionByBlockNumberAndIndex(number int64, idx hex
} }
height := uint64(header.Height) height := uint64(header.Height)
transaction := newRPCTransaction(ethTx, common.BytesToHash(header.Hash()), &height, uint64(idx)) return newRPCTransaction(*ethTx, common.BytesToHash(header.Hash()), &height, uint64(idx))
return transaction, nil
} }
// GetTransactionReceipt returns the transaction receipt identified by hash. // GetTransactionReceipt returns the transaction receipt identified by hash.
@ -699,7 +725,7 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
if err != nil { if err != nil {
return nil, err return nil, err
} }
blockHash := common.BytesToHash(block.BlockMeta.Header.Hash()) blockHash := common.BytesToHash(block.Block.Header.Hash())
// Convert tx bytes to eth transaction // Convert tx bytes to eth transaction
ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx) ethTx, err := bytesToEthTx(e.cliCtx, tx.Tx)
@ -726,7 +752,10 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
e.cliCtx.Codec.MustUnmarshalJSON(res, &logs) e.cliCtx.Codec.MustUnmarshalJSON(res, &logs)
txData := tx.TxResult.GetData() txData := tx.TxResult.GetData()
contractAddress, bloomFilter, _, _ := types.DecodeReturnData(txData) data, err := types.DecodeResultData(txData)
if err != nil {
return nil, err
}
fields := map[string]interface{}{ fields := map[string]interface{}{
"blockHash": blockHash, "blockHash": blockHash,
@ -739,12 +768,12 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
"cumulativeGasUsed": nil, // ignore until needed "cumulativeGasUsed": nil, // ignore until needed
"contractAddress": nil, "contractAddress": nil,
"logs": logs.Logs, "logs": logs.Logs,
"logsBloom": bloomFilter, "logsBloom": data.Bloom,
"status": status, "status": status,
} }
if contractAddress != (common.Address{}) { if data.Address != (common.Address{}) {
fields["contractAddress"] = contractAddress fields["contractAddress"] = data.Address
} }
return fields, nil return fields, nil
@ -766,7 +795,11 @@ func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) {
} }
// * Should check signer and reference against accounts the node manages in future // * Should check signer and reference against accounts the node manages in future
rpcTx := newRPCTransaction(ethTx, common.Hash{}, nil, 0) rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0)
if err != nil {
return nil, err
}
transactions = append(transactions, rpcTx) transactions = append(transactions, rpcTx)
} }
@ -871,13 +904,14 @@ func (e *PublicEthAPI) getGasLimit() (int64, error) {
} }
// generateFromArgs populates tx message with args (used in RPC API) // generateFromArgs populates tx message with args (used in RPC API)
func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.EthereumTxMsg, err error) { func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*types.MsgEthereumTx, error) {
var nonce uint64 var (
nonce uint64
var gasLimit uint64 gasLimit uint64
err error
)
amount := (*big.Int)(args.Value) amount := (*big.Int)(args.Value)
gasPrice := (*big.Int)(args.GasPrice) gasPrice := (*big.Int)(args.GasPrice)
if args.GasPrice == nil { if args.GasPrice == nil {
@ -899,7 +933,7 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe
} }
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return nil, fmt.Errorf(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
} }
// Sets input to either Input or Data, if both are set and not equal error above returns // Sets input to either Input or Data, if both are set and not equal error above returns
@ -916,6 +950,7 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe
return nil, fmt.Errorf("contract creation without any data provided") return nil, fmt.Errorf("contract creation without any data provided")
} }
} }
if args.Gas == nil { if args.Gas == nil {
callArgs := CallArgs{ callArgs := CallArgs{
From: &args.From, From: &args.From,
@ -925,11 +960,15 @@ func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (msg *types.Ethe
Value: args.Value, Value: args.Value,
Data: args.Data, Data: args.Data,
} }
g, _ := e.EstimateGas(callArgs) g, err := e.EstimateGas(callArgs)
if err != nil {
return nil, err
}
gasLimit = uint64(g) gasLimit = uint64(g)
} else { } else {
gasLimit = (uint64)(*args.Gas) gasLimit = (uint64)(*args.Gas)
} }
msg := types.NewMsgEthereumTx(nonce, args.To, amount, gasLimit, gasPrice, input)
return types.NewEthereumTxMsg(nonce, args.To, amount, gasLimit, gasPrice, input), nil return &msg, nil
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/version"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
) )
const ( const (
@ -92,51 +93,34 @@ func TestEth_protocolVersion(t *testing.T) {
expectedRes := hexutil.Uint(version.ProtocolVersion) expectedRes := hexutil.Uint(version.ProtocolVersion)
rpcRes, err := call("eth_protocolVersion", []string{}) rpcRes, err := call("eth_protocolVersion", []string{})
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
var res hexutil.Uint var res hexutil.Uint
err = res.UnmarshalJSON(rpcRes.Result) err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
t.Logf("Got protocol version: %s\n", res.String()) t.Logf("Got protocol version: %s\n", res.String())
require.Equal(t, expectedRes, res, "expected: %s got: %s\n", expectedRes.String(), rpcRes.Result)
if res != expectedRes {
t.Fatalf("expected: %s got: %s\n", expectedRes.String(), rpcRes.Result)
}
} }
func TestEth_blockNumber(t *testing.T) { func TestEth_blockNumber(t *testing.T) {
rpcRes, err := call("eth_blockNumber", []string{}) rpcRes, err := call("eth_blockNumber", []string{})
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
var res hexutil.Uint64 var res hexutil.Uint64
err = res.UnmarshalJSON(rpcRes.Result) err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
t.Logf("Got block number: %s\n", res.String()) t.Logf("Got block number: %s\n", res.String())
} }
func TestEth_GetBalance(t *testing.T) { func TestEth_GetBalance(t *testing.T) {
rpcRes, err := call("eth_getBalance", []string{addrA, "0x0"}) rpcRes, err := call("eth_getBalance", []string{addrA, "0x0"})
if err != nil { require.NoError(t, err)
t.Fatal(err)
return
}
var res hexutil.Big var res hexutil.Big
err = res.UnmarshalJSON(rpcRes.Result) err = res.UnmarshalJSON(rpcRes.Result)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
t.Logf("Got balance %s for %s\n", res.String(), addrA) t.Logf("Got balance %s for %s\n", res.String(), addrA)
@ -149,40 +133,27 @@ func TestEth_GetBalance(t *testing.T) {
func TestEth_GetStorageAt(t *testing.T) { func TestEth_GetStorageAt(t *testing.T) {
expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
rpcRes, err := call("eth_getStorageAt", []string{addrA, string(addrAStoreKey), "0x0"}) rpcRes, err := call("eth_getStorageAt", []string{addrA, string(addrAStoreKey), "0x0"})
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
var storage hexutil.Bytes var storage hexutil.Bytes
err = storage.UnmarshalJSON(rpcRes.Result) err = storage.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
t.Logf("Got value [%X] for %s with key %X\n", storage, addrA, addrAStoreKey) t.Logf("Got value [%X] for %s with key %X\n", storage, addrA, addrAStoreKey)
if !bytes.Equal(storage, expectedRes) { require.True(t, bytes.Equal(storage, expectedRes), "expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage))
t.Errorf("expected: %d (%d bytes) got: %d (%d bytes)", expectedRes, len(expectedRes), storage, len(storage))
}
} }
func TestEth_GetCode(t *testing.T) { func TestEth_GetCode(t *testing.T) {
expectedRes := hexutil.Bytes{} expectedRes := hexutil.Bytes{}
rpcRes, err := call("eth_getCode", []string{addrA, "0x0"}) rpcRes, err := call("eth_getCode", []string{addrA, "0x0"})
if err != nil { require.NoError(t, err)
t.Error(err)
}
var code hexutil.Bytes var code hexutil.Bytes
err = code.UnmarshalJSON(rpcRes.Result) err = code.UnmarshalJSON(rpcRes.Result)
if err != nil { require.NoError(t, err)
t.Fatal(err)
}
t.Logf("Got code [%X] for %s\n", code, addrA) t.Logf("Got code [%X] for %s\n", code, addrA)
if !bytes.Equal(expectedRes, code) { require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code)
t.Errorf("expected: %X got: %X", expectedRes, code)
}
} }

45
x/evm/abci.go Normal file
View File

@ -0,0 +1,45 @@
package evm
import (
"math/big"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// 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) {
// 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.SetBlockHashMapping(ctx, req.Header.LastBlockId.GetHash(), req.Header.GetHeight()-1)
k.Bloom = big.NewInt(0)
k.TxCount = 0
}
// EndBlock updates the accounts and commits states objects to the KV Store
func EndBlock(k Keeper, ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
// Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
// Update account balances before committing other parts of state
k.CommitStateDB.UpdateAccounts()
// Commit state objects to KV store
_, err := k.CommitStateDB.WithContext(ctx).Commit(true)
if err != nil {
panic(err)
}
// Clear accounts cache after account data has been committed
k.CommitStateDB.ClearStateObjects()
return []abci.ValidatorUpdate{}
}

View File

@ -5,7 +5,13 @@ import (
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
) )
// nolint
const ( const (
ModuleName = types.ModuleName
StoreKey = types.StoreKey
CodeKey = types.StoreKey
BlockKey = types.BlockKey
RouterKey = types.RouterKey
QueryProtocolVersion = types.QueryProtocolVersion QueryProtocolVersion = types.QueryProtocolVersion
QueryBalance = types.QueryBalance QueryBalance = types.QueryBalance
QueryBlockNumber = types.QueryBlockNumber QueryBlockNumber = types.QueryBlockNumber
@ -19,10 +25,13 @@ const (
QueryAccount = types.QueryAccount QueryAccount = types.QueryAccount
) )
// nolint
var ( var (
NewKeeper = keeper.NewKeeper NewKeeper = keeper.NewKeeper
TxDecoder = types.TxDecoder
) )
//nolint
type ( type (
Keeper = keeper.Keeper Keeper = keeper.Keeper
QueryResAccount = types.QueryResAccount QueryResAccount = types.QueryResAccount

View File

@ -85,7 +85,7 @@ func GetCmdGenTx(cdc *codec.Codec) *cobra.Command {
} }
// TODO: Potentially allow overriding of gas price and gas limit // TODO: Potentially allow overriding of gas price and gas limit
msg := types.NewEmintMsg(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(), msg := types.NewMsgEthermint(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(),
sdk.NewInt(emint.DefaultGasPrice), data, from) sdk.NewInt(emint.DefaultGasPrice), data, from)
err = msg.ValidateBasic() err = msg.ValidateBasic()
@ -137,7 +137,7 @@ func GetCmdGenCreateTx(cdc *codec.Codec) *cobra.Command {
} }
// TODO: Potentially allow overriding of gas price and gas limit // TODO: Potentially allow overriding of gas price and gas limit
msg := types.NewEmintMsg(seq, nil, sdk.NewInt(amount), txBldr.Gas(), msg := types.NewMsgEthermint(seq, nil, sdk.NewInt(amount), txBldr.Gas(),
sdk.NewInt(emint.DefaultGasPrice), data, from) sdk.NewInt(emint.DefaultGasPrice), data, from)
err = msg.ValidateBasic() err = msg.ValidateBasic()

View File

@ -18,23 +18,20 @@ import (
func NewHandler(k Keeper) sdk.Handler { func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) { switch msg := msg.(type) {
case types.EthereumTxMsg: case types.MsgEthereumTx:
return handleETHTxMsg(ctx, k, msg) return HandleMsgEthereumTx(ctx, k, msg)
case *types.EmintMsg: case types.MsgEthermint:
return handleEmintMsg(ctx, k, *msg) return HandleMsgEthermint(ctx, k, msg)
default: default:
errMsg := fmt.Sprintf("Unrecognized ethermint Msg type: %v", msg.Type()) errMsg := fmt.Sprintf("unrecognized ethermint msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result() return sdk.ErrUnknownRequest(errMsg).Result()
} }
} }
} }
// Handle an Ethereum specific tx // HandleMsgEthereumTx handles an Ethereum specific tx
func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Result { func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk.Result {
if err := msg.ValidateBasic(); err != nil { ctx = ctx.WithEventManager(sdk.NewEventManager())
return err.Result()
}
// parse the chainID from a string to a base-10 integer // parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok { if !ok {
@ -44,14 +41,14 @@ func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Resu
// Verify signature and retrieve sender address // Verify signature and retrieve sender address
sender, err := msg.VerifySig(intChainID) sender, err := msg.VerifySig(intChainID)
if err != nil { if err != nil {
return emint.ErrInvalidSender(err.Error()).Result() return sdk.ResultFromError(err)
} }
// Encode transaction by default Tx encoder // Encode transaction by default Tx encoder
txEncoder := authutils.GetTxEncoder(types.ModuleCdc) txEncoder := authutils.GetTxEncoder(types.ModuleCdc)
txBytes, err := txEncoder(msg) txBytes, err := txEncoder(msg)
if err != nil { if err != nil {
return sdk.ErrInternal(err.Error()).Result() return sdk.ResultFromError(err)
} }
txHash := tmtypes.Tx(txBytes).Hash() txHash := tmtypes.Tx(txBytes).Hash()
ethHash := common.BytesToHash(txHash) ethHash := common.BytesToHash(txHash)
@ -70,21 +67,53 @@ func handleETHTxMsg(ctx sdk.Context, k Keeper, msg types.EthereumTxMsg) sdk.Resu
Simulate: ctx.IsCheckTx(), Simulate: ctx.IsCheckTx(),
} }
// Prepare db for logs // Prepare db for logs
k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount.Get()) k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount)
k.TxCount.Increment() k.TxCount++
bloom, res := st.TransitionCSDB(ctx) // TODO: move to keeper
if res.IsOK() { returnData, err := st.TransitionCSDB(ctx)
k.Bloom.Or(k.Bloom, bloom) if err != nil {
} return sdk.ResultFromError(err)
return res
} }
func handleEmintMsg(ctx sdk.Context, k Keeper, msg types.EmintMsg) sdk.Result { // update block bloom filter
if err := msg.ValidateBasic(); err != nil { k.Bloom.Or(k.Bloom, returnData.Bloom)
return err.Result()
// update transaction logs in KVStore
err = k.SetTransactionLogs(ctx, returnData.Logs, txHash)
if err != nil {
return sdk.ResultFromError(err)
} }
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Data.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
),
})
if msg.Data.Recipient != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Data.Recipient.String()),
),
)
}
// set the events to the result
returnData.Result.Events = ctx.EventManager().Events()
return *returnData.Result
}
// HandleMsgEthermint handles a MsgEthermint
func HandleMsgEthermint(ctx sdk.Context, k Keeper, msg types.MsgEthermint) sdk.Result {
ctx = ctx.WithEventManager(sdk.NewEventManager())
// parse the chainID from a string to a base-10 integer // parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10) intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok { if !ok {
@ -109,9 +138,36 @@ func handleEmintMsg(ctx sdk.Context, k Keeper, msg types.EmintMsg) sdk.Result {
} }
// Prepare db for logs // Prepare db for logs
k.CommitStateDB.Prepare(common.Hash{}, common.Hash{}, k.TxCount.Get()) // Cannot provide tx hash k.CommitStateDB.Prepare(common.Hash{}, common.Hash{}, k.TxCount) // Cannot provide tx hash
k.TxCount.Increment() k.TxCount++
_, res := st.TransitionCSDB(ctx) returnData, err := st.TransitionCSDB(ctx)
return res if err != nil {
return sdk.ResultFromError(err)
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthermint,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()),
),
})
if msg.Recipient != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthermint,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Recipient.String()),
),
)
}
// set the events to the result
returnData.Result.Events = ctx.EventManager().Events()
return *returnData.Result
} }

89
x/evm/handler_test.go Normal file
View File

@ -0,0 +1,89 @@
package evm_test
import (
"math/big"
"testing"
"time"
"github.com/stretchr/testify/suite"
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/x/evm"
"github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
)
type EvmTestSuite struct {
suite.Suite
ctx sdk.Context
handler sdk.Handler
app *app.EthermintApp
}
func (suite *EvmTestSuite) SetupTest() {
checkTx := false
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.handler = evm.NewHandler(suite.app.EvmKeeper)
}
func TestEvmTestSuite(t *testing.T) {
suite.Run(t, new(EvmTestSuite))
}
func (suite *EvmTestSuite) TestHandler_Logs() {
// Test contract:
// pragma solidity ^0.5.1;
// contract Test {
// event Hello(uint256 indexed world);
// constructor() public {
// emit Hello(17);
// }
// }
// {
// "linkReferences": {},
// "object": "6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029",
// "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x11 PUSH32 0x775A94827B8FD9B519D36CD827093C664F93347070A554F65E4A6F56CD738898 PUSH1 0x40 MLOAD PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG2 PUSH1 0x35 DUP1 PUSH1 0x4B PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG1 PUSH6 0x627A7A723058 KECCAK256 PUSH13 0xAB665F0F557620554BB45ADF26 PUSH8 0x8D2BD349B8A4314 0xbd SELFDESTRUCT KECCAK256 0x5e 0xe8 DIFFICULTY 0xe EXTCODECOPY 0x24 STOP 0x29 ",
// "sourceMap": "25:119:0:-;;;90:52;8:9:-1;5:2;;;30:1;27;20:12;5:2;90:52:0;132:2;126:9;;;;;;;;;;25:119;;;;;;"
// }
gasLimit := uint64(100000)
gasPrice := big.NewInt(1000000)
priv, err := crypto.GenerateKey()
suite.Require().NoError(err, "failed to create key")
bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029")
tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode)
tx.Sign(big.NewInt(3), priv)
result := suite.handler(suite.ctx, tx)
suite.Require().True(result.IsOK())
resultData, err := types.DecodeResultData(result.Data)
suite.Require().NoError(err, "failed to decode result data")
suite.Require().Equal(len(resultData.Logs), 1)
suite.Require().Equal(len(resultData.Logs[0].Topics), 2)
hash := []byte{1}
err = suite.app.EvmKeeper.SetTransactionLogs(suite.ctx, resultData.Logs, hash)
suite.Require().NoError(err, "failed to set logs")
logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash)
suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, resultData.Logs)
}

View File

@ -2,6 +2,8 @@ package keeper
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors"
"fmt" "fmt"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -9,7 +11,6 @@ import (
ethvm "github.com/ethereum/go-ethereum/core/vm" ethvm "github.com/ethereum/go-ethereum/core/vm"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
ethstate "github.com/ethereum/go-ethereum/core/state" ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
@ -24,35 +25,22 @@ type Keeper struct {
cdc *codec.Codec cdc *codec.Codec
// Store key required to update the block bloom filter mappings needed for the // Store key required to update the block bloom filter mappings needed for the
// Web3 API // Web3 API
storeKey sdk.StoreKey blockKey sdk.StoreKey
CommitStateDB *types.CommitStateDB CommitStateDB *types.CommitStateDB
TxCount *count TxCount int
Bloom *big.Int Bloom *big.Int
} }
// TODO: move to types
type count int
func (c *count) Get() int {
return (int)(*c)
}
func (c *count) Increment() {
*c++
}
func (c *count) Reset() {
*c = 0
}
// NewKeeper generates new evm module keeper // NewKeeper generates new evm module keeper
func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey, func NewKeeper(
storeKey sdk.StoreKey, cdc *codec.Codec) Keeper { cdc *codec.Codec, blockKey, codeKey, storeKey sdk.StoreKey,
ak types.AccountKeeper,
) Keeper {
return Keeper{ return Keeper{
cdc: cdc, cdc: cdc,
storeKey: storeKey, blockKey: blockKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, ak, storageKey, codeKey), CommitStateDB: types.NewCommitStateDB(sdk.Context{}, codeKey, storeKey, ak),
TxCount: new(count), TxCount: 0,
Bloom: big.NewInt(0), Bloom: big.NewInt(0),
} }
} }
@ -64,21 +52,23 @@ func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey,
// SetBlockHashMapping sets the mapping from block consensus hash to block height // SetBlockHashMapping sets the mapping from block consensus hash to block height
func (k *Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) { func (k *Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.blockKey)
if !bytes.Equal(hash, []byte{}) { if !bytes.Equal(hash, []byte{}) {
store.Set(hash, k.cdc.MustMarshalBinaryLengthPrefixed(height)) bz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(hash, bz)
} }
} }
// GetBlockHashMapping gets block height from block consensus hash // 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) (height int64) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.blockKey)
bz := store.Get(hash) bz := store.Get(hash)
if bytes.Equal(bz, []byte{}) { if bytes.Equal(bz, []byte{}) {
panic(fmt.Errorf("block with hash %s not found", ethcmn.BytesToHash(hash))) panic(fmt.Errorf("block with hash %s not found", ethcmn.BytesToHash(hash)))
} }
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &height)
return height = int64(binary.BigEndian.Uint64(bz))
return height
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -87,23 +77,54 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// SetBlockBloomMapping sets the mapping from block height to bloom bits // SetBlockBloomMapping sets the mapping from block height to bloom bits
func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) { func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) error {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.blockKey)
heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height) bz := sdk.Uint64ToBigEndian(uint64(height))
if !bytes.Equal(heightHash, []byte{}) { if len(bz) == 0 {
store.Set(heightHash, bloom.Bytes()) return fmt.Errorf("block with bloombits %v not found", bloom)
} }
store.Set(types.BloomKey(bz), bloom.Bytes())
return nil
} }
// GetBlockBloomMapping gets bloombits from block height // GetBlockBloomMapping gets bloombits from block height
func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) ethtypes.Bloom { func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) (ethtypes.Bloom, error) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.blockKey)
heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height) bz := sdk.Uint64ToBigEndian(uint64(height))
bloom := store.Get(heightHash) if len(bz) == 0 {
if bytes.Equal(heightHash, []byte{}) { return ethtypes.BytesToBloom([]byte{}), fmt.Errorf("block with height %d not found", height)
panic(fmt.Errorf("block with bloombits %s not found", bloom))
} }
return ethtypes.BytesToBloom(bloom)
bloom := store.Get(types.BloomKey(bz))
if len(bloom) == 0 {
return ethtypes.BytesToBloom([]byte{}), fmt.Errorf("block with bloombits %v not found", bloom)
}
return ethtypes.BytesToBloom(bloom), nil
}
// SetBlockLogs sets the transaction's logs in the KVStore
func (k *Keeper) SetTransactionLogs(ctx sdk.Context, logs []*ethtypes.Log, hash []byte) error {
store := ctx.KVStore(k.blockKey)
encLogs, err := types.EncodeLogs(logs)
if err != nil {
return err
}
store.Set(types.LogsKey(hash), encLogs)
return nil
}
// GetBlockLogs gets the logs for a transaction from the KVStore
func (k *Keeper) GetTransactionLogs(ctx sdk.Context, hash []byte) ([]*ethtypes.Log, error) {
store := ctx.KVStore(k.blockKey)
encLogs := store.Get(types.LogsKey(hash))
if len(encLogs) == 0 {
return nil, errors.New("cannot get transaction logs")
}
return types.DecodeLogs(encLogs)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -225,13 +246,18 @@ func (k *Keeper) GetCommittedState(ctx sdk.Context, addr ethcmn.Address, hash et
} }
// GetLogs calls CommitStateDB.GetLogs using the passed in context // GetLogs calls CommitStateDB.GetLogs using the passed in context
func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) []*ethtypes.Log { func (k *Keeper) GetLogs(ctx sdk.Context, hash ethcmn.Hash) ([]*ethtypes.Log, error) {
return k.CommitStateDB.WithContext(ctx).GetLogs(hash) logs, err := k.CommitStateDB.WithContext(ctx).GetLogs(hash)
if err != nil {
return nil, err
} }
// Logs calls CommitStateDB.Logs using the passed in context return logs, nil
func (k *Keeper) Logs(ctx sdk.Context) []*ethtypes.Log { }
return k.CommitStateDB.WithContext(ctx).Logs()
// AllLogs calls CommitStateDB.AllLogs using the passed in context
func (k *Keeper) AllLogs(ctx sdk.Context) []*ethtypes.Log {
return k.CommitStateDB.WithContext(ctx).AllLogs()
} }
// GetRefund calls CommitStateDB.GetRefund using the passed in context // GetRefund calls CommitStateDB.GetRefund using the passed in context
@ -264,13 +290,17 @@ func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.H
} }
// Finalise calls CommitStateDB.Finalise using the passed in context // Finalise calls CommitStateDB.Finalise using the passed in context
func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) { func (k *Keeper) Finalise(ctx sdk.Context, deleteEmptyObjects bool) error {
k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects) return k.CommitStateDB.WithContext(ctx).Finalise(deleteEmptyObjects)
} }
// IntermediateRoot calls CommitStateDB.IntermediateRoot using the passed in context // IntermediateRoot calls CommitStateDB.IntermediateRoot using the passed in context
func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) { func (k *Keeper) IntermediateRoot(ctx sdk.Context, deleteEmptyObjects bool) error {
k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects) _, err := k.CommitStateDB.WithContext(ctx).IntermediateRoot(deleteEmptyObjects)
if err != nil {
return err
}
return nil
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -1,110 +1,78 @@
package keeper package keeper_test
import ( import (
"math/big" "math/big"
"testing" "testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/app"
evmtypes "github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/keeper"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmlog "github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
) )
var ( var address = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1")
address = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1")
accKey = sdk.NewKVStoreKey("acc") type KeeperTestSuite struct {
storageKey = sdk.NewKVStoreKey(evmtypes.EvmStoreKey) suite.Suite
codeKey = sdk.NewKVStoreKey(evmtypes.EvmCodeKey)
blockKey = sdk.NewKVStoreKey(evmtypes.EvmBlockKey)
logger = tmlog.NewNopLogger() ctx sdk.Context
) querier sdk.Querier
app *app.EthermintApp
func newTestCodec() *codec.Codec {
cdc := codec.New()
evmtypes.RegisterCodec(cdc)
types.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
} }
func TestDBStorage(t *testing.T) { func (suite *KeeperTestSuite) SetupTest() {
// create logger, codec and root multi-store checkTx := false
cdc := newTestCodec()
// The ParamsKeeper handles parameter storage for the application suite.app = app.Setup(checkTx)
keyParams := sdk.NewKVStoreKey(params.StoreKey) suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) suite.querier = keeper.NewQuerier(suite.app.EvmKeeper)
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
// Set specific supspaces
authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace)
ak := auth.NewAccountKeeper(cdc, accKey, authSubspace, types.ProtoBaseAccount)
ek := NewKeeper(ak, storageKey, codeKey, blockKey, cdc)
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
// mount stores
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey, blockKey}
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil)
} }
// load latest version (root) func TestKeeperTestSuite(t *testing.T) {
err := cms.LoadLatestVersion() suite.Run(t, new(KeeperTestSuite))
require.NoError(t, err) }
// First execution
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
ctx = ctx.WithBlockHeight(1)
func (suite *KeeperTestSuite) TestDBStorage() {
// Perform state transitions // Perform state transitions
ek.SetBalance(ctx, address, big.NewInt(5)) suite.app.EvmKeeper.CreateAccount(suite.ctx, address)
ek.SetNonce(ctx, address, 4) suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(5))
ek.SetState(ctx, address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) suite.app.EvmKeeper.SetNonce(suite.ctx, address, 4)
ek.SetCode(ctx, address, []byte{0x1}) suite.app.EvmKeeper.SetState(suite.ctx, address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3"))
suite.app.EvmKeeper.SetCode(suite.ctx, address, []byte{0x1})
// Test block hash mapping functionality // Test block hash mapping functionality
ek.SetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7) suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7)
ek.SetBlockHashMapping(ctx, []byte{0x43, 0x32}, 8) suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}, 8)
// Test block height mapping functionality // Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
ek.SetBlockBloomMapping(ctx, testBloom, 4) err := suite.app.EvmKeeper.SetBlockBloomMapping(suite.ctx, testBloom, 4)
suite.Require().NoError(err, "failed to set block bloom mapping")
// Get those state transitions // Get those state transitions
require.Equal(t, ek.GetBalance(ctx, address).Cmp(big.NewInt(5)), 0) suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, address).Cmp(big.NewInt(5)), 0)
require.Equal(t, ek.GetNonce(ctx, address), uint64(4)) suite.Require().Equal(suite.app.EvmKeeper.GetNonce(suite.ctx, address), uint64(4))
require.Equal(t, ek.GetState(ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3"))
require.Equal(t, ek.GetCode(ctx, address), []byte{0x1}) suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, address), []byte{0x1})
require.Equal(t, ek.GetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7)) suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7))
require.Equal(t, ek.GetBlockHashMapping(ctx, []byte{0x43, 0x32}), int64(8)) suite.Require().Equal(suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}), int64(8))
bloom, err := suite.app.EvmKeeper.GetBlockBloomMapping(suite.ctx, 4)
require.Equal(t, ek.GetBlockBloomMapping(ctx, 4), testBloom) suite.Require().NoError(err)
suite.Require().Equal(bloom, testBloom)
// commit stateDB // commit stateDB
_, err = ek.Commit(ctx, false) _, err = suite.app.EvmKeeper.Commit(suite.ctx, false)
require.NoError(t, err, "failed to commit StateDB") suite.Require().NoError(err, "failed to commit StateDB")
// simulate BaseApp EndBlocker commitment // simulate BaseApp EndBlocker commitment
ms.Write() suite.app.Commit()
cms.Commit()
} }

View File

@ -1,6 +1,7 @@
package keeper package keeper
import ( import (
"fmt"
"strconv" "strconv"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -15,7 +16,7 @@ import (
// NewQuerier is the module level router for state queries // NewQuerier is the module level router for state queries
func NewQuerier(keeper Keeper) sdk.Querier { func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) {
switch path[0] { switch path[0] {
case types.QueryProtocolVersion: case types.QueryProtocolVersion:
return queryProtocolVersion(keeper) return queryProtocolVersion(keeper)
@ -48,140 +49,146 @@ func NewQuerier(keeper Keeper) sdk.Querier {
func queryProtocolVersion(keeper Keeper) ([]byte, sdk.Error) { func queryProtocolVersion(keeper Keeper) ([]byte, sdk.Error) {
vers := version.ProtocolVersion vers := version.ProtocolVersion
res, err := codec.MarshalJSONIndent(keeper.cdc, hexutil.Uint(vers)) bz, err := codec.MarshalJSONIndent(keeper.cdc, hexutil.Uint(vers))
if err != nil { if err != nil {
panic("could not marshal result to JSON") return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryBalance(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryBalance(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.HexToAddress(path[1]) addr := ethcmn.HexToAddress(path[1])
balance := keeper.GetBalance(ctx, addr) balance := keeper.GetBalance(ctx, addr)
bRes := types.QueryResBalance{Balance: utils.MarshalBigInt(balance)} res := types.QueryResBalance{Balance: utils.MarshalBigInt(balance)}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) {
num := ctx.BlockHeight() num := ctx.BlockHeight()
bnRes := types.QueryResBlockNumber{Number: num} bnRes := types.QueryResBlockNumber{Number: num}
res, err := codec.MarshalJSONIndent(keeper.cdc, bnRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, bnRes)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.HexToAddress(path[1]) addr := ethcmn.HexToAddress(path[1])
key := ethcmn.HexToHash(path[2]) key := ethcmn.HexToHash(path[2])
val := keeper.GetState(ctx, addr, key) val := keeper.GetState(ctx, addr, key)
bRes := types.QueryResStorage{Value: val.Bytes()} res := types.QueryResStorage{Value: val.Bytes()}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryCode(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryCode(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.HexToAddress(path[1]) addr := ethcmn.HexToAddress(path[1])
code := keeper.GetCode(ctx, addr) code := keeper.GetCode(ctx, addr)
cRes := types.QueryResCode{Code: code} res := types.QueryResCode{Code: code}
res, err := codec.MarshalJSONIndent(keeper.cdc, cRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryNonce(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryNonce(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.HexToAddress(path[1]) addr := ethcmn.HexToAddress(path[1])
nonce := keeper.GetNonce(ctx, addr) nonce := keeper.GetNonce(ctx, addr)
nRes := types.QueryResNonce{Nonce: nonce} nRes := types.QueryResNonce{Nonce: nonce}
res, err := codec.MarshalJSONIndent(keeper.cdc, nRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, nRes)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
blockHash := ethcmn.FromHex(path[1]) blockHash := ethcmn.FromHex(path[1])
blockNumber := keeper.GetBlockHashMapping(ctx, blockHash) blockNumber := keeper.GetBlockHashMapping(ctx, blockHash)
bRes := types.QueryResBlockNumber{Number: blockNumber} res := types.QueryResBlockNumber{Number: blockNumber}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil return bz, nil
} }
func queryBlockLogsBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryBlockLogsBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
num, err := strconv.ParseInt(path[1], 10, 64) num, err := strconv.ParseInt(path[1], 10, 64)
if err != nil { if err != nil {
panic("could not unmarshall block number: " + err.Error()) return nil, sdk.ErrInternal(fmt.Sprintf("could not unmarshall block number: %s", err.Error()))
} }
bloom := keeper.GetBlockBloomMapping(ctx, num) bloom, err := keeper.GetBlockBloomMapping(ctx, num)
bRes := types.QueryBloomFilter{Bloom: bloom}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(fmt.Sprintf("failed to get block bloom mapping: %s", err.Error()))
} }
return res, nil res := types.QueryBloomFilter{Bloom: bloom}
bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
return bz, nil
} }
func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
txHash := ethcmn.HexToHash(path[1]) txHash := ethcmn.HexToHash(path[1])
logs := keeper.GetLogs(ctx, txHash) logs, err := keeper.GetLogs(ctx, txHash)
bRes := types.QueryETHLogs{Logs: logs}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return res, nil res := types.QueryETHLogs{Logs: logs}
bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil {
return nil, sdk.ErrInternal(err.Error())
}
return bz, nil
} }
func queryLogs(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { func queryLogs(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) {
logs := keeper.Logs(ctx) logs := keeper.AllLogs(ctx)
lRes := types.QueryETHLogs{Logs: logs} res := types.QueryETHLogs{Logs: logs}
l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return l, nil return bz, nil
} }
func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.HexToAddress(path[1]) addr := ethcmn.HexToAddress(path[1])
so := keeper.GetOrNewStateObject(ctx, addr) so := keeper.GetOrNewStateObject(ctx, addr)
lRes := types.QueryResAccount{ res := types.QueryResAccount{
Balance: utils.MarshalBigInt(so.Balance()), Balance: utils.MarshalBigInt(so.Balance()),
CodeHash: so.CodeHash(), CodeHash: so.CodeHash(),
Nonce: so.Nonce(), Nonce: so.Nonce(),
} }
l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) bz, err := codec.MarshalJSONIndent(keeper.cdc, res)
if err != nil { if err != nil {
panic("could not marshal result to JSON: " + err.Error()) return nil, sdk.ErrInternal(err.Error())
} }
return l, nil return bz, nil
} }

View File

@ -2,19 +2,20 @@ package evm
import ( import (
"encoding/json" "encoding/json"
"math/big"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/ethermint/x/evm/client/cli" "github.com/cosmos/ethermint/x/evm/client/cli"
"github.com/cosmos/ethermint/x/evm/keeper" "github.com/cosmos/ethermint/x/evm/keeper"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
) )
var _ module.AppModuleBasic = AppModuleBasic{} var _ module.AppModuleBasic = AppModuleBasic{}
@ -107,33 +108,13 @@ func (am AppModule) NewQuerierHandler() sdk.Querier {
} }
// BeginBlock function for module at start of each block // BeginBlock function for module at start of each block
func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) { func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
// Consider removing this when using evm as module without web3 API BeginBlock(am.keeper, ctx, req)
bloom := ethtypes.BytesToBloom(am.keeper.Bloom.Bytes())
am.keeper.SetBlockBloomMapping(ctx, bloom, bl.Header.GetHeight()-1)
am.keeper.SetBlockHashMapping(ctx, bl.Header.LastBlockId.GetHash(), bl.Header.GetHeight()-1)
am.keeper.Bloom = big.NewInt(0)
am.keeper.TxCount.Reset()
} }
// EndBlock function for module at end of block // EndBlock function for module at end of block
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
// Gas costs are handled within msg handler so costs should be ignored return EndBlock(am.keeper, ctx, req)
ebCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
// Update account balances before committing other parts of state
am.keeper.CommitStateDB.UpdateAccounts()
// Commit state objects to KV store
_, err := am.keeper.CommitStateDB.WithContext(ebCtx).Commit(true)
if err != nil {
panic(err)
}
// Clear accounts cache after account data has been committed
am.keeper.CommitStateDB.ClearStateObjects()
return []abci.ValidatorUpdate{}
} }
// InitGenesis instantiates the genesis state // InitGenesis instantiates the genesis state

View File

@ -9,14 +9,13 @@ var ModuleCdc = codec.New()
func init() { func init() {
cdc := codec.New() cdc := codec.New()
codec.RegisterCrypto(cdc) codec.RegisterCrypto(cdc)
ModuleCdc = cdc.Seal() ModuleCdc = cdc.Seal()
} }
// RegisterCodec registers concrete types and interfaces on the given codec. // RegisterCodec registers concrete types and interfaces on the given codec.
func RegisterCodec(cdc *codec.Codec) { func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&EthereumTxMsg{}, "ethermint/MsgEthereumTx", nil) cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil)
cdc.RegisterConcrete(&EmintMsg{}, "ethermint/MsgEmint", nil) cdc.RegisterConcrete(MsgEthermint{}, "ethermint/MsgEthermint", nil)
cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodableTxData", nil)
} }

View File

@ -1,12 +0,0 @@
package types
import (
ethstate "github.com/ethereum/go-ethereum/core/state"
)
// RawDump returns a raw state dump.
//
// TODO: Implement if we need it, especially for the RPC API.
func (csdb *CommitStateDB) RawDump() ethstate.Dump {
return ethstate.Dump{}
}

View File

@ -4,21 +4,22 @@ import (
"fmt" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
) )
var ( var (
_ sdk.Msg = EmintMsg{} _ sdk.Msg = MsgEthermint{}
) )
const ( const (
// TypeEmintMsg defines the type string of Emint message // TypeMsgEthermint defines the type string of Ethermint message
TypeEmintMsg = "emint_tx" TypeMsgEthermint = "ethermint"
) )
// EmintMsg implements a cosmos equivalent structure for Ethereum transactions // MsgEthermint implements a cosmos equivalent structure for Ethereum transactions
type EmintMsg struct { type MsgEthermint struct {
AccountNonce uint64 `json:"nonce"` AccountNonce uint64 `json:"nonce"`
Price sdk.Int `json:"gasPrice"` Price sdk.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"` GasLimit uint64 `json:"gas"`
@ -30,12 +31,12 @@ type EmintMsg struct {
From sdk.AccAddress `json:"from"` From sdk.AccAddress `json:"from"`
} }
// NewEmintMsg returns a reference to a new Ethermint transaction // NewMsgEthermint returns a reference to a new Ethermint transaction
func NewEmintMsg( func NewMsgEthermint(
nonce uint64, to *sdk.AccAddress, amount sdk.Int, nonce uint64, to *sdk.AccAddress, amount sdk.Int,
gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress, gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress,
) EmintMsg { ) MsgEthermint {
return EmintMsg{ return MsgEthermint{
AccountNonce: nonce, AccountNonce: nonce,
Price: gasPrice, Price: gasPrice,
GasLimit: gasLimit, GasLimit: gasLimit,
@ -47,18 +48,18 @@ func NewEmintMsg(
} }
// Route should return the name of the module // Route should return the name of the module
func (msg EmintMsg) Route() string { return RouterKey } func (msg MsgEthermint) Route() string { return RouterKey }
// Type returns the action of the message // Type returns the action of the message
func (msg EmintMsg) Type() string { return TypeEmintMsg } func (msg MsgEthermint) Type() string { return TypeMsgEthermint }
// GetSignBytes encodes the message for signing // GetSignBytes encodes the message for signing
func (msg EmintMsg) GetSignBytes() []byte { func (msg MsgEthermint) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
} }
// ValidateBasic runs stateless checks on the message // ValidateBasic runs stateless checks on the message
func (msg EmintMsg) ValidateBasic() sdk.Error { func (msg MsgEthermint) ValidateBasic() sdk.Error {
if msg.Price.Sign() != 1 { if msg.Price.Sign() != 1 {
return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Price)) return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Price))
} }
@ -72,13 +73,13 @@ func (msg EmintMsg) ValidateBasic() sdk.Error {
} }
// GetSigners defines whose signature is required // GetSigners defines whose signature is required
func (msg EmintMsg) GetSigners() []sdk.AccAddress { func (msg MsgEthermint) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.From} return []sdk.AccAddress{msg.From}
} }
// To returns the recipient address of the transaction. It returns nil if the // To returns the recipient address of the transaction. It returns nil if the
// transaction is a contract creation. // transaction is a contract creation.
func (msg EmintMsg) To() *ethcmn.Address { func (msg MsgEthermint) To() *ethcmn.Address {
if msg.Recipient == nil { if msg.Recipient == nil {
return nil return nil
} }

View File

@ -8,19 +8,19 @@ import (
"github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/secp256k1"
) )
func TestEmintMsg(t *testing.T) { func TestMsgEthermint(t *testing.T) {
addr := newSdkAddress() addr := newSdkAddress()
fromAddr := newSdkAddress() fromAddr := newSdkAddress()
msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
require.NotNil(t, msg) require.NotNil(t, msg)
require.Equal(t, msg.Recipient, &addr) require.Equal(t, msg.Recipient, &addr)
require.Equal(t, msg.Route(), RouterKey) require.Equal(t, msg.Route(), RouterKey)
require.Equal(t, msg.Type(), TypeEmintMsg) require.Equal(t, msg.Type(), TypeMsgEthermint)
} }
func TestEmintMsgValidation(t *testing.T) { func TestMsgEthermintValidation(t *testing.T) {
testCases := []struct { testCases := []struct {
nonce uint64 nonce uint64
to *sdk.AccAddress to *sdk.AccAddress
@ -38,7 +38,7 @@ func TestEmintMsgValidation(t *testing.T) {
} }
for i, tc := range testCases { for i, tc := range testCases {
msg := NewEmintMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from) msg := NewMsgEthermint(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from)
if tc.expectPass { if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", i) require.Nil(t, msg.ValidateBasic(), "test: %v", i)
@ -52,13 +52,13 @@ func TestEmintEncodingAndDecoding(t *testing.T) {
addr := newSdkAddress() addr := newSdkAddress()
fromAddr := newSdkAddress() fromAddr := newSdkAddress()
msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr) msg := NewMsgEthermint(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
raw, err := cdc.MarshalBinaryBare(msg) raw, err := ModuleCdc.MarshalBinaryBare(msg)
require.NoError(t, err) require.NoError(t, err)
var msg2 EmintMsg var msg2 MsgEthermint
err = cdc.UnmarshalBinaryBare(raw, &msg2) err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, msg.AccountNonce, msg2.AccountNonce) require.Equal(t, msg.AccountNonce, msg2.AccountNonce)

11
x/evm/types/events.go Normal file
View File

@ -0,0 +1,11 @@
package types
// Evm module events
const (
EventTypeEthermint = TypeMsgEthermint
EventTypeEthereumTx = TypeMsgEthereumTx
AttributeKeyContractAddress = "contract"
AttributeKeyRecipient = "recipient"
AttributeValueCategory = ModuleName
)

View File

@ -0,0 +1,14 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
)
// AccountKeeper defines the expected account keeper interface
type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
SetAccount(ctx sdk.Context, account authexported.Account)
RemoveAccount(ctx sdk.Context, account authexported.Account)
}

View File

@ -1,7 +1,7 @@
package types package types
import ( import (
"fmt" "errors"
"math/big" "math/big"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/types"
@ -28,10 +28,10 @@ type (
func ValidateGenesis(data GenesisState) error { func ValidateGenesis(data GenesisState) error {
for _, acct := range data.Accounts { for _, acct := range data.Accounts {
if len(acct.Address.Bytes()) == 0 { if len(acct.Address.Bytes()) == 0 {
return fmt.Errorf("invalid GenesisAccount Error: Missing Address") return errors.New("invalid GenesisAccount: address cannot be empty")
} }
if acct.Balance == nil { if acct.Balance == nil {
return fmt.Errorf("invalid GenesisAccount Error: Missing Balance") return errors.New("invalid GenesisAccount: balance cannot be empty")
} }
} }
return nil return nil

View File

@ -4,13 +4,24 @@ const (
// ModuleName string name of module // ModuleName string name of module
ModuleName = "evm" ModuleName = "evm"
// EvmStoreKey key for ethereum storage data // StoreKey key for ethereum storage data (StateDB)
EvmStoreKey = "evmstore" StoreKey = ModuleName
// EvmCodeKey key for ethereum code data // CodeKey key for ethereum code data
EvmCodeKey = "evmcode" CodeKey = ModuleName + "code"
// EvmBlockKey key for ethereum block data // BlockKey key
EvmBlockKey = "evmblock" BlockKey = ModuleName + "block"
// RouterKey uses module name for routing // RouterKey uses module name for routing
RouterKey = ModuleName RouterKey = ModuleName
) )
var bloomPrefix = []byte("bloom")
var logsPrefix = []byte("logs")
func BloomKey(key []byte) []byte {
return append(bloomPrefix, key...)
}
func LogsKey(key []byte) []byte {
return append(logsPrefix, key...)
}

View File

@ -19,21 +19,20 @@ import (
) )
var ( var (
_ sdk.Msg = EthereumTxMsg{} _ sdk.Msg = MsgEthereumTx{}
_ sdk.Tx = EthereumTxMsg{} _ sdk.Tx = MsgEthereumTx{}
) )
var big8 = big.NewInt(8) var big8 = big.NewInt(8)
// message type and route constants // message type and route constants
const ( const (
TypeEthereumTxMsg = "ethereum_tx" TypeMsgEthereumTx = "ethereum"
RouteEthereumTxMsg = RouterKey
) )
// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message. // MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
type ( type (
EthereumTxMsg struct { MsgEthereumTx struct {
Data TxData Data TxData
// caches // caches
@ -69,28 +68,28 @@ type (
} }
) )
// NewEthereumTxMsg returns a reference to a new Ethereum transaction message. // NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
func NewEthereumTxMsg( func NewMsgEthereumTx(
nonce uint64, to *ethcmn.Address, amount *big.Int, nonce uint64, to *ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *EthereumTxMsg { ) MsgEthereumTx {
return newEthereumTxMsg(nonce, to, amount, gasLimit, gasPrice, payload) return newMsgEthereumTx(nonce, to, amount, gasLimit, gasPrice, payload)
} }
// NewEthereumTxMsgContract returns a reference to a new Ethereum transaction // NewMsgEthereumTxContract returns a reference to a new Ethereum transaction
// message designated for contract creation. // message designated for contract creation.
func NewEthereumTxMsgContract( func NewMsgEthereumTxContract(
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *EthereumTxMsg { ) MsgEthereumTx {
return newEthereumTxMsg(nonce, nil, amount, gasLimit, gasPrice, payload) return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
} }
func newEthereumTxMsg( func newMsgEthereumTx(
nonce uint64, to *ethcmn.Address, amount *big.Int, nonce uint64, to *ethcmn.Address, amount *big.Int,
gasLimit uint64, gasPrice *big.Int, payload []byte, gasLimit uint64, gasPrice *big.Int, payload []byte,
) *EthereumTxMsg { ) MsgEthereumTx {
if len(payload) > 0 { if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload) payload = ethcmn.CopyBytes(payload)
@ -115,20 +114,20 @@ func newEthereumTxMsg(
txData.Price.Set(gasPrice) txData.Price.Set(gasPrice)
} }
return &EthereumTxMsg{Data: txData} return MsgEthereumTx{Data: txData}
} }
// Route returns the route value of an EthereumTxMsg. // Route returns the route value of an MsgEthereumTx.
func (msg EthereumTxMsg) Route() string { return RouteEthereumTxMsg } func (msg MsgEthereumTx) Route() string { return RouterKey }
// Type returns the type value of an EthereumTxMsg. // Type returns the type value of an MsgEthereumTx.
func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg } func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation // ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an sdk.Error if validation fails. // checks of a Transaction. If returns an error if validation fails.
func (msg EthereumTxMsg) ValidateBasic() sdk.Error { func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
if msg.Data.Price.Sign() != 1 { if msg.Data.Price.Sign() != 1 {
return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Data.Price)) return types.ErrInvalidValue(fmt.Sprintf("price must be positive: %x", msg.Data.Price))
} }
// Amount can be 0 // Amount can be 0
@ -141,16 +140,12 @@ func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
// To returns the recipient address of the transaction. It returns nil if the // To returns the recipient address of the transaction. It returns nil if the
// transaction is a contract creation. // transaction is a contract creation.
func (msg EthereumTxMsg) To() *ethcmn.Address { func (msg MsgEthereumTx) To() *ethcmn.Address {
if msg.Data.Recipient == nil {
return nil
}
return msg.Data.Recipient return msg.Data.Recipient
} }
// GetMsgs returns a single EthereumTxMsg as an sdk.Msg. // GetMsgs returns a single MsgEthereumTx as an sdk.Msg.
func (msg EthereumTxMsg) GetMsgs() []sdk.Msg { func (msg MsgEthereumTx) GetMsgs() []sdk.Msg {
return []sdk.Msg{msg} return []sdk.Msg{msg}
} }
@ -159,7 +154,7 @@ func (msg EthereumTxMsg) GetMsgs() []sdk.Msg {
// //
// NOTE: This method cannot be used as a chain ID is needed to recover the signer // NOTE: This method cannot be used as a chain ID is needed to recover the signer
// from the signature. Use 'VerifySig' instead. // from the signature. Use 'VerifySig' instead.
func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress { func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
panic("must use 'VerifySig' with a chain ID to get the signer") panic("must use 'VerifySig' with a chain ID to get the signer")
} }
@ -168,13 +163,13 @@ func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress {
// //
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes // NOTE: This method cannot be used as a chain ID is needed to create valid bytes
// to sign over. Use 'RLPSignBytes' instead. // to sign over. Use 'RLPSignBytes' instead.
func (msg EthereumTxMsg) GetSignBytes() []byte { func (msg MsgEthereumTx) GetSignBytes() []byte {
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign") panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
} }
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a // RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
// given chainID used for signing. // given chainID used for signing.
func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash { func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
return rlpHash([]interface{}{ return rlpHash([]interface{}{
msg.Data.AccountNonce, msg.Data.AccountNonce,
msg.Data.Price, msg.Data.Price,
@ -187,12 +182,12 @@ func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
} }
// EncodeRLP implements the rlp.Encoder interface. // EncodeRLP implements the rlp.Encoder interface.
func (msg *EthereumTxMsg) EncodeRLP(w io.Writer) error { func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &msg.Data) return rlp.Encode(w, &msg.Data)
} }
// DecodeRLP implements the rlp.Decoder interface. // DecodeRLP implements the rlp.Decoder interface.
func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error { func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
_, size, _ := s.Kind() _, size, _ := s.Kind()
err := s.Decode(&msg.Data) err := s.Decode(&msg.Data)
@ -204,7 +199,7 @@ func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error {
} }
// Hash hashes the RLP encoding of a transaction. // Hash hashes the RLP encoding of a transaction.
func (msg *EthereumTxMsg) Hash() ethcmn.Hash { func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
if hash := msg.hash.Load(); hash != nil { if hash := msg.hash.Load(); hash != nil {
return hash.(ethcmn.Hash) return hash.(ethcmn.Hash)
} }
@ -219,7 +214,7 @@ func (msg *EthereumTxMsg) Hash() ethcmn.Hash {
// takes a private key and chainID to sign an Ethereum transaction according to // 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 // EIP155 standard. It mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature. // fields of the Transaction's Signature.
func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
txHash := msg.RLPSignBytes(chainID) txHash := msg.RLPSignBytes(chainID)
sig, err := ethcrypto.Sign(txHash[:], priv) sig, err := ethcrypto.Sign(txHash[:], priv)
@ -252,7 +247,7 @@ func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
// VerifySig attempts to verify a Transaction's signature for a given chainID. // VerifySig attempts to verify a Transaction's signature for a given chainID.
// A derived address is returned upon success or an error if recovery fails. // A derived address is returned upon success or an error if recovery fails.
func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) { func (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
signer := ethtypes.NewEIP155Signer(chainID) signer := ethtypes.NewEIP155Signer(chainID)
if sc := msg.from.Load(); sc != nil { if sc := msg.from.Load(); sc != nil {
@ -284,19 +279,19 @@ func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
} }
// Cost returns amount + gasprice * gaslimit. // Cost returns amount + gasprice * gaslimit.
func (msg EthereumTxMsg) Cost() *big.Int { func (msg MsgEthereumTx) Cost() *big.Int {
total := msg.Fee() total := msg.Fee()
total.Add(total, msg.Data.Amount) total.Add(total, msg.Data.Amount)
return total return total
} }
// Fee returns gasprice * gaslimit. // Fee returns gasprice * gaslimit.
func (msg EthereumTxMsg) Fee() *big.Int { func (msg MsgEthereumTx) Fee() *big.Int {
return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit)) return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit))
} }
// ChainID returns which chain id this transaction was signed for (if at all) // ChainID returns which chain id this transaction was signed for (if at all)
func (msg *EthereumTxMsg) ChainID() *big.Int { func (msg *MsgEthereumTx) ChainID() *big.Int {
return deriveChainID(msg.Data.V) return deriveChainID(msg.Data.V)
} }
@ -317,7 +312,7 @@ func deriveChainID(v *big.Int) *big.Int {
// Auxiliary // Auxiliary
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and // TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
// EthereumTxMsg transactions. // MsgEthereumTx transactions.
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder { func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) { return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx sdk.Tx var tx sdk.Tx
@ -328,7 +323,6 @@ func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
if err != nil { if err != nil {
fmt.Println(err.Error())
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error()) return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
} }

View File

@ -1,23 +1,11 @@
package types package types
import ( import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/ethermint/utils" "github.com/cosmos/ethermint/utils"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
) )
var cdc = codec.New()
func init() {
RegisterAmino(cdc)
}
// RegisterAmino registers all crypto related types in the given (amino) codec.
func RegisterAmino(cdc *codec.Codec) {
cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodedMessage", nil)
}
// EncodableTxData implements the Ethereum transaction data structure. It is used // EncodableTxData implements the Ethereum transaction data structure. It is used
// solely as intended in Ethereum abiding by the protocol. // solely as intended in Ethereum abiding by the protocol.
type EncodableTxData struct { type EncodableTxData struct {
@ -38,12 +26,12 @@ type EncodableTxData struct {
} }
func marshalAmino(td EncodableTxData) (string, error) { func marshalAmino(td EncodableTxData) (string, error) {
bz, err := cdc.MarshalBinaryBare(td) bz, err := ModuleCdc.MarshalBinaryBare(td)
return string(bz), err return string(bz), err
} }
func unmarshalAmino(td *EncodableTxData, text string) error { func unmarshalAmino(td *EncodableTxData, text string) error {
return cdc.UnmarshalBinaryBare([]byte(text), td) return ModuleCdc.UnmarshalBinaryBare([]byte(text), td)
} }
// MarshalAmino defines custom encoding scheme for TxData // MarshalAmino defines custom encoding scheme for TxData

View File

@ -18,17 +18,17 @@ import (
func TestMsgEthereumTx(t *testing.T) { func TestMsgEthereumTx(t *testing.T) {
addr := GenerateEthAddress() addr := GenerateEthAddress()
msg1 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) msg1 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg1) require.NotNil(t, msg1)
require.Equal(t, *msg1.Data.Recipient, addr) require.Equal(t, *msg1.Data.Recipient, addr)
msg2 := NewEthereumTxMsgContract(0, nil, 100000, nil, []byte("test")) msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg2) require.NotNil(t, msg2)
require.Nil(t, msg2.Data.Recipient) require.Nil(t, msg2.Data.Recipient)
msg3 := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) msg3 := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
require.Equal(t, msg3.Route(), RouteEthereumTxMsg) require.Equal(t, msg3.Route(), RouterKey)
require.Equal(t, msg3.Type(), TypeEthereumTxMsg) require.Equal(t, msg3.Type(), TypeMsgEthereumTx)
require.Panics(t, func() { msg3.GetSigners() }) require.Panics(t, func() { msg3.GetSigners() })
require.Panics(t, func() { msg3.GetSignBytes() }) require.Panics(t, func() { msg3.GetSignBytes() })
} }
@ -49,7 +49,7 @@ func TestMsgEthereumTxValidation(t *testing.T) {
} }
for i, tc := range testCases { for i, tc := range testCases {
msg := NewEthereumTxMsg(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload) msg := NewMsgEthereumTx(tc.nonce, &tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
if tc.expectPass { if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", i) require.Nil(t, msg.ValidateBasic(), "test: %v", i)
@ -63,26 +63,26 @@ func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
chainID := big.NewInt(3) chainID := big.NewInt(3)
msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
hash := msg.RLPSignBytes(chainID) hash := msg.RLPSignBytes(chainID)
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash)) require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
} }
func TestMsgEthereumTxRLPEncode(t *testing.T) { func TestMsgEthereumTxRLPEncode(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
raw, err := rlp.EncodeToBytes(msg) raw, err := rlp.EncodeToBytes(&msg)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080"), raw) require.Equal(t, ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080"), raw)
} }
func TestMsgEthereumTxRLPDecode(t *testing.T) { func TestMsgEthereumTxRLPDecode(t *testing.T) {
var msg EthereumTxMsg var msg MsgEthereumTx
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080") raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
expectedMsg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) expectedMsg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
err := rlp.Decode(bytes.NewReader(raw), &msg) err := rlp.Decode(bytes.NewReader(raw), &msg)
require.NoError(t, err) require.NoError(t, err)
@ -91,7 +91,7 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) {
func TestMsgEthereumTxHash(t *testing.T) { func TestMsgEthereumTxHash(t *testing.T) {
addr := ethcmn.BytesToAddress([]byte("test_address")) addr := ethcmn.BytesToAddress([]byte("test_address"))
msg := NewEthereumTxMsg(0, &addr, nil, 100000, nil, []byte("test")) msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
hash := msg.Hash() hash := msg.Hash()
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash)) require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
@ -106,7 +106,7 @@ func TestMsgEthereumTxSig(t *testing.T) {
addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes()) addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes())
// require valid signature passes validation // require valid signature passes validation
msg := NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) msg := NewMsgEthereumTx(0, &addr1, nil, 100000, nil, []byte("test"))
msg.Sign(chainID, priv1.ToECDSA()) msg.Sign(chainID, priv1.ToECDSA())
signer, err := msg.VerifySig(chainID) signer, err := msg.VerifySig(chainID)
@ -115,7 +115,7 @@ func TestMsgEthereumTxSig(t *testing.T) {
require.NotEqual(t, addr2, signer) require.NotEqual(t, addr2, signer)
// require invalid chain ID fail validation // require invalid chain ID fail validation
msg = NewEthereumTxMsg(0, &addr1, nil, 100000, nil, []byte("test")) msg = NewMsgEthereumTx(0, &addr1, nil, 100000, nil, []byte("test"))
msg.Sign(chainID, priv1.ToECDSA()) msg.Sign(chainID, priv1.ToECDSA())
signer, err = msg.VerifySig(big.NewInt(4)) signer, err = msg.VerifySig(big.NewInt(4))
@ -125,7 +125,7 @@ func TestMsgEthereumTxSig(t *testing.T) {
func TestMsgEthereumTxAmino(t *testing.T) { func TestMsgEthereumTxAmino(t *testing.T) {
addr := GenerateEthAddress() addr := GenerateEthAddress()
msg := NewEthereumTxMsg(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) msg := NewMsgEthereumTx(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test"))
msg.Data.V = big.NewInt(1) msg.Data.V = big.NewInt(1)
msg.Data.R = big.NewInt(2) msg.Data.R = big.NewInt(2)
@ -134,7 +134,7 @@ func TestMsgEthereumTxAmino(t *testing.T) {
raw, err := ModuleCdc.MarshalBinaryBare(msg) raw, err := ModuleCdc.MarshalBinaryBare(msg)
require.NoError(t, err) require.NoError(t, err)
var msg2 EthereumTxMsg var msg2 MsgEthereumTx
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2) err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err) require.NoError(t, err)

View File

@ -73,20 +73,21 @@ type (
} }
) )
func newObject(db *CommitStateDB, accProto authexported.Account) *stateObject { func newStateObject(db *CommitStateDB, accProto authexported.Account) *stateObject {
acc, ok := accProto.(*types.Account) ethermintAccount, ok := accProto.(*types.Account)
if !ok { if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", accProto)) panic(fmt.Sprintf("invalid account type for state object: %T", accProto))
} }
if acc.CodeHash == nil { // set empty code hash
acc.CodeHash = emptyCodeHash if ethermintAccount.CodeHash == nil {
ethermintAccount.CodeHash = emptyCodeHash
} }
return &stateObject{ return &stateObject{
stateDB: db, stateDB: db,
account: acc, account: ethermintAccount,
address: ethcmn.BytesToAddress(acc.Address.Bytes()), address: ethcmn.BytesToAddress(ethermintAccount.GetAddress().Bytes()),
originStorage: make(types.Storage), originStorage: make(types.Storage),
dirtyStorage: make(types.Storage), dirtyStorage: make(types.Storage),
} }
@ -188,7 +189,7 @@ func (so *stateObject) setBalance(amount sdk.Int) {
so.account.SetBalance(amount) so.account.SetBalance(amount)
} }
// SetNonce sets the state object's nonce (sequence number). // SetNonce sets the state object's nonce (i.e sequence number of the account).
func (so *stateObject) SetNonce(nonce uint64) { func (so *stateObject) SetNonce(nonce uint64) {
so.stateDB.journal.append(nonceChange{ so.stateDB.journal.append(nonceChange{
account: &so.address, account: &so.address,
@ -199,6 +200,9 @@ func (so *stateObject) SetNonce(nonce uint64) {
} }
func (so *stateObject) setNonce(nonce uint64) { func (so *stateObject) setNonce(nonce uint64) {
if so.account == nil {
panic("state object account is empty")
}
so.account.Sequence = nonce so.account.Sequence = nonce
} }
@ -216,7 +220,7 @@ func (so *stateObject) markSuicided() {
// commitState commits all dirty storage to a KVStore. // commitState commits all dirty storage to a KVStore.
func (so *stateObject) commitState() { func (so *stateObject) commitState() {
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storageKey) store := ctx.KVStore(so.stateDB.storeKey)
for key, value := range so.dirtyStorage { for key, value := range so.dirtyStorage {
delete(so.dirtyStorage, key) delete(so.dirtyStorage, key)
@ -258,22 +262,32 @@ func (so stateObject) Address() ethcmn.Address {
// Balance returns the state object's current balance. // Balance returns the state object's current balance.
func (so *stateObject) Balance() *big.Int { func (so *stateObject) Balance() *big.Int {
return so.account.Balance().BigInt() balance := so.account.Balance().BigInt()
if balance == nil {
return zeroBalance
}
return balance
} }
// CodeHash returns the state object's code hash. // CodeHash returns the state object's code hash.
func (so *stateObject) CodeHash() []byte { func (so *stateObject) CodeHash() []byte {
if so.account == nil || len(so.account.CodeHash) == 0 {
return emptyCodeHash
}
return so.account.CodeHash return so.account.CodeHash
} }
// Nonce returns the state object's current nonce (sequence number). // Nonce returns the state object's current nonce (sequence number).
func (so *stateObject) Nonce() uint64 { func (so *stateObject) Nonce() uint64 {
if so.account == nil {
return 0
}
return so.account.Sequence return so.account.Sequence
} }
// Code returns the contract code associated with this object, if any. // Code returns the contract code associated with this object, if any.
func (so *stateObject) Code(_ ethstate.Database) []byte { func (so *stateObject) Code(_ ethstate.Database) []byte {
if so.code != nil { if len(so.code) > 0 {
return so.code return so.code
} }
@ -286,10 +300,9 @@ func (so *stateObject) Code(_ ethstate.Database) []byte {
code := store.Get(so.CodeHash()) code := store.Get(so.CodeHash())
if len(code) == 0 { if len(code) == 0 {
so.setError(fmt.Errorf("failed to get code hash %x for address: %x", so.CodeHash(), so.Address())) so.setError(fmt.Errorf("failed to get code hash %x for address %s", so.CodeHash(), so.Address().String()))
} }
so.code = code
return code return code
} }
@ -321,7 +334,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
// otherwise load the value from the KVStore // otherwise load the value from the KVStore
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storageKey) store := ctx.KVStore(so.stateDB.storeKey)
rawValue := store.Get(prefixKey.Bytes()) rawValue := store.Get(prefixKey.Bytes())
if len(rawValue) > 0 { if len(rawValue) > 0 {
@ -341,7 +354,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
func (so *stateObject) ReturnGas(gas *big.Int) {} func (so *stateObject) ReturnGas(gas *big.Int) {}
func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject { func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
newStateObj := newObject(db, so.account) newStateObj := newStateObject(db, so.account)
newStateObj.code = so.code newStateObj.code = so.code
newStateObj.dirtyStorage = so.dirtyStorage.Copy() newStateObj.dirtyStorage = so.dirtyStorage.Copy()
@ -355,9 +368,11 @@ func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
// empty returns whether the account is considered empty. // empty returns whether the account is considered empty.
func (so *stateObject) empty() bool { func (so *stateObject) empty() bool {
return so.account.Sequence == 0 && return so.account == nil ||
(so.account != nil &&
so.account.Sequence == 0 &&
so.account.Balance().Sign() == 0 && so.account.Balance().Sign() == 0 &&
bytes.Equal(so.account.CodeHash, emptyCodeHash) bytes.Equal(so.account.CodeHash, emptyCodeHash))
} }
// EncodeRLP implements rlp.Encoder. // EncodeRLP implements rlp.Encoder.

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -27,14 +28,24 @@ type StateTransition struct {
Simulate bool Simulate bool
} }
// ReturnData represents what's returned from a transition
type ReturnData struct {
Logs []*ethtypes.Log
Bloom *big.Int
Result *sdk.Result
}
// TODO: move to keeper
// TransitionCSDB performs an evm state transition from a transaction // TransitionCSDB performs an evm state transition from a transaction
func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result) { // TODO: update godoc, it doesn't explain what it does in depth.
func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
returnData := new(ReturnData)
contractCreation := st.Recipient == nil contractCreation := st.Recipient == nil
cost, err := core.IntrinsicGas(st.Payload, contractCreation, true) cost, err := core.IntrinsicGas(st.Payload, contractCreation, true)
if err != nil { if err != nil {
return nil, sdk.ErrOutOfGas("invalid intrinsic gas for transaction").Result() return nil, fmt.Errorf("invalid intrinsic gas for transaction: %s", err.Error())
} }
// This gas limit the the transaction gas limit with intrinsic gas subtracted // This gas limit the the transaction gas limit with intrinsic gas subtracted
@ -68,21 +79,20 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result)
CanTransfer: core.CanTransfer, CanTransfer: core.CanTransfer,
Transfer: core.Transfer, Transfer: core.Transfer,
Origin: st.Sender, Origin: st.Sender,
Coinbase: common.Address{}, Coinbase: common.Address{}, // TODO: explain why this is empty
BlockNumber: big.NewInt(ctx.BlockHeight()), BlockNumber: big.NewInt(ctx.BlockHeight()),
Time: big.NewInt(ctx.BlockHeader().Time.Unix()), Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
Difficulty: big.NewInt(0x30000), // unused Difficulty: big.NewInt(0), // unused. Only required in PoW context
GasLimit: gasLimit, GasLimit: gasLimit,
GasPrice: ctx.MinGasPrices().AmountOf(emint.DenomDefault).Int, GasPrice: ctx.MinGasPrices().AmountOf(emint.DenomDefault).Int,
} }
vmenv := vm.NewEVM(context, csdb, GenerateChainConfig(st.ChainID), vm.Config{}) evm := vm.NewEVM(context, csdb, GenerateChainConfig(st.ChainID), vm.Config{})
var ( var (
ret []byte ret []byte
leftOverGas uint64 leftOverGas uint64
addr common.Address addr common.Address
vmerr error
senderRef = vm.AccountRef(st.Sender) senderRef = vm.AccountRef(st.Sender)
) )
@ -91,52 +101,77 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*big.Int, sdk.Result)
// Set nonce of sender account before evm state transition for usage in generating Create address // Set nonce of sender account before evm state transition for usage in generating Create address
st.Csdb.SetNonce(st.Sender, st.AccountNonce) st.Csdb.SetNonce(st.Sender, st.AccountNonce)
if contractCreation { switch contractCreation {
ret, addr, leftOverGas, vmerr = vmenv.Create(senderRef, st.Payload, gasLimit, st.Amount) case true:
} else { ret, addr, leftOverGas, err = evm.Create(senderRef, st.Payload, gasLimit, st.Amount)
default:
// Increment the nonce for the next transaction (just for evm state transition) // Increment the nonce for the next transaction (just for evm state transition)
csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1) csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1)
ret, leftOverGas, err = evm.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
ret, leftOverGas, vmerr = vmenv.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
} }
if err != nil {
return nil, err
}
gasConsumed := gasLimit - leftOverGas
// Resets nonce to value pre state transition // Resets nonce to value pre state transition
st.Csdb.SetNonce(st.Sender, currentNonce) st.Csdb.SetNonce(st.Sender, currentNonce)
// Generate bloom filter to be saved in tx receipt data // Generate bloom filter to be saved in tx receipt data
bloomInt := big.NewInt(0) bloomInt := big.NewInt(0)
var bloomFilter ethtypes.Bloom var bloomFilter ethtypes.Bloom
var logs []*ethtypes.Log
if st.THash != nil && !st.Simulate { if st.THash != nil && !st.Simulate {
logs := csdb.GetLogs(*st.THash) logs, err = csdb.GetLogs(*st.THash)
if err != nil {
return nil, err
}
bloomInt = ethtypes.LogsBloom(logs) bloomInt = ethtypes.LogsBloom(logs)
bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes()) bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
} }
// Encode all necessary data into slice of bytes to return in sdk result // Encode all necessary data into slice of bytes to return in sdk result
returnData := EncodeReturnData(addr, bloomFilter, ret) res := &ResultData{
Address: addr,
Bloom: bloomFilter,
Logs: logs,
Ret: ret,
}
resultData, err := EncodeResultData(res)
if err != nil {
return nil, err
}
// handle errors // handle errors
if vmerr != nil { if err != nil {
res := emint.ErrVMExecution(vmerr.Error()).Result() if err == vm.ErrOutOfGas || err == vm.ErrCodeStoreOutOfGas {
if vmerr == vm.ErrOutOfGas || vmerr == vm.ErrCodeStoreOutOfGas { return nil, fmt.Errorf("evm execution went out of gas: %s", err.Error())
res = sdk.ErrOutOfGas("EVM execution went out of gas").Result()
} }
res.Data = returnData
// Consume gas before returning // Consume gas before returning
ctx.GasMeter().ConsumeGas(gasLimit-leftOverGas, "EVM execution consumption") ctx.GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption")
return nil, res return nil, err
} }
// TODO: Refund unused gas here, if intended in future // TODO: Refund unused gas here, if intended in future
if !st.Simulate { if !st.Simulate {
// Finalise state if not a simulated transaction // Finalise state if not a simulated transaction
st.Csdb.Finalise(true) // Change to depend on config // TODO: change to depend on config
if err := st.Csdb.Finalise(true); err != nil {
return nil, err
}
} }
// Consume gas from evm execution // Consume gas from evm execution
// Out of gas check does not need to be done here since it is done within the EVM execution // Out of gas check does not need to be done here since it is done within the EVM execution
ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasLimit-leftOverGas, "EVM execution consumption") ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption")
return bloomInt, sdk.Result{Data: returnData, GasUsed: st.GasLimit - leftOverGas} returnData.Logs = logs
returnData.Bloom = bloomInt
returnData.Result = &sdk.Result{Data: resultData, GasUsed: gasConsumed}
return returnData, nil
} }

View File

@ -7,8 +7,6 @@ import (
"sync" "sync"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
emint "github.com/cosmos/ethermint/types" emint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
@ -41,9 +39,9 @@ type CommitStateDB struct {
// StateDB interface. Perhaps there is a better way. // StateDB interface. Perhaps there is a better way.
ctx sdk.Context ctx sdk.Context
ak auth.AccountKeeper
storageKey sdk.StoreKey
codeKey sdk.StoreKey codeKey sdk.StoreKey
storeKey sdk.StoreKey // i.e storage key
accountKeeper AccountKeeper
// maps that hold 'live' objects, which will get modified while processing a // maps that hold 'live' objects, which will get modified while processing a
// state transition // state transition
@ -84,12 +82,14 @@ type CommitStateDB struct {
// //
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the // CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
// key/value space matters in determining the merkle root. // key/value space matters in determining the merkle root.
func NewCommitStateDB(ctx sdk.Context, ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey) *CommitStateDB { func NewCommitStateDB(
ctx sdk.Context, codeKey, storeKey sdk.StoreKey, ak AccountKeeper,
) *CommitStateDB {
return &CommitStateDB{ return &CommitStateDB{
ctx: ctx, ctx: ctx,
ak: ak,
storageKey: storageKey,
codeKey: codeKey, codeKey: codeKey,
storeKey: storeKey,
accountKeeper: ak,
stateObjects: make(map[ethcmn.Address]*stateObject), stateObjects: make(map[ethcmn.Address]*stateObject),
stateObjectsDirty: make(map[ethcmn.Address]struct{}), stateObjectsDirty: make(map[ethcmn.Address]struct{}),
logs: make(map[ethcmn.Hash][]*ethtypes.Log), logs: make(map[ethcmn.Hash][]*ethtypes.Log),
@ -288,12 +288,28 @@ func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Ha
} }
// GetLogs returns the current logs for a given hash in the state. // GetLogs returns the current logs for a given hash in the state.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) []*ethtypes.Log { func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
return csdb.logs[hash] if csdb.logs[hash] != nil {
return csdb.logs[hash], nil
}
store := csdb.ctx.KVStore(csdb.storeKey)
encLogs := store.Get(LogsKey(hash[:]))
if len(encLogs) == 0 {
return []*ethtypes.Log{}, nil
}
logs, err := DecodeLogs(encLogs)
if err != nil {
return []*ethtypes.Log{}, err
}
return logs, nil
} }
// Logs returns all the current logs in the state. // Logs returns all the current logs in the state.
func (csdb *CommitStateDB) Logs() []*ethtypes.Log { func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log {
// nolint: prealloc // nolint: prealloc
var logs []*ethtypes.Log var logs []*ethtypes.Log
for _, lgs := range csdb.logs { for _, lgs := range csdb.logs {
@ -364,7 +380,9 @@ func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error)
} }
// update the object in the KVStore // update the object in the KVStore
csdb.updateStateObject(so) if err := csdb.updateStateObject(so); err != nil {
return ethcmn.Hash{}, err
}
} }
delete(csdb.stateObjectsDirty, addr) delete(csdb.stateObjectsDirty, addr)
@ -379,7 +397,7 @@ func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error)
// Finalise finalizes the state objects (accounts) state by setting their state, // Finalise finalizes the state objects (accounts) state by setting their state,
// removing the csdb destructed objects and clearing the journal as well as the // removing the csdb destructed objects and clearing the journal as well as the
// refunds. // refunds.
func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) { func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) error {
for addr := range csdb.journal.dirties { for addr := range csdb.journal.dirties {
so, exist := csdb.stateObjects[addr] so, exist := csdb.stateObjects[addr]
if !exist { if !exist {
@ -401,7 +419,9 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) {
// Set all the dirty state storage items for the state object in the // Set all the dirty state storage items for the state object in the
// KVStore and finally set the account in the account mapper. // KVStore and finally set the account in the account mapper.
so.commitState() so.commitState()
csdb.updateStateObject(so) if err := csdb.updateStateObject(so); err != nil {
return err
}
} }
csdb.stateObjectsDirty[addr] = struct{}{} csdb.stateObjectsDirty[addr] = struct{}{}
@ -409,6 +429,7 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) {
// invalidate journal because reverting across transactions is not allowed // invalidate journal because reverting across transactions is not allowed
csdb.clearJournalAndRefund() csdb.clearJournalAndRefund()
return nil
} }
// IntermediateRoot returns the current root hash of the state. It is called in // IntermediateRoot returns the current root hash of the state. It is called in
@ -418,21 +439,24 @@ func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) {
// NOTE: The SDK has not concept or method of getting any intermediate merkle // NOTE: The SDK has not concept or method of getting any intermediate merkle
// root as commitment of the merkle-ized tree doesn't happen until the // root as commitment of the merkle-ized tree doesn't happen until the
// BaseApps' EndBlocker. // BaseApps' EndBlocker.
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash { func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) (ethcmn.Hash, error) {
csdb.Finalise(deleteEmptyObjects) if err := csdb.Finalise(deleteEmptyObjects); err != nil {
return ethcmn.Hash{}, err
}
return ethcmn.Hash{} return ethcmn.Hash{}, nil
} }
// updateStateObject writes the given state object to the store. // updateStateObject writes the given state object to the store.
func (csdb *CommitStateDB) updateStateObject(so *stateObject) { func (csdb *CommitStateDB) updateStateObject(so *stateObject) error {
csdb.ak.SetAccount(csdb.ctx, so.account) csdb.accountKeeper.SetAccount(csdb.ctx, so.account)
return nil
} }
// deleteStateObject removes the given state object from the state store. // deleteStateObject removes the given state object from the state store.
func (csdb *CommitStateDB) deleteStateObject(so *stateObject) { func (csdb *CommitStateDB) deleteStateObject(so *stateObject) {
so.deleted = true so.deleted = true
csdb.ak.RemoveAccount(csdb.ctx, so.account) csdb.accountKeeper.RemoveAccount(csdb.ctx, so.account)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -543,10 +567,10 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
// UpdateAccounts updates the nonce and coin balances of accounts // UpdateAccounts updates the nonce and coin balances of accounts
func (csdb *CommitStateDB) UpdateAccounts() { func (csdb *CommitStateDB) UpdateAccounts() {
for addr, so := range csdb.stateObjects { for addr, so := range csdb.stateObjects {
currAcc := csdb.ak.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes())) currAcc := csdb.accountKeeper.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes()))
emintAcc, ok := currAcc.(*emint.Account) emintAcc, ok := currAcc.(*emint.Account)
if ok { if ok {
if (so.Balance() != emintAcc.Balance().BigInt()) || (so.Nonce() != emintAcc.GetSequence()) { if so.Balance() != emintAcc.Balance().BigInt() || so.Nonce() != emintAcc.GetSequence() {
// If queried account's balance or nonce are invalid, update the account pointer // If queried account's balance or nonce are invalid, update the account pointer
so.account = emintAcc so.account = emintAcc
} }
@ -602,9 +626,9 @@ func (csdb *CommitStateDB) Copy() *CommitStateDB {
// copy all the basic fields, initialize the memory ones // copy all the basic fields, initialize the memory ones
state := &CommitStateDB{ state := &CommitStateDB{
ctx: csdb.ctx, ctx: csdb.ctx,
ak: csdb.ak,
storageKey: csdb.storageKey,
codeKey: csdb.codeKey, codeKey: csdb.codeKey,
storeKey: csdb.storeKey,
accountKeeper: csdb.accountKeeper,
stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)), stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)),
stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)), stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)),
refund: csdb.refund, refund: csdb.refund,
@ -663,8 +687,9 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu
return nil return nil
} }
store := csdb.ctx.KVStore(csdb.storageKey) store := csdb.ctx.KVStore(csdb.storeKey)
iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes()) iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes())
defer iter.Close()
for ; iter.Valid(); iter.Next() { for ; iter.Valid(); iter.Next() {
key := ethcmn.BytesToHash(iter.Key()) key := ethcmn.BytesToHash(iter.Key())
@ -678,7 +703,6 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu
cb(key, ethcmn.BytesToHash(value)) cb(key, ethcmn.BytesToHash(value))
} }
iter.Close()
return nil return nil
} }
@ -698,8 +722,9 @@ func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) StateObject
func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) { func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) {
prevObj = csdb.getStateObject(addr) prevObj = csdb.getStateObject(addr)
acc := csdb.ak.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes())) acc := csdb.accountKeeper.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes()))
newObj = newObject(csdb, acc)
newObj = newStateObject(csdb, acc)
newObj.setNonce(0) // sets the object to dirty newObj.setNonce(0) // sets the object to dirty
if prevObj == nil { if prevObj == nil {
@ -732,14 +757,14 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta
} }
// otherwise, attempt to fetch the account from the account mapper // otherwise, attempt to fetch the account from the account mapper
acc := csdb.ak.GetAccount(csdb.ctx, addr.Bytes()) acc := csdb.accountKeeper.GetAccount(csdb.ctx, addr.Bytes())
if acc == nil { if acc == nil {
csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes())) csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes()))
return nil return nil
} }
// insert the state object into the live set // insert the state object into the live set
so := newObject(csdb, acc) so := newStateObject(csdb, acc)
csdb.setStateObject(so) csdb.setStateObject(so)
return so return so
@ -748,3 +773,10 @@ func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *sta
func (csdb *CommitStateDB) setStateObject(so *stateObject) { func (csdb *CommitStateDB) setStateObject(so *stateObject) {
csdb.stateObjects[so.Address()] = so csdb.stateObjects[so.Address()] = so
} }
// RawDump returns a raw state dump.
//
// TODO: Implement if we need it, especially for the RPC API.
func (csdb *CommitStateDB) RawDump() ethstate.Dump {
return ethstate.Dump{}
}

View File

@ -1,80 +1,40 @@
package types package types_test
import ( import (
"math/big" "math/big"
"testing"
"github.com/cosmos/cosmos-sdk/codec" "github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/store"
sdkstore "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/x/evm/keeper"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmlog "github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
) )
func newTestCodec() *codec.Codec { // nolint: unused
cdc := codec.New() type StateDBTestSuite struct {
suite.Suite
RegisterCodec(cdc) ctx sdk.Context
types.RegisterCodec(cdc) querier sdk.Querier
auth.RegisterCodec(cdc) app *app.EthermintApp
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
} }
func setupStateDB() (*CommitStateDB, error) { func (suite *StateDBTestSuite) SetupTest() {
accKey := sdk.NewKVStoreKey("acc") checkTx := false
storageKey := sdk.NewKVStoreKey(EvmStoreKey)
codeKey := sdk.NewKVStoreKey(EvmCodeKey)
logger := tmlog.NewNopLogger()
db := dbm.NewMemDB() suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1})
// create logger, codec and root multi-store suite.querier = keeper.NewQuerier(suite.app.EvmKeeper)
cdc := newTestCodec()
cms := store.NewCommitMultiStore(db)
// The ParamsKeeper handles parameter storage for the application
keyParams := sdk.NewKVStoreKey(params.StoreKey)
tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey)
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
// Set specific supspaces
authSubspace := paramsKeeper.Subspace(auth.DefaultParamspace)
ak := auth.NewAccountKeeper(cdc, accKey, authSubspace, types.ProtoBaseAccount)
// mount stores
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey}
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil)
} }
cms.SetPruning(sdkstore.PruneNothing) func (suite *StateDBTestSuite) TestBloomFilter() {
stateDB := suite.app.EvmKeeper.CommitStateDB
// load latest version (root)
if err := cms.LoadLatestVersion(); err != nil {
return nil, err
}
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
return NewCommitStateDB(ctx, ak, storageKey, codeKey), nil
}
func TestBloomFilter(t *testing.T) {
stateDB, err := setupStateDB()
require.NoError(t, err)
// Prepare db for logs // Prepare db for logs
tHash := ethcmn.BytesToHash([]byte{0x1}) tHash := ethcmn.BytesToHash([]byte{0x1})
@ -87,14 +47,15 @@ func TestBloomFilter(t *testing.T) {
stateDB.AddLog(&log) stateDB.AddLog(&log)
// Get log from db // Get log from db
logs := stateDB.GetLogs(tHash) logs, err := stateDB.GetLogs(tHash)
require.Equal(t, len(logs), 1) suite.Require().NoError(err)
suite.Require().Equal(len(logs), 1)
// get logs bloom from the log // get logs bloom from the log
bloomInt := ethtypes.LogsBloom(logs) bloomInt := ethtypes.LogsBloom(logs)
bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes()) bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes())
// Check to make sure bloom filter will succeed on // Check to make sure bloom filter will succeed on
require.True(t, ethtypes.BloomLookup(bloomFilter, contractAddress)) suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress))
require.False(t, ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2)))) suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))))
} }

View File

@ -13,11 +13,6 @@ import (
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
const (
bloomIdx = ethcmn.AddressLength
returnIdx = bloomIdx + ethtypes.BloomByteLength
)
// GenerateEthAddress generates an Ethereum address. // GenerateEthAddress generates an Ethereum address.
func GenerateEthAddress() ethcmn.Address { func GenerateEthAddress() ethcmn.Address {
priv, err := crypto.GenerateKey() priv, err := crypto.GenerateKey()
@ -52,23 +47,41 @@ func rlpHash(x interface{}) (hash ethcmn.Hash) {
return hash return hash
} }
// ResultData represents the data returned in an sdk.Result
type ResultData struct {
Address ethcmn.Address
Bloom ethtypes.Bloom
Logs []*ethtypes.Log
Ret []byte
}
// EncodeReturnData takes all of the necessary data from the EVM execution // EncodeReturnData takes all of the necessary data from the EVM execution
// and returns the data as a byte slice // and returns the data as a byte slice encoded with amino
func EncodeReturnData(addr ethcmn.Address, bloom ethtypes.Bloom, evmRet []byte) []byte { func EncodeResultData(data *ResultData) ([]byte, error) {
// Append address, bloom, evm return bytes in that order return ModuleCdc.MarshalBinaryLengthPrefixed(data)
returnData := append(addr.Bytes(), bloom.Bytes()...)
return append(returnData, evmRet...)
} }
// DecodeReturnData decodes the byte slice of values to their respective types // DecodeResultData decodes an amino-encoded byte slice into ReturnData
func DecodeReturnData(bytes []byte) (addr ethcmn.Address, bloom ethtypes.Bloom, ret []byte, err error) { func DecodeResultData(in []byte) (ResultData, error) {
if len(bytes) >= returnIdx { data := new(ResultData)
addr = ethcmn.BytesToAddress(bytes[:bloomIdx]) err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, data)
bloom = ethtypes.BytesToBloom(bytes[bloomIdx:returnIdx]) if err != nil {
ret = bytes[returnIdx:] return ResultData{}, err
} else { }
err = fmt.Errorf("invalid format for encoded data, message must be an EVM state transition") return *data, nil
} }
return // EncodeLogs encodes an array of logs using amino
func EncodeLogs(logs []*ethtypes.Log) ([]byte, error) {
return ModuleCdc.MarshalBinaryLengthPrefixed(logs)
}
// DecodeLogs decodes an amino-encoded byte array into an array of logs
func DecodeLogs(in []byte) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, &logs)
if err != nil {
return nil, err
}
return logs, nil
} }

View File

@ -13,12 +13,23 @@ func TestEvmDataEncoding(t *testing.T) {
bloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) bloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
ret := []byte{0x5, 0x8} ret := []byte{0x5, 0x8}
encoded := EncodeReturnData(addr, bloom, ret) data := &ResultData{
Address: addr,
decAddr, decBloom, decRet, err := DecodeReturnData(encoded) Bloom: bloom,
Logs: []*ethtypes.Log{{
require.NoError(t, err) Data: []byte{1, 2, 3, 4},
require.Equal(t, addr, decAddr) BlockNumber: 17,
require.Equal(t, bloom, decBloom) }},
require.Equal(t, ret, decRet) Ret: ret,
}
enc, err := EncodeResultData(data)
require.NoError(t, err)
res, err := DecodeResultData(enc)
require.NoError(t, err)
require.Equal(t, addr, res.Address)
require.Equal(t, bloom, res.Bloom)
require.Equal(t, data.Logs, res.Logs)
require.Equal(t, ret, res.Ret)
} }