Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Emint tx type for eth_call and logs setup #118

Merged
merged 3 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 52 additions & 68 deletions x/evm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package evm
import (
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"

sdk "github.com/cosmos/cosmos-sdk/types"
authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
emint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"

tm "github.com/tendermint/tendermint/types"
)

// NewHandler returns a handler for Ethermint type messages.
Expand All @@ -20,6 +20,8 @@ func NewHandler(keeper Keeper) sdk.Handler {
switch msg := msg.(type) {
case types.EthereumTxMsg:
return handleETHTxMsg(ctx, keeper, msg)
case types.EmintMsg:
return handleEmintMsg(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized ethermint Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
Expand All @@ -44,82 +46,64 @@ func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk
if err != nil {
return emint.ErrInvalidSender(err.Error()).Result()
}
contractCreation := msg.To() == nil

// Pay intrinsic gas
// TODO: Check config for homestead enabled
cost, err := core.IntrinsicGas(msg.Data.Payload, contractCreation, true)
if err != nil {
return emint.ErrInvalidIntrinsicGas(err.Error()).Result()
st := types.StateTransition{
Sender: sender,
AccountNonce: msg.Data.AccountNonce,
Price: msg.Data.Price,
GasLimit: msg.Data.GasLimit,
Recipient: msg.Data.Recipient,
Amount: msg.Data.Amount,
Payload: msg.Data.Payload,
Csdb: keeper.csdb,
ChainID: intChainID,
}

usableGas := msg.Data.GasLimit - cost

// Create context for evm
context := vm.Context{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Origin: sender,
Coinbase: common.Address{},
BlockNumber: big.NewInt(ctx.BlockHeight()),
Time: big.NewInt(time.Now().Unix()),
Difficulty: big.NewInt(0x30000), // unused
GasLimit: ctx.GasMeter().Limit(),
GasPrice: ctx.MinGasPrices().AmountOf(emint.DenomDefault).Int,
}

vmenv := vm.NewEVM(context, keeper.csdb.WithContext(ctx), types.GenerateChainConfig(intChainID), vm.Config{})

var (
leftOverGas uint64
addr common.Address
vmerr error
senderRef = vm.AccountRef(sender)
)

if contractCreation {
_, addr, leftOverGas, vmerr = vmenv.Create(senderRef, msg.Data.Payload, usableGas, msg.Data.Amount)
} else {
// Increment the nonce for the next transaction
keeper.csdb.SetNonce(sender, keeper.csdb.GetNonce(sender)+1)
_, leftOverGas, vmerr = vmenv.Call(senderRef, *msg.To(), msg.Data.Payload, usableGas, msg.Data.Amount)
}

// handle errors
if vmerr != nil {
return emint.ErrVMExecution(vmerr.Error()).Result()
// Encode transaction by default Tx encoder
txEncoder := authutils.GetTxEncoder(types.ModuleCdc)
txBytes, err := txEncoder(msg)
if err != nil {
return sdk.ErrInternal(err.Error()).Result()
}
txHash := tm.Tx(txBytes).Hash()

// Refund remaining gas from tx (Check these values and ensure gas is being consumed correctly)
refundGas(keeper.csdb, &leftOverGas, msg.Data.GasLimit, context.GasPrice, sender)
// Prepare db for logs
keeper.csdb.Prepare(common.BytesToHash(txHash), common.Hash{}, keeper.txCount.get())
keeper.txCount.increment()

// add balance for the processor of the tx (determine who rewards are being processed to)
// TODO: Double check nothing needs to be done here
return st.TransitionCSDB(ctx)
}

keeper.csdb.Finalise(true) // Change to depend on config
func handleEmintMsg(ctx sdk.Context, keeper Keeper, msg types.EmintMsg) sdk.Result {
if err := msg.ValidateBasic(); err != nil {
return err.Result()
}

// TODO: Consume gas from sender
// parse the chainID from a string to a base-10 integer
intChainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
if !ok {
return emint.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())).Result()
}

return sdk.Result{Data: addr.Bytes(), GasUsed: msg.Data.GasLimit - leftOverGas}
}
st := types.StateTransition{
Sender: common.BytesToAddress(msg.From.Bytes()),
AccountNonce: msg.AccountNonce,
Price: msg.Price.BigInt(),
GasLimit: msg.GasLimit,
Amount: msg.Amount.BigInt(),
Payload: msg.Payload,
Csdb: keeper.csdb,
ChainID: intChainID,
}

func refundGas(
st vm.StateDB, gasRemaining *uint64, initialGas uint64, gasPrice *big.Int,
from common.Address,
) {
// Apply refund counter, capped to half of the used gas.
refund := (initialGas - *gasRemaining) / 2
if refund > st.GetRefund() {
refund = st.GetRefund()
if msg.Recipient != nil {
to := common.BytesToAddress(msg.Recipient.Bytes())
st.Recipient = &to
}
*gasRemaining += refund

// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(*gasRemaining), gasPrice)
st.AddBalance(from, remaining)
// Prepare db for logs
keeper.csdb.Prepare(common.Hash{}, common.Hash{}, keeper.txCount.get()) // Cannot provide tx hash
keeper.txCount.increment()

// // Also return remaining gas to the block gas counter so it is
// // available for the next transaction.
// TODO: Return gas to block gas meter?
// st.gp.AddGas(st.gas)
return st.TransitionCSDB(ctx)
}
18 changes: 17 additions & 1 deletion x/evm/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ type Keeper struct {
csdb *types.CommitStateDB
cdc *codec.Codec
blockKey sdk.StoreKey
txCount *count
}

type count int

func (c *count) get() int {
return (int)(*c)
}

func (c *count) increment() {
*c = *c + 1
}

func (c *count) reset() {
*c = 0
}

// NewKeeper generates new evm module keeper
Expand All @@ -32,6 +47,7 @@ func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey,
csdb: types.NewCommitStateDB(sdk.Context{}, ak, storageKey, codeKey),
cdc: cdc,
blockKey: blockKey,
txCount: new(count),
}
}

Expand All @@ -53,7 +69,7 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64
store := ctx.KVStore(k.blockKey)
bz := store.Get(hash)
if bytes.Equal(bz, []byte{}) {
panic(fmt.Errorf("block with hash %s not found", ethcmn.Bytes2Hex(hash)))
panic(fmt.Errorf("block with hash %s not found", ethcmn.BytesToHash(hash)))
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &height)
return
Expand Down
1 change: 1 addition & 0 deletions x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func (am AppModule) NewQuerierHandler() sdk.Querier {
func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) {
// Consider removing this when using evm as module without web3 API
am.keeper.SetBlockHashMapping(ctx, bl.Header.LastBlockId.GetHash(), bl.Header.GetHeight()-1)
am.keeper.txCount.reset()
}

// EndBlock function for module at end of block
Expand Down
2 changes: 2 additions & 0 deletions x/evm/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/cosmos/ethermint/crypto"
)

