x/erc20
module of the Cosmos EVM.
The x/erc20
module enables the Cosmos EVM to support a trustless, on-chain bidirectional internal conversion of tokens between the EVM and Cosmos runtimes, specifically the x/vm
and x/bank
modules. This allows token holders on a Cosmos EVM chain to instantaneously convert their native Cosmos sdk.Coins
(in this document referred to as “Coin(s)”) to ERC-20 (aka “Token(s)”) and vice versa, while retaining fungibility with the original asset on the issuing environment/runtime (EVM or Cosmos) and preserving ownership of the ERC-20 contract.
This governance functionality is implemented using the Cosmos-SDK gov
module with custom proposal types for registering and updating the canonical mappings respectively.
Why is this important? Cosmos and the EVM are two runtimes that are not compatible by default. The native Cosmos Coins cannot be used in applications that require the ERC-20 standard. Cosmos coins are held on the x/bank
module (with access to module methods like querying the supply or balances) and ERC-20 Tokens live on smart contracts. This problem is similar to wETH, with the difference, that it not only applies to gas tokens, but to all Cosmos Coins (IBC vouchers, staking and gov coins, etc.) as well.
With the x/erc20
users of a Cosmos EVM chain can
x/erc20
module maintains a canonical one-to-one mapping of native Cosmos Coin denomination to ERC20 Token contract addresses (i.e sdk.Coin
←→ ERC20), called TokenPair
. The conversion of the ERC20 tokens ←→ Coin of a given pair can be enabled or disabled via governance.
sdk.Coin
that is native to the bank module. It can be either the native staking/gas denomination (e.g. ATOM, etc) or an IBC fungible token voucher (i.e. with denom format of ibc/{hash}
).
When a proposal is initiated for an existing native Cosmos Coin, the erc20 module will deploy a factory ERC20 contract, representing the ERC20 token for the token pair, giving the module ownership of that contract.
Metadata
is used to deploy a ERC20 contract:
{NAME} channel-{channel}
ibc{NAME}-{channel}
Metadata
Cosmos coin token representation of {contractAddress}
0
{uint32(erc20Data.Decimals)}
{"erc20/%s", address}
{erc20Data.Name}
{types.CreateDenom(strContract)}
{erc20Data.Symbol}
ToggleTokenConversionProposal
, so that the conversions between the token pair’s tokens can be enabled or disabled.
ConvertCoin
Tx. Vice versa, the ConvertERC20
Tx allows holders of ERC20 tokens on the Cosmos EVM chain to convert ERC-20 tokens back to their native Cosmos Coin representation.
Depending on the ownership of the ERC20 contract, the ERC20 tokens either follow a burn/mint or a transfer/escrow mechanism during conversion.
transfer
method, which is responsible for sending an amount
of tokens to a given recipient
could include code to siphon some amount of tokens intended for the recipient into a different predefined account, which is owned by the malicious contract deployer.
More sophisticated malicious implementations might also inherit code from customized ERC20 contracts that include malicous behaviour. For an overview of more extensive examples, please review the x/erc20 audit, section IF-EVMOS-06: IERC20 Contracts may execute arbitrary code
.
As the x/erc20
module allows any arbitrary ERC20 contract to be registered through governance, it is essential that the proposer or the voters manually verify during voting phase that the proposed contract uses the default ERC20.sol implementation.
Here are our recommendations for the reviewing process:
x/erc20
module keeps the following objects in state:
State Object | Description | Key | Value | Store |
---|---|---|---|---|
TokenPair | Token Pair bytecode | []byte{1} + []byte(id) | []byte{tokenPair} | KV |
TokenPairByERC20 | Token Pair id bytecode by erc20 contract bytes | []byte{2} + []byte(erc20) | []byte(id) | KV |
TokenPairByDenom | Token Pair id bytecode by denom string | []byte{3} + []byte(denom) | []byte(id) | KV |
sdk.Coin
←→ ERC20).
TokenPair
is obtained by obtaining the SHA256 hash of the ERC20 hex contract address and the Coin denomination using the following function:
ConvertCoin
and ConvertERC20
functionalities use the owner field to check whether the token being used is a native Coin or a native ERC20. The field is based on the token registration proposal type (RegisterCoinProposal
= 1, RegisterERC20Proposal
= 2).
The Owner
enumerates the ownership of a ERC20 contract.
Owner
can be checked with the following helper functions:
TokenPairByERC20
and TokenPairByDenom
are additional state objects for querying a token pair id.
x/erc20
module’s GenesisState
defines the state necessary for initializing the chain from a previous exported height. It contains the module parameters and the registered token pairs :
RegisterCoinProposal
or RegisterERC20Proposal
, there are four possible conversion state transitions.
RegisterCoinProposal
MsgVote
and proposal passes
Metadata
field on the proposal content.RegisterERC20Proposal
MsgVote
and proposal passesMetadata
from the ERC20 details.TokenPair
can be done via:
ConvertCoin
and ConvertERC20)
MsgEthereumTx
that leverages the EVM hook)TokenPair
has been created through a RegisterCoinProposal
governance proposal. The proposal created an ERC20
contract (ERC20Mintable by openzeppelin) of the ERC20 token representation of the Coin from the ModuleAccount
, assigning it as the owner
of the contract and thus granting it the permission to call the mint()
and burnFrom()
methods of the ERC20.ModuleAccount
should have the Minter Role on the ERC20. Otherwise, the user could unilaterally mint an infinite supply of the ERC20 token and then convert them to the native Coin
ModuleAccount
(owner) should be the only ones that have the Burn Role for a Cosmos Coin
ConvertCoin
Tx
ModuleAccount
mint()
ERC20 tokens from the ModuleAccount
address and send minted tokens to recipient addressConvertERC20
Tx
ModuleAccount
burnCoins()
on ERC20 to burn ERC20 tokens from the user balanceTokenPair
has been created through a RegisterERC20Proposal
governance proposal. The ModuleAccount
is not the owner of the contract, so it can’t mint new tokens or burn on behalf of the user. The mechanism described below follows the same model as the ICS20 standard, by using escrow & mint / burn & unescrow logic.ConvertERC20
Tx
ModuleAccount
Approval
event found in logs to prevent malicious contract behaviour
ConvertCoin
Tx
ModuleAccount
Approval
event found in logs to prevent malicious contract behaviour
sdk.Msg
concrete types that result in the state transitions defined on the previous section.
RegisterCoinProposal
Content
type to register a token pair from a Cosmos Coin. Governance users vote on this proposal and it automatically executes the custom handler for RegisterCoinProposal
when the vote passes.
RegisterERC20Proposal
Content
type to register a token pair from an ERC20 Token. Governance users vote on this proposal and it automatically executes the custom handler for RegisterERC20Proposal
when the vote passes.
MsgConvertCoin
MsgConvertCoin
message to convert a Cosmos Coin to a ERC20 token.
MsgConvertERC20
MsgConvertERC20
message to convert a ERC20 token to a native Cosmos coin.
ToggleTokenConversionProposal
ConvertERC20
msg) cannot be done here, as the balance prior to the transaction is not available in the hook.
ModuleAccount
address to escrow them
burn()
ERC20 method from the ModuleAccount
. Note that this is the same as 1.2, but since the tokens are already on the ModuleAccount balance, we burn the tokens from the module address instead of calling burnFrom()
. Also note that we don’t need to mint because 1.1 coin to erc20 escrows the coinModuleAccount
to escrow them
x/erc20
module emits the following events:
Type | Attribute Key | Attribute Value |
---|---|---|
register_coin | "cosmos_coin" | {denom} |
register_coin | "erc20_token" | {erc20_address} |
Type | Attribute Key | Attribute Value |
---|---|---|
register_erc20 | "cosmos_coin" | {denom} |
register_erc20 | "erc20_token" | {erc20_address} |
Type | Attribute Key | Attribute Value |
---|---|---|
toggle_token_conversion | "erc20_token" | {erc20_address} |
toggle_token_conversion | "cosmos_coin" | {denom} |
Type | Attribute Key | Attribute Value |
---|---|---|
convert_coin | "sender" | {msg.Sender} |
convert_coin | "receiver" | {msg.Receiver} |
convert_coin | "amount" | {msg.Coin.Amount.String()} |
convert_coin | "cosmos_coin" | {denom} |
convert_coin | "erc20_token" | {erc20_address} |
Type | Attribute Key | Attribute Value |
---|---|---|
convert_erc20 | "sender" | {msg.Sender} |
convert_erc20 | "receiver" | {msg.Receiver} |
convert_erc20 | "amount" | {msg.Amount.String()} |
convert_erc20 | "cosmos_coin" | {denom} |
convert_erc20 | "erc20_token" | {msg.ContractAddress} |
Key | Type | Default Value |
---|---|---|
EnableErc20 | bool | true |
EnableEVMHook | bool | true |
EnableErc20
parameter toggles all state transitions in the module. When the parameter is disabled, it will prevent all token pair registration and conversion functionality.
EnableEVMHook
parameter enables the EVM hook to convert an ERC20 token to a Cosmos Coin by transferring the Tokens through a MsgEthereumTx
to the ModuleAddress
Ethereum address.
appd
commands added with the x/erc20
module. You can obtain the full list by using the appd -h
command. A CLI command can look like this:
Command | Subcommand | Description |
---|---|---|
query erc20 | params | Get erc20 params |
query erc20 | token-pair | Get registered token pair |
query erc20 | token-pairs | Get all registered token pairs |
Command | Subcommand | Description |
---|---|---|
tx erc20 | convert-coin | Convert a Cosmos Coin to ERC20 |
tx erc20 | convert-erc20 | Convert a ERC20 to Cosmos Coin |
tx gov submit-legacy-proposal
commands allow users to query create a proposal using the governance module CLI:
register-coin
Allows users to submit a RegisterCoinProposal
. Submit a proposal to register a Cosmos coin to the erc20 along with an initial deposit. Upon passing, the proposal details must be supplied via a JSON file.
register-erc20
Allows users to submit a RegisterERC20Proposal
. Submit a proposal to register ERC20 tokens along with an initial deposit. To register multiple tokens in one proposal pass them after each other e.g. register-erc20 <contract-address1> <contract-address2>
.
toggle-token-conversion
Allows users to submit a ToggleTokenConversionProposal
.
MsgUpdateParams
with the desired changes on the x/erc20
module parameters. To do this, you will have to provide a JSON file with the correspondiong message in the submit-proposal
command.
For more information on how to draft a proposal, refer to the Cosmos SDK governance documentation.
Verb | Method | Description |
---|---|---|
gRPC | cosmos.evm.erc20.v1.Query/Params | Get erc20 params |
gRPC | cosmos.evm.erc20.v1.Query/TokenPair | Get registered token pair |
gRPC | cosmos.evm.erc20.v1.Query/TokenPairs | Get all registered token pairs |
GET | /cosmos/evm/erc20/v1/params | Get erc20 params |
GET | /cosmos/evm/erc20/v1/token_pair | Get registered token pair |
GET | /cosmos/evm/erc20/v1/token_pairs | Get all registered token pairs |
Verb | Method | Description |
---|---|---|
gRPC | cosmos.evm.erc20.v1.Msg/ConvertCoin | Convert a Cosmos Coin to ERC20 |
gRPC | cosmos.evm.erc20.v1.Msg/ConvertERC20 | Convert a ERC20 to Cosmos Coin |
GET | /cosmos/evm/erc20/v1/tx/convert_coin | Convert a Cosmos Coin to ERC20 |
GET | /cosmos/evm/erc20/v1/tx/convert_erc20 | Convert a ERC20 to Cosmos Coin |