laconicd/x/evm/handler.go
Austin Abell 1b5c33cf33
EVM Transaction handler and contract creation (#96)
* WIP implementing state transition function

* Error handling and application setup fix

* Fixed error comment

* Allow creation of state objects with a BaseAccount

* Fixed parameters and finalise state after transaction

* updated transaction signing and cli signature

* Set up consistent account encoding and decoding

* Update txbuilder to get sequence before generating eth tx

* Added create functionality to the CLI command

* Remove need to copy over context for statedb interactions

* Updated account retriever

* Cleaned up handler code and updated TODO

* Make recoverEthSig private again

* Add error check for committing to kv store

* Remove commented out code

* Update evm chain config for state transition

* Add time in context for dapps
2019-09-18 09:51:18 -04:00

132 lines
4.0 KiB
Go

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"
emint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
// NewHandler returns a handler for Ethermint type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case types.EthereumTxMsg:
return handleETHTxMsg(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized ethermint Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle an Ethereum specific tx
func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk.Result {
if err := msg.ValidateBasic(); err != nil {
return err.Result()
}
// 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()
}
// Verify signature and retrieve sender address
sender, err := msg.VerifySig(intChainID)
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()
}
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()
}
// 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)
// add balance for the processor of the tx (determine who rewards are being processed to)
// TODO: Double check nothing needs to be done here
keeper.csdb.Finalise(true) // Change to depend on config
// TODO: Remove commit from tx handler (should be done at end of block)
_, err = keeper.csdb.Commit(true)
if err != nil {
return sdk.ErrUnknownRequest("Failed to write data to kv store").Result()
}
// TODO: Consume gas from sender
return sdk.Result{Log: addr.Hex(), GasUsed: msg.Data.GasLimit - leftOverGas}
}
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()
}
*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)
// // 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)
}