Skip to content

Commit

Permalink
Merge pull request #395 from irisnet/feature/support-swap-to-erc20
Browse files Browse the repository at this point in the history
feat: implement `SwapToERC20`
  • Loading branch information
mitch1024 authored Apr 16, 2024
2 parents e1f4d7e + 1ec49e0 commit f728b7f
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 13 deletions.
98 changes: 98 additions & 0 deletions modules/token/keeper/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
29 changes: 21 additions & 8 deletions modules/token/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"encoding/hex"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -310,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 {
Expand All @@ -325,6 +323,21 @@ 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
}

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
}
22 changes: 17 additions & 5 deletions modules/token/types/v1/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,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() {
Expand All @@ -418,7 +416,21 @@ 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 tokentypes.IsValidEthAddress(m.Receiver) {
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
}

Expand Down
11 changes: 11 additions & 0 deletions modules/token/types/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

0 comments on commit f728b7f

Please sign in to comment.