From fb6582d11f9810d9b5368cce5975033f4e0aa61a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 15 Oct 2024 17:35:17 -0400 Subject: [PATCH] refactor(nexus)!: distinguish locking of bank coin vs nexus assets --- x/axelarnet/keeper/msg_server.go | 4 +- x/axelarnet/message_handler.go | 4 +- x/axelarnet/message_handler_test.go | 14 +- x/axelarnet/module_test.go | 2 +- x/axelarnet/types/expected_keepers.go | 1 + x/axelarnet/types/mock/expected_keepers.go | 148 +++++++++++++++------ x/nexus/keeper/lockable_asset.go | 131 +++++++++--------- 7 files changed, 183 insertions(+), 121 deletions(-) diff --git a/x/axelarnet/keeper/msg_server.go b/x/axelarnet/keeper/msg_server.go index 01a0f8438..cbbe0153d 100644 --- a/x/axelarnet/keeper/msg_server.go +++ b/x/axelarnet/keeper/msg_server.go @@ -79,7 +79,7 @@ func (s msgServer) CallContract(c context.Context, req *types.CallContractReques }) if req.Fee != nil { - lockableAsset, err := s.nexus.NewLockableAsset(ctx, s.ibcK, s.bank, req.Fee.Amount) + lockableAsset, err := s.nexus.NewLockableAssetFromCosmosCoin(ctx, s.ibcK, s.bank, req.Fee.Amount) if err != nil { return nil, sdkerrors.Wrap(err, "unrecognized fee denom") } @@ -185,7 +185,7 @@ func (s msgServer) ConfirmDeposit(c context.Context, req *types.ConfirmDepositRe return nil, fmt.Errorf("recipient chain '%s' is not activated", recipient.Chain.Name) } - lockableAsset, err := s.nexus.NewLockableAsset(ctx, s.ibcK, s.bank, coin) + lockableAsset, err := s.nexus.NewLockableAssetFromCosmosCoin(ctx, s.ibcK, s.bank, coin) if err != nil { return nil, err } diff --git a/x/axelarnet/message_handler.go b/x/axelarnet/message_handler.go index 84bc45137..9e6d1c736 100644 --- a/x/axelarnet/message_handler.go +++ b/x/axelarnet/message_handler.go @@ -389,7 +389,7 @@ func extractTokenFromPacketData(ctx sdk.Context, ibcK keeper.IBCKeeper, n types. denom = denomTrace.IBCDenom() } - return n.NewLockableAsset(ctx, ibcK, b, sdk.NewCoin(denom, amount)) + return n.NewLockableAssetFromCosmosCoin(ctx, ibcK, b, sdk.NewCoin(denom, amount)) } // deductFee pays the fee and returns the updated transfer amount with the fee deducted @@ -416,7 +416,7 @@ func deductFee(ctx sdk.Context, n types.Nexus, b types.BankKeeper, ibcK types.IB // subtract fee from transfer value coinAfterFee := token.GetCoin(ctx).Sub(feeCoin) - return funcs.Must(n.NewLockableAsset(ctx, ibcK, b, coinAfterFee)), b.SendCoins(ctx, types.AxelarIBCAccount, recipient, sdk.NewCoins(feeCoin)) + return funcs.Must(n.NewLockableAssetFromCosmosCoin(ctx, ibcK, b, coinAfterFee)), b.SendCoins(ctx, types.AxelarIBCAccount, recipient, sdk.NewCoins(feeCoin)) } // validateReceiver rejects uppercase GMP account address diff --git a/x/axelarnet/message_handler_test.go b/x/axelarnet/message_handler_test.go index fdde40074..c42d4bd37 100644 --- a/x/axelarnet/message_handler_test.go +++ b/x/axelarnet/message_handler_test.go @@ -77,7 +77,7 @@ func TestHandleMessage(t *testing.T) { })) channelK.SendPacketFunc = func(sdk.Context, *captypes.Capability, ibcexported.PacketI) error { return nil } n = &mock.NexusMock{ - NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { + NewLockableAssetFromCosmosCoinFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { lockableAsset = &nexusmock.LockableAssetMock{ GetAssetFunc: func() sdk.Coin { return coin }, GetCoinFunc: func(_ sdk.Context) sdk.Coin { return coin }, @@ -496,7 +496,7 @@ func TestHandleMessageWithToken(t *testing.T) { GetCoinFunc: func(_ sdk.Context) sdk.Coin { return sdk.NewCoin(denom, funcs.MustOk(sdk.NewIntFromString(amount))) }, } n = &mock.NexusMock{ - NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { + NewLockableAssetFromCosmosCoinFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { return lockableAsset, nil }, SetNewMessageFunc: func(ctx sdk.Context, msg nexus.GeneralMessage) error { @@ -677,9 +677,9 @@ func TestHandleMessageWithToken(t *testing.T) { Then("should return ack success", func(t *testing.T) { assert.True(t, axelarnet.OnRecvMessage(ctx, k, ibcK, n, b, r, packet).Success()) assert.Equal(t, genMsg.Status, nexus.Approved) - assert.Len(t, n.NewLockableAssetCalls(), 2) - assert.Equal(t, n.NewLockableAssetCalls()[0].Coin.Amount, funcs.MustOk(sdk.NewIntFromString(amount))) - assert.Equal(t, n.NewLockableAssetCalls()[1].Coin.Amount, sdk.OneInt()) + assert.Len(t, n.NewLockableAssetFromCosmosCoinCalls(), 2) + assert.Equal(t, n.NewLockableAssetFromCosmosCoinCalls()[0].Coin.Amount, funcs.MustOk(sdk.NewIntFromString(amount))) + assert.Equal(t, n.NewLockableAssetFromCosmosCoinCalls()[1].Coin.Amount, sdk.OneInt()) }). Run(t) } @@ -744,7 +744,7 @@ func TestHandleSendToken(t *testing.T) { GetCoinFunc: func(_ sdk.Context) sdk.Coin { return sdk.NewCoin(denom, funcs.MustOk(sdk.NewIntFromString(amount))) }, } n = &mock.NexusMock{ - NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { + NewLockableAssetFromCosmosCoinFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { return lockableAsset, nil }, SetNewMessageFunc: func(sdk.Context, nexus.GeneralMessage) error { return nil }, @@ -929,7 +929,7 @@ func TestTokenAndDestChainNotFound(t *testing.T) { channelK.SendPacketFunc = func(sdk.Context, *captypes.Capability, ibcexported.PacketI) error { return nil } lockableAsset = &nexusmock.LockableAssetMock{} n = &mock.NexusMock{ - NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { + NewLockableAssetFromCosmosCoinFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { return lockableAsset, nil }, SetNewMessageFunc: func(ctx sdk.Context, msg nexus.GeneralMessage) error { diff --git a/x/axelarnet/module_test.go b/x/axelarnet/module_test.go index 280d66bfe..9b584888d 100644 --- a/x/axelarnet/module_test.go +++ b/x/axelarnet/module_test.go @@ -91,7 +91,7 @@ func TestIBCModule(t *testing.T) { transferK := ibctransferkeeper.NewKeeper(encCfg.Codec, sdk.NewKVStoreKey("transfer"), transferSubspace, &mock.ChannelKeeperMock{}, &mock.ChannelKeeperMock{}, &mock.PortKeeperMock{}, accountK, bankK, scopedTransferK) lockableAsset = &nexusmock.LockableAssetMock{} n = &mock.NexusMock{ - NewLockableAssetFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { + NewLockableAssetFromCosmosCoinFunc: func(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) { return lockableAsset, nil }, } diff --git a/x/axelarnet/types/expected_keepers.go b/x/axelarnet/types/expected_keepers.go index f490243af..c01e41600 100644 --- a/x/axelarnet/types/expected_keepers.go +++ b/x/axelarnet/types/expected_keepers.go @@ -88,6 +88,7 @@ type Nexus interface { GenerateMessageID(ctx sdk.Context) (string, []byte, uint64) ValidateAddress(ctx sdk.Context, address nexus.CrossChainAddress) error NewLockableAsset(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) + NewLockableAssetFromCosmosCoin(ctx sdk.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin sdk.Coin) (nexus.LockableAsset, error) } // BankKeeper defines the expected interface contract the vesting module requires diff --git a/x/axelarnet/types/mock/expected_keepers.go b/x/axelarnet/types/mock/expected_keepers.go index 9c8be2a2b..7515985e1 100644 --- a/x/axelarnet/types/mock/expected_keepers.go +++ b/x/axelarnet/types/mock/expected_keepers.go @@ -754,6 +754,9 @@ var _ axelarnettypes.Nexus = &NexusMock{} // NewLockableAssetFunc: func(ctx cosmossdktypes.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin cosmossdktypes.Coin) (github_com_axelarnetwork_axelar_core_x_nexus_exported.LockableAsset, error) { // panic("mock out the NewLockableAsset method") // }, +// NewLockableAssetFromCosmosCoinFunc: func(ctx cosmossdktypes.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin cosmossdktypes.Coin) (github_com_axelarnetwork_axelar_core_x_nexus_exported.LockableAsset, error) { +// panic("mock out the NewLockableAssetFromCosmosCoin method") +// }, // RateLimitTransferFunc: func(ctx cosmossdktypes.Context, chain github_com_axelarnetwork_axelar_core_x_nexus_exported.ChainName, asset cosmossdktypes.Coin, direction github_com_axelarnetwork_axelar_core_x_nexus_exported.TransferDirection) error { // panic("mock out the RateLimitTransfer method") // }, @@ -890,6 +893,9 @@ type NexusMock struct { // NewLockableAssetFunc mocks the NewLockableAsset method. NewLockableAssetFunc func(ctx cosmossdktypes.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin cosmossdktypes.Coin) (github_com_axelarnetwork_axelar_core_x_nexus_exported.LockableAsset, error) + // NewLockableAssetFromCosmosCoinFunc mocks the NewLockableAssetFromCosmosCoin method. + NewLockableAssetFromCosmosCoinFunc func(ctx cosmossdktypes.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin cosmossdktypes.Coin) (github_com_axelarnetwork_axelar_core_x_nexus_exported.LockableAsset, error) + // RateLimitTransferFunc mocks the RateLimitTransfer method. RateLimitTransferFunc func(ctx cosmossdktypes.Context, chain github_com_axelarnetwork_axelar_core_x_nexus_exported.ChainName, asset cosmossdktypes.Coin, direction github_com_axelarnetwork_axelar_core_x_nexus_exported.TransferDirection) error @@ -1145,6 +1151,17 @@ type NexusMock struct { // Coin is the coin argument value. Coin cosmossdktypes.Coin } + // NewLockableAssetFromCosmosCoin holds details about calls to the NewLockableAssetFromCosmosCoin method. + NewLockableAssetFromCosmosCoin []struct { + // Ctx is the ctx argument value. + Ctx cosmossdktypes.Context + // Ibc is the ibc argument value. + Ibc nexustypes.IBCKeeper + // Bank is the bank argument value. + Bank nexustypes.BankKeeper + // Coin is the coin argument value. + Coin cosmossdktypes.Coin + } // RateLimitTransfer holds details about calls to the RateLimitTransfer method. RateLimitTransfer []struct { // Ctx is the ctx argument value. @@ -1257,49 +1274,50 @@ type NexusMock struct { Address github_com_axelarnetwork_axelar_core_x_nexus_exported.CrossChainAddress } } - lockActivateChain sync.RWMutex - lockActivateWasmConnection sync.RWMutex - lockAddChainMaintainer sync.RWMutex - lockArchivePendingTransfer sync.RWMutex - lockDeactivateChain sync.RWMutex - lockDeactivateWasmConnection sync.RWMutex - lockDequeueRouteMessage sync.RWMutex - lockEnqueueForTransfer sync.RWMutex - lockEnqueueTransfer sync.RWMutex - lockExportGenesis sync.RWMutex - lockGenerateMessageID sync.RWMutex - lockGetChain sync.RWMutex - lockGetChainByNativeAsset sync.RWMutex - lockGetChainMaintainerStates sync.RWMutex - lockGetChainMaintainers sync.RWMutex - lockGetChains sync.RWMutex - lockGetFeeInfo sync.RWMutex - lockGetMessage sync.RWMutex - lockGetParams sync.RWMutex - lockGetRecipient sync.RWMutex - lockGetTransferFees sync.RWMutex - lockGetTransfersForChainPaginated sync.RWMutex - lockInitGenesis sync.RWMutex - lockIsAssetRegistered sync.RWMutex - lockIsChainActivated sync.RWMutex - lockIsChainMaintainer sync.RWMutex - lockIsWasmConnectionActivated sync.RWMutex - lockLinkAddresses sync.RWMutex - lockLogger sync.RWMutex - lockNewLockableAsset sync.RWMutex - lockRateLimitTransfer sync.RWMutex - lockRegisterAsset sync.RWMutex - lockRegisterFee sync.RWMutex - lockRemoveChainMaintainer sync.RWMutex - lockRouteMessage sync.RWMutex - lockSetChain sync.RWMutex - lockSetMessageExecuted sync.RWMutex - lockSetMessageFailed sync.RWMutex - lockSetNewMessage sync.RWMutex - lockSetParams sync.RWMutex - lockSetRateLimit sync.RWMutex - lockSubTransferFee sync.RWMutex - lockValidateAddress sync.RWMutex + lockActivateChain sync.RWMutex + lockActivateWasmConnection sync.RWMutex + lockAddChainMaintainer sync.RWMutex + lockArchivePendingTransfer sync.RWMutex + lockDeactivateChain sync.RWMutex + lockDeactivateWasmConnection sync.RWMutex + lockDequeueRouteMessage sync.RWMutex + lockEnqueueForTransfer sync.RWMutex + lockEnqueueTransfer sync.RWMutex + lockExportGenesis sync.RWMutex + lockGenerateMessageID sync.RWMutex + lockGetChain sync.RWMutex + lockGetChainByNativeAsset sync.RWMutex + lockGetChainMaintainerStates sync.RWMutex + lockGetChainMaintainers sync.RWMutex + lockGetChains sync.RWMutex + lockGetFeeInfo sync.RWMutex + lockGetMessage sync.RWMutex + lockGetParams sync.RWMutex + lockGetRecipient sync.RWMutex + lockGetTransferFees sync.RWMutex + lockGetTransfersForChainPaginated sync.RWMutex + lockInitGenesis sync.RWMutex + lockIsAssetRegistered sync.RWMutex + lockIsChainActivated sync.RWMutex + lockIsChainMaintainer sync.RWMutex + lockIsWasmConnectionActivated sync.RWMutex + lockLinkAddresses sync.RWMutex + lockLogger sync.RWMutex + lockNewLockableAsset sync.RWMutex + lockNewLockableAssetFromCosmosCoin sync.RWMutex + lockRateLimitTransfer sync.RWMutex + lockRegisterAsset sync.RWMutex + lockRegisterFee sync.RWMutex + lockRemoveChainMaintainer sync.RWMutex + lockRouteMessage sync.RWMutex + lockSetChain sync.RWMutex + lockSetMessageExecuted sync.RWMutex + lockSetMessageFailed sync.RWMutex + lockSetNewMessage sync.RWMutex + lockSetParams sync.RWMutex + lockSetRateLimit sync.RWMutex + lockSubTransferFee sync.RWMutex + lockValidateAddress sync.RWMutex } // ActivateChain calls ActivateChainFunc. @@ -2390,6 +2408,50 @@ func (mock *NexusMock) NewLockableAssetCalls() []struct { return calls } +// NewLockableAssetFromCosmosCoin calls NewLockableAssetFromCosmosCoinFunc. +func (mock *NexusMock) NewLockableAssetFromCosmosCoin(ctx cosmossdktypes.Context, ibc nexustypes.IBCKeeper, bank nexustypes.BankKeeper, coin cosmossdktypes.Coin) (github_com_axelarnetwork_axelar_core_x_nexus_exported.LockableAsset, error) { + if mock.NewLockableAssetFromCosmosCoinFunc == nil { + panic("NexusMock.NewLockableAssetFromCosmosCoinFunc: method is nil but Nexus.NewLockableAssetFromCosmosCoin was just called") + } + callInfo := struct { + Ctx cosmossdktypes.Context + Ibc nexustypes.IBCKeeper + Bank nexustypes.BankKeeper + Coin cosmossdktypes.Coin + }{ + Ctx: ctx, + Ibc: ibc, + Bank: bank, + Coin: coin, + } + mock.lockNewLockableAssetFromCosmosCoin.Lock() + mock.calls.NewLockableAssetFromCosmosCoin = append(mock.calls.NewLockableAssetFromCosmosCoin, callInfo) + mock.lockNewLockableAssetFromCosmosCoin.Unlock() + return mock.NewLockableAssetFromCosmosCoinFunc(ctx, ibc, bank, coin) +} + +// NewLockableAssetFromCosmosCoinCalls gets all the calls that were made to NewLockableAssetFromCosmosCoin. +// Check the length with: +// +// len(mockedNexus.NewLockableAssetFromCosmosCoinCalls()) +func (mock *NexusMock) NewLockableAssetFromCosmosCoinCalls() []struct { + Ctx cosmossdktypes.Context + Ibc nexustypes.IBCKeeper + Bank nexustypes.BankKeeper + Coin cosmossdktypes.Coin +} { + var calls []struct { + Ctx cosmossdktypes.Context + Ibc nexustypes.IBCKeeper + Bank nexustypes.BankKeeper + Coin cosmossdktypes.Coin + } + mock.lockNewLockableAssetFromCosmosCoin.RLock() + calls = mock.calls.NewLockableAssetFromCosmosCoin + mock.lockNewLockableAssetFromCosmosCoin.RUnlock() + return calls +} + // RateLimitTransfer calls RateLimitTransferFunc. func (mock *NexusMock) RateLimitTransfer(ctx cosmossdktypes.Context, chain github_com_axelarnetwork_axelar_core_x_nexus_exported.ChainName, asset cosmossdktypes.Coin, direction github_com_axelarnetwork_axelar_core_x_nexus_exported.TransferDirection) error { if mock.RateLimitTransferFunc == nil { diff --git a/x/nexus/keeper/lockable_asset.go b/x/nexus/keeper/lockable_asset.go index 3bdd508f4..c2dabede2 100644 --- a/x/nexus/keeper/lockable_asset.go +++ b/x/nexus/keeper/lockable_asset.go @@ -2,7 +2,6 @@ package keeper import ( "fmt" - "strings" sdk "github.com/cosmos/cosmos-sdk/types" ibctypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" @@ -13,13 +12,6 @@ import ( "github.com/axelarnetwork/utils/funcs" ) -// NewLockableAsset creates a new lockable asset. -// The coin denom can either be an actual bank Coin (e.g. uaxl, ICS20 coin) , -// or the registered asset name (e.g. base denom for an ICS20 coin) -func (k Keeper) NewLockableAsset(ctx sdk.Context, ibc types.IBCKeeper, bank types.BankKeeper, coin sdk.Coin) (exported.LockableAsset, error) { - return newLockableAsset(ctx, k, ibc, bank, coin) -} - // lockableAsset provides functionality to lock and release coins type lockableAsset struct { sdk.Coin @@ -29,6 +21,38 @@ type lockableAsset struct { bank types.BankKeeper } +// NewLockableAsset creates a new lockable asset from a nexus registered asset name. +func (k Keeper) NewLockableAsset(ctx sdk.Context, ibc types.IBCKeeper, bank types.BankKeeper, coin sdk.Coin) (exported.LockableAsset, error) { + return newLockableAsset(ctx, k, ibc, bank, coin) +} + +// NewLockableAssetFromCosmosCoin creates a new lockable asset from a Cosmos x/bank Coin (e.g. uaxl, ICS20 coin). +func (k Keeper) NewLockableAssetFromCosmosCoin(ctx sdk.Context, ibc types.IBCKeeper, bank types.BankKeeper, coin sdk.Coin) (exported.LockableAsset, error) { + if isIBCDenom(coin.GetDenom()) { + denomTrace, err := ibc.ParseIBCDenom(ctx, coin.GetDenom()) + if err != nil { + return lockableAsset{}, err + } + + coin = sdk.NewCoin(denomTrace.GetBaseDenom(), coin.Amount) + } + + asset, err := newLockableAsset(ctx, k, ibc, bank, coin) + if err != nil { + return lockableAsset{}, err + } + + if convertedCoin, err := asset.getCoin(ctx); err != nil { + return lockableAsset{}, err + } else if convertedCoin != coin { + // validate that the converted coin denom is the same as the original denom + // this is needed for ICS20 coins whose IBC denom trace must correspond to the registered IBC path + return lockableAsset{}, fmt.Errorf("converted coin %s is different from the original coin %s", convertedCoin, coin) + } + + return asset, nil +} + // newLockableAsset creates a coin struct, assign a coin type and normalize the denom if it's a ICS20 token func newLockableAsset(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, bank types.BankKeeper, coin sdk.Coin) (lockableAsset, error) { denom := coin.GetDenom() @@ -38,17 +62,6 @@ func newLockableAsset(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, b return lockableAsset{}, err } - // If coin type is ICS20, we need to normalize it to convert from 'ibc/{hash}' - // to native asset denom so that nexus could recognize it - if coinType == types.ICS20 && isIBCDenom(denom) { - denomTrace, err := ibc.ParseIBCDenom(ctx, denom) - if err != nil { - return lockableAsset{}, err - } - - coin = sdk.NewCoin(denomTrace.GetBaseDenom(), coin.Amount) - } - c := lockableAsset{ Coin: coin, coinType: coinType, @@ -57,10 +70,6 @@ func newLockableAsset(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, b bank: bank, } - if _, err := c.getCoin(ctx); err != nil { - return lockableAsset{}, err - } - return c, nil } @@ -106,7 +115,7 @@ func (c lockableAsset) UnlockTo(ctx sdk.Context, toAddr sdk.AccAddress) error { func (c lockableAsset) getCoin(ctx sdk.Context) (sdk.Coin, error) { switch c.coinType { case types.ICS20: - return c.toICS20(ctx) + return toICS20(ctx, c.nexus, c.ibc, c.Coin) case types.Native, types.External: return c.Coin, nil default: @@ -114,30 +123,6 @@ func (c lockableAsset) getCoin(ctx sdk.Context) (sdk.Coin, error) { } } -func (c lockableAsset) toICS20(ctx sdk.Context) (sdk.Coin, error) { - if c.coinType != types.ICS20 { - return sdk.Coin{}, fmt.Errorf("%s is not ICS20 token", c.GetDenom()) - } - - // check if the asset registered with a path - chain, ok := c.nexus.GetChainByNativeAsset(ctx, c.GetDenom()) - if !ok { - return sdk.Coin{}, fmt.Errorf("asset %s is not linked to a cosmos chain", c.GetDenom()) - } - - path, ok := c.ibc.GetIBCPath(ctx, chain.Name) - if !ok { - return sdk.Coin{}, fmt.Errorf("path not found for chain %s", chain.Name) - } - - trace := ibctypes.DenomTrace{ - Path: path, - BaseDenom: c.GetDenom(), - } - - return sdk.NewCoin(trace.IBCDenom(), c.Amount), nil -} - func lock(ctx sdk.Context, bank types.BankKeeper, fromAddr sdk.AccAddress, coin sdk.Coin) error { return bank.SendCoins(ctx, fromAddr, exported.GetEscrowAddress(coin.GetDenom()), sdk.NewCoins(coin)) } @@ -176,9 +161,6 @@ func mint(ctx sdk.Context, bank types.BankKeeper, toAddr sdk.AccAddress, coin sd func getCoinType(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, denom string) (types.CoinType, error) { switch { - // check if the format of token denomination is 'ibc/{hash}' - case isIBCDenom(denom): - return types.ICS20, nil // check if the denom is the registered asset name for an ICS20 coin from a cosmos chain case isFromExternalCosmosChain(ctx, nexus, ibc, denom): return types.ICS20, nil @@ -194,28 +176,16 @@ func getCoinType(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, denom // isFromExternalCosmosChain returns true if the denom is a nexus-registered // asset name for an ICS20 coin originating from a cosmos chain func isFromExternalCosmosChain(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, denom string) bool { - chain, ok := nexus.GetChainByNativeAsset(ctx, denom) - if !ok { + if _, err := getIBCPath(ctx, nexus, ibc, denom); err != nil { return false } - _, ok = ibc.GetIBCPath(ctx, chain.Name) - - return ok + return true } // isIBCDenom validates that the given denomination is a valid ICS token representation (ibc/{hash}) func isIBCDenom(denom string) bool { - if err := sdk.ValidateDenom(denom); err != nil { - return false - } - - denomSplit := strings.SplitN(denom, "/", 2) - if len(denomSplit) != 2 || denomSplit[0] != ibctypes.DenomPrefix { - return false - } - - if _, err := ibctypes.ParseHexHash(denomSplit[1]); err != nil { + if err := ibctypes.ValidateIBCDenom(denom); err != nil { return false } @@ -227,3 +197,32 @@ func isNativeAssetOnAxelarnet(ctx sdk.Context, nexus types.Nexus, denom string) return ok && chain.Name.Equals(axelarnet.Axelarnet.Name) } + +func getIBCPath(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, asset string) (string, error) { + // check if the asset registered with a path + chain, ok := nexus.GetChainByNativeAsset(ctx, asset) + if !ok { + return "", fmt.Errorf("asset %s is not linked to a cosmos chain", asset) + } + + path, ok := ibc.GetIBCPath(ctx, chain.Name) + if !ok { + return "", fmt.Errorf("path not found for chain %s", chain.Name) + } + + return path, nil +} + +func toICS20(ctx sdk.Context, nexus types.Nexus, ibc types.IBCKeeper, coin sdk.Coin) (sdk.Coin, error) { + path, err := getIBCPath(ctx, nexus, ibc, coin.GetDenom()) + if err != nil { + return sdk.Coin{}, err + } + + trace := ibctypes.DenomTrace{ + Path: path, + BaseDenom: coin.GetDenom(), + } + + return sdk.NewCoin(trace.IBCDenom(), coin.Amount), nil +}