docs: update EVM spec

This commit is contained in:
Federico Kunze Küllmer 2022-04-22 14:57:48 +02:00
parent 0543a28941
commit a121224dbc

View File

@ -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:
@ -63,15 +66,21 @@ 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
} }
@ -85,13 +94,13 @@ func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*et
} }
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
} }
@ -108,21 +117,26 @@ func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*et
// 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
@ -140,15 +154,15 @@ func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*et
// 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(),
) )
@ -160,10 +174,10 @@ func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*et
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
@ -171,7 +185,6 @@ func (k Keeper) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*et
} }
return nil return nil
}
``` ```
Lastly, register the hook in `app.go`: Lastly, register the hook in `app.go`: