Skip to content

Commit

Permalink
Merge pull request #401 from irisnet/feature/add-test-token
Browse files Browse the repository at this point in the history
test: add test & fix error
  • Loading branch information
mitch1024 authored Apr 17, 2024
2 parents 40a71dc + 1b6a273 commit 35dc751
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 34 deletions.
51 changes: 38 additions & 13 deletions modules/token/keeper/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,17 @@ func (k Keeper) DeployERC20(
name string,
symbol string,
minUnit string,
scale int8,
scale uint8,
) (common.Address, error) {
token, err := k.buildERC20Token(ctx, name, symbol, minUnit, uint32(scale))
if err != nil {
return common.Address{}, err
}

if len(token.Contract) > 0 {
return common.Address{}, errorsmod.Wrapf(types.ErrERC20AlreadyExists, "token: %s already deployed erc20 contract: %s", token.Symbol, token.Contract)
}

contractArgs, err := contracts.ERC20TokenContract.ABI.Pack(
"",
name,
Expand Down Expand Up @@ -61,6 +70,9 @@ func (k Keeper) DeployERC20(
return common.Address{}, errorsmod.Wrapf(types.ErrVMExecution, "failed to deploy contract for %s, reason: %s", name, result.Revert())
}

token.Contract = contractAddr.String()
k.upsertToken(ctx, *token)

ctx.EventManager().EmitTypedEvent(&v1.EventDeployERC20{
Symbol: symbol,
Name: name,
Expand Down Expand Up @@ -159,7 +171,7 @@ func (k Keeper) SwapToERC20(
return err
}

if err := k.MintERC20(ctx, contract, receiver, amount.Amount.Uint64()); err != nil {
if err := k.MintERC20(ctx, contract, receiver, amount.Amount.BigInt()); err != nil {
return err
}

Expand All @@ -185,9 +197,12 @@ func (k Keeper) SwapToERC20(
func (k Keeper) MintERC20(
ctx sdk.Context,
contract, to common.Address,
amount uint64,
amount *big.Int,
) error {
balanceBefore := k.BalanceOf(ctx, contract, to)
balanceBefore, err := k.BalanceOf(ctx, contract, to)
if err != nil {
return err
}

abi := contracts.ERC20TokenContract.ABI
res, err := k.CallEVM(ctx, abi, k.moduleAddress(), contract, true, contracts.MethodMint, to, amount)
Expand All @@ -203,8 +218,11 @@ func (k Keeper) MintERC20(
)
}

balanceAfter := k.BalanceOf(ctx, contract, to)
expectBalance := big.NewInt(0).Add(balanceBefore, big.NewInt(int64(amount)))
balanceAfter, err := k.BalanceOf(ctx, contract, to)
if err != nil {
return err
}
expectBalance := big.NewInt(0).Add(balanceBefore, 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",
Expand All @@ -230,7 +248,11 @@ func (k Keeper) BurnERC20(
contract, from common.Address,
amount *big.Int,
) error {
balanceBefore := k.BalanceOf(ctx, contract, from)
balanceBefore, err := k.BalanceOf(ctx, contract, from)
if err != nil {
return err
}

if r := balanceBefore.Cmp(amount); r < 0 {
return errorsmod.Wrapf(
sdkerrors.ErrInsufficientFunds,
Expand All @@ -250,7 +272,10 @@ func (k Keeper) BurnERC20(
return errorsmod.Wrapf(types.ErrVMExecution, "failed to burn %d", amount)
}

balanceAfter := k.BalanceOf(ctx, contract, from)
balanceAfter, err := k.BalanceOf(ctx, contract, from)
if err != nil {
return err
}
expectBalance := big.NewInt(0).Sub(balanceBefore, amount)
if r := expectBalance.Cmp(balanceAfter); r != 0 {
return errorsmod.Wrapf(
Expand All @@ -275,24 +300,24 @@ func (k Keeper) BurnERC20(
func (k Keeper) BalanceOf(
ctx sdk.Context,
contract, account common.Address,
) *big.Int {
) (*big.Int, error) {
abi := contracts.ERC20TokenContract.ABI
res, err := k.CallEVM(ctx, abi, k.moduleAddress(), contract, false, contracts.MethodBalanceOf, account)
if err != nil {
return nil
return nil, err
}

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

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

return balance
return balance, nil
}

func (k Keeper) moduleAddress() common.Address {
Expand Down
109 changes: 109 additions & 0 deletions modules/token/keeper/erc20_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper_test

import (
"math/big"

"github.com/cometbft/cometbft/crypto/tmhash"
"github.com/ethereum/go-ethereum/common"

sdk "github.com/cosmos/cosmos-sdk/types"

v1 "github.com/irisnet/irismod/modules/token/types/v1"
)

func (suite *KeeperTestSuite) TestDeployERC20() {
token := v1.NewToken("btc", "Bitcoin Network", "satoshi", 18, 21000000, 21000000, false, owner)

err := suite.keeper.IssueToken(
suite.ctx, token.Symbol, token.Name,
token.MinUnit, token.Scale, token.InitialSupply,
token.MaxSupply, token.Mintable, token.GetOwner(),
)
suite.NoError(err)

hash, err := suite.keeper.DeployERC20(suite.ctx, token.Name, token.Symbol, token.MinUnit, uint8(token.Scale))
suite.NoError(err)
token.Contract = hash.Hex()

actual, err := suite.keeper.GetToken(suite.ctx, token.Symbol)
suite.NoError(err)

suite.EqualValues(&token, actual.(*v1.Token))
}

func (suite *KeeperTestSuite) TestSwapFromERC20() {
token := v1.NewToken("btc", "Bitcoin Network", "satoshi", 18, 21000000, 21000000, false, owner)

err := suite.keeper.IssueToken(
suite.ctx, token.Symbol, token.Name,
token.MinUnit, token.Scale, token.InitialSupply,
token.MaxSupply, token.Mintable, token.GetOwner(),
)
suite.NoError(err)

contract, err := suite.keeper.DeployERC20(suite.ctx, token.Name, token.Symbol, token.MinUnit, uint8(token.Scale))
suite.NoError(err)
token.Contract = contract.Hex()

actual, err := suite.keeper.GetToken(suite.ctx, token.Symbol)
suite.NoError(err)

suite.EqualValues(&token, actual.(*v1.Token))

cosmosAddr := sdk.AccAddress(tmhash.SumTruncated([]byte("TestSwapFromERC20")))
amount := big.NewInt(2e18)
evmAddr := common.BytesToAddress(cosmosAddr.Bytes())

suite.Run("mint erc20", func() {
err = suite.keeper.MintERC20(suite.ctx, contract, evmAddr, amount)
suite.NoError(err)
})

suite.Run("swap from erc20", func() {
wantedAmount := sdk.NewCoin(token.MinUnit, sdk.NewInt(1e18))

err = suite.keeper.SwapFromERC20(suite.ctx, evmAddr, cosmosAddr, wantedAmount)
suite.NoError(err)

actual := suite.bk.GetBalance(suite.ctx, cosmosAddr, token.MinUnit)
suite.True(wantedAmount.Equal(actual), "SwapFromERC20 failed: %s != %s", wantedAmount.String(), actual.String())

balance, err := suite.keeper.BalanceOf(suite.ctx, contract, evmAddr)
suite.NoError(err)

expect := big.NewInt(0).Sub(amount, wantedAmount.Amount.BigInt())
suite.True(expect.Cmp(balance) == 0, "SwapFromERC20 failed balance: %s != %s", expect.String(), balance.String())
})
}

func (suite *KeeperTestSuite) TestSwapToERC20() {
token := v1.NewToken("btc", "Bitcoin Network", "satoshi", 18, 21000000, 21000000, false, owner)

err := suite.keeper.IssueToken(
suite.ctx, token.Symbol, token.Name,
token.MinUnit, token.Scale, token.InitialSupply,
token.MaxSupply, token.Mintable, token.GetOwner(),
)
suite.NoError(err)

contract, err := suite.keeper.DeployERC20(suite.ctx, token.Name, token.Symbol, token.MinUnit, uint8(token.Scale))
suite.NoError(err)

sender := token.GetOwner()
receiver := common.BytesToAddress(sender.Bytes())

balanceBefore := suite.bk.GetBalance(suite.ctx, sender,token.MinUnit)
suite.Run("swap to erc20", func() {
amount := sdk.NewCoin(token.MinUnit, sdk.NewInt(1e18))

err = suite.keeper.SwapToERC20(suite.ctx, sender, receiver, amount)
suite.NoError(err)

balance, err := suite.keeper.BalanceOf(suite.ctx, contract, receiver)
suite.NoError(err)
suite.True(amount.Amount.BigInt().Cmp(balance) == 0, "SwapToERC20 failed %s != %s", amount.String(), balance.String())

actual := suite.bk.GetBalance(suite.ctx, sender, token.MinUnit)
suite.True(balanceBefore.Sub(amount).IsEqual(actual), "SwapToERC20 failed %s != %s", balanceBefore.String(), actual.String())
})
}
2 changes: 1 addition & 1 deletion modules/token/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (k Keeper) CallEVM(
method string,
args ...interface{},
) (*types.Result, error) {
data, err := contractABI.Pack(method, args...)
data, err := contractABI.Pack(method, args...)
if err != nil {
return nil, errorsmod.Wrap(
tokentypes.ErrABIPack,
Expand Down
14 changes: 1 addition & 13 deletions modules/token/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,10 @@ func (m msgServer) DeployERC20(goCtx context.Context, msg *v1.MsgDeployERC20) (*
}

ctx := sdk.UnwrapSDKContext(goCtx)
token, err := m.k.buildERC20Token(ctx, msg.Name, msg.Symbol, msg.MinUnit, msg.Scale)
_, err := m.k.DeployERC20(ctx, msg.Name, msg.Symbol, msg.MinUnit, uint8(msg.Scale))
if err != nil {
return nil, err
}

if len(token.Contract) > 0 {
return nil, errorsmod.Wrapf(types.ErrERC20AlreadyExists, "token: %s already deployed erc20 contract: %s", token.Symbol, token.Contract)
}

contractAddr, err := m.k.DeployERC20(ctx, token.Name, token.Symbol, token.MinUnit, int8(token.Scale))
if err != nil {
return nil, err
}

token.Contract = contractAddr.String()
m.k.upsertToken(ctx, *token)
return &v1.MsgDeployERC20Response{}, nil
}

Expand Down
94 changes: 87 additions & 7 deletions simapp/mocks/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package mocks

import (
"context"
"fmt"
"math/big"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"

"github.com/irisnet/irismod/contracts"
tokentypes "github.com/irisnet/irismod/modules/token/types"
"github.com/irisnet/irismod/types"
)
Expand All @@ -26,7 +30,35 @@ type evm struct {

// ApplyMessage implements types.EVMKeeper.
func (e *evm) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*types.Result, error) {
panic("unimplemented")
isCreate := msg.To() == nil
if isCreate {
contractAddr := crypto.CreateAddress(msg.From(), msg.Nonce())

data := msg.Data()[len(contracts.ERC20TokenContract.Bin):]
argss, err := contracts.ERC20TokenContract.ABI.Constructor.Inputs.Unpack(data)
if err != nil {
return nil, err
}
name, _ := argss[0].(string)
symbol, _ := argss[1].(string)
scale, _ := argss[2].(uint8)
e.erc20s[contractAddr] = &erc20{
address: contractAddr,
scale: scale,
name: name,
symbol: symbol,
balance: make(map[common.Address]*big.Int),
}
return &types.Result{
Hash: contractAddr.Hex(),
}, nil
}

erc20Contract, ok := e.erc20s[*msg.To()]
if !ok {
return nil, fmt.Errorf("erc20 contract not found")
}
return e.dispatch(erc20Contract, msg.Data())
}

// ChainID implements types.EVMKeeper.
Expand All @@ -39,23 +71,71 @@ func (e *evm) EstimateGas(ctx context.Context, req *types.EthCallRequest) (uint6
return 3000000, nil
}

// FeeDenom implements types.EVMKeeper.
func (e *evm) FeeDenom() string {
return "eris"
}

// SupportedKey implements types.EVMKeeper.
func (e *evm) SupportedKey(pubKey cryptotypes.PubKey) bool {
return true
}

func (e *evm) dispatch(contract *erc20, data []byte) (*types.Result, error) {
method, err := contracts.ERC20TokenContract.ABI.MethodById(data[0:4])
if err != nil {
return nil, err
}

ret, err := contract.call(method, data[4:])
if err != nil {
return nil, err
}
return &types.Result{
Hash: contract.address.Hex(),
Ret: ret,
}, nil
}

type erc20 struct {
scale int8
address common.Address
scale uint8
name, symbol string

balance map[common.Address]*big.Int
}

func (erc20 erc20) call(method *abi.Method, data []byte) ([]byte, error) {
args, err := method.Inputs.Unpack(data)
if err != nil {
return nil, err
}

switch method.Name {
case "name":
return method.Outputs.Pack(erc20.name)
case "symbol":
return method.Outputs.Pack(erc20.symbol)
case "decimals":
return method.Outputs.Pack(erc20.scale)
case "balanceOf":
balance,ok := erc20.balance[args[0].(common.Address)]
if !ok {
return method.Outputs.Pack(big.NewInt(0))
}
return method.Outputs.Pack(balance)
case "mint":
to := args[0].(common.Address)
balance,ok := erc20.balance[args[0].(common.Address)]
if !ok {
balance = big.NewInt(0)
}
erc20.balance[to] = new(big.Int).Add(balance, args[1].(*big.Int))
return nil, nil
case "burn":
from := args[0].(common.Address)
erc20.balance[from] = new(big.Int).Sub(erc20.balance[from], args[1].(*big.Int))
return nil, nil
default:
return nil, fmt.Errorf("unknown method %s", method.Name)
}
}

type transferKeeper struct{}

// HasTrace implements types.ICS20Keeper.
Expand Down

0 comments on commit 35dc751

Please sign in to comment.