// ModuleCdc defines the codec to be used by evm module
var ModuleCdc = codec.New()

func init() {
Expand All @@ -19,5 +20,6 @@ func init() {
// RegisterCodec registers concrete types and interfaces on the given codec.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&EthereumTxMsg{}, "ethermint/MsgEthereumTx", nil)
cdc.RegisterConcrete(&EmintMsg{}, "ethermint/MsgEmint", nil)
crypto.RegisterCodec(cdc)
}
88 changes: 88 additions & 0 deletions x/evm/types/emint_msg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package types

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)

var (
_ sdk.Msg = EmintMsg{}
)

const (
// TypeEmintMsg defines the type string of Emint message
TypeEmintMsg = "emint_tx"
)

// EmintMsg implements a cosmos equivalent structure for Ethereum transactions
type EmintMsg struct {
AccountNonce uint64 `json:"nonce"`
Price sdk.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *sdk.AccAddress `json:"to" rlp:"nil"` // nil means contract creation
Amount sdk.Int `json:"value"`
Payload []byte `json:"input"`

// From address (formerly derived from signature)
From sdk.AccAddress `json:"from"`
}

// NewEmintMsg returns a reference to a new Ethermint transaction
func NewEmintMsg(
nonce uint64, to *sdk.AccAddress, amount sdk.Int,
gasLimit uint64, gasPrice sdk.Int, payload []byte, from sdk.AccAddress,
) EmintMsg {
return EmintMsg{
AccountNonce: nonce,
Price: gasPrice,
GasLimit: gasLimit,
Recipient: to,
Amount: amount,
Payload: payload,
From: from,
}
}

// Route should return the name of the module
func (msg EmintMsg) Route() string { return RouterKey }

// Type returns the action of the message
func (msg EmintMsg) Type() string { return TypeEmintMsg }

// GetSignBytes encodes the message for signing
func (msg EmintMsg) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
}

// ValidateBasic runs stateless checks on the message
func (msg EmintMsg) ValidateBasic() sdk.Error {
if msg.Price.Sign() != 1 {
return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Price))
}

// Amount can be 0
if msg.Amount.Sign() == -1 {
return types.ErrInvalidValue(fmt.Sprintf("amount cannot be negative: %x", msg.Amount))
}

return nil
}

// GetSigners defines whose signature is required
func (msg EmintMsg) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.From}
}

// To returns the recipient address of the transaction. It returns nil if the
// transaction is a contract creation.
func (msg EmintMsg) To() *ethcmn.Address {
if msg.Recipient == nil {
return nil
}

addr := ethcmn.BytesToAddress(msg.Recipient.Bytes())
return &addr
}
76 changes: 76 additions & 0 deletions x/evm/types/emint_msg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package types

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/secp256k1"
)

func TestEmintMsg(t *testing.T) {
addr := newSdkAddress()
fromAddr := newSdkAddress()

msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)
require.NotNil(t, msg)
require.Equal(t, msg.Recipient, &addr)

require.Equal(t, msg.Route(), RouterKey)
require.Equal(t, msg.Type(), TypeEmintMsg)
}

func TestEmintMsgValidation(t *testing.T) {
testCases := []struct {
nonce uint64
to *sdk.AccAddress
amount sdk.Int
gasLimit uint64
gasPrice sdk.Int
payload []byte
expectPass bool
from sdk.AccAddress
}{
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(100000), expectPass: true},
{amount: sdk.NewInt(0), gasPrice: sdk.NewInt(100000), expectPass: true},
{amount: sdk.NewInt(-1), gasPrice: sdk.NewInt(100000), expectPass: false},
{amount: sdk.NewInt(100), gasPrice: sdk.NewInt(-1), expectPass: false},
}

for i, tc := range testCases {
msg := NewEmintMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload, tc.from)

if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", i)
}
}
}

func TestEmintEncodingAndDecoding(t *testing.T) {
addr := newSdkAddress()
fromAddr := newSdkAddress()

msg := NewEmintMsg(0, &addr, sdk.NewInt(1), 100000, sdk.NewInt(2), []byte("test"), fromAddr)

raw, err := cdc.MarshalBinaryBare(msg)
require.NoError(t, err)

var msg2 EmintMsg
err = cdc.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err)

require.Equal(t, msg.AccountNonce, msg2.AccountNonce)
require.Equal(t, msg.Recipient, msg2.Recipient)
require.Equal(t, msg.Amount, msg2.Amount)
require.Equal(t, msg.GasLimit, msg2.GasLimit)
require.Equal(t, msg.Price, msg2.Price)
require.Equal(t, msg.Payload, msg2.Payload)
require.Equal(t, msg.From, msg2.From)
}

func newSdkAddress() sdk.AccAddress {
tmpKey := secp256k1.GenPrivKey().PubKey()
return sdk.AccAddress(tmpKey.Address().Bytes())
}
Loading