Skip to content

Commit

Permalink
Support ibc tokens to crc20 (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomas-nguy authored Sep 9, 2021
1 parent f7980bd commit a90d406
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 27 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features

- [cronos#11](https://github.com/crypto-org-chain/cronos/pull/11) embed gravity bridge module
- [cronos#35](https://github.com/crypto-org-chain/cronos/pull/35) add support for ibc hook
- [cronos#55](https://github.com/crypto-org-chain/cronos/pull/55) add support for ibc token conversion to crc20

- [cronos#45](https://github.com/crypto-org-chain/cronos/pull/45) Allow evm contract to call bank send and gravity send

Expand Down
2 changes: 1 addition & 1 deletion x/cronos/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (suite *CronosTestSuite) TestMsgConvertVouchers() {
"Correct address with non supported coin denom",
types.NewMsgConvertVouchers(suite.address.String(), sdk.NewCoins(sdk.NewCoin("fake", sdk.NewInt(1)))),
func() {},
errors.New("coin fake is not supported"),
errors.New("coin fake is not supported for wrapping"),
},
}

Expand Down
6 changes: 3 additions & 3 deletions x/cronos/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ func (k Keeper) DeployModuleCRC20(ctx sdk.Context, denom string) (common.Address
// ConvertCoinFromNativeToCRC20 convert native token to erc20 token
func (k Keeper) ConvertCoinFromNativeToCRC20(ctx sdk.Context, sender common.Address, coin sdk.Coin, autoDeploy bool) error {
if !types.IsValidDenomToWrap(coin.Denom) {
return errors.New("denom is not supported for wrapping")
return fmt.Errorf("coin %s is not supported for wrapping", coin.Denom)
}

var err error
// external contract is returned in preference to auto-deployed ones
contract, found := k.GetContractByDenom(ctx, coin.Denom)
if !found {
if !autoDeploy {
return errors.New("no contract found for the denom")
return fmt.Errorf("no contract found for the denom %s", coin.Denom)
}
contract, err = k.DeployModuleCRC20(ctx, coin.Denom)
if err != nil {
Expand All @@ -126,7 +126,7 @@ func (k Keeper) ConvertCoinFromNativeToCRC20(ctx sdk.Context, sender common.Addr
func (k Keeper) ConvertCoinFromCRC20ToNative(ctx sdk.Context, contract common.Address, receiver common.Address, amount sdk.Int) error {
denom, found := k.GetDenomByContract(ctx, contract)
if !found {
return errors.New("the contract address is not mapped to native token")
return fmt.Errorf("the contract address %s is not mapped to native token", contract.String())
}

err := k.bankKeeper.SendCoins(
Expand Down
59 changes: 39 additions & 20 deletions x/cronos/keeper/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"

"github.com/armon/go-metrics"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -52,7 +54,11 @@ func (k Keeper) ConvertVouchersToEvmCoins(ctx sdk.Context, from string, coins sd
}

default:
return fmt.Errorf("coin %s is not supported", c.Denom)
// TODO use autoDeploy boolean in Params.go
err := k.ConvertCoinFromNativeToCRC20(ctx, common.BytesToAddress(acc.Bytes()), c, true)
if err != nil {
return err
}
}
}
defer func() {
Expand Down Expand Up @@ -115,31 +121,20 @@ func (k Keeper) IbcTransferCoins(ctx sdk.Context, from, destination string, coin
return err
}

channelID, err := k.GetSourceChannelID(ctx, params.IbcCroDenom)
err = k.ibcSendTransfer(ctx, acc, destination, ibcCoin)
if err != nil {
return err
}
// Transfer coins to receiver through IBC
// We use current time for timeout timestamp and zero height for timeoutHeight
// it means it can never fail by timeout
// TODO Might need to consider add timeout option in configuration.
timeoutTimestamp := ctx.BlockTime().UnixNano()
timeoutHeight := ibcclienttypes.ZeroHeight()
err = k.transferKeeper.SendTransfer(
ctx,
ibctransfertypes.PortID,
channelID,
ibcCoin,
acc,
destination,
timeoutHeight,
uint64(timeoutTimestamp))

default:
_, found := k.GetContractByDenom(ctx, c.Denom)
if !found {
return fmt.Errorf("coin %s is not supported", c.Denom)
}
err = k.ibcSendTransfer(ctx, acc, destination, c)
if err != nil {
return err
}

default:
return fmt.Errorf("coin %s is not supported", c.Denom)
}
}

Expand All @@ -156,3 +151,27 @@ func (k Keeper) IbcTransferCoins(ctx sdk.Context, from, destination string, coin
}()
return nil
}

func (k Keeper) ibcSendTransfer(ctx sdk.Context, sender sdk.AccAddress, destination string, coin sdk.Coin) error {
// Coin needs to be a voucher so that we can extract the channel id from the denom
channelID, err := k.GetSourceChannelID(ctx, coin.Denom)
if err != nil {
return err
}

// Transfer coins to receiver through IBC
// We use current time for timeout timestamp and zero height for timeoutHeight
// it means it can never fail by timeout
// TODO Might need to consider add timeout option in configuration.
timeoutTimestamp := ctx.BlockTime().UnixNano()
timeoutHeight := ibcclienttypes.ZeroHeight()
return k.transferKeeper.SendTransfer(
ctx,
ibctransfertypes.PortID,
channelID,
coin,
sender,
destination,
timeoutHeight,
uint64(timeoutTimestamp))
}
70 changes: 67 additions & 3 deletions x/cronos/keeper/ibc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package keeper_test

import (
"errors"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/crypto-org-chain/cronos/app"
cronosmodulekeeper "github.com/crypto-org-chain/cronos/x/cronos/keeper"
keepertest "github.com/crypto-org-chain/cronos/x/cronos/keeper/mock"
"github.com/crypto-org-chain/cronos/x/cronos/types"
"github.com/ethereum/go-ethereum/common"
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
"math/big"
)

const CorrectIbcDenom = "ibc/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

func (suite *KeeperTestSuite) TestConvertVouchersToEvmCoins() {

privKey, err := ethsecp256k1.GenerateKey()
Expand Down Expand Up @@ -45,7 +50,7 @@ func (suite *KeeperTestSuite) TestConvertVouchersToEvmCoins() {
address.String(),
sdk.NewCoins(sdk.NewCoin("fake", sdk.NewInt(1))),
func() {},
errors.New("coin fake is not supported"),
errors.New("coin fake is not supported for wrapping"),
func() {},
},
{
Expand All @@ -57,7 +62,7 @@ func (suite *KeeperTestSuite) TestConvertVouchersToEvmCoins() {
func() {},
},
{
"Correct address with enough IBC CRO token",
"Correct address with enough IBC CRO token : Should receive CRO tokens",
address.String(),
sdk.NewCoins(sdk.NewCoin(types.IbcCroDenomDefaultValue, sdk.NewInt(123))),
func() {
Expand All @@ -79,6 +84,37 @@ func (suite *KeeperTestSuite) TestConvertVouchersToEvmCoins() {
suite.Require().Equal(sdk.NewInt(1230000000000), evmCoin.Amount)
},
},
{
"Correct address with not enough IBC token",
address.String(),
sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(1))),
func() {},
fmt.Errorf("0%s is smaller than 1%s: insufficient funds", CorrectIbcDenom, CorrectIbcDenom),
func() {},
},
{
"Correct address with IBC token : Should receive CRC20 tokens",
address.String(),
sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))),
func() {
suite.MintCoins(address, sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))))
// Verify balance IBC coin pre operation
ibcCroCoin := suite.GetBalance(address, CorrectIbcDenom)
suite.Require().Equal(sdk.NewInt(123), ibcCroCoin.Amount)
},
nil,
func() {
// Verify balance IBC coin post operation
ibcCroCoin := suite.GetBalance(address, CorrectIbcDenom)
suite.Require().Equal(sdk.NewInt(0), ibcCroCoin.Amount)
// Verify CRC20 balance post operation
contract, found := suite.app.CronosKeeper.GetContractByDenom(suite.ctx, CorrectIbcDenom)
suite.Require().True(found)
ret, err := suite.app.CronosKeeper.CallModuleCRC20(suite.ctx, contract, "balanceOf", common.BytesToAddress(address.Bytes()))
suite.Require().NoError(err)
suite.Require().Equal(big.NewInt(123), big.NewInt(0).SetBytes(ret))
},
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -158,7 +194,7 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() {
func() {},
},
{
"Correct address with enough EVM token",
"Correct address with enough EVM token : Should receive IBC CRO token",
address.String(),
"to",
sdk.NewCoins(sdk.NewCoin(suite.evmParam.EvmDenom, sdk.NewInt(1230000000000))),
Expand All @@ -183,6 +219,34 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() {
suite.Require().Equal(sdk.NewInt(0), evmCoin.Amount)
},
},
{
"Correct address with non correct IBC token denom",
address.String(),
"to",
sdk.NewCoins(sdk.NewCoin("incorrect", sdk.NewInt(123))),
func() {
// Add support for the IBC token
suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, "incorrect", common.HexToAddress("0x11"))
},
errors.New("incorrect is invalid: ibc cro denom is invalid"),
func() {
},
},
{
"Correct address with correct IBC token denom",
address.String(),
"to",
sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))),
func() {
// Mint IBC token for user
suite.MintCoins(address, sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))))
// Add support for the IBC token
suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, CorrectIbcDenom, common.HexToAddress("0x11"))
},
nil,
func() {
},
},
}

for _, tc := range testCases {
Expand Down
6 changes: 6 additions & 0 deletions x/cronos/keeper/mock/ibckeeper_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@ func (i IbcKeeperMock) GetDenomTrace(ctx sdk.Context, denomTraceHash tmbytes.Hex
BaseDenom: "basetcro",
}, true
}
if denomTraceHash.String() == "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" {
return types.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "correctIBCToken",
}, true
}
return types.DenomTrace{}, false
}

0 comments on commit a90d406

Please sign in to comment.