Skip to content

feat!: permissionless erc20 registration #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
### FEATURES

- [\#69](https://github.com/cosmos/evm/pull/69) Add new `x/precisebank` module with bank decimal extension for EVM usage.
- [\#84](https://github.com/cosmos/evm/pull/84) permissionless erc20 registration to cosmos coin conversion

### STATE BREAKING

Expand Down
293 changes: 148 additions & 145 deletions api/cosmos/evm/erc20/v1/tx.pulsar.go

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions proto/cosmos/evm/erc20/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
(amino.dont_omitempty) = true
];
// receiver is the bech32 address to receive native Cosmos coins
string receiver = 3;
string receiver = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// sender is the hex address from the owner of the given ERC20 tokens
string sender = 4;
}
Expand All @@ -74,7 +74,7 @@
string receiver = 2;
// sender is the cosmos bech32 address from the owner of the given Cosmos
// coins
string sender = 3;
string sender = 3 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}

// MsgConvertCoinResponse returns no fields
Expand Down Expand Up @@ -104,10 +104,10 @@
// an Erc20 contract token pair.
message MsgRegisterERC20 {
option (amino.name) = "cosmos/evm/x/erc20/MsgRegisterERC20";
option (cosmos.msg.v1.signer) = "authority";
option (cosmos.msg.v1.signer) = "signer";

// authority is the address of the governance account.
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// signer is the address registering the erc20 pairs
string signer = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];

Check failure on line 110 in proto/cosmos/evm/erc20/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" with name "signer" on message "MsgRegisterERC20" changed option "json_name" from "authority" to "signer".

Check failure on line 110 in proto/cosmos/evm/erc20/v1/tx.proto

View workflow job for this annotation

GitHub Actions / break-check

Field "1" on message "MsgRegisterERC20" changed name from "authority" to "signer".

// erc20addresses is a slice of ERC20 token contract hex addresses
repeated string erc20addresses = 2;
Expand Down
2 changes: 1 addition & 1 deletion tests/ibc/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func SetupNativeErc20(t *testing.T, chain *evmibctesting.TestChain) *NativeErc20

// Register the contract
_, err = evmApp.Erc20Keeper.RegisterERC20(evmCtx, &erc20types.MsgRegisterERC20{
Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(),
Signer: authtypes.NewModuleAddress(govtypes.ModuleName).String(), // does not have to be gov
Erc20Addresses: []string{contractAddr.Hex()},
})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion testutil/integration/os/utils/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func RegisterERC20(tf factory.TxFactory, network network.Network, data ERC20Regi
}

proposal := erc20types.MsgRegisterERC20{
Authority: authtypes.NewModuleAddress("gov").String(),
Signer: authtypes.NewModuleAddress("gov").String(),
Erc20Addresses: data.Addresses,
}

Expand Down
31 changes: 31 additions & 0 deletions x/erc20/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func NewTxCmd() *cobra.Command {
txCmd.AddCommand(
NewConvertCoinCmd(),
NewConvertERC20Cmd(),
NewMsgRegisterERC20Cmd(),
)
return txCmd
}
Expand Down Expand Up @@ -127,3 +128,33 @@ func NewConvertCoinCmd() *cobra.Command {
flags.AddTxFlagsToCmd(cmd)
return cmd
}

func NewMsgRegisterERC20Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "register-erc20 [CONTRACT_ADDRESS...]",
Short: "Register a native ERC20 token",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

for _, contract := range args {
if err := cosmosevmtypes.ValidateAddress(contract); err != nil {
return fmt.Errorf("invalid ERC20 contract address %w", err)
}
}

msg := &types.MsgRegisterERC20{
Signer: cliCtx.GetFromAddress().String(),
Erc20Addresses: args,
}

return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)
return cmd
}
9 changes: 2 additions & 7 deletions x/erc20/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,20 +315,15 @@ func (k *Keeper) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams)
return &types.MsgUpdateParamsResponse{}, nil
}

// RegisterERC20 implements the gRPC MsgServer interface. After a successful governance vote
// it updates creates the token pair for an ERC20 contract if the requested authority
// is the Cosmos SDK governance module account
// RegisterERC20 implements the gRPC MsgServer interface. Any account can permissionlessly
// register a native ERC20 contract to map to a Cosmos Coin.
func (k *Keeper) RegisterERC20(goCtx context.Context, req *types.MsgRegisterERC20) (*types.MsgRegisterERC20Response, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
// Check if the conversion is globally enabled
if !k.IsERC20Enabled(ctx) {
return nil, types.ErrERC20Disabled.Wrap("registration is currently disabled by governance")
}

if err := k.validateAuthority(req.Authority); err != nil {
return nil, err
}

for _, addr := range req.Erc20Addresses {
if !common.IsHexAddress(addr) {
return nil, errortypes.ErrInvalidAddress.Wrapf("invalid ERC20 contract address: %s", addr)
Expand Down
2 changes: 1 addition & 1 deletion x/erc20/keeper/proposals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func (suite *KeeperTestSuite) TestRegisterERC20() {
tc.malleate()

_, err = suite.network.App.Erc20Keeper.RegisterERC20(ctx, &types.MsgRegisterERC20{
Authority: authtypes.NewModuleAddress("gov").String(),
Signer: suite.keyring.GetAccAddr(0).String(),
Erc20Addresses: []string{contractAddr.Hex()},
})
metadata, found := suite.network.App.BankKeeper.GetDenomMetaData(ctx, coinName)
Expand Down
5 changes: 5 additions & 0 deletions x/erc20/types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ func (m MsgUpdateParams) GetSignBytes() []byte {

// ValidateBasic does a sanity check of the provided data
func (m *MsgRegisterERC20) ValidateBasic() error {
_, err := sdk.AccAddressFromBech32(m.Signer)
if err != nil {
return errorsmod.Wrap(err, "invalid signer address")
}

for _, addr := range m.Erc20Addresses {
if !common.IsHexAddress(addr) {
return errortypes.ErrInvalidAddress.Wrapf("invalid ERC20 contract address: %s", addr)
Expand Down
123 changes: 62 additions & 61 deletions x/erc20/types/tx.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion x/erc20/types/tx.pb.gw.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading