From 34b8f71272e80bac60fbe2b3c20e05ba88c038e7 Mon Sep 17 00:00:00 2001 From: Thomas Nguy Date: Wed, 8 Sep 2021 16:04:22 +0900 Subject: [PATCH 1/4] support ibc to crc20 --- x/cronos/handler_test.go | 2 +- x/cronos/keeper/evm.go | 6 ++-- x/cronos/keeper/ibc.go | 62 +++++++++++++++++++++++++------------ x/cronos/keeper/ibc_test.go | 2 +- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/x/cronos/handler_test.go b/x/cronos/handler_test.go index de76256fca..c49f061c54 100644 --- a/x/cronos/handler_test.go +++ b/x/cronos/handler_test.go @@ -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"), }, } diff --git a/x/cronos/keeper/evm.go b/x/cronos/keeper/evm.go index 3713ebc6c8..25a1a1cd05 100644 --- a/x/cronos/keeper/evm.go +++ b/x/cronos/keeper/evm.go @@ -94,7 +94,7 @@ 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 @@ -102,7 +102,7 @@ func (k Keeper) ConvertCoinFromNativeToCRC20(ctx sdk.Context, sender common.Addr 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 { @@ -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( diff --git a/x/cronos/keeper/ibc.go b/x/cronos/keeper/ibc.go index d45e665387..6102f0d611 100644 --- a/x/cronos/keeper/ibc.go +++ b/x/cronos/keeper/ibc.go @@ -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" @@ -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, false) + if err != nil { + return err + } } } defer func() { @@ -115,31 +121,25 @@ 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: + contract, found := k.GetContractByDenom(ctx, c.Denom) + if !found { + return fmt.Errorf("coin %s is not supported", c.Denom) + } + err := k.ConvertCoinFromCRC20ToNative(ctx, contract, common.BytesToAddress(acc.Bytes()), c.Amount) if err != nil { return err } - default: - return fmt.Errorf("coin %s is not supported", c.Denom) + err = k.ibcSendTransfer(ctx, acc, destination, c) + if err != nil { + return err + } } } @@ -156,3 +156,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)) +} diff --git a/x/cronos/keeper/ibc_test.go b/x/cronos/keeper/ibc_test.go index 08ddc5edb0..f48b183c89 100644 --- a/x/cronos/keeper/ibc_test.go +++ b/x/cronos/keeper/ibc_test.go @@ -45,7 +45,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() {}, }, { From 7e3d836fba7da1e8b940c3e9c46f13c03e455242 Mon Sep 17 00:00:00 2001 From: Thomas Nguy Date: Wed, 8 Sep 2021 18:50:09 +0900 Subject: [PATCH 2/4] add unit test --- x/cronos/keeper/ibc.go | 2 +- x/cronos/keeper/ibc_test.go | 101 ++++++++++++++++++++++++- x/cronos/keeper/mock/ibckeeper_mock.go | 6 ++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/x/cronos/keeper/ibc.go b/x/cronos/keeper/ibc.go index 6102f0d611..1c0e89ca3d 100644 --- a/x/cronos/keeper/ibc.go +++ b/x/cronos/keeper/ibc.go @@ -55,7 +55,7 @@ func (k Keeper) ConvertVouchersToEvmCoins(ctx sdk.Context, from string, coins sd default: // TODO use autoDeploy boolean in Params.go - err := k.ConvertCoinFromNativeToCRC20(ctx, common.BytesToAddress(acc.Bytes()), c, false) + err := k.ConvertCoinFromNativeToCRC20(ctx, common.BytesToAddress(acc.Bytes()), c, true) if err != nil { return err } diff --git a/x/cronos/keeper/ibc_test.go b/x/cronos/keeper/ibc_test.go index f48b183c89..68c1759457 100644 --- a/x/cronos/keeper/ibc_test.go +++ b/x/cronos/keeper/ibc_test.go @@ -2,14 +2,20 @@ 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" + "strings" ) +const CorrectIbcDenom = "ibc/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + func (suite *KeeperTestSuite) TestConvertVouchersToEvmCoins() { privKey, err := ethsecp256k1.GenerateKey() @@ -57,7 +63,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() { @@ -79,6 +85,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 { @@ -102,6 +139,7 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { privKey, err := ethsecp256k1.GenerateKey() suite.Require().NoError(err) address := sdk.AccAddress(privKey.PubKey().Address()) + contractAddress := common.Address{} testCases := []struct { name string @@ -158,7 +196,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))), @@ -183,6 +221,63 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { suite.Require().Equal(sdk.NewInt(0), evmCoin.Amount) }, }, + { + "Correct address with not enough CR20 token", + address.String(), + "to", + sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))), + func() { + // Deploy CRC20 token contract + contractAddress, err := suite.app.CronosKeeper.DeployModuleCRC20(suite.ctx, CorrectIbcDenom) + suite.Require().NoError(err) + suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, CorrectIbcDenom, contractAddress) + // Verify IBC coin pre operation + ibcCoin := suite.GetBalance(address, CorrectIbcDenom) + suite.Require().Equal(sdk.NewInt(0), ibcCoin.Amount) + // Mint IBC coin for contract address + suite.MintCoins(sdk.AccAddress(contractAddress.Bytes()), sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123)))) + }, + errors.New("call contract failed: 0x658660A24B791726Ac482Eaad6a99d8C45677006, burn_by_cronos_module"), + func() { + }, + }, + { + "Correct address with enough CRC20 token : Should receive IBC token", + address.String(), + "to", + sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))), + func() { + // Deploy and Mint CRC20 tokens for user + contractAddress, err := suite.app.CronosKeeper.DeployModuleCRC20(suite.ctx, CorrectIbcDenom) + suite.Require().NoError(err) + suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, CorrectIbcDenom, contractAddress) + _, err = suite.app.CronosKeeper.CallModuleCRC20( + suite.ctx, contractAddress, "mint_by_cronos_module", common.BytesToAddress(address.Bytes()), big.NewInt(123)) + suite.Require().NoError(err) + // Verify balance CRC20 pre operation + ret, err := suite.app.CronosKeeper.CallModuleCRC20( + suite.ctx, contractAddress, "balanceOf", common.BytesToAddress(address.Bytes())) + suite.Require().NoError(err) + suite.Require().Equal(big.NewInt(123), big.NewInt(0).SetBytes(ret)) + // Verify IBC coin pre operation + ibcCoin := suite.GetBalance(address, CorrectIbcDenom) + suite.Require().Equal(sdk.NewInt(0), ibcCoin.Amount) + + // Mint IBC coin for contract address + suite.MintCoins(sdk.AccAddress(contractAddress.Bytes()), sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123)))) + }, + nil, + func() { + // Verify balance CRC20 post operation + ret, err := suite.app.CronosKeeper.CallModuleCRC20( + suite.ctx, contractAddress, "balanceOf", common.BytesToAddress(address.Bytes())) + suite.Require().NoError(err) + suite.Require().Equal(big.NewInt(0), big.NewInt(0).SetBytes(ret)) + // Verify IBC coin post operation + ibcCoin := suite.GetBalance(address, CorrectIbcDenom) + suite.Require().Equal(sdk.NewInt(123), ibcCoin.Amount) + }, + }, } for _, tc := range testCases { @@ -203,7 +298,7 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { tc.malleate() err := suite.app.CronosKeeper.IbcTransferCoins(suite.ctx, tc.from, tc.to, tc.coin) if tc.expectedError != nil { - suite.Require().EqualError(err, tc.expectedError.Error()) + suite.Require().True(strings.Contains(err.Error(),tc.expectedError.Error())) } else { suite.Require().NoError(err) tc.postCheck() diff --git a/x/cronos/keeper/mock/ibckeeper_mock.go b/x/cronos/keeper/mock/ibckeeper_mock.go index 177c3658f3..69db40649f 100644 --- a/x/cronos/keeper/mock/ibckeeper_mock.go +++ b/x/cronos/keeper/mock/ibckeeper_mock.go @@ -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 } From 6656fddbcf91b5646cbc39366872d4c5ed2fcdd6 Mon Sep 17 00:00:00 2001 From: Thomas Nguy Date: Wed, 8 Sep 2021 19:20:22 +0900 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2ae5f98e3..90439fd0da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From ffd236962b5a50ea9bb4556aab186a58eb27d316 Mon Sep 17 00:00:00 2001 From: Thomas Nguy Date: Thu, 9 Sep 2021 12:29:58 +0900 Subject: [PATCH 4/4] fix IbcTransferCoins logic for hook case --- x/cronos/keeper/ibc.go | 7 +---- x/cronos/keeper/ibc_test.go | 53 ++++++++----------------------------- 2 files changed, 12 insertions(+), 48 deletions(-) diff --git a/x/cronos/keeper/ibc.go b/x/cronos/keeper/ibc.go index 1c0e89ca3d..5cd6f17c15 100644 --- a/x/cronos/keeper/ibc.go +++ b/x/cronos/keeper/ibc.go @@ -127,15 +127,10 @@ func (k Keeper) IbcTransferCoins(ctx sdk.Context, from, destination string, coin } default: - contract, found := k.GetContractByDenom(ctx, c.Denom) + _, found := k.GetContractByDenom(ctx, c.Denom) if !found { return fmt.Errorf("coin %s is not supported", c.Denom) } - err := k.ConvertCoinFromCRC20ToNative(ctx, contract, common.BytesToAddress(acc.Bytes()), c.Amount) - if err != nil { - return err - } - err = k.ibcSendTransfer(ctx, acc, destination, c) if err != nil { return err diff --git a/x/cronos/keeper/ibc_test.go b/x/cronos/keeper/ibc_test.go index 68c1759457..ee6950856e 100644 --- a/x/cronos/keeper/ibc_test.go +++ b/x/cronos/keeper/ibc_test.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/tharsis/ethermint/crypto/ethsecp256k1" "math/big" - "strings" ) const CorrectIbcDenom = "ibc/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" @@ -139,7 +138,6 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { privKey, err := ethsecp256k1.GenerateKey() suite.Require().NoError(err) address := sdk.AccAddress(privKey.PubKey().Address()) - contractAddress := common.Address{} testCases := []struct { name string @@ -222,60 +220,31 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { }, }, { - "Correct address with not enough CR20 token", + "Correct address with non correct IBC token denom", address.String(), "to", - sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))), + sdk.NewCoins(sdk.NewCoin("incorrect", sdk.NewInt(123))), func() { - // Deploy CRC20 token contract - contractAddress, err := suite.app.CronosKeeper.DeployModuleCRC20(suite.ctx, CorrectIbcDenom) - suite.Require().NoError(err) - suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, CorrectIbcDenom, contractAddress) - // Verify IBC coin pre operation - ibcCoin := suite.GetBalance(address, CorrectIbcDenom) - suite.Require().Equal(sdk.NewInt(0), ibcCoin.Amount) - // Mint IBC coin for contract address - suite.MintCoins(sdk.AccAddress(contractAddress.Bytes()), sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123)))) + // Add support for the IBC token + suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, "incorrect", common.HexToAddress("0x11")) }, - errors.New("call contract failed: 0x658660A24B791726Ac482Eaad6a99d8C45677006, burn_by_cronos_module"), + errors.New("incorrect is invalid: ibc cro denom is invalid"), func() { }, }, { - "Correct address with enough CRC20 token : Should receive IBC token", + "Correct address with correct IBC token denom", address.String(), "to", sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123))), func() { - // Deploy and Mint CRC20 tokens for user - contractAddress, err := suite.app.CronosKeeper.DeployModuleCRC20(suite.ctx, CorrectIbcDenom) - suite.Require().NoError(err) - suite.app.CronosKeeper.SetAutoContractForDenom(suite.ctx, CorrectIbcDenom, contractAddress) - _, err = suite.app.CronosKeeper.CallModuleCRC20( - suite.ctx, contractAddress, "mint_by_cronos_module", common.BytesToAddress(address.Bytes()), big.NewInt(123)) - suite.Require().NoError(err) - // Verify balance CRC20 pre operation - ret, err := suite.app.CronosKeeper.CallModuleCRC20( - suite.ctx, contractAddress, "balanceOf", common.BytesToAddress(address.Bytes())) - suite.Require().NoError(err) - suite.Require().Equal(big.NewInt(123), big.NewInt(0).SetBytes(ret)) - // Verify IBC coin pre operation - ibcCoin := suite.GetBalance(address, CorrectIbcDenom) - suite.Require().Equal(sdk.NewInt(0), ibcCoin.Amount) - - // Mint IBC coin for contract address - suite.MintCoins(sdk.AccAddress(contractAddress.Bytes()), sdk.NewCoins(sdk.NewCoin(CorrectIbcDenom, sdk.NewInt(123)))) + // 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() { - // Verify balance CRC20 post operation - ret, err := suite.app.CronosKeeper.CallModuleCRC20( - suite.ctx, contractAddress, "balanceOf", common.BytesToAddress(address.Bytes())) - suite.Require().NoError(err) - suite.Require().Equal(big.NewInt(0), big.NewInt(0).SetBytes(ret)) - // Verify IBC coin post operation - ibcCoin := suite.GetBalance(address, CorrectIbcDenom) - suite.Require().Equal(sdk.NewInt(123), ibcCoin.Amount) }, }, } @@ -298,7 +267,7 @@ func (suite *KeeperTestSuite) TestIbcTransferCoins() { tc.malleate() err := suite.app.CronosKeeper.IbcTransferCoins(suite.ctx, tc.from, tc.to, tc.coin) if tc.expectedError != nil { - suite.Require().True(strings.Contains(err.Error(),tc.expectedError.Error())) + suite.Require().EqualError(err, tc.expectedError.Error()) } else { suite.Require().NoError(err) tc.postCheck()