From be7b6d5cebb964f8aa585a4a0e622bf08a06b1d9 Mon Sep 17 00:00:00 2001 From: Nikolas De Giorgis Date: Tue, 23 Jul 2024 10:28:07 +0100 Subject: [PATCH 1/4] (feat) Add possibility to transfer entire balance. (#6877) * Add possibility to transfer entire balance. * Added entry to the Changelog. * Added e2e test * Added forwarding * Update modules/apps/transfer/keeper/relay.go Co-authored-by: DimitrisJim * Move UnboundedSpendLimit to token.go * add documentation * add test to compatibility matrices * PR Feedback. --------- Co-authored-by: DimitrisJim Co-authored-by: Carlos Rodriguez (cherry picked from commit 92e1f387b0c222dd2cc44c7df7981b0ca96a27f5) # Conflicts: # .github/compatibility-test-matrices/main/transfer-v2-multidenom-chain-a.json # .github/compatibility-test-matrices/release-v9.0.x/transfer-v2-multidenom-chain-a.json # .github/compatibility-test-matrices/unreleased/transfer-v2-multidenom.json # CHANGELOG.md # docs/docs/02-apps/01-transfer/04-messages.md # docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md # e2e/tests/transfer/base_test.go # modules/apps/transfer/keeper/relay.go # modules/apps/transfer/keeper/relay_test.go # modules/apps/transfer/types/token.go # modules/apps/transfer/types/transfer_authorization.go # testing/chain.go --- .../main/transfer-v2-multidenom-chain-a.json | 19 + .../transfer-v2-multidenom-chain-a.json | 19 + .../unreleased/transfer-v2-multidenom.json | 19 + CHANGELOG.md | 7 + docs/docs/02-apps/01-transfer/04-messages.md | 86 +++ .../01-transfer/10-ICS20-v1/04-messages.md | 65 ++ e2e/tests/transfer/base_test.go | 644 ++++++++++++++++++ modules/apps/transfer/keeper/relay.go | 10 + modules/apps/transfer/keeper/relay_test.go | 38 ++ modules/apps/transfer/types/token.go | 58 ++ .../transfer/types/transfer_authorization.go | 25 +- testing/chain.go | 9 + 12 files changed, 996 insertions(+), 3 deletions(-) create mode 100644 .github/compatibility-test-matrices/main/transfer-v2-multidenom-chain-a.json create mode 100644 .github/compatibility-test-matrices/release-v9.0.x/transfer-v2-multidenom-chain-a.json create mode 100644 .github/compatibility-test-matrices/unreleased/transfer-v2-multidenom.json create mode 100644 docs/docs/02-apps/01-transfer/04-messages.md create mode 100644 docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md create mode 100644 e2e/tests/transfer/base_test.go create mode 100644 modules/apps/transfer/types/token.go diff --git a/.github/compatibility-test-matrices/main/transfer-v2-multidenom-chain-a.json b/.github/compatibility-test-matrices/main/transfer-v2-multidenom-chain-a.json new file mode 100644 index 00000000000..fb04ec8ba4d --- /dev/null +++ b/.github/compatibility-test-matrices/main/transfer-v2-multidenom-chain-a.json @@ -0,0 +1,19 @@ +{ + "chain-a": [ + "main" + ], + "chain-b": [ + "main" + ], + "entrypoint": [ + "TestTransferTestSuite" + ], + "test": [ + "TestMsgTransfer_Succeeds_Nonincentivized_MultiDenom", + "TestMsgTransfer_EntireBalance", + "TestMsgTransfer_Fails_InvalidAddress_MultiDenom" + ], + "relayer-type": [ + "hermes" + ] +} \ No newline at end of file diff --git a/.github/compatibility-test-matrices/release-v9.0.x/transfer-v2-multidenom-chain-a.json b/.github/compatibility-test-matrices/release-v9.0.x/transfer-v2-multidenom-chain-a.json new file mode 100644 index 00000000000..e75a7b6c73e --- /dev/null +++ b/.github/compatibility-test-matrices/release-v9.0.x/transfer-v2-multidenom-chain-a.json @@ -0,0 +1,19 @@ +{ + "chain-a": [ + "release-v9.0.x" + ], + "chain-b": [ + "release-v9.0.x" + ], + "entrypoint": [ + "TestTransferTestSuite" + ], + "test": [ + "TestMsgTransfer_Succeeds_Nonincentivized_MultiDenom", + "TestMsgTransfer_EntireBalance", + "TestMsgTransfer_Fails_InvalidAddress_MultiDenom" + ], + "relayer-type": [ + "hermes" + ] +} \ No newline at end of file diff --git a/.github/compatibility-test-matrices/unreleased/transfer-v2-multidenom.json b/.github/compatibility-test-matrices/unreleased/transfer-v2-multidenom.json new file mode 100644 index 00000000000..da1134e3fa6 --- /dev/null +++ b/.github/compatibility-test-matrices/unreleased/transfer-v2-multidenom.json @@ -0,0 +1,19 @@ +{ + "chain-a": [ + "release-v9.0.x" + ], + "chain-b": [ + "release-v9.0.x" + ], + "entrypoint": [ + "TestTransferTestSuite" + ], + "test": [ + "TestMsgTransfer_Succeeds_Nonincentivized_MultiDenom", + "TestMsgTransfer_EntireBalance", + "TestMsgTransfer_Fails_InvalidAddress_MultiDenom" + ], + "relayer-type": [ + "hermes" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2205df42599..434f256364f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,13 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +<<<<<<< HEAD +======= +* (apps/transfer) [\#6492](https://github.com/cosmos/ibc-go/pull/6492) Added new `Tokens` field to `MsgTransfer` to enable sending of multiple denoms, and deprecated the `Token` field. +* (apps/transfer) [\#6693](https://github.com/cosmos/ibc-go/pull/6693) Added new `Forwarding` field to `MsgTransfer` to enable forwarding tokens through multiple intermediary chains with a single transaction. This also enables automatic unwinding of tokens to their native chain. `x/authz` support for transfer allows granters to specify a set of possible forwarding hops that are allowed for grantees. +* (apps/transfer) [\#6877](https://github.com/cosmos/ibc-go/pull/6877) Added the possibility to transfer the entire user balance of a particular denomination by using [`UnboundedSpendLimit`](https://github.com/cosmos/ibc-go/blob/715f00eef8727da41db25fdd4763b709bdbba07e/modules/apps/transfer/types/transfer_authorization.go#L253-L255) as the token amount. + +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) ### Bug Fixes ## [v7.6.0](https://github.com/cosmos/ibc-go/releases/tag/v7.6.0) - 2024-06-20 diff --git a/docs/docs/02-apps/01-transfer/04-messages.md b/docs/docs/02-apps/01-transfer/04-messages.md new file mode 100644 index 00000000000..4d121985fdc --- /dev/null +++ b/docs/docs/02-apps/01-transfer/04-messages.md @@ -0,0 +1,86 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + // Deprecated: Use Tokens instead. + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string + Tokens []sdk.Coin + Forwarding *Forwarding +} + +type Forwarding struct { + Unwind bool + Hops []Hop +} + +type Hop struct { + PortId string + ChannelId string +} +``` + +:::info +Multi-denom token transfers and token forwarding are features supported only on ICS20 v2 transfer channels. +::: + +If `Forwarding` is `nil`, this message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Tokens` must not be empty. +- Each `Coin` in `Tokens` must satisfy the following: + - `Amount` must be positive. + - `Denom` must be a valid IBC denomination, as defined in [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +If `Forwarding` is not `nil`, then to use forwarding you must either set `Unwind` to true or provide a non-empty list of `Hops`. Setting both `Unwind` to true and providing a non-empty list of `Hops` is allowed, but the total number of hops that is formed as a combination of the hops needed to unwind the tokens and the hops to forward them afterwards to the final destination must not exceed 8. When using forwarding, timeout must be specified using only `TimeoutTimestamp` (i.e. `TimeoutHeight` must be zero). Please note that the timeout timestamp must take into account the time that it may take tokens to be forwarded through the intermediary chains. Additionally, please note that the `MsgTransfer` will fail if: + +- `Hops` is not empty, and the number of elements of `Hops` is greater than 8, or either the `PortId` or `ChannelId` of any of the `Hops` is not a valid identifier. +- `Unwind` is true, and either the coins to be transferred have different denomination traces, or `SourcePort` and `SourceChannel` are not empty strings (they must be empty because they are set by the transfer module, since it has access to the denomination trace information and is thus able to know the source port ID, channel ID to use in order to unwind the tokens). If `Unwind` is true, the transfer module expects the tokens in `MsgTransfer` to not be native to the sending chain (i.e. they must be IBC vouchers). + +Please note that the `Token` field is deprecated and users should now use `Tokens` instead. If `Token` is used then `Tokens` must be empty. Similarly, if `Tokens` is used then `Token` should be left empty. This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corrsponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the [callbacks middleware](../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). + +Please note that the memo field is always meant to be consumed only on the final destination chain. This means that the transfer module will guarantee that the memo field in the intermediary chains is empty. diff --git a/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md b/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md new file mode 100644 index 00000000000..08fc05e88c3 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md @@ -0,0 +1,65 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/ics20-v1/messages +--- + +:::warning +This document is relevant only for fungible token transfers over channels on v1 of the ICS-20 protocol. +::: + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid: + - `Amount` is not positive. + - `Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corrsponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the [callbacks middleware](../../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/e2e/tests/transfer/base_test.go b/e2e/tests/transfer/base_test.go new file mode 100644 index 00000000000..745a0d57f07 --- /dev/null +++ b/e2e/tests/transfer/base_test.go @@ -0,0 +1,644 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + "time" + + "github.com/strangelove-ventures/interchaintest/v8/ibc" + test "github.com/strangelove-ventures/interchaintest/v8/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" +) + +func TestTransferTestSuite(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuite)) +} + +type TransferTestSuite struct { + transferTester +} + +// transferTester defines some helper functions that can be used in various test suites +// that test transfer functionality. +type transferTester struct { + testsuite.E2ETestSuite +} + +// QueryTransferParams queries the on-chain send enabled param for the transfer module +func (s *transferTester) QueryTransferParams(ctx context.Context, chain ibc.Chain) transfertypes.Params { + res, err := query.GRPCQuery[transfertypes.QueryParamsResponse](ctx, chain, &transfertypes.QueryParamsRequest{}) + s.Require().NoError(err) + return *res.Params +} + +// CreateTransferPath sets up a path between chainA and chainB with a transfer channel and returns the relayer wired +// up to watch the channel and port IDs created. +func (s *transferTester) CreateTransferPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { + relayer, channel := s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) + s.T().Logf("test %s running on portID %s channelID %s", testName, channel.PortID, channel.ChannelID) + return relayer, channel +} + +// TestMsgTransfer_Succeeds_Nonincentivized will test sending successful IBC transfers from chainA to chainB. +// The transfer will occur over a basic transfer channel (non incentivized) and both native and non-native tokens +// will be sent forwards and backwards in the IBC transfer timeline (both chains will act as source and receiver chains). +func (s *TransferTestSuite) TestMsgTransfer_Succeeds_Nonincentivized() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + + // NOTE: t.Parallel() should be called before SetupPath in all tests. + // t.Name() must be stored in a variable before t.Parallel() otherwise t.Name() is not + // deterministic. + t.Parallel() + + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainBVersion := chainB.Config().Images[0].Version + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + // TODO: https://github.com/cosmos/ibc-go/issues/6743 + // t.Run("ensure capability module BeginBlock is executed", func(t *testing.T) { + // // by restarting the chain we ensure that the capability module's BeginBlocker is executed. + // s.Require().NoError(chainA.(*cosmos.CosmosChain).StopAllNodes(ctx)) + // s.Require().NoError(chainA.(*cosmos.CosmosChain).StartAllNodes(ctx)) + // s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") + // }) + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + + // TODO: cannot query total escrow if tests in parallel are using the same denom. + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // + // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) + // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) + // } + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + if testvalues.TokenMetadataFeatureReleases.IsSupported(chainBVersion) { + t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { + s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) + }) + } + + t.Run("non-native IBC token transfer from chainB to chainA, receiver is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferCoins(chainBIBCToken.IBCDenom()), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + s.Require().Equal(sdkmath.ZeroInt(), actualBalance) + + // https://github.com/cosmos/ibc-go/issues/6742 + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainBVersion) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainB, chainBIBCToken.IBCDenom()) + // s.Require().NoError(err) + // s.Require().Equal(sdk.NewCoin(chainBIBCToken.IBCDenom(), sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because sending chain is not source for tokens + // } + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) + + // https://github.com/cosmos/ibc-go/issues/6742 + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { + // t.Run("tokens are un-escrowed", func(t *testing.T) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // s.Require().Equal(sdk.NewCoin(chainADenom, sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because tokens have come back + // }) + // } +} + +// TestMsgTransfer_Succeeds_MultiDenom will test sending successful IBC transfers from chainA to chainB. +// A multidenom transfer with native chainB tokens and IBC tokens from chainA is executed from chainB to chainA. +func (s *TransferTestSuite) TestMsgTransfer_Succeeds_Nonincentivized_MultiDenom() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + chainBDenom := chainB.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + chainAIBCToken := testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("native chainA tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + + // https://github.com/cosmos/ibc-go/issues/6742 + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // + // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) + // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { + s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) + }) + + // send the native chainB denom and also the ibc token from chainA + transferCoins := []sdk.Coin{ + testvalues.DefaultTransferAmount(chainBIBCToken.IBCDenom()), + testvalues.DefaultTransferAmount(chainBDenom), + } + + t.Run("native token from chain B and non-native IBC token from chainA, both to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + t.Run("chain A native denom", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("chain B IBC denom", func(t *testing.T) { + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + }) + + // https://github.com/cosmos/ibc-go/issues/6742 + // t.Run("native chainA tokens are un-escrowed", func(t *testing.T) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // s.Require().Equal(sdk.NewCoin(chainADenom, sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because tokens have come back + // }) +} + +// TestMsgTransfer_Fails_InvalidAddress_MultiDenom attempts to send a multidenom IBC transfer +// to an invalid address and ensures that the tokens on the sending chain are returned to the sender. +func (s *TransferTestSuite) TestMsgTransfer_Fails_InvalidAddress_MultiDenom() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + chainBDenom := chainB.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("native chainA tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + + // https://github.com/cosmos/ibc-go/issues/6742 + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // + // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) + // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { + s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) + }) + + // send the native chainB denom and also the ibc token from chainA + transferCoins := []sdk.Coin{ + testvalues.DefaultTransferAmount(chainBIBCToken.IBCDenom()), + testvalues.DefaultTransferAmount(chainBDenom), + } + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + + t.Run("native token from chain B and non-native IBC token from chainA, both to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("tokens are sent from chain B", func(t *testing.T) { + t.Run("native chainB tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("non-native chainA IBC denom are burned", func(t *testing.T) { + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + s.Require().Equal(int64(0), actualBalance.Int64()) + }) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + }) + + t.Run("tokens are returned to sender on chainB", func(t *testing.T) { + t.Run("native chainB denom", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("non-native chainA IBC denom", func(t *testing.T) { + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + }) +} + +// TestMsgTransfer_Fails_InvalidAddress attempts to send an IBC transfer to an invalid address and ensures +// that the tokens on the sending chain are unescrowed. +func (s *TransferTestSuite) TestMsgTransfer_Fails_InvalidAddress() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("native IBC token transfer from chainA to invalid address", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + }) + + t.Run("token transfer amount unescrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) +} + +func (s *TransferTestSuite) TestMsgTransfer_Timeout_Nonincentivized() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, _ := s.GetChains() + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + chainBWalletAmount := ibc.WalletAmount{ + Address: chainBWallet.FormattedAddress(), // destination address + Denom: chainA.Config().Denom, + Amount: sdkmath.NewInt(testvalues.IBCTransferAmount), + } + + t.Run("IBC transfer packet timesout", func(t *testing.T) { + tx, err := chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName(), chainBWalletAmount, ibc.TransferOptions{Timeout: testvalues.ImmediatelyTimeout()}) + s.Require().NoError(err) + s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") + time.Sleep(time.Nanosecond * 1) // want it to timeout immediately + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("ensure escrowed tokens have been refunded to sender due to timeout", func(t *testing.T) { + // ensure destination address did not receive any tokens + bal, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, bal) + + // ensure that the sender address has been successfully refunded the full amount + bal, err = s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, bal) + }) +} + +// This can be used to test sending with a transfer packet with a memo given different combinations of +// ibc-go versions. +// +// TestMsgTransfer_WithMemo will test sending IBC transfers from chainA to chainB +// If the chains contain a version of FungibleTokenPacketData with memo, both send and receive should succeed. +// If one of the chains contains a version of FungibleTokenPacketData without memo, then receiving a packet with +// memo should fail in that chain +func (s *TransferTestSuite) TestMsgTransfer_WithMemo() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer with memo from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "memo", nil) + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packets relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64()) + }) +} + +// TestMsgTransfer_EntireBalance tests that it is possible to transfer the entire balance +// of a given denom by using types.UnboundedSpendLimit as the amount. +func (s *TransferTestSuite) TestMsgTransfer_EntireBalance() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + coinFromA := testvalues.DefaultTransferAmount(chainADenom) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(coinFromA), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packets relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(coinFromA.Amount.Int64(), actualBalance.Int64()) + + actualBalance, err = query.Balance(ctx, chainA, chainAAddress, chainADenom) + + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance.Int64()) + }) + + t.Run("send entire balance from B to A", func(t *testing.T) { + transferCoins := sdk.NewCoins((sdk.NewCoin(chainBIBCToken.IBCDenom(), transfertypes.UnboundedSpendLimit())), sdk.NewCoin(chainB.Config().Denom, transfertypes.UnboundedSpendLimit())) + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) + s.AssertTxSuccess(transferTxResp) + }) + + chainAIBCToken := testsuite.GetIBCToken(chainB.Config().Denom, channelA.PortID, channelA.ChannelID) + t.Run("packets relayed", func(t *testing.T) { + // test that chainA has the entire balance back of its native token. + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainADenom) + + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) + + // test that chainA has the entirety of chainB's token IBC denom. + actualBalance, err = query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) + + // Tests that chainB has a zero balance for both. + actualBalance, err = query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Zero(actualBalance.Int64()) + + actualBalance, err = query.Balance(ctx, chainB, chainBAddress, chainB.Config().Denom) + + s.Require().NoError(err) + s.Require().Zero(actualBalance.Int64()) + }) +} diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index 29ea46a6b57..5a3f7f59c9d 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -77,12 +77,22 @@ func (k Keeper) sendTransfer( // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic fullDenomPath := token.Denom +<<<<<<< HEAD var err error // deconstruct the token denomination into the denomination trace info // to determine if the sender is the source chain if strings.HasPrefix(token.Denom, "ibc/") { fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) +======= + for _, coin := range coins { + // Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom. + if coin.Amount.Equal(types.UnboundedSpendLimit()) { + coin.Amount = k.bankKeeper.GetBalance(ctx, sender, coin.Denom).Amount + } + + token, err := k.tokenFromCoin(ctx, coin) +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) if err != nil { return 0, err } diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index e5eeb9a8ef6..64ac7347d58 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -59,7 +59,45 @@ func (suite *KeeperTestSuite) TestSendTransfer() { }, true, }, { +<<<<<<< HEAD "source channel not found", +======= + "successful transfer of native token with ics20-1", + func() { + coins = sdk.NewCoins(coins[0]) + + // Set version to isc20-1. + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { + channel.Version = types.V1 + }) + }, + nil, + }, + { + "successful transfer with empty forwarding hops and ics20-1", + func() { + coins = sdk.NewCoins(coins[0]) + + // Set version to isc20-1. + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { + channel.Version = types.V1 + }) + }, + nil, + }, + { + "successful transfer of entire balance", + func() { + coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit())) + var ok bool + expEscrowAmounts[0], ok = sdkmath.NewIntFromString(ibctesting.DefaultGenesisAccBalance) + suite.Require().True(ok) + }, + nil, + }, + { + "failure: source channel not found", +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) func() { // channel references wrong ID path.EndpointA.ChannelID = ibctesting.InvalidID diff --git a/modules/apps/transfer/types/token.go b/modules/apps/transfer/types/token.go new file mode 100644 index 00000000000..d30a765f0f2 --- /dev/null +++ b/modules/apps/transfer/types/token.go @@ -0,0 +1,58 @@ +package types + +import ( + "math/big" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Tokens is a slice of Tokens +type Tokens []Token + +// maxUint256 is the maximum value for a 256 bit unsigned integer. +var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + +// Validate validates a token denomination and amount. +func (t Token) Validate() error { + if err := t.Denom.Validate(); err != nil { + return errorsmod.Wrap(err, "invalid token denom") + } + + amount, ok := sdkmath.NewIntFromString(t.Amount) + if !ok { + return errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", t.Amount) + } + + if !amount.IsPositive() { + return errorsmod.Wrapf(ErrInvalidAmount, "amount must be strictly positive: got %d", amount) + } + + return nil +} + +// ToCoin converts a Token to an sdk.Coin. +// +// The function parses the Amount field of the Token into an sdkmath.Int and returns a new sdk.Coin with +// the IBCDenom of the Token's Denom field and the parsed Amount. +// If the Amount cannot be parsed, an error is returned with a wrapped error message. +func (t Token) ToCoin() (sdk.Coin, error) { + transferAmount, ok := sdkmath.NewIntFromString(t.Amount) + if !ok { + return sdk.Coin{}, errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", transferAmount) + } + + coin := sdk.NewCoin(t.Denom.IBCDenom(), transferAmount) + return coin, nil +} + +// UnboundedSpendLimit returns the sentinel value that can be used +// as the amount for a denomination's spend limit for which spend limit updating +// should be disabled. Please note that using this sentinel value means that a grantee +// will be granted the privilege to do ICS20 token transfers for the total amount +// of the denomination available at the granter's account. +func UnboundedSpendLimit() sdkmath.Int { + return sdkmath.NewIntFromBigInt(maxUint256) +} diff --git a/modules/apps/transfer/types/transfer_authorization.go b/modules/apps/transfer/types/transfer_authorization.go index 1af6f9c9c43..77bddece4fa 100644 --- a/modules/apps/transfer/types/transfer_authorization.go +++ b/modules/apps/transfer/types/transfer_authorization.go @@ -1,10 +1,21 @@ package types import ( +<<<<<<< HEAD "math/big" "strings" sdkmath "cosmossdk.io/math" +======= + "context" + "slices" + "strings" + + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" @@ -15,9 +26,6 @@ import ( var _ authz.Authorization = &TransferAuthorization{} -// maxUint256 is the maximum value for a 256 bit unsigned integer. -var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) - // NewTransferAuthorization creates a new TransferAuthorization object. func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { return &TransferAuthorization{ @@ -175,6 +183,7 @@ func validateMemo(ctx sdk.Context, memo string, allowedMemos []string) error { return sdkerrors.Wrapf(ErrInvalidAuthorization, "not allowed memo: %s", memo) } +<<<<<<< HEAD // UnboundedSpendLimit returns the sentinel value that can be used // as the amount for a denomination's spend limit for which spend limit updating // should be disabled. Please note that using this sentinel value means that a grantee @@ -182,4 +191,14 @@ func validateMemo(ctx sdk.Context, memo string, allowedMemos []string) error { // of the denomination available at the granter's account. func UnboundedSpendLimit() sdkmath.Int { return sdk.NewIntFromBigInt(maxUint256) +======= +// getAllocationIndex ranges through a set of allocations, and returns the index of the allocation if found. If not, returns -1. +func getAllocationIndex(msg MsgTransfer, allocations []Allocation) int { + for index, allocation := range allocations { + if allocation.SourceChannel == msg.SourceChannel && allocation.SourcePort == msg.SourcePort { + return index + } + } + return allocationNotFound +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) } diff --git a/testing/chain.go b/testing/chain.go index b34003adc2d..d26991ebe0a 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -42,6 +42,10 @@ type SenderAccount struct { SenderAccount authtypes.AccountI } +const ( + DefaultGenesisAccBalance = "10000000000000000000" +) + // TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI // header and the validators of the TestChain. It also contains a field called ChainID. This // is the clientID that *other* chains use to refer to this TestChain. The SenderAccount @@ -104,8 +108,13 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va for i := 0; i < MaxAccounts; i++ { senderPrivKey := secp256k1.GenPrivKey() acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) +<<<<<<< HEAD amount, ok := sdk.NewIntFromString("10000000000000000000") require.True(t, ok) +======= + amount, ok := sdkmath.NewIntFromString(DefaultGenesisAccBalance) + require.True(tb, ok) +>>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) // add sender account balance := banktypes.Balance{ From d58dfa055b133fedf6865e68e0a746b0150ebed6 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 23 Jul 2024 22:45:29 +0200 Subject: [PATCH 2/4] fix conflicts --- CHANGELOG.md | 5 - e2e/tests/transfer/base_test.go | 644 ------------------ modules/apps/transfer/keeper/relay.go | 15 +- modules/apps/transfer/keeper/relay_test.go | 35 +- modules/apps/transfer/types/coin.go | 14 + modules/apps/transfer/types/token.go | 58 -- .../transfer/types/transfer_authorization.go | 33 - testing/chain.go | 7 +- 8 files changed, 23 insertions(+), 788 deletions(-) delete mode 100644 e2e/tests/transfer/base_test.go delete mode 100644 modules/apps/transfer/types/token.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 434f256364f..7b2f57f7b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,13 +46,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features -<<<<<<< HEAD -======= -* (apps/transfer) [\#6492](https://github.com/cosmos/ibc-go/pull/6492) Added new `Tokens` field to `MsgTransfer` to enable sending of multiple denoms, and deprecated the `Token` field. -* (apps/transfer) [\#6693](https://github.com/cosmos/ibc-go/pull/6693) Added new `Forwarding` field to `MsgTransfer` to enable forwarding tokens through multiple intermediary chains with a single transaction. This also enables automatic unwinding of tokens to their native chain. `x/authz` support for transfer allows granters to specify a set of possible forwarding hops that are allowed for grantees. * (apps/transfer) [\#6877](https://github.com/cosmos/ibc-go/pull/6877) Added the possibility to transfer the entire user balance of a particular denomination by using [`UnboundedSpendLimit`](https://github.com/cosmos/ibc-go/blob/715f00eef8727da41db25fdd4763b709bdbba07e/modules/apps/transfer/types/transfer_authorization.go#L253-L255) as the token amount. ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) ### Bug Fixes ## [v7.6.0](https://github.com/cosmos/ibc-go/releases/tag/v7.6.0) - 2024-06-20 diff --git a/e2e/tests/transfer/base_test.go b/e2e/tests/transfer/base_test.go deleted file mode 100644 index 745a0d57f07..00000000000 --- a/e2e/tests/transfer/base_test.go +++ /dev/null @@ -1,644 +0,0 @@ -//go:build !test_e2e - -package transfer - -import ( - "context" - "testing" - "time" - - "github.com/strangelove-ventures/interchaintest/v8/ibc" - test "github.com/strangelove-ventures/interchaintest/v8/testutil" - testifysuite "github.com/stretchr/testify/suite" - - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/cosmos/ibc-go/e2e/testsuite" - "github.com/cosmos/ibc-go/e2e/testsuite/query" - "github.com/cosmos/ibc-go/e2e/testvalues" - transfertypes "github.com/cosmos/ibc-go/v9/modules/apps/transfer/types" -) - -func TestTransferTestSuite(t *testing.T) { - testifysuite.Run(t, new(TransferTestSuite)) -} - -type TransferTestSuite struct { - transferTester -} - -// transferTester defines some helper functions that can be used in various test suites -// that test transfer functionality. -type transferTester struct { - testsuite.E2ETestSuite -} - -// QueryTransferParams queries the on-chain send enabled param for the transfer module -func (s *transferTester) QueryTransferParams(ctx context.Context, chain ibc.Chain) transfertypes.Params { - res, err := query.GRPCQuery[transfertypes.QueryParamsResponse](ctx, chain, &transfertypes.QueryParamsRequest{}) - s.Require().NoError(err) - return *res.Params -} - -// CreateTransferPath sets up a path between chainA and chainB with a transfer channel and returns the relayer wired -// up to watch the channel and port IDs created. -func (s *transferTester) CreateTransferPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { - relayer, channel := s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) - s.T().Logf("test %s running on portID %s channelID %s", testName, channel.PortID, channel.ChannelID) - return relayer, channel -} - -// TestMsgTransfer_Succeeds_Nonincentivized will test sending successful IBC transfers from chainA to chainB. -// The transfer will occur over a basic transfer channel (non incentivized) and both native and non-native tokens -// will be sent forwards and backwards in the IBC transfer timeline (both chains will act as source and receiver chains). -func (s *TransferTestSuite) TestMsgTransfer_Succeeds_Nonincentivized() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - - // NOTE: t.Parallel() should be called before SetupPath in all tests. - // t.Name() must be stored in a variable before t.Parallel() otherwise t.Name() is not - // deterministic. - t.Parallel() - - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainBVersion := chainB.Config().Images[0].Version - chainADenom := chainA.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - chainBAddress := chainBWallet.FormattedAddress() - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") - // TODO: https://github.com/cosmos/ibc-go/issues/6743 - // t.Run("ensure capability module BeginBlock is executed", func(t *testing.T) { - // // by restarting the chain we ensure that the capability module's BeginBlocker is executed. - // s.Require().NoError(chainA.(*cosmos.CosmosChain).StopAllNodes(ctx)) - // s.Require().NoError(chainA.(*cosmos.CosmosChain).StartAllNodes(ctx)) - // s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") - // }) - - t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - - // TODO: cannot query total escrow if tests in parallel are using the same denom. - // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) - // s.Require().NoError(err) - // - // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) - // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) - // } - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - - expected := testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance.Int64()) - }) - - if testvalues.TokenMetadataFeatureReleases.IsSupported(chainBVersion) { - t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { - s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) - }) - } - - t.Run("non-native IBC token transfer from chainB to chainA, receiver is source of tokens", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferCoins(chainBIBCToken.IBCDenom()), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - - s.Require().Equal(sdkmath.ZeroInt(), actualBalance) - - // https://github.com/cosmos/ibc-go/issues/6742 - // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainBVersion) { - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainB, chainBIBCToken.IBCDenom()) - // s.Require().NoError(err) - // s.Require().Equal(sdk.NewCoin(chainBIBCToken.IBCDenom(), sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because sending chain is not source for tokens - // } - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) - - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - s.Require().Equal(expected, actualBalance) - }) - - // https://github.com/cosmos/ibc-go/issues/6742 - // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { - // t.Run("tokens are un-escrowed", func(t *testing.T) { - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) - // s.Require().NoError(err) - // s.Require().Equal(sdk.NewCoin(chainADenom, sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because tokens have come back - // }) - // } -} - -// TestMsgTransfer_Succeeds_MultiDenom will test sending successful IBC transfers from chainA to chainB. -// A multidenom transfer with native chainB tokens and IBC tokens from chainA is executed from chainB to chainA. -func (s *TransferTestSuite) TestMsgTransfer_Succeeds_Nonincentivized_MultiDenom() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainADenom := chainA.Config().Denom - chainBDenom := chainB.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - chainBAddress := chainBWallet.FormattedAddress() - - chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) - chainAIBCToken := testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) - - t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") - - t.Run("native chainA tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - - // https://github.com/cosmos/ibc-go/issues/6742 - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) - // s.Require().NoError(err) - // - // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) - // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - - expected := testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance.Int64()) - }) - - t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { - s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) - }) - - // send the native chainB denom and also the ibc token from chainA - transferCoins := []sdk.Coin{ - testvalues.DefaultTransferAmount(chainBIBCToken.IBCDenom()), - testvalues.DefaultTransferAmount(chainBDenom), - } - - t.Run("native token from chain B and non-native IBC token from chainA, both to chainA", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) - - t.Run("chain A native denom", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("chain B IBC denom", func(t *testing.T) { - actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) - s.Require().NoError(err) - - expected := testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance.Int64()) - }) - }) - - // https://github.com/cosmos/ibc-go/issues/6742 - // t.Run("native chainA tokens are un-escrowed", func(t *testing.T) { - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) - // s.Require().NoError(err) - // s.Require().Equal(sdk.NewCoin(chainADenom, sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because tokens have come back - // }) -} - -// TestMsgTransfer_Fails_InvalidAddress_MultiDenom attempts to send a multidenom IBC transfer -// to an invalid address and ensures that the tokens on the sending chain are returned to the sender. -func (s *TransferTestSuite) TestMsgTransfer_Fails_InvalidAddress_MultiDenom() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainADenom := chainA.Config().Denom - chainBDenom := chainB.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - chainBAddress := chainBWallet.FormattedAddress() - - chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) - - t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") - - t.Run("native chainA tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - - // https://github.com/cosmos/ibc-go/issues/6742 - // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) - // s.Require().NoError(err) - // - // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) - // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - - expected := testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance.Int64()) - }) - - t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { - s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) - }) - - // send the native chainB denom and also the ibc token from chainA - transferCoins := []sdk.Coin{ - testvalues.DefaultTransferAmount(chainBIBCToken.IBCDenom()), - testvalues.DefaultTransferAmount(chainBDenom), - } - - t.Run("stop relayer", func(t *testing.T) { - s.StopRelayer(ctx, relayer) - }) - - t.Run("native token from chain B and non-native IBC token from chainA, both to chainA", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") - - t.Run("tokens are sent from chain B", func(t *testing.T) { - t.Run("native chainB tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("non-native chainA IBC denom are burned", func(t *testing.T) { - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - s.Require().Equal(int64(0), actualBalance.Int64()) - }) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) - }) - - t.Run("tokens are returned to sender on chainB", func(t *testing.T) { - t.Run("native chainB denom", func(t *testing.T) { - actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("non-native chainA IBC denom", func(t *testing.T) { - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - s.Require().NoError(err) - - expected := testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance.Int64()) - }) - }) -} - -// TestMsgTransfer_Fails_InvalidAddress attempts to send an IBC transfer to an invalid address and ensures -// that the tokens on the sending chain are unescrowed. -func (s *TransferTestSuite) TestMsgTransfer_Fails_InvalidAddress() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainADenom := chainA.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") - - t.Run("native IBC token transfer from chainA to invalid address", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - t.Run("packets are relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - }) - - t.Run("token transfer amount unescrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - s.Require().Equal(expected, actualBalance) - }) -} - -func (s *TransferTestSuite) TestMsgTransfer_Timeout_Nonincentivized() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, _ := s.GetChains() - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - - chainBWalletAmount := ibc.WalletAmount{ - Address: chainBWallet.FormattedAddress(), // destination address - Denom: chainA.Config().Denom, - Amount: sdkmath.NewInt(testvalues.IBCTransferAmount), - } - - t.Run("IBC transfer packet timesout", func(t *testing.T) { - tx, err := chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName(), chainBWalletAmount, ibc.TransferOptions{Timeout: testvalues.ImmediatelyTimeout()}) - s.Require().NoError(err) - s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") - time.Sleep(time.Nanosecond * 1) // want it to timeout immediately - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - t.Run("ensure escrowed tokens have been refunded to sender due to timeout", func(t *testing.T) { - // ensure destination address did not receive any tokens - bal, err := s.GetChainBNativeBalance(ctx, chainBWallet) - s.Require().NoError(err) - s.Require().Equal(testvalues.StartingTokenAmount, bal) - - // ensure that the sender address has been successfully refunded the full amount - bal, err = s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - s.Require().Equal(testvalues.StartingTokenAmount, bal) - }) -} - -// This can be used to test sending with a transfer packet with a memo given different combinations of -// ibc-go versions. -// -// TestMsgTransfer_WithMemo will test sending IBC transfers from chainA to chainB -// If the chains contain a version of FungibleTokenPacketData with memo, both send and receive should succeed. -// If one of the chains contains a version of FungibleTokenPacketData without memo, then receiving a packet with -// memo should fail in that chain -func (s *TransferTestSuite) TestMsgTransfer_WithMemo() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainADenom := chainA.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - chainBAddress := chainBWallet.FormattedAddress() - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") - - t.Run("IBC token transfer with memo from chainA to chainB", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "memo", nil) - s.AssertTxSuccess(transferTxResp) - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount - s.Require().Equal(expected, actualBalance) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) - - t.Run("packets relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - - s.Require().NoError(err) - s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64()) - }) -} - -// TestMsgTransfer_EntireBalance tests that it is possible to transfer the entire balance -// of a given denom by using types.UnboundedSpendLimit as the amount. -func (s *TransferTestSuite) TestMsgTransfer_EntireBalance() { - t := s.T() - ctx := context.TODO() - - testName := t.Name() - t.Parallel() - relayer, channelA := s.CreateTransferPath(testName) - - chainA, chainB := s.GetChains() - - chainADenom := chainA.Config().Denom - - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - chainAAddress := chainAWallet.FormattedAddress() - - chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) - chainBAddress := chainBWallet.FormattedAddress() - - coinFromA := testvalues.DefaultTransferAmount(chainADenom) - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") - - t.Run("IBC token transfer from chainA to chainB", func(t *testing.T) { - transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, sdk.NewCoins(coinFromA), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainA), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - t.Run("tokens are escrowed", func(t *testing.T) { - actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) - s.Require().NoError(err) - - s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance) - }) - - t.Run("start relayer", func(t *testing.T) { - s.StartRelayer(relayer, testName) - }) - - chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) - - t.Run("packets relayed", func(t *testing.T) { - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - - s.Require().NoError(err) - s.Require().Equal(coinFromA.Amount.Int64(), actualBalance.Int64()) - - actualBalance, err = query.Balance(ctx, chainA, chainAAddress, chainADenom) - - s.Require().NoError(err) - s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance.Int64()) - }) - - t.Run("send entire balance from B to A", func(t *testing.T) { - transferCoins := sdk.NewCoins((sdk.NewCoin(chainBIBCToken.IBCDenom(), transfertypes.UnboundedSpendLimit())), sdk.NewCoin(chainB.Config().Denom, transfertypes.UnboundedSpendLimit())) - transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoins, chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainB), 0, "", nil) - s.AssertTxSuccess(transferTxResp) - }) - - chainAIBCToken := testsuite.GetIBCToken(chainB.Config().Denom, channelA.PortID, channelA.ChannelID) - t.Run("packets relayed", func(t *testing.T) { - // test that chainA has the entire balance back of its native token. - s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) - actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainADenom) - - s.Require().NoError(err) - s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) - - // test that chainA has the entirety of chainB's token IBC denom. - actualBalance, err = query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) - - s.Require().NoError(err) - s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) - - // Tests that chainB has a zero balance for both. - actualBalance, err = query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) - - s.Require().NoError(err) - s.Require().Zero(actualBalance.Int64()) - - actualBalance, err = query.Balance(ctx, chainB, chainBAddress, chainB.Config().Denom) - - s.Require().NoError(err) - s.Require().Zero(actualBalance.Int64()) - }) -} diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go index 5a3f7f59c9d..3a36b6c6580 100644 --- a/modules/apps/transfer/keeper/relay.go +++ b/modules/apps/transfer/keeper/relay.go @@ -77,22 +77,12 @@ func (k Keeper) sendTransfer( // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic fullDenomPath := token.Denom -<<<<<<< HEAD var err error // deconstruct the token denomination into the denomination trace info // to determine if the sender is the source chain if strings.HasPrefix(token.Denom, "ibc/") { fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) -======= - for _, coin := range coins { - // Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom. - if coin.Amount.Equal(types.UnboundedSpendLimit()) { - coin.Amount = k.bankKeeper.GetBalance(ctx, sender, coin.Denom).Amount - } - - token, err := k.tokenFromCoin(ctx, coin) ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) if err != nil { return 0, err } @@ -103,6 +93,11 @@ func (k Keeper) sendTransfer( telemetry.NewLabel(coretypes.LabelDestinationChannel, destinationChannel), } + // Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom. + if token.Amount.Equal(types.UnboundedSpendLimit()) { + token.Amount = k.bankKeeper.GetBalance(ctx, sender, token.Denom).Amount + } + // NOTE: SendTransfer simply sends the denomination as it exists on its own // chain inside the packet data. The receiving chain will perform denom // prefixing as necessary. diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index 64ac7347d58..48e268945e1 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -59,45 +59,16 @@ func (suite *KeeperTestSuite) TestSendTransfer() { }, true, }, { -<<<<<<< HEAD - "source channel not found", -======= - "successful transfer of native token with ics20-1", - func() { - coins = sdk.NewCoins(coins[0]) - - // Set version to isc20-1. - path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { - channel.Version = types.V1 - }) - }, - nil, - }, - { - "successful transfer with empty forwarding hops and ics20-1", - func() { - coins = sdk.NewCoins(coins[0]) - - // Set version to isc20-1. - path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { - channel.Version = types.V1 - }) - }, - nil, - }, - { "successful transfer of entire balance", func() { - coins = sdk.NewCoins(sdk.NewCoin(coins[0].Denom, types.UnboundedSpendLimit())) + coin.Amount = types.UnboundedSpendLimit() var ok bool - expEscrowAmounts[0], ok = sdkmath.NewIntFromString(ibctesting.DefaultGenesisAccBalance) + expEscrowAmount, ok = sdk.NewIntFromString(ibctesting.DefaultGenesisAccBalance) suite.Require().True(ok) - }, - nil, + }, true, }, { "failure: source channel not found", ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) func() { // channel references wrong ID path.EndpointA.ChannelID = ibctesting.InvalidID diff --git a/modules/apps/transfer/types/coin.go b/modules/apps/transfer/types/coin.go index 6abc5367cbe..8155f2b7e1d 100644 --- a/modules/apps/transfer/types/coin.go +++ b/modules/apps/transfer/types/coin.go @@ -2,12 +2,17 @@ package types import ( "fmt" + "math/big" "strings" "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) +// maxUint256 is the maximum value for a 256 bit unsigned integer. +var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + // SenderChainIsSource returns false if the denomination originally came // from the receiving chain and true otherwise. func SenderChainIsSource(sourcePort, sourceChannel, denom string) bool { @@ -46,3 +51,12 @@ func GetTransferCoin(portID, channelID, baseDenom string, amount math.Int) sdk.C denomTrace := ParseDenomTrace(GetPrefixedDenom(portID, channelID, baseDenom)) return sdk.NewCoin(denomTrace.IBCDenom(), amount) } + +// UnboundedSpendLimit returns the sentinel value that can be used +// as the amount for a denomination's spend limit for which spend limit updating +// should be disabled. Please note that using this sentinel value means that a grantee +// will be granted the privilege to do ICS20 token transfers for the total amount +// of the denomination available at the granter's account. +func UnboundedSpendLimit() sdkmath.Int { + return sdkmath.NewIntFromBigInt(maxUint256) +} diff --git a/modules/apps/transfer/types/token.go b/modules/apps/transfer/types/token.go deleted file mode 100644 index d30a765f0f2..00000000000 --- a/modules/apps/transfer/types/token.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import ( - "math/big" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Tokens is a slice of Tokens -type Tokens []Token - -// maxUint256 is the maximum value for a 256 bit unsigned integer. -var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) - -// Validate validates a token denomination and amount. -func (t Token) Validate() error { - if err := t.Denom.Validate(); err != nil { - return errorsmod.Wrap(err, "invalid token denom") - } - - amount, ok := sdkmath.NewIntFromString(t.Amount) - if !ok { - return errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", t.Amount) - } - - if !amount.IsPositive() { - return errorsmod.Wrapf(ErrInvalidAmount, "amount must be strictly positive: got %d", amount) - } - - return nil -} - -// ToCoin converts a Token to an sdk.Coin. -// -// The function parses the Amount field of the Token into an sdkmath.Int and returns a new sdk.Coin with -// the IBCDenom of the Token's Denom field and the parsed Amount. -// If the Amount cannot be parsed, an error is returned with a wrapped error message. -func (t Token) ToCoin() (sdk.Coin, error) { - transferAmount, ok := sdkmath.NewIntFromString(t.Amount) - if !ok { - return sdk.Coin{}, errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", transferAmount) - } - - coin := sdk.NewCoin(t.Denom.IBCDenom(), transferAmount) - return coin, nil -} - -// UnboundedSpendLimit returns the sentinel value that can be used -// as the amount for a denomination's spend limit for which spend limit updating -// should be disabled. Please note that using this sentinel value means that a grantee -// will be granted the privilege to do ICS20 token transfers for the total amount -// of the denomination available at the granter's account. -func UnboundedSpendLimit() sdkmath.Int { - return sdkmath.NewIntFromBigInt(maxUint256) -} diff --git a/modules/apps/transfer/types/transfer_authorization.go b/modules/apps/transfer/types/transfer_authorization.go index 77bddece4fa..8676c6fb679 100644 --- a/modules/apps/transfer/types/transfer_authorization.go +++ b/modules/apps/transfer/types/transfer_authorization.go @@ -1,21 +1,8 @@ package types import ( -<<<<<<< HEAD - "math/big" "strings" - sdkmath "cosmossdk.io/math" -======= - "context" - "slices" - "strings" - - "github.com/cosmos/gogoproto/proto" - - errorsmod "cosmossdk.io/errors" - ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" @@ -182,23 +169,3 @@ func validateMemo(ctx sdk.Context, memo string, allowedMemos []string) error { return sdkerrors.Wrapf(ErrInvalidAuthorization, "not allowed memo: %s", memo) } - -<<<<<<< HEAD -// UnboundedSpendLimit returns the sentinel value that can be used -// as the amount for a denomination's spend limit for which spend limit updating -// should be disabled. Please note that using this sentinel value means that a grantee -// will be granted the privilege to do ICS20 token transfers for the total amount -// of the denomination available at the granter's account. -func UnboundedSpendLimit() sdkmath.Int { - return sdk.NewIntFromBigInt(maxUint256) -======= -// getAllocationIndex ranges through a set of allocations, and returns the index of the allocation if found. If not, returns -1. -func getAllocationIndex(msg MsgTransfer, allocations []Allocation) int { - for index, allocation := range allocations { - if allocation.SourceChannel == msg.SourceChannel && allocation.SourcePort == msg.SourcePort { - return index - } - } - return allocationNotFound ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) -} diff --git a/testing/chain.go b/testing/chain.go index d26991ebe0a..7ad257b3a80 100644 --- a/testing/chain.go +++ b/testing/chain.go @@ -108,13 +108,8 @@ func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, va for i := 0; i < MaxAccounts; i++ { senderPrivKey := secp256k1.GenPrivKey() acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) -<<<<<<< HEAD - amount, ok := sdk.NewIntFromString("10000000000000000000") + amount, ok := sdk.NewIntFromString(DefaultGenesisAccBalance) require.True(t, ok) -======= - amount, ok := sdkmath.NewIntFromString(DefaultGenesisAccBalance) - require.True(tb, ok) ->>>>>>> 92e1f387 ((feat) Add possibility to transfer entire balance. (#6877)) // add sender account balance := banktypes.Balance{ From 5a20f8a451a523ae01afb006bb4bcc1e012d1434 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 23 Jul 2024 22:55:29 +0200 Subject: [PATCH 3/4] lint --- modules/apps/transfer/types/coin.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/apps/transfer/types/coin.go b/modules/apps/transfer/types/coin.go index 8155f2b7e1d..a8cebdb62c2 100644 --- a/modules/apps/transfer/types/coin.go +++ b/modules/apps/transfer/types/coin.go @@ -5,7 +5,6 @@ import ( "math/big" "strings" - "cosmossdk.io/math" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -47,7 +46,7 @@ func GetPrefixedDenom(portID, channelID, baseDenom string) string { // GetTransferCoin creates a transfer coin with the port ID and channel ID // prefixed to the base denom. -func GetTransferCoin(portID, channelID, baseDenom string, amount math.Int) sdk.Coin { +func GetTransferCoin(portID, channelID, baseDenom string, amount sdkmath.Int) sdk.Coin { denomTrace := ParseDenomTrace(GetPrefixedDenom(portID, channelID, baseDenom)) return sdk.NewCoin(denomTrace.IBCDenom(), amount) } From 826641339e081083f2915b0c6d49f94eb4ad7a60 Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Tue, 23 Jul 2024 23:02:51 +0200 Subject: [PATCH 4/4] delete docs --- docs/docs/02-apps/01-transfer/04-messages.md | 86 ------------------- .../01-transfer/10-ICS20-v1/04-messages.md | 65 -------------- modules/apps/transfer/keeper/relay_test.go | 2 +- 3 files changed, 1 insertion(+), 152 deletions(-) delete mode 100644 docs/docs/02-apps/01-transfer/04-messages.md delete mode 100644 docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md diff --git a/docs/docs/02-apps/01-transfer/04-messages.md b/docs/docs/02-apps/01-transfer/04-messages.md deleted file mode 100644 index 4d121985fdc..00000000000 --- a/docs/docs/02-apps/01-transfer/04-messages.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: Messages -sidebar_label: Messages -sidebar_position: 4 -slug: /apps/transfer/messages ---- - -# Messages - -## `MsgTransfer` - -A fungible token cross chain transfer is achieved by using the `MsgTransfer`: - -```go -type MsgTransfer struct { - SourcePort string - SourceChannel string - // Deprecated: Use Tokens instead. - Token sdk.Coin - Sender string - Receiver string - TimeoutHeight ibcexported.Height - TimeoutTimestamp uint64 - Memo string - Tokens []sdk.Coin - Forwarding *Forwarding -} - -type Forwarding struct { - Unwind bool - Hops []Hop -} - -type Hop struct { - PortId string - ChannelId string -} -``` - -:::info -Multi-denom token transfers and token forwarding are features supported only on ICS20 v2 transfer channels. -::: - -If `Forwarding` is `nil`, this message is expected to fail if: - -- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). -- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). -- `Tokens` must not be empty. -- Each `Coin` in `Tokens` must satisfy the following: - - `Amount` must be positive. - - `Denom` must be a valid IBC denomination, as defined in [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). -- `Sender` is empty. -- `Receiver` is empty or contains more than 2048 bytes. -- `Memo` contains more than 32768 bytes. -- `TimeoutHeight` and `TimeoutTimestamp` are both zero. - -If `Forwarding` is not `nil`, then to use forwarding you must either set `Unwind` to true or provide a non-empty list of `Hops`. Setting both `Unwind` to true and providing a non-empty list of `Hops` is allowed, but the total number of hops that is formed as a combination of the hops needed to unwind the tokens and the hops to forward them afterwards to the final destination must not exceed 8. When using forwarding, timeout must be specified using only `TimeoutTimestamp` (i.e. `TimeoutHeight` must be zero). Please note that the timeout timestamp must take into account the time that it may take tokens to be forwarded through the intermediary chains. Additionally, please note that the `MsgTransfer` will fail if: - -- `Hops` is not empty, and the number of elements of `Hops` is greater than 8, or either the `PortId` or `ChannelId` of any of the `Hops` is not a valid identifier. -- `Unwind` is true, and either the coins to be transferred have different denomination traces, or `SourcePort` and `SourceChannel` are not empty strings (they must be empty because they are set by the transfer module, since it has access to the denomination trace information and is thus able to know the source port ID, channel ID to use in order to unwind the tokens). If `Unwind` is true, the transfer module expects the tokens in `MsgTransfer` to not be native to the sending chain (i.e. they must be IBC vouchers). - -Please note that the `Token` field is deprecated and users should now use `Tokens` instead. If `Token` is used then `Tokens` must be empty. Similarly, if `Tokens` is used then `Token` should be left empty. This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. - -The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. - -If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corrsponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. - -### Memo - -The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. - -For example, the following memo field is used by the [callbacks middleware](../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: - -```jsonc -{ - "src_callback": { - "address": "callbackAddressString", - // optional - "gas_limit": "userDefinedGasLimitString", - } -} -``` - -You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). - -Please note that the memo field is always meant to be consumed only on the final destination chain. This means that the transfer module will guarantee that the memo field in the intermediary chains is empty. diff --git a/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md b/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md deleted file mode 100644 index 08fc05e88c3..00000000000 --- a/docs/docs/02-apps/01-transfer/10-ICS20-v1/04-messages.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Messages -sidebar_label: Messages -sidebar_position: 4 -slug: /apps/transfer/ics20-v1/messages ---- - -:::warning -This document is relevant only for fungible token transfers over channels on v1 of the ICS-20 protocol. -::: - -# Messages - -## `MsgTransfer` - -A fungible token cross chain transfer is achieved by using the `MsgTransfer`: - -```go -type MsgTransfer struct { - SourcePort string - SourceChannel string - Token sdk.Coin - Sender string - Receiver string - TimeoutHeight ibcexported.Height - TimeoutTimestamp uint64 - Memo string -} -``` - -This message is expected to fail if: - -- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). -- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). -- `Token` is invalid: - - `Amount` is not positive. - - `Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). -- `Sender` is empty. -- `Receiver` is empty or contains more than 2048 bytes. -- `Memo` contains more than 32768 bytes. -- `TimeoutHeight` and `TimeoutTimestamp` are both zero. - -This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. - -The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. - -If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corrsponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. - -### Memo - -The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. - -For example, the following memo field is used by the [callbacks middleware](../../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: - -```jsonc -{ - "src_callback": { - "address": "callbackAddressString", - // optional - "gas_limit": "userDefinedGasLimitString", - } -} -``` - -You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go index 48e268945e1..c1ea80166ef 100644 --- a/modules/apps/transfer/keeper/relay_test.go +++ b/modules/apps/transfer/keeper/relay_test.go @@ -68,7 +68,7 @@ func (suite *KeeperTestSuite) TestSendTransfer() { }, true, }, { - "failure: source channel not found", + "source channel not found", func() { // channel references wrong ID path.EndpointA.ChannelID = ibctesting.InvalidID