Skip to content

Commit

Permalink
implement SwapFromERC20
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamer-zq committed Apr 15, 2024
1 parent 7eae3bb commit f3760bf
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 4 deletions.
5 changes: 4 additions & 1 deletion contracts/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (

const (
EventSwapToNative = "SwapToNative"
MethodMint = "mint"
MethodBurn = "burn"
MethodBalanceOf = "balanceOf"
)

var (
Expand All @@ -28,4 +31,4 @@ func init() {
if len(ERC20TokenContract.Bin) == 0 {
panic("load contract failed")
}
}
}
129 changes: 129 additions & 0 deletions modules/token/keeper/erc20.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper

import (
"math/big"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -69,6 +71,133 @@ func (k Keeper) DeployERC20(
return contractAddr, nil
}

// SwapFromERC20 executes a swap from an ERC20 token to a native token.
//
// Parameters:
//
// ctx - the context in which the swap is executed
// sender - the address of the sender
// receiver - the address of the receiver
// wantedAmount - the amount of the token to be swapped out
//
// Return type: error
func (k Keeper) SwapFromERC20(
ctx sdk.Context,
sender common.Address,
receiver sdk.AccAddress,
wantedAmount sdk.Coin,
) error {
token, err := k.getTokenByMinUnit(ctx, wantedAmount.Denom)
if err != nil {
return err
}
if len(token.Contract) == 0 {
return errorsmod.Wrapf(types.ErrERC20NotDeployed, "The token %s is not bound to the corresponding erc20 token", wantedAmount.Denom)
}

contract := common.HexToAddress(token.Contract)
amount := wantedAmount.Amount.BigInt()
balance := k.BalanceOf(ctx, contract, sender)
if r := balance.Cmp(amount); r < 0 {
return errorsmod.Wrapf(
sdkerrors.ErrInsufficientFunds,
"balance: %d, swap: %d",
balance,
amount,
)
}
if err := k.BurnERC20(ctx, contract, sender, amount.Uint64()); err != nil {
return err
}

mintedCoins := sdk.NewCoins(wantedAmount)
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, mintedCoins); err != nil {
return err
}

if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, receiver, mintedCoins); err != nil {
return err
}

ctx.EventManager().EmitTypedEvent(&v1.EventSwapFromERC20{
FromContract: contract.String(),
WantedAmount: &wantedAmount,
Sender: sender.String(),
Receiver: receiver.String(),
})
return nil
}

// BurnERC20 burns a specific amount of ERC20 tokens from a given contract and address.
//
// Parameters:
// - ctx: the context in which the transaction is executed
// - contract: the contract address of the ERC20 token
// - from: the address from which the tokens are burned
// - amount: the amount of tokens to burn
//
// Returns an error.
func (k Keeper) BurnERC20(
ctx sdk.Context,
contract, from common.Address,
amount uint64,
) error {
balanceBefore := k.BalanceOf(ctx, contract, from)
abi := contracts.ERC20TokenContract.ABI
res, err := k.CallEVM(ctx, abi, k.moduleAddress(), contract, true, contracts.MethodBurn, from, amount)
if err != nil {
return err
}

if res.Failed() {
return errorsmod.Wrapf(types.ErrVMExecution, "failed to burn %d", amount)
}

balanceAfter := k.BalanceOf(ctx, contract, from)
expectBalance := big.NewInt(0).Sub(balanceBefore, big.NewInt(int64(amount)))
if r := expectBalance.Cmp(balanceAfter); r != 0 {
return errorsmod.Wrapf(
types.ErrVMExecution, "failed to burn contract: %s, expect %d, actual %d, ",
contract.String(),
expectBalance.Int64(),
balanceAfter.Int64(),
)
}
return nil
}

// BalanceOf retrieves the balance of a specific account in the contract.
//
// Parameters:
// - ctx: the sdk.Context for the function
// - contract: the address of the contract
// - account: the address of the account to retrieve the balance for
//
// Returns:
// - *big.Int: the balance of the specified account
func (k Keeper) BalanceOf(
ctx sdk.Context,
contract, account common.Address,
) *big.Int {
abi := contracts.ERC20TokenContract.ABI
res, err := k.CallEVM(ctx, abi, k.moduleAddress(), contract, false, contracts.MethodBalanceOf, account)
if err != nil {
return nil
}

unpacked, err := abi.Unpack(contracts.MethodBalanceOf, res.Ret)
if err != nil || len(unpacked) == 0 {
return nil
}

balance, ok := unpacked[0].(*big.Int)
if !ok {
return nil
}

return balance
}

func (k Keeper) moduleAddress() common.Address {
moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName)
return common.BytesToAddress(moduleAddr.Bytes())
Expand Down
38 changes: 38 additions & 0 deletions modules/token/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
Expand All @@ -15,6 +16,43 @@ import (
"github.com/irisnet/irismod/types"
)

// CallEVM calls the EVM with the provided contract ABI, sender and receiver addresses, method, and arguments.
//
// Parameters:
// - ctx: the context in which the EVM call is executed
// - contractABI: the ABI of the contract
// - from: the sender address
// - to: the receiver address
// - commit: boolean indicating whether the EVM call should be committed
// - method: the name of the method to be called
// - args: the arguments to be passed to the method
//
// Returns:
// - *types.Result: the result of the EVM call
// - error: an error if the EVM call encounters any issues
func (k Keeper) CallEVM(
ctx sdk.Context,
contractABI abi.ABI,
from, to common.Address,
commit bool,
method string,
args ...interface{},
) (*types.Result, error) {
data, err := contractABI.Pack(method, args...)
if err != nil {
return nil, errorsmod.Wrap(
tokentypes.ErrABIPack,
errorsmod.Wrap(err, "failed to create transaction data").Error(),
)
}

resp, err := k.CallEVMWithData(ctx, from, &to, data, commit)
if err != nil {
return nil, errorsmod.Wrapf(err, "contract call failed: method '%s', contract '%s'", method, to)
}
return resp, nil
}

// CallEVMWithData executes an Ethereum Virtual Machine (EVM) call with the provided data.
//
// Parameters:
Expand Down
22 changes: 20 additions & 2 deletions modules/token/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"

"github.com/irisnet/irismod/modules/token/types"
v1 "github.com/irisnet/irismod/modules/token/types/v1"
Expand Down Expand Up @@ -302,8 +303,25 @@ func (m msgServer) DeployERC20(goCtx context.Context, msg *v1.MsgDeployERC20) (*
}

// SwapFromERC20 implements v1.MsgServer.
func (m msgServer) SwapFromERC20(context.Context, *v1.MsgSwapFromERC20) (*v1.MsgSwapFromERC20Response, error) {
panic("unimplemented")
func (m msgServer) SwapFromERC20(goCtx context.Context, msg *v1.MsgSwapFromERC20) (*v1.MsgSwapFromERC20Response, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
sender, err := sdk.AccAddressFromBech32(msg.Receiver)
if err != nil {
return nil, err
}

receiver := sender
if len(msg.Receiver) > 0 {
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 {
return nil, err
}
return &v1.MsgSwapFromERC20Response{}, nil
}

// SwapToERC20 implements v1.MsgServer.
Expand Down
16 changes: 15 additions & 1 deletion modules/token/types/v1/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,21 @@ func (m *MsgDeployERC20) GetSigners() []sdk.AccAddress {

// ValidateBasic implements Msg
func (m *MsgSwapFromERC20) ValidateBasic() error {
// TODO
if _, err := sdk.AccAddressFromBech32(m.Sender); err != nil {
return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender 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() {
return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.WantedAmount.String())
}

if !m.WantedAmount.IsPositive() {
return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.WantedAmount.String())
}
return nil
}

Expand Down

0 comments on commit f3760bf

Please sign in to comment.