docs: update EVM spec
This commit is contained in:
parent
0543a28941
commit
a121224dbc
@ -4,7 +4,9 @@ order: 6
|
|||||||
|
|
||||||
# Hooks
|
# 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,
|
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
|
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
|
```go
|
||||||
type EvmHooks interface {
|
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.
|
`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
|
```go
|
||||||
func (k *Keeper) PostTxProcessing(txHash common.Hash, logs []*ethtypes.Log) error {
|
func (k *Keeper) PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error {
|
||||||
if k.hooks == nil {
|
if k.hooks == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return k.hooks.PostTxProcessing(k.Ctx(), txHash, logs)
|
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.
|
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:
|
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);
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
|
|
||||||
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
|
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
|
||||||
require(sender != address(0), "ERC20: transfer from the zero address");
|
require(sender != address(0), "ERC20: transfer from the zero address");
|
||||||
require(recipient != address(0), "ERC20: transfer to 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[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
|
||||||
_balances[recipient] = _balances[recipient].add(amount);
|
_balances[recipient] = _balances[recipient].add(amount);
|
||||||
emit Transfer(sender, recipient, amount);
|
emit Transfer(sender, recipient, amount);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -63,115 +66,125 @@ The application will register a `BankSendHook` to the `EvmKeeper`. It recognizes
|
|||||||
const ERC20EventTransfer = "Transfer"
|
const ERC20EventTransfer = "Transfer"
|
||||||
|
|
||||||
// PostTxProcessing implements EvmHooks.PostTxProcessing
|
// PostTxProcessing implements EvmHooks.PostTxProcessing
|
||||||
func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
|
func (k Keeper) PostTxProcessing(
|
||||||
params := k.GetParams(ctx)
|
ctx sdk.Context,
|
||||||
if !params.EnableEVMHook {
|
msg core.Message,
|
||||||
return sdkerrors.Wrap(types.ErrInternalTokenPair, "EVM Hook is currently disabled")
|
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 {
|
for i, log := range receipt.Logs {
|
||||||
if len(log.Topics) < 3 {
|
if len(log.Topics) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
eventID := log.Topics[0] // event ID
|
eventID := log.Topics[0] // event ID
|
||||||
|
|
||||||
event, err := erc20.EventByID(eventID)
|
event, err := erc20.EventByID(eventID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// invalid event for ERC20
|
// invalid event for ERC20
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Name != types.ERC20EventTransfer {
|
if event.Name != types.ERC20EventTransfer {
|
||||||
k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
|
h.k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
transferEvent, err := erc20.Unpack(event.Name, log.Data)
|
transferEvent, err := erc20.Unpack(event.Name, log.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
|
h.k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(transferEvent) == 0 {
|
if len(transferEvent) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, ok := transferEvent[0].(*big.Int)
|
tokens, ok := transferEvent[0].(*big.Int)
|
||||||
// safety check and ignore if amount not positive
|
// safety check and ignore if amount not positive
|
||||||
if !ok || tokens == nil || tokens.Sign() != 1 {
|
if !ok || tokens == nil || tokens.Sign() != 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the contract is a registered token pair
|
// check that the contract is a registered token pair
|
||||||
contractAddr := log.Address
|
contractAddr := log.Address
|
||||||
|
|
||||||
id := k.GetERC20Map(ctx, contractAddr)
|
id := h.k.GetERC20Map(ctx, contractAddr)
|
||||||
|
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
// no token is registered for the caller contract
|
// no token is registered for the caller contract
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pair, found := k.GetTokenPair(ctx, id)
|
pair, found := h.k.GetTokenPair(ctx, id)
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that relaying for the pair is enabled
|
// check that conversion for the pair is enabled
|
||||||
if !pair.Enabled {
|
if !pair.Enabled {
|
||||||
return fmt.Errorf("internal relaying is disabled for pair %s, please create a governance proposal", contractAddr) // convert to SDK error
|
// 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
|
// ignore as the burning always transfers to the zero address
|
||||||
to := common.BytesToAddress(log.Topics[2].Bytes())
|
to := common.BytesToAddress(log.Topics[2].Bytes())
|
||||||
if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
|
if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the event is Burn from the ERC20Burnable interface
|
// 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
|
// 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
|
// create the corresponding sdk.Coin that is paired with ERC20
|
||||||
coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}
|
coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}
|
||||||
|
|
||||||
// Mint the coin only if ERC20 is external
|
// Mint the coin only if ERC20 is external
|
||||||
switch pair.ContractOwner {
|
switch pair.ContractOwner {
|
||||||
case types.OWNER_MODULE:
|
case types.OWNER_MODULE:
|
||||||
_, err = k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, "burn", tokens)
|
_, err = h.k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, true, "burn", tokens)
|
||||||
case types.OWNER_EXTERNAL:
|
case types.OWNER_EXTERNAL:
|
||||||
err = k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
|
err = h.k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
|
||||||
default:
|
default:
|
||||||
err = types.ErrUndefinedOwner
|
err = types.ErrUndefinedOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
k.Logger(ctx).Debug(
|
h.k.Logger(ctx).Debug(
|
||||||
"failed to process EVM hook for ER20 -> coin conversion",
|
"failed to process EVM hook for ER20 -> coin conversion",
|
||||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only need last 20 bytes from log.topics
|
// Only need last 20 bytes from log.topics
|
||||||
from := common.BytesToAddress(log.Topics[1].Bytes())
|
from := common.BytesToAddress(log.Topics[1].Bytes())
|
||||||
recipient := sdk.AccAddress(from.Bytes())
|
recipient := sdk.AccAddress(from.Bytes())
|
||||||
|
|
||||||
// transfer the tokens from ModuleAccount to sender address
|
// transfer the tokens from ModuleAccount to sender address
|
||||||
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
|
if err := h.k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
|
||||||
k.Logger(ctx).Debug(
|
h.k.Logger(ctx).Debug(
|
||||||
"failed to process EVM hook for ER20 -> coin conversion",
|
"failed to process EVM hook for ER20 -> coin conversion",
|
||||||
"tx-hash", txHash.Hex(), "log-idx", i,
|
"tx-hash", receipt.TxHash.Hex(), "log-idx", i,
|
||||||
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
"coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Lastly, register the hook in `app.go`:
|
Lastly, register the hook in `app.go`:
|
||||||
|
Loading…
Reference in New Issue
Block a user