From 7554d5fa3d35c1800138d6c9fdb7efb55d16b60a Mon Sep 17 00:00:00 2001 From: dreamer Date: Mon, 15 Apr 2024 16:01:02 +0800 Subject: [PATCH 1/5] implement SwapToERC20 --- modules/token/keeper/erc20.go | 98 ++++++++++++++++++++++++++++++ modules/token/keeper/msg_server.go | 23 ++++++- modules/token/types/v1/msgs.go | 20 +++++- 3 files changed, 138 insertions(+), 3 deletions(-) diff --git a/modules/token/keeper/erc20.go b/modules/token/keeper/erc20.go index 08102837..231ca42c 100644 --- a/modules/token/keeper/erc20.go +++ b/modules/token/keeper/erc20.go @@ -118,6 +118,104 @@ func (k Keeper) SwapFromERC20( return nil } +// SwapToERC20 executes a swap from a native token to its ERC20 token counterpart +// +// Parameters: +// - ctx: the context +// - sender: the sender of the amount +// - receiver: the receiver of the erc20 token +// - amount: the amount to be swapped +// +// Returns: +// - error: error if any. +func (k Keeper) SwapToERC20( + ctx sdk.Context, + sender sdk.AccAddress, + receiver common.Address, + amount sdk.Coin, +) error { + receiverAcc := k.accountKeeper.GetAccount(ctx, sdk.AccAddress(receiver.Bytes())) + if receiverAcc != nil { + if !k.evmKeeper.SupportedKey(receiverAcc.GetPubKey()) { + return errorsmod.Wrapf(types.ErrUnsupportedKey, "key %s", receiverAcc.GetPubKey()) + } + } + + token, err := k.getTokenByMinUnit(ctx, amount.Denom) + if err != nil { + return err + } + if len(token.Contract) == 0 { + return errorsmod.Wrapf(types.ErrERC20NotDeployed, "token: %s is not bound to the corresponding erc20 token", amount.Denom) + } + contract := common.HexToAddress(token.Contract) + + amt := sdk.NewCoins(amount) + if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amt); err != nil { + return err + } + + if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, amt); err != nil { + return err + } + + if err := k.MintERC20(ctx, contract, receiver, amount.Amount.Uint64()); err != nil { + return err + } + + ctx.EventManager().EmitTypedEvent(&v1.EventSwapToERC20{ + Amount: amount, + Sender: sender.String(), + Receiver: receiver.String(), + ToContract: token.Contract, + }) + return nil +} + +// MintERC20 mints ERC20 tokens to an account. +// +// Parameters: +// - ctx: the sdk.Context for the function +// - contract: the address of the contract +// - to: the address of the receiver +// - amount: the amount to mint +// +// Returns: +// - err : error if any +func (k Keeper) MintERC20( + ctx sdk.Context, + contract, to common.Address, + amount uint64, +) error { + balanceBefore := k.BalanceOf(ctx, contract, to) + + abi := contracts.ERC20TokenContract.ABI + res, err := k.CallEVM(ctx, abi, k.moduleAddress(), contract, true, contracts.MethodMint, to, amount) + if err != nil { + return err + } + + if res.Failed() { + return errorsmod.Wrapf( + types.ErrVMExecution, "failed to mint contract: %s, reason: %s", + contract.String(), + res.Revert(), + ) + } + + balanceAfter := k.BalanceOf(ctx, contract, to) + expectBalance := big.NewInt(0).Add(balanceBefore, big.NewInt(int64(amount))) + if r := expectBalance.Cmp(balanceAfter); r != 0 { + return errorsmod.Wrapf( + types.ErrVMExecution, "failed to mint token correctly, expected after-mint amount is incorrect: %s, expected %d, actual %d", + contract.String(), + expectBalance.Int64(), + balanceAfter.Int64(), + ) + } + return nil +} + // BurnERC20 burns a specific amount of ERC20 tokens from a given contract and address. // // Parameters: diff --git a/modules/token/keeper/msg_server.go b/modules/token/keeper/msg_server.go index e294d0bd..e0a6ea08 100644 --- a/modules/token/keeper/msg_server.go +++ b/modules/token/keeper/msg_server.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "encoding/hex" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -325,6 +326,24 @@ func (m msgServer) SwapFromERC20(goCtx context.Context, msg *v1.MsgSwapFromERC20 } // SwapToERC20 implements v1.MsgServer. -func (m msgServer) SwapToERC20(context.Context, *v1.MsgSwapToERC20) (*v1.MsgSwapToERC20Response, error) { - panic("unimplemented") +func (m msgServer) SwapToERC20(goCtx context.Context, msg *v1.MsgSwapToERC20) (*v1.MsgSwapToERC20Response, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + sender, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return nil, err + } + + receiver := common.BytesToAddress(sender.Bytes()) + if len(msg.Receiver) > 0 { + bz, err := hex.DecodeString(msg.Receiver) + if err != nil { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", msg.Receiver) + } + receiver = common.BytesToAddress(bz) + } + + if err := m.k.SwapToERC20(ctx, sender, receiver, msg.Amount); err != nil { + return nil, err + } + return &v1.MsgSwapToERC20Response{}, nil } diff --git a/modules/token/types/v1/msgs.go b/modules/token/types/v1/msgs.go index 57ddded9..3c5d0f94 100644 --- a/modules/token/types/v1/msgs.go +++ b/modules/token/types/v1/msgs.go @@ -1,6 +1,7 @@ package v1 import ( + "encoding/hex" fmt "fmt" "regexp" @@ -418,7 +419,24 @@ func (m *MsgSwapFromERC20) GetSigners() []sdk.AccAddress { // ValidateBasic implements Msg func (m *MsgSwapToERC20) ValidateBasic() error { - // TODO + if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) + } + + if len(m.Receiver) > 0 { + _, err := hex.DecodeString(m.Receiver) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", m.Receiver) + } + } + + if !m.Amount.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } + + if !m.Amount.IsPositive() { + return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) + } return nil } From c9850767a7db05a62e3bf01b366d3c3ba2eb52f5 Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 16 Apr 2024 09:19:12 +0800 Subject: [PATCH 2/5] SwapFromERC20 add check --- modules/token/keeper/erc20.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/token/keeper/erc20.go b/modules/token/keeper/erc20.go index 231ca42c..e0285a89 100644 --- a/modules/token/keeper/erc20.go +++ b/modules/token/keeper/erc20.go @@ -87,6 +87,13 @@ func (k Keeper) SwapFromERC20( receiver sdk.AccAddress, wantedAmount sdk.Coin, ) error { + senderAcc := k.accountKeeper.GetAccount(ctx, sdk.AccAddress(sender.Bytes())) + if senderAcc != nil { + if !k.evmKeeper.SupportedKey(senderAcc.GetPubKey()) { + return errorsmod.Wrapf(types.ErrUnsupportedKey, "key %s", senderAcc.GetPubKey()) + } + } + token, err := k.getTokenByMinUnit(ctx, wantedAmount.Denom) if err != nil { return err From a04c7539ac63434e84ea3e1860be463761e76e98 Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 16 Apr 2024 10:48:58 +0800 Subject: [PATCH 3/5] Revert "SwapFromERC20 add check" This reverts commit c9850767a7db05a62e3bf01b366d3c3ba2eb52f5. --- modules/token/keeper/erc20.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/token/keeper/erc20.go b/modules/token/keeper/erc20.go index e0285a89..231ca42c 100644 --- a/modules/token/keeper/erc20.go +++ b/modules/token/keeper/erc20.go @@ -87,13 +87,6 @@ func (k Keeper) SwapFromERC20( receiver sdk.AccAddress, wantedAmount sdk.Coin, ) error { - senderAcc := k.accountKeeper.GetAccount(ctx, sdk.AccAddress(sender.Bytes())) - if senderAcc != nil { - if !k.evmKeeper.SupportedKey(senderAcc.GetPubKey()) { - return errorsmod.Wrapf(types.ErrUnsupportedKey, "key %s", senderAcc.GetPubKey()) - } - } - token, err := k.getTokenByMinUnit(ctx, wantedAmount.Denom) if err != nil { return err From a1a54d370745f762c2c11376df122fc75095b0dd Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 16 Apr 2024 11:05:14 +0800 Subject: [PATCH 4/5] receiver must not be empty --- modules/token/keeper/msg_server.go | 20 +++++++------------- modules/token/types/v1/msgs.go | 14 +++++--------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/modules/token/keeper/msg_server.go b/modules/token/keeper/msg_server.go index e0a6ea08..dbd1f0af 100644 --- a/modules/token/keeper/msg_server.go +++ b/modules/token/keeper/msg_server.go @@ -311,12 +311,9 @@ func (m msgServer) SwapFromERC20(goCtx context.Context, msg *v1.MsgSwapFromERC20 return nil, err } - receiver := sender - if len(msg.Receiver) > 0 { - receiver, err = sdk.AccAddressFromBech32(msg.Receiver) - if err != nil { - return nil, err - } + receiver, err := sdk.AccAddressFromBech32(msg.Receiver) + if err != nil { + return nil, err } if err := m.k.SwapFromERC20(ctx, common.BytesToAddress(sender.Bytes()), receiver, msg.WantedAmount); err != nil { @@ -333,14 +330,11 @@ func (m msgServer) SwapToERC20(goCtx context.Context, msg *v1.MsgSwapToERC20) (* return nil, err } - receiver := common.BytesToAddress(sender.Bytes()) - if len(msg.Receiver) > 0 { - bz, err := hex.DecodeString(msg.Receiver) - if err != nil { - return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", msg.Receiver) - } - receiver = common.BytesToAddress(bz) + bz, err := hex.DecodeString(msg.Receiver) + if err != nil { + return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", msg.Receiver) } + receiver := common.BytesToAddress(bz) if err := m.k.SwapToERC20(ctx, sender, receiver, msg.Amount); err != nil { return nil, err diff --git a/modules/token/types/v1/msgs.go b/modules/token/types/v1/msgs.go index 3c5d0f94..229c45f9 100644 --- a/modules/token/types/v1/msgs.go +++ b/modules/token/types/v1/msgs.go @@ -395,10 +395,8 @@ func (m *MsgSwapFromERC20) ValidateBasic() error { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } - if len(m.Receiver) > 0 { - if _, err := sdk.AccAddressFromBech32(m.Receiver); err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid receiver address (%s)", err) - } + if _, err := sdk.AccAddressFromBech32(m.Receiver); err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid receiver address (%s)", err) } if !m.WantedAmount.IsValid() { @@ -423,11 +421,9 @@ func (m *MsgSwapToERC20) ValidateBasic() error { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } - if len(m.Receiver) > 0 { - _, err := hex.DecodeString(m.Receiver) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", m.Receiver) - } + _, err := hex.DecodeString(m.Receiver) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", m.Receiver) } if !m.Amount.IsValid() { From 1ec49e07cd4d9132bc67e0c1a7f409bd29183be3 Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 16 Apr 2024 12:01:01 +0800 Subject: [PATCH 5/5] add check for eth address --- modules/token/types/v1/msgs.go | 4 +--- modules/token/types/validation.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/token/types/v1/msgs.go b/modules/token/types/v1/msgs.go index 229c45f9..0153ae7c 100644 --- a/modules/token/types/v1/msgs.go +++ b/modules/token/types/v1/msgs.go @@ -1,7 +1,6 @@ package v1 import ( - "encoding/hex" fmt "fmt" "regexp" @@ -421,8 +420,7 @@ func (m *MsgSwapToERC20) ValidateBasic() error { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address (%s)", err) } - _, err := hex.DecodeString(m.Receiver) - if err != nil { + if tokentypes.IsValidEthAddress(m.Receiver) { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expecting a hex address of 0x, got %s", m.Receiver) } diff --git a/modules/token/types/validation.go b/modules/token/types/validation.go index 704ba5df..3a58eb10 100644 --- a/modules/token/types/validation.go +++ b/modules/token/types/validation.go @@ -50,6 +50,11 @@ var ( regexpMinUintFmt = fmt.Sprintf("^[a-z][a-z0-9]{%d,%d}$", MinimumMinUnitLen-1, MaximumMinUnitLen-1) regexpMinUint = regexp.MustCompile(regexpMinUintFmt).MatchString + + regexpEthAddressLowerStr = "^0x[0-9a-f]{40}$" + regexpEthAddressUpperStr = "^0x[0-9A-F]{40}$" + regexpEthAddressLower = regexp.MustCompile(regexpEthAddressLowerStr).MatchString + regexpEthAddressUpper = regexp.MustCompile(regexpEthAddressUpperStr).MatchString ) // ValidateInitialSupply verifies whether the initial supply is legal @@ -115,3 +120,9 @@ func ValidateCoin(coin sdk.Coin) error { } return ValidateMinUnit(coin.Denom) } + +// IsValidEthAddress checks if the given address is valid ethereum address +func IsValidEthAddress(address string) bool { + address = strings.ToLower(address) + return regexpEthAddressLower(address) || regexpEthAddressUpper(address) +}