forked from cerc-io/laconicd-deprecated
update fork
This commit is contained in:
+70
-21
@@ -6,32 +6,87 @@ order: 1
|
||||
|
||||
## EVM
|
||||
|
||||
The Ethereum Virtual Machine (EVM) is a computation engine which can be thought of as one single entity maintained by thousands of connected computers running an Ethereum client. It is considered to be the part of the Ethereum protocol that handles the deployment and execution of [smart contracts](https://ethereum.org/en/developers/docs/smart-contracts/).
|
||||
The Ethereum Virtual Machine (EVM) is a computation engine which can be thought of as one single entity maintained by thousands of connected computers (nodes) running an Ethereum client. As a virtual machine ([VM](https://en.wikipedia.org/wiki/Virtual_machine)), the EVM is responisble for computing changes to the state deterministically regardless of its environment (hardware and OS). This means that every node has to get the exact same result given an identical starting state and transaction (tx).
|
||||
|
||||
To make a clear distinction: The Ethereum protocol describes a blockchain, in which all Ethereum accounts and smart contracts live. It has only one canonical state (a data structure, which keeps all accounts) at any given block in the chain. The EVM, however, is the [state machine](https://en.wikipedia.org/wiki/Finite-state_machine) that defines the rules for computing a new valid state from block to block. It is an isolated runtime, which means that code running inside the EVM has no access to network, filesystem, or other processes.
|
||||
The EVM is considered to be the part of the Ethereum protocol that handles the deployment and execution of [smart contracts](https://ethereum.org/en/developers/docs/smart-contracts/). To make a clear distinction:
|
||||
|
||||
The `x/evm` module implements the EVM as a Cosmos SDK module. It allows users to interact with the EVM by submitting Ethereum transactions and executing their containing messages on the given state to evoke a state transition.
|
||||
* The Ethereum protocol describes a blockchain, in which all Ethereum accounts and smart contracts live. It has only one canonical state (a data structure, which keeps all accounts) at any given block in the chain.
|
||||
* The EVM, however, is the [state machine](https://en.wikipedia.org/wiki/Finite-state_machine) that defines the rules for computing a new valid state from block to block. It is an isolated runtime, which means that code running inside the EVM has no access to network, filesystem, or other processes (not external APIs).
|
||||
|
||||
### State Transition with Smart Contracts
|
||||
The `x/evm` module implements the EVM as a Cosmos SDK module. It allows users to interact with the EVM by submitting Ethereum txs and executing their containing messages on the given state to evoke a state transition.
|
||||
|
||||
A state transition on the EVM can be initiated through a transaction that either deploys or calls a smart contract.
|
||||
### State
|
||||
|
||||
Smart contracts are just like regular accounts on the blockchain, which additionally store executable code in an Ethereum-specific binary format (EVM bytecode). They are typically written in an Ethereum high level language, compiled into byte code using an EVM compiler, and finally deployed on the blockchain, by submitting a transaction using an Ethereum client. Whenever another account makes a message call to that deployed contract, it executes its EVM bytecode to perform calculations and further transactions.
|
||||
The Ethereum state is a data structure, implemented as a [Merkle Patricia Trie](https://en.wikipedia.org/wiki/Merkle_tree), that keeps all accounts on the chain. The EVM makes changes to this data structure resulting in a new state with a different State Root. Ethereum can therefore be seen as a state chain that transitions from one state to another by executing transations in a block using the EVM. A new block of txs can be described through its Block header (parent hash, block number, time stamp, nonce, receipts,...).
|
||||
|
||||
### Opcodes
|
||||
### Accounts
|
||||
|
||||
The EVM operates as a stack-based machine, where transactions carry a payload of Opcodes, that are used to specify the interaction with a smart contract.
|
||||
There are two types of accounts that can be stored in state at a given address:
|
||||
|
||||
Typically contracts expose a public ABI, which is a list of supported ways a user can interact with a contract. To interact with a contract, a user will submit a transaction carrying any amount of wei (including 0) and a data payload formatted according to the ABI, specifying the type of interaction and any additional parameters. Each Opcode execution requires gas that needs to be payed with the transaction. The EVM is therefore considered quasi-turing complete, as it allows any arbitrary computation, but the amount of computations during a contract execution is limited to the amount gas provided in the transaction.
|
||||
* **Externally Owned Account (EOA)**: Has nonce (tx counter) and balance
|
||||
* **Smart Contract**: Has nonce, balance, (immutable) code hash, storage root (another Merkle Patricia Trie)
|
||||
|
||||
Smart contracts are just like regular accounts on the blockchain, which additionally store executable code in an Ethereum-specific binary format, known as **EVM bytecode**. They are typically written in an Ethereum high level language such as Solidity which is compiled down to EVM bytecode and deployed on the blockchain, by submitting a tx using an Ethereum client.
|
||||
|
||||
### Architecture
|
||||
|
||||
The EVM operates as a stack-based machine. It's main architecture components consist of:
|
||||
|
||||
* Virtual ROM: contract code is pulled into this read only memory when processing txs
|
||||
* Machine state (volatile): changes as the EVM runs and is wiped clean after processing each tx
|
||||
* Program counter (PC)
|
||||
* Gas: keeps track of how much gas is used
|
||||
* Stack and Memory: compute state changes
|
||||
* Access to account storage (persistent)
|
||||
|
||||
### State Transitions with Smart Contracts
|
||||
|
||||
Typically smart contracts expose a public ABI, which is a list of supported ways a user can interact with a contract. To interact with a contract and invoke a state transition, a user will submit a tx carrying any amount of gas and a data payload formatted according to the ABI, specifying the type of interaction and any additional parameters. When the tx is received, the EVM executes the smart contracts's EVM bytecode using the tx payload.
|
||||
|
||||
### Executing EVM bytecode
|
||||
|
||||
A contract's EVM bytecode consists of basic operations (add, multiply, store, etc...), called **Opcodes**. Each Opcode execution requires gas that needs to be payed with the tx. The EVM is therefore considered quasi-turing complete, as it allows any arbitrary computation, but the amount of computations during a contract execution is limited to the amount of gas provided in the tx. Each Opcode's [**gas cost**](https://www.evm.codes/) reflects the cost of running these operations on actual computer hardware (e.g. `ADD = 3gas` and `SSTORE = 100gas`). To calculate the gas consumption of a tx, the gas cost is multiplied by the **gas price**, which can change depending on the demand of the network at the time. If the network is under heavy load, you might have to pay a highter gas price to get your tx executed. If the gas limit is hit (out of gas execption) no changes to the Ethereum state are applied, except that the sender's nonce increments and their balance goes down to pay for wasting the EVM's time.
|
||||
|
||||
Smart contracts can also call other smart contracts. Each call to a new contract creates a new instance of the EVM (including a new stack and memory). Each call passes the sandbox state to the next EVM. If the gas runs out, all state changes are discareded. Otherwise they are kept.
|
||||
|
||||
For further reading, please refer to:
|
||||
|
||||
- [EVM](https://eth.wiki/concepts/evm/evm)
|
||||
- [EVM Architecture](https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture)
|
||||
- [What is Ethereum](https://ethdocs.org/en/latest/introduction/what-is-ethereum.html#what-is-ethereum)
|
||||
- [Opcodes](https://www.ethervm.io/)
|
||||
* [EVM](https://eth.wiki/concepts/evm/evm)
|
||||
* [EVM Architecture](https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture)
|
||||
* [What is Ethereum](https://ethdocs.org/en/latest/introduction/what-is-ethereum.html#what-is-ethereum)
|
||||
* [Opcodes](https://www.ethervm.io/)
|
||||
|
||||
## StateDB
|
||||
## Ethermint as Geth implementation
|
||||
|
||||
Ethermint is an implementation of the [Etherum protocal in Golang](https://geth.ethereum.org/docs/getting-started) (Geth) as a Cosmos SDK module. Geth includes an implementation of the EVM to compute state transitions. Have a look at the [go-etheruem source code](https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go) to see how the EVM opcodes are implemented. Just as Geth can be run as an Ethereum node, Ethermint can be run as a node to compute state transitions with the EVM. Ethermint supports Geth's standard [Ethereum JSON-RPC APIs](https://docs.evmos.org/developers/json-rpc/endpoints.html) in order to be Web3 and EVM compatible.
|
||||
|
||||
### JSON-RPC
|
||||
|
||||
JSON-RPC is a stateless, lightweight remote procedure call (RPC) protocol. Primarily this specification defines several data structures and the rules around their processing. It is transport agnostic in that the concepts can be used within the same process, over sockets, over HTTP, or in many various message passing environments. It uses JSON (RFC 4627) as a data format.
|
||||
|
||||
#### JSON-RPC Example: `eth_call`
|
||||
|
||||
The JSON-RPC method [`eth_call`](https://docs.evmos.org/developers/json-rpc/endpoints.html#eth-call) allows you to execute messages against contracts. Usually, you need to send a transaction to a Geth node to include it in the mempool, then nodes gossip between each other and eventually the transaction is included in a block and gets executed. `eth_call` however lets you send data to a contract and see what happens without commiting a transaction.
|
||||
|
||||
In the Geth implementation, calling the endpoint roughly goes through the following steps:
|
||||
|
||||
1. The `eth_call` request is transformed to call the `func (s *PublicBlockchainAPI) Call()` function using the `eth` namespace
|
||||
2. [`Call()`](https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L982) is given the transaction arguments, the block to call against and optional overides that modify the state to call against. It then calls `DoCall()`
|
||||
3. [`DoCall()`](https://github.com/ethereum/go-ethereum/blob/d575a2d3bc76dfbdefdd68b6cffff115542faf75/internal/ethapi/api.go#L891) transforms the arguments into a `ethtypes.message`, instantiates an EVM and applies the message with `core.ApplyMessage`
|
||||
4. [`ApplyMessage()`](https://github.com/ethereum/go-ethereum/blob/d575a2d3bc76dfbdefdd68b6cffff115542faf75/core/state_transition.go#L180) calls the state transition `TransitionDb()`
|
||||
5. [`TransitionDb()`](https://github.com/ethereum/go-ethereum/blob/d575a2d3bc76dfbdefdd68b6cffff115542faf75/core/state_transition.go#L275) either `Create()`s a new contract or `Call()`s a contract
|
||||
6. [`evm.Call()`](https://github.com/ethereum/go-ethereum/blob/d575a2d3bc76dfbdefdd68b6cffff115542faf75/core/vm/evm.go#L168) runs the interpreter `evm.interpreter.Run()` to execute the message. If the execution fails, the state is reverted to a snapshot taken before the execution and gas is consumed.
|
||||
7. [`Run()`](https://github.com/ethereum/go-ethereum/blob/d575a2d3bc76dfbdefdd68b6cffff115542faf75/core/vm/interpreter.go#L116) performs a loop to execute the opcodes.
|
||||
|
||||
The ethermint implementatiom is similar and makes use of the gRPC query client which is included in the Cosmos SDK:
|
||||
|
||||
1. `eth_call` request is transformed to call the `func (e *PublicAPI) Call` function using the `eth` namespace
|
||||
2. [`Call()`](https://github.com/evmos/ethermint/blob/main/rpc/namespaces/ethereum/eth/api.go#L639) calls `doCall()`
|
||||
3. [`doCall()`](https://github.com/evmos/ethermint/blob/main/rpc/namespaces/ethereum/eth/api.go#L656) transforms the arguments into a `EthCallRequest` and calls `EthCall()` using the query client of the evm module.
|
||||
4. [`EthCall()`](https://github.com/evmos/ethermint/blob/main/x/evm/keeper/grpc_query.go#L212) transforms the arguments into a `ethtypes.message` and calls `ApplyMessageWithConfig()
|
||||
5. [`ApplyMessageWithConfig()`](https://github.com/evmos/ethermint/blob/d5598932a7f06158b7a5e3aa031bbc94eaaae32c/x/evm/keeper/state_transition.go#L341) instantiates an EVM and either `Create()`s a new contract or `Call()`s a contract using the Geth implementation.
|
||||
|
||||
### StateDB
|
||||
|
||||
The `StateDB` interface from [go-ethereum](https://github.com/ethereum/go-ethereum/blob/master/core/vm/interface.go) represents an EVM database for full state querying. EVM state transitions are enabled by this interface, which in the `x/evm` module is implemented by the `Keeper`. The implementation of this interface is what makes Ethermint EVM compatible.
|
||||
|
||||
@@ -41,12 +96,6 @@ The application using the `x/evm` module interacts with the Tendermint Core Cons
|
||||
|
||||
Ethereum transactions that are submitted to the `x/evm` module take part in a this consensus process before being executed and changing the application state. We encourage to understand the basics of the [Tendermint consensus engine](https://docs.tendermint.com/master/introduction/what-is-tendermint.html#intro-to-abci) in order to understand state transitions in detail.
|
||||
|
||||
## JSON-RPC
|
||||
|
||||
JSON-RPC is a stateless, lightweight remote procedure call (RPC) protocol. Primarily this specification defines several data structures and the rules around their processing. It is transport agnostic in that the concepts can be used within the same process, over sockets, over HTTP, or in many various message passing environments. It uses JSON (RFC 4627) as a data format.
|
||||
|
||||
Ethermint supports all standard web3 [JSON-RPC](https://evmos.dev/api/json-rpc/server.html) APIs. For more info check the client section.
|
||||
|
||||
## Transaction Logs
|
||||
|
||||
On every `x/evm` transaction, the result contains the Ethereum `Log`s from the state machine execution that are used by the JSON-RPC Web3 server for filter querying and for processing the EVM Hooks.
|
||||
@@ -59,4 +108,4 @@ Bloom is the bloom filter value in bytes for each block that can be used for fil
|
||||
|
||||
::: tip
|
||||
👉 **Note**: Since they are not stored on state, Transaction Logs and Block Blooms are not persisted after upgrades. A user must use an archival node after upgrades in order to obtain legacy chain events.
|
||||
:::
|
||||
:::
|
||||
|
||||
+71
-71
@@ -28,55 +28,55 @@ The `StateDB` interface is implemented by the `StateDB` in the `x/evm/statedb` m
|
||||
```go
|
||||
// github.com/ethereum/go-ethereum/core/vm/interface.go
|
||||
type StateDB interface {
|
||||
CreateAccount(common.Address)
|
||||
CreateAccount(common.Address)
|
||||
|
||||
SubBalance(common.Address, *big.Int)
|
||||
AddBalance(common.Address, *big.Int)
|
||||
GetBalance(common.Address) *big.Int
|
||||
SubBalance(common.Address, *big.Int)
|
||||
AddBalance(common.Address, *big.Int)
|
||||
GetBalance(common.Address) *big.Int
|
||||
|
||||
GetNonce(common.Address) uint64
|
||||
SetNonce(common.Address, uint64)
|
||||
GetNonce(common.Address) uint64
|
||||
SetNonce(common.Address, uint64)
|
||||
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCode(common.Address) []byte
|
||||
SetCode(common.Address, []byte)
|
||||
GetCodeSize(common.Address) int
|
||||
GetCodeHash(common.Address) common.Hash
|
||||
GetCode(common.Address) []byte
|
||||
SetCode(common.Address, []byte)
|
||||
GetCodeSize(common.Address) int
|
||||
|
||||
AddRefund(uint64)
|
||||
SubRefund(uint64)
|
||||
GetRefund() uint64
|
||||
AddRefund(uint64)
|
||||
SubRefund(uint64)
|
||||
GetRefund() uint64
|
||||
|
||||
GetCommittedState(common.Address, common.Hash) common.Hash
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
SetState(common.Address, common.Hash, common.Hash)
|
||||
GetCommittedState(common.Address, common.Hash) common.Hash
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
SetState(common.Address, common.Hash, common.Hash)
|
||||
|
||||
Suicide(common.Address) bool
|
||||
HasSuicided(common.Address) bool
|
||||
Suicide(common.Address) bool
|
||||
HasSuicided(common.Address) bool
|
||||
|
||||
// Exist reports whether the given account exists in state.
|
||||
// Notably this should also return true for suicided accounts.
|
||||
Exist(common.Address) bool
|
||||
// Empty returns whether the given account is empty. Empty
|
||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||
Empty(common.Address) bool
|
||||
// Exist reports whether the given account exists in state.
|
||||
// Notably this should also return true for suicided accounts.
|
||||
Exist(common.Address) bool
|
||||
// Empty returns whether the given account is empty. Empty
|
||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||
Empty(common.Address) bool
|
||||
|
||||
PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddAddressToAccessList(addr common.Address)
|
||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||
PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddAddressToAccessList(addr common.Address)
|
||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||
|
||||
RevertToSnapshot(int)
|
||||
Snapshot() int
|
||||
RevertToSnapshot(int)
|
||||
Snapshot() int
|
||||
|
||||
AddLog(*types.Log)
|
||||
AddPreimage(common.Hash, []byte)
|
||||
AddLog(*types.Log)
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
|
||||
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
|
||||
}
|
||||
```
|
||||
|
||||
@@ -137,10 +137,10 @@ marked as suicided.
|
||||
Supports a transaction type that contains an [access list](https://eips.ethereum.org/EIPS/eip-2930), a list of addresses, and storage keys that the transaction plans to access. The access list state is kept in memory and discarded after the transaction committed.
|
||||
|
||||
- `PrepareAccessList()` handles the preparatory steps for executing a state transition with regards to both EIP-2929 and EIP-2930. This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
|
||||
- Add sender to access list (EIP-2929)
|
||||
- Add destination to access list (EIP-2929)
|
||||
- Add precompiles to access list (EIP-2929)
|
||||
- Add the contents of the optional tx access list (EIP-2930)
|
||||
- Add sender to access list (EIP-2929)
|
||||
- Add destination to access list (EIP-2929)
|
||||
- Add precompiles to access list (EIP-2929)
|
||||
- Add the contents of the optional tx access list (EIP-2930)
|
||||
- `AddressInAccessList()` returns true if the address is registered.
|
||||
- `SlotInAccessList()` checks if the address and the slots are registered.
|
||||
- `AddAddressToAccessList()` adds the given address to the access list. If the address is already in the access list, this function performs a no-op.
|
||||
@@ -173,40 +173,40 @@ To support the interface functionality, it imports 4 module Keepers:
|
||||
|
||||
```go
|
||||
type Keeper struct {
|
||||
// Protobuf codec
|
||||
cdc codec.BinaryCodec
|
||||
// Store key required for the EVM Prefix KVStore. It is required by:
|
||||
// - storing account's Storage State
|
||||
// - storing account's Code
|
||||
// - storing Bloom filters by block height. Needed for the Web3 API.
|
||||
// For the full list, check the module specification
|
||||
storeKey sdk.StoreKey
|
||||
// Protobuf codec
|
||||
cdc codec.BinaryCodec
|
||||
// Store key required for the EVM Prefix KVStore. It is required by:
|
||||
// - storing account's Storage State
|
||||
// - storing account's Code
|
||||
// - storing Bloom filters by block height. Needed for the Web3 API.
|
||||
// For the full list, check the module specification
|
||||
storeKey sdk.StoreKey
|
||||
|
||||
// key to access the transient store, which is reset on every block during Commit
|
||||
transientKey sdk.StoreKey
|
||||
// key to access the transient store, which is reset on every block during Commit
|
||||
transientKey sdk.StoreKey
|
||||
|
||||
// module specific parameter space that can be configured through governance
|
||||
paramSpace paramtypes.Subspace
|
||||
// access to account state
|
||||
accountKeeper types.AccountKeeper
|
||||
// update balance and accounting operations with coins
|
||||
bankKeeper types.BankKeeper
|
||||
// access historical headers for EVM state transition execution
|
||||
stakingKeeper types.StakingKeeper
|
||||
// fetch EIP1559 base fee and parameters
|
||||
feeMarketKeeper types.FeeMarketKeeper
|
||||
// module specific parameter space that can be configured through governance
|
||||
paramSpace paramtypes.Subspace
|
||||
// access to account state
|
||||
accountKeeper types.AccountKeeper
|
||||
// update balance and accounting operations with coins
|
||||
bankKeeper types.BankKeeper
|
||||
// access historical headers for EVM state transition execution
|
||||
stakingKeeper types.StakingKeeper
|
||||
// fetch EIP1559 base fee and parameters
|
||||
feeMarketKeeper types.FeeMarketKeeper
|
||||
|
||||
// chain ID number obtained from the context's chain id
|
||||
eip155ChainID *big.Int
|
||||
// chain ID number obtained from the context's chain id
|
||||
eip155ChainID *big.Int
|
||||
|
||||
// Tracer used to collect execution traces from the EVM transaction execution
|
||||
tracer string
|
||||
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
|
||||
// For more info check https://geth.ethereum.org/docs/dapp/tracing
|
||||
debug bool
|
||||
// Tracer used to collect execution traces from the EVM transaction execution
|
||||
tracer string
|
||||
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
|
||||
// For more info check https://geth.ethereum.org/docs/dapp/tracing
|
||||
debug bool
|
||||
|
||||
// EVM Hooks for tx post-processing
|
||||
hooks types.EvmHooks
|
||||
// EVM Hooks for tx post-processing
|
||||
hooks types.EvmHooks
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -41,17 +41,17 @@ The `anteHandler` is run for every transaction. It checks if the `Tx` is an Ethe
|
||||
- `EthValidateBasicDecorator(evmKeeper)` validates the fields of a Ethereum type Cosmos `Tx` msg
|
||||
- `EthSigVerificationDecorator(evmKeeper)` validates that the registered chain id is the same as the one on the message, and that the signer address matches the one defined on the message. It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work. Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user won't see the error message.
|
||||
- `EthAccountVerificationDecorator(ak, bankKeeper, evmKeeper)` that the sender balance is greater than the total transaction cost. The account will be set to store if it doesn't exist, i.e cannot be found on store. This AnteHandler decorator will fail if:
|
||||
- any of the msgs is not a MsgEthereumTx
|
||||
- from address is empty
|
||||
- account balance is lower than the transaction cost
|
||||
- any of the msgs is not a MsgEthereumTx
|
||||
- from address is empty
|
||||
- account balance is lower than the transaction cost
|
||||
- `EthNonceVerificationDecorator(ak)` validates that the transaction nonces are valid and equivalent to the sender account’s current nonce.
|
||||
- `EthGasConsumeDecorator(evmKeeper)` validates that the Ethereum tx message has enough to cover intrinsic gas (during CheckTx only) and that the sender has enough balance to pay for the gas cost. Intrinsic gas for a transaction is the amount of gas that the transaction uses before the transaction is executed. The gas is a constant value plus any cost incurred by additional bytes of data supplied with the transaction. This AnteHandler decorator will fail if:
|
||||
- the transaction contains more than one message
|
||||
- the message is not a MsgEthereumTx
|
||||
- sender account cannot be found
|
||||
- transaction's gas limit is lower than the intrinsic gas
|
||||
- user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
|
||||
- transaction or block gas meter runs out of gas
|
||||
- the transaction contains more than one message
|
||||
- the message is not a MsgEthereumTx
|
||||
- sender account cannot be found
|
||||
- transaction's gas limit is lower than the intrinsic gas
|
||||
- user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
|
||||
- transaction or block gas meter runs out of gas
|
||||
- `CanTransferDecorator(evmKeeper, feeMarketKeeper)` creates an EVM from the message and calls the BlockContext CanTransfer function to see if the address can execute the transaction.
|
||||
- `EthIncrementSenderSequenceDecorator(ak)` handles incrementing the sequence of the signer (i.e sender). If the transaction is a contract creation, the nonce will be incremented during the transaction execution and not within this AnteHandler decorator.
|
||||
|
||||
|
||||
+112
-112
@@ -12,16 +12,16 @@ An EVM state transition can be achieved by using the `MsgEthereumTx`. This messa
|
||||
|
||||
```go
|
||||
type MsgEthereumTx struct {
|
||||
// inner transaction data
|
||||
Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// encoded storage size of the transaction
|
||||
Size_ float64 `protobuf:"fixed64,2,opt,name=size,proto3" json:"-"`
|
||||
// transaction hash in hex format
|
||||
Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty" rlp:"-"`
|
||||
// ethereum signer address in hex format. This address value is checked
|
||||
// against the address derived from the signature (V, R, S) using the
|
||||
// secp256k1 elliptic curve
|
||||
From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"`
|
||||
// inner transaction data
|
||||
Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// DEPRECATED: encoded storage size of the transaction
|
||||
Size_ float64 `protobuf:"fixed64,2,opt,name=size,proto3" json:"-"`
|
||||
// transaction hash in hex format
|
||||
Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty" rlp:"-"`
|
||||
// ethereum signer address in hex format. This address value is checked
|
||||
// against the address derived from the signature (V, R, S) using the
|
||||
// secp256k1 elliptic curve
|
||||
From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
@@ -46,35 +46,35 @@ The `MsgEthreumTx` can be converted to the go-ethereum `Transaction` and `Messag
|
||||
```go
|
||||
// AsTransaction creates an Ethereum Transaction type from the msg fields
|
||||
func (msg MsgEthereumTx) AsTransaction() *ethtypes.Transaction {
|
||||
txData, err := UnpackTxData(msg.Data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
txData, err := UnpackTxData(msg.Data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ethtypes.NewTx(txData.AsEthereumData())
|
||||
return ethtypes.NewTx(txData.AsEthereumData())
|
||||
}
|
||||
|
||||
// AsMessage returns the transaction as a core.Message.
|
||||
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
|
||||
msg := Message{
|
||||
nonce: tx.Nonce(),
|
||||
gasLimit: tx.Gas(),
|
||||
gasPrice: new(big.Int).Set(tx.GasPrice()),
|
||||
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
|
||||
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
|
||||
to: tx.To(),
|
||||
amount: tx.Value(),
|
||||
data: tx.Data(),
|
||||
accessList: tx.AccessList(),
|
||||
isFake: false,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
|
||||
}
|
||||
var err error
|
||||
msg.from, err = Sender(s, tx)
|
||||
return msg, err
|
||||
msg := Message{
|
||||
nonce: tx.Nonce(),
|
||||
gasLimit: tx.Gas(),
|
||||
gasPrice: new(big.Int).Set(tx.GasPrice()),
|
||||
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
|
||||
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
|
||||
to: tx.To(),
|
||||
amount: tx.Value(),
|
||||
data: tx.Data(),
|
||||
accessList: tx.AccessList(),
|
||||
isFake: false,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
|
||||
}
|
||||
var err error
|
||||
msg.from, err = Sender(s, tx)
|
||||
return msg, err
|
||||
}
|
||||
```
|
||||
|
||||
@@ -91,26 +91,26 @@ In order for the signature verification to be valid, the `TxData` must contain
|
||||
// The function will fail if the sender address is not defined for the msg or if
|
||||
// the sender is not registered on the keyring
|
||||
func (msg *MsgEthereumTx) Sign(ethSigner ethtypes.Signer, keyringSigner keyring.Signer) error {
|
||||
from := msg.GetFrom()
|
||||
if from.Empty() {
|
||||
return fmt.Errorf("sender address not defined for message")
|
||||
}
|
||||
from := msg.GetFrom()
|
||||
if from.Empty() {
|
||||
return fmt.Errorf("sender address not defined for message")
|
||||
}
|
||||
|
||||
tx := msg.AsTransaction()
|
||||
txHash := ethSigner.Hash(tx)
|
||||
tx := msg.AsTransaction()
|
||||
txHash := ethSigner.Hash(tx)
|
||||
|
||||
sig, _, err := keyringSigner.SignByAddress(from, txHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig, _, err := keyringSigner.SignByAddress(from, txHash.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err = tx.WithSignature(ethSigner, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx, err = tx.WithSignature(ethSigner, sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg.FromEthereumTx(tx)
|
||||
return nil
|
||||
msg.FromEthereumTx(tx)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
@@ -128,24 +128,24 @@ The transaction data of regular Ethereum transactions.
|
||||
|
||||
```go
|
||||
type LegacyTx struct {
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas price defines the value for each gas unit
|
||||
GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,3,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,4,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the unsigned integer value of the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas price defines the value for each gas unit
|
||||
GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,3,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,4,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the unsigned integer value of the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
@@ -162,29 +162,29 @@ The transaction data of EIP-1559 dynamic fee transactions.
|
||||
|
||||
```go
|
||||
type DynamicFeeTx struct {
|
||||
// destination EVM chain ID
|
||||
ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas tip cap defines the max value for the gas tip
|
||||
GasTipCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_tip_cap,json=gasTipCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_tip_cap,omitempty"`
|
||||
// gas fee cap defines the max value for the gas fee
|
||||
GasFeeCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=gas_fee_cap,json=gasFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_fee_cap,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,5,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,6,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Accesses AccessList `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,10,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,11,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,12,opt,name=s,proto3" json:"s,omitempty"`
|
||||
// destination EVM chain ID
|
||||
ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas tip cap defines the max value for the gas tip
|
||||
GasTipCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_tip_cap,json=gasTipCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_tip_cap,omitempty"`
|
||||
// gas fee cap defines the max value for the gas fee
|
||||
GasFeeCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=gas_fee_cap,json=gasFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_fee_cap,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,5,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,6,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Accesses AccessList `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,10,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,11,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,12,opt,name=s,proto3" json:"s,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
@@ -204,27 +204,27 @@ The transaction data of EIP-2930 access list transactions.
|
||||
|
||||
```go
|
||||
type AccessListTx struct {
|
||||
// destination EVM chain ID
|
||||
ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas price defines the value for each gas unit
|
||||
GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,4,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the unsigned integer value of the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Accesses AccessList `protobuf:"bytes,8,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,9,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,10,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,11,opt,name=s,proto3" json:"s,omitempty"`
|
||||
// destination EVM chain ID
|
||||
ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
|
||||
// nonce corresponds to the account nonce (transaction sequence).
|
||||
Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
// gas price defines the value for each gas unit
|
||||
GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
|
||||
// gas defines the gas limit defined for the transaction.
|
||||
GasLimit uint64 `protobuf:"varint,4,opt,name=gas,proto3" json:"gas,omitempty"`
|
||||
// hex formatted address of the recipient
|
||||
To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"`
|
||||
// value defines the unsigned integer value of the transaction amount.
|
||||
Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
|
||||
// input defines the data payload bytes of the transaction.
|
||||
Data []byte `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"`
|
||||
Accesses AccessList `protobuf:"bytes,8,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
|
||||
// v defines the signature value
|
||||
V []byte `protobuf:"bytes,9,opt,name=v,proto3" json:"v,omitempty"`
|
||||
// r defines the signature value
|
||||
R []byte `protobuf:"bytes,10,opt,name=r,proto3" json:"r,omitempty"`
|
||||
// s define the signature value
|
||||
S []byte `protobuf:"bytes,11,opt,name=s,proto3" json:"s,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+116
-103
@@ -4,7 +4,9 @@ order: 6
|
||||
|
||||
# Hooks
|
||||
|
||||
The evm module implements an `EvmHooks` interface that extend the `Tx` processing logic externally. This supports EVM contracts to call native cosmos modules by
|
||||
The `x/evm` module implements an `EvmHooks` interface that extend and customize the `Tx` processing logic externally.
|
||||
|
||||
This supports EVM contracts to call native cosmos modules by
|
||||
|
||||
1. defining a log signature and emitting the specific log from the smart contract,
|
||||
2. recognizing those logs in the native tx processing code, and
|
||||
@@ -14,7 +16,8 @@ To do this, the interface includes a `PostTxProcessing` hook that registers cus
|
||||
|
||||
```go
|
||||
type EvmHooks interface {
|
||||
PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error
|
||||
// Must be called after tx is processed successfully, if return an error, the whole transaction is reverted.
|
||||
PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,11 +26,11 @@ type EvmHooks interface {
|
||||
`PostTxProcessing` is only called after a EVM transaction finished successfully and delegates the call to underlying hooks. If no hook has been registered, this function returns with a `nil` error.
|
||||
|
||||
```go
|
||||
func (k *Keeper) PostTxProcessing(txHash common.Hash, logs []*ethtypes.Log) error {
|
||||
if k.hooks == nil {
|
||||
return nil
|
||||
}
|
||||
return k.hooks.PostTxProcessing(k.Ctx(), txHash, logs)
|
||||
func (k *Keeper) PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error {
|
||||
if k.hooks == nil {
|
||||
return nil
|
||||
}
|
||||
return k.hooks.PostTxProcessing(k.Ctx(), msg, receipt)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -35,9 +38,9 @@ It's executed in the same cache context as the EVM transaction, if it returns an
|
||||
|
||||
The error returned by the hooks is translated to a VM error `failed to process native logs`, the detailed error message is stored in the return value. The message is sent to native modules asynchronously, there's no way for the caller to catch and recover the error.
|
||||
|
||||
## Use Case: Call Native erc20 Module on Evmos
|
||||
## Use Case: Call Native ERC20 Module on Evmos
|
||||
|
||||
Here is an example taken from the [Evmos erc20 module](https://evmos.dev/modules/erc20/) that shows how the `EVMHooks` supports a contract calling a native module to convert ERC-20 Tokens intor Cosmos native Coins. Following the steps from above.
|
||||
Here is an example taken from the Evmos [erc20 module](https://evmos.dev/modules/erc20/) that shows how the `EVMHooks` supports a contract calling a native module to convert ERC-20 Tokens into Cosmos native Coins. Following the steps from above.
|
||||
|
||||
You can define and emit a `Transfer` log signature in the smart contract like this:
|
||||
|
||||
@@ -45,14 +48,14 @@ You can define and emit a `Transfer` log signature in the smart contract like th
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
|
||||
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
|
||||
require(sender != address(0), "ERC20: transfer from the zero address");
|
||||
require(recipient != address(0), "ERC20: transfer to the zero address");
|
||||
require(sender != address(0), "ERC20: transfer from the zero address");
|
||||
require(recipient != address(0), "ERC20: transfer to the zero address");
|
||||
|
||||
_beforeTokenTransfer(sender, recipient, amount);
|
||||
_beforeTokenTransfer(sender, recipient, amount);
|
||||
|
||||
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
|
||||
_balances[recipient] = _balances[recipient].add(amount);
|
||||
emit Transfer(sender, recipient, amount);
|
||||
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
|
||||
_balances[recipient] = _balances[recipient].add(amount);
|
||||
emit Transfer(sender, recipient, amount);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -63,115 +66,125 @@ The application will register a `BankSendHook` to the `EvmKeeper`. It recognizes
|
||||
const ERC20EventTransfer = "Transfer"
|
||||
|
||||
// PostTxProcessing implements EvmHooks.PostTxProcessing
|
||||
func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
|
||||
params := k.GetParams(ctx)
|
||||
if !params.EnableEVMHook {
|
||||
return sdkerrors.Wrap(types.ErrInternalTokenPair, "EVM Hook is currently disabled")
|
||||
}
|
||||
func (k Keeper) PostTxProcessing(
|
||||
ctx sdk.Context,
|
||||
msg core.Message,
|
||||
receipt *ethtypes.Receipt,
|
||||
) error {
|
||||
params := h.k.GetParams(ctx)
|
||||
if !params.EnableErc20 || !params.EnableEVMHook {
|
||||
// no error is returned to allow for other post processing txs
|
||||
// to pass
|
||||
return nil
|
||||
}
|
||||
|
||||
erc20 := contracts.ERC20BurnableContract.ABI
|
||||
erc20 := contracts.ERC20BurnableContract.ABI
|
||||
|
||||
for i, log := range logs {
|
||||
if len(log.Topics) < 3 {
|
||||
continue
|
||||
}
|
||||
for i, log := range receipt.Logs {
|
||||
if len(log.Topics) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
eventID := log.Topics[0] // event ID
|
||||
eventID := log.Topics[0] // event ID
|
||||
|
||||
event, err := erc20.EventByID(eventID)
|
||||
if err != nil {
|
||||
// invalid event for ERC20
|
||||
continue
|
||||
}
|
||||
event, err := erc20.EventByID(eventID)
|
||||
if err != nil {
|
||||
// invalid event for ERC20
|
||||
continue
|
||||
}
|
||||
|
||||
if event.Name != types.ERC20EventTransfer {
|
||||
k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
|
||||
continue
|
||||
}
|
||||
if event.Name != types.ERC20EventTransfer {
|
||||
h.k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
|
||||
continue
|
||||
}
|
||||
|
||||
transferEvent, err := erc20.Unpack(event.Name, log.Data)
|
||||
if err != nil {
|
||||
k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
|
||||
continue
|
||||
}
|
||||
transferEvent, err := erc20.Unpack(event.Name, log.Data)
|
||||
if err != nil {
|
||||
h.k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if len(transferEvent) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(transferEvent) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
tokens, ok := transferEvent[0].(*big.Int)
|
||||
// safety check and ignore if amount not positive
|
||||
if !ok || tokens == nil || tokens.Sign() != 1 {
|
||||
continue
|
||||
}
|
||||
tokens, ok := transferEvent[0].(*big.Int)
|
||||
// safety check and ignore if amount not positive
|
||||
if !ok || tokens == nil || tokens.Sign() != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// check that the contract is a registered token pair
|
||||
contractAddr := log.Address
|
||||
// check that the contract is a registered token pair
|
||||
contractAddr := log.Address
|
||||
|
||||
id := k.GetERC20Map(ctx, contractAddr)
|
||||
id := h.k.GetERC20Map(ctx, contractAddr)
|
||||
|
||||
if len(id) == 0 {
|
||||
// no token is registered for the caller contract
|
||||
continue
|
||||
}
|
||||
if len(id) == 0 {
|
||||
// no token is registered for the caller contract
|
||||
continue
|
||||
}
|
||||
|
||||
pair, found := k.GetTokenPair(ctx, id)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
pair, found := h.k.GetTokenPair(ctx, id)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// check that relaying for the pair is enabled
|
||||
if !pair.Enabled {
|
||||
return fmt.Errorf("internal relaying is disabled for pair %s, please create a governance proposal", contractAddr) // convert to SDK error
|
||||
}
|
||||
// check that conversion for the pair is enabled
|
||||
if !pair.Enabled {
|
||||
// continue to allow transfers for the ERC20 in case the token pair is disabled
|
||||
h.k.Logger(ctx).Debug(
|
||||
"ERC20 token -> Cosmos coin conversion is disabled for pair",
|
||||
"coin", pair.Denom, "contract", pair.Erc20Address,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// ignore as the burning always transfers to the zero address
|
||||
to := common.BytesToAddress(log.Topics[2].Bytes())
|
||||
if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
|
||||
continue
|
||||
}
|
||||
// ignore as the burning always transfers to the zero address
|
||||
to := common.BytesToAddress(log.Topics[2].Bytes())
|
||||
if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check that the event is Burn from the ERC20Burnable interface
|
||||
// NOTE: assume that if they are burning the token that has been registered as a pair, they want to mint a Cosmos coin
|
||||
// check that the event is Burn from the ERC20Burnable interface
|
||||
// NOTE: assume that if they are burning the token that has been registered as a pair, they want to mint a Cosmos coin
|
||||
|
||||
// create the corresponding sdk.Coin that is paired with ERC20
|
||||
coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}
|
||||
// create the corresponding sdk.Coin that is paired with ERC20
|
||||
coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}
|
||||
|
||||
// Mint the coin only if ERC20 is external
|
||||
switch pair.ContractOwner {
|
||||
case types.OWNER_MODULE:
|
||||
_, err = k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, "burn", tokens)
|
||||
case types.OWNER_EXTERNAL:
|
||||
err = k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
|
||||
default:
|
||||
err = types.ErrUndefinedOwner
|
||||
}
|
||||
// Mint the coin only if ERC20 is external
|
||||
switch pair.ContractOwner {
|
||||
case types.OWNER_MODULE:
|
||||
_, err = h.k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, true, "burn", tokens)
|
||||
case types.OWNER_EXTERNAL:
|
||||
err = h.k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
|
||||
default:
|
||||
err = types.ErrUndefinedOwner
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
k.Logger(ctx).Debug(
|
||||
"failed to process EVM hook for ER20 -> coin conversion",
|
||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
h.k.Logger(ctx).Debug(
|
||||
"failed to process EVM hook for ER20 -> coin conversion",
|
||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// Only need last 20 bytes from log.topics
|
||||
from := common.BytesToAddress(log.Topics[1].Bytes())
|
||||
recipient := sdk.AccAddress(from.Bytes())
|
||||
// Only need last 20 bytes from log.topics
|
||||
from := common.BytesToAddress(log.Topics[1].Bytes())
|
||||
recipient := sdk.AccAddress(from.Bytes())
|
||||
|
||||
// transfer the tokens from ModuleAccount to sender address
|
||||
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
|
||||
k.Logger(ctx).Debug(
|
||||
"failed to process EVM hook for ER20 -> coin conversion",
|
||||
"tx-hash", txHash.Hex(), "log-idx", i,
|
||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// transfer the tokens from ModuleAccount to sender address
|
||||
if err := h.k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
|
||||
h.k.Logger(ctx).Debug(
|
||||
"failed to process EVM hook for ER20 -> coin conversion",
|
||||
"tx-hash", receipt.TxHash.Hex(), "log-idx", i,
|
||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
```
|
||||
|
||||
Lastly, register the hook in `app.go`:
|
||||
|
||||
@@ -22,7 +22,6 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM
|
||||
| message | `"action"` | `"ethereum"` |
|
||||
| message | `"module"` | `"evm"` |
|
||||
|
||||
|
||||
Additionally, the EVM module emits an event during `EndBlock` for the filter query block bloom.
|
||||
|
||||
## ABCI
|
||||
|
||||
@@ -77,4 +77,3 @@ By default, all block configuration fields but `ConstantinopleBlock`, are enable
|
||||
| MuirGlacierBlock | 0 |
|
||||
| BerlinBlock | 0 |
|
||||
| LondonBlock | 0 |
|
||||
|
||||
|
||||
@@ -104,4 +104,4 @@ For an overview on the JSON-RPC methods and namespaces supported on Ethermint,
|
||||
| Verb | Method | Description |
|
||||
| ------ | --------------------------------- | ------------------------------- |
|
||||
| `gRPC` | `ethermint.evm.v1.Msg/EthereumTx` | Submit an Ethereum transactions |
|
||||
| `POST` | `/ethermint/evm/v1/ethereum_tx` | Submit an Ethereum transactions |
|
||||
| `POST` | `/ethermint/evm/v1/ethereum_tx` | Submit an Ethereum transactions |
|
||||
|
||||
@@ -17,7 +17,7 @@ The growth of EVM-based chains (e.g. Ethereum), however, has uncovered several s
|
||||
|
||||
The `x/evm` module provides this EVM familiarity on a scalable, high-throughput Proof-of-Stake blockchain. It is built as a [Cosmos SDK module](https://docs.cosmos.network/master/building-modules/intro.html) which allows for the deployment of smart contracts, interaction with the EVM state machine (state transitions), and the use of EVM tooling. It can be used on Cosmos application-specific blockchains, which alleviate the aforementioned concerns through high transaction throughput via [Tendermint Core](https://github.com/tendermint/tendermint), fast transaction finality, and horizontal scalability via [IBC](https://ibcprotocol.org/).
|
||||
|
||||
The `x/evm` is part of the [ethermint library](https://pkg.go.dev/github.com/cerc-io/laconicd). For an example of how Ethermint can be used on any Cosmos-SDK chain, please refer to [Evmos](https://www.github.com/tharsis/evmos).
|
||||
The `x/evm` is part of the [ethermint library](https://pkg.go.dev/github.com/evmos/ethermint). For an example of how Ethermint can be used on any Cosmos-SDK chain, please refer to [Evmos](https://www.github.com/tharsis/evmos).
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -34,8 +34,8 @@ The `x/evm` is part of the [ethermint library](https://pkg.go.dev/github.com/cer
|
||||
## Module Architecture
|
||||
|
||||
> **NOTE:**: If you're not familiar with the overall module structure from
|
||||
> the SDK modules, please check this [document](https://docs.cosmos.network/master/building-modules/structure.html) as
|
||||
> prerequisite reading.
|
||||
the SDK modules, please check this [document](https://docs.cosmos.network/master/building-modules/structure.html) as
|
||||
prerequisite reading.
|
||||
|
||||
```shell
|
||||
evm/
|
||||
|
||||
Reference in New Issue
Block a user