> ## Documentation Index
> Fetch the complete documentation index at: https://injectivelabs-docs-ai-sdk.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

`x/evm` 모듈은 `Tx` 처리 로직을 외부에서 확장하고 사용자 정의할 수 있는 `EvmHooks` 인터페이스를 구현합니다.

이는 EVM 컨트랙트가 다음을 통해 네이티브 cosmos 모듈을 호출할 수 있도록 지원합니다:

1. log signature를 정의하고 스마트 컨트랙트에서 특정 로그를 내보내고,
2. 네이티브 트랜잭션 처리 코드에서 해당 로그를 인식하고,
3. 네이티브 모듈 호출로 변환합니다.

이를 위해 인터페이스에는 `EvmKeeper`에 사용자 정의 `Tx` hook을 등록하는 `PostTxProcessing` hook이 포함되어 있습니다. 이러한 `Tx` hook은 EVM 상태 전환이 완료되고 실패하지 않은 후에 처리됩니다. EVM 모듈에는 기본 hook이 구현되어 있지 않습니다.

```go theme={null}
type EvmHooks interface {
	// 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
}
```

## `PostTxProcessing`

`PostTxProcessing`은 EVM 트랜잭션이 성공적으로 완료된 후에만 호출되며 기본 hook에 호출을 위임합니다. 등록된 hook이 없으면 이 함수는 `nil` 오류와 함께 반환됩니다.

```go theme={null}
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)
}
```

EVM 트랜잭션과 동일한 cache context에서 실행되며, 오류를 반환하면 전체 EVM 트랜잭션이 되돌려집니다. hook 구현자가 트랜잭션을 되돌리고 싶지 않으면 항상 `nil`을 반환할 수 있습니다.

hook이 반환하는 오류는 VM 오류 `failed to process native logs`로 변환되고 자세한 오류 메시지는 반환 값에 저장됩니다. 메시지는 네이티브 모듈로 비동기적으로 전송되며 호출자가 오류를 catch하고 복구할 방법이 없습니다.

## Use Case: Call Native ERC20 Module on Injective

다음은 `EVMHooks`가 ERC-20 Token을 Cosmos 네이티브 Coin으로 변환하기 위해 네이티브 모듈을 호출하는 컨트랙트를 지원하는 방법을 보여주는 Injective [erc20 모듈]()에서 가져온 예입니다. 위의 단계를 따릅니다.

다음과 같이 스마트 컨트랙트에서 `Transfer` log signature를 정의하고 내보낼 수 있습니다:

```solidity theme={null}
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");

  _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);
}
```

애플리케이션은 `EvmKeeper`에 `BankSendHook`을 등록합니다. ethereum 트랜잭션 `Log`를 인식하고 bank 모듈의 `SendCoinsFromAccountToAccount` 메서드 호출로 변환합니다:

```go theme={null}

const ERC20EventTransfer = "Transfer"

// PostTxProcessing implements EvmHooks.PostTxProcessing
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

	for i, log := range receipt.Logs {
		if len(log.Topics) < 3 {
			continue
		}

		eventID := log.Topics[0] // event ID

		event, err := erc20.EventByID(eventID)
		if err != nil {
			// invalid event for ERC20
			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 {
			h.k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
			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
		}

		// check that the contract is a registered token pair
		contractAddr := log.Address

		id := h.k.GetERC20Map(ctx, contractAddr)

		if len(id) == 0 {
			// no token is registered for the caller contract
			continue
		}

		pair, found := h.k.GetTokenPair(ctx, id)
		if !found {
			continue
		}

		// 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
		}

		// 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)}}

		// 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 {
			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())

		// 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
```

마지막으로 `app.go`에서 hook을 등록합니다:

```go theme={null}
app.EvmKeeper = app.EvmKeeper.SetHooks(app.Erc20Keeper)
```
