diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2c7eeaf76d9..60ee78e69845 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,7 +44,7 @@ jobs: test-coverage-run-1: runs-on: ubuntu-latest needs: split-test-files - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v3 @@ -60,7 +60,7 @@ jobs: if: "env.GIT_DIFF != ''" - name: test & coverage report creation run: | - cat xaa.txt | xargs go test -mod=readonly -timeout 8m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' + cat xaa.txt | xargs go test -mod=readonly -timeout 15m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' if: "env.GIT_DIFF != ''" - name: filter out DONTCOVER run: | @@ -81,7 +81,7 @@ jobs: test-coverage-run-2: runs-on: ubuntu-latest needs: split-test-files - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v3 @@ -97,7 +97,7 @@ jobs: if: "env.GIT_DIFF != ''" - name: test & coverage report creation run: | - cat xab.txt | xargs go test -mod=readonly -timeout 6m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' + cat xab.txt | xargs go test -mod=readonly -timeout 15m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' if: "env.GIT_DIFF != ''" - name: filter out DONTCOVER run: | @@ -118,7 +118,7 @@ jobs: test-coverage-run-3: runs-on: ubuntu-latest needs: split-test-files - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v3 @@ -134,7 +134,7 @@ jobs: if: "env.GIT_DIFF != ''" - name: test & coverage report creation run: | - cat xac.txt | xargs go test -mod=readonly -timeout 6m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' + cat xac.txt | xargs go test -mod=readonly -timeout 15m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' if: "env.GIT_DIFF != ''" - name: filter out DONTCOVER run: | @@ -155,7 +155,7 @@ jobs: test-coverage-run-4: runs-on: ubuntu-latest needs: split-test-files - timeout-minutes: 10 + timeout-minutes: 15 steps: - uses: actions/checkout@v2 - uses: technote-space/get-diff-action@v3 @@ -171,7 +171,7 @@ jobs: if: "env.GIT_DIFF != ''" - name: test & coverage report creation run: | - cat xad.txt | xargs go test -mod=readonly -timeout 6m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' + cat xad.txt | xargs go test -mod=readonly -timeout 15m -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' if: "env.GIT_DIFF != ''" - name: filter out DONTCOVER run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 085535b8b0a8..f3ff8b6076bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -283,9 +283,10 @@ Buffers for state serialization instead of Amino. ### Improvements +* (x/ibc-transfer) [\#6871](https://github.com/cosmos/cosmos-sdk/pull/6871) Implement [ADR 001 - Coin Source Tracing](./docs/architecture/adr-001-coin-source-tracing.md). * (types) [\#7027](https://github.com/cosmos/cosmos-sdk/pull/7027) `Coin(s)` and `DecCoin(s)` updates: * Bump denomination max length to 128 - * Allow unicode letters and numbers in denominations + * Allow uppercase letters and numbers in denominations to support [ADR 001](./docs/architecture/adr-001-coin-source-tracing.md) * Added `Validate` function that returns a descriptive error * (baseapp) [\#6186](https://github.com/cosmos/cosmos-sdk/issues/6186) Support emitting events during `AnteHandler` execution. * (x/auth) [\#5702](https://github.com/cosmos/cosmos-sdk/pull/5702) Add parameter querying support for `x/auth`. diff --git a/docs/architecture/adr-001-coin-source-tracing.md b/docs/architecture/adr-001-coin-source-tracing.md index d0d2d6d5337b..be3ff2386522 100644 --- a/docs/architecture/adr-001-coin-source-tracing.md +++ b/docs/architecture/adr-001-coin-source-tracing.md @@ -3,10 +3,11 @@ ## Changelog - 2020-07-09: Initial Draft +- 2020-08-11: Implementation changes ## Status -Proposed +Accepted, Implemented ## Context @@ -114,7 +115,7 @@ trace the token back to the originating chain, as specified on ICS20. The new proposed format will be the following: ```golang -ibcDenom = "ibc/" + hash(trace + "/" + base denom) +ibcDenom = "ibc/" + hash(trace path + "/" + base denom) ``` The hash function will be a SHA256 hash of the fields of the `DenomTrace`: @@ -124,7 +125,7 @@ The hash function will be a SHA256 hash of the fields of the `DenomTrace`: // information message DenomTrace { // chain of port/channel identifiers used for tracing the source of the fungible token - string trace = 1; + string path = 1; // base denomination of the relayed fungible token string base_denom = 2; } @@ -133,50 +134,23 @@ message DenomTrace { The `IBCDenom` function constructs the `Coin` denomination used when creating the ICS20 fungible token packet data: ```golang -// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields. +// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula: +// +// hash = sha256(tracePath + "/" + baseDenom) func (dt DenomTrace) Hash() tmbytes.HexBytes { - return tmhash.Sum(dt.Trace + "/" + dt.BaseDenom) + return tmhash.Sum(dt.Path + "/" + dt.BaseDenom) } -// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(trace + baseDenom)}'. If the trace is empty, it will return the base denomination. +// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(tracePath + baseDenom)}'. +// If the trace is empty, it will return the base denomination. func (dt DenomTrace) IBCDenom() string { - if dt.Trace != "" { + if dt.Path != "" { return fmt.Sprintf("ibc/%s", dt.Hash()) } return dt.BaseDenom } ``` -In order to trim the denomination trace prefix when sending/receiving fungible tokens, the `RemovePrefix` function is provided. - -> NOTE: the prefix addition must be done on the client side. - -```golang -// RemovePrefix trims the first portID/channelID pair from the trace info. If the trace is already empty it will perform a no-op. If the trace is incorrectly constructed or doesn't have separators it will return an error. -func (dt *DenomTrace) RemovePrefix() error { - if dt.Trace == "" { - return nil - } - - traceSplit := strings.SplitN(dt.Trace, "/", 3) - - var err error - switch { - // NOTE: other cases are checked during msg validation - case len(traceSplit) == 2: - dt.Trace = "" - case len(traceSplit) == 3: - dt.Trace = traceSplit[2] - } - - if err != nil { - return err - } - - return nil -} -``` - ### `x/ibc-transfer` Changes In order to retrieve the trace information from an IBC denomination, a lookup table needs to be @@ -217,44 +191,30 @@ hash, if the trace info is provided, or that the base denominations matches: ```golang func (msg MsgTransfer) ValidateBasic() error { // ... - if err := msg.Trace.Validate(); err != nil { - return err - } - if err := ValidateIBCDenom(msg.Token.Denom, msg.Trace); err != nil { - return err - } - // ... + return ValidateIBCDenom(msg.Token.Denom) } ``` ```golang -// ValidateIBCDenom checks that the denomination for an IBC fungible token is valid. It returns error if the trace `hash` is invalid -func ValidateIBCDenom(denom string, trace DenomTrace) error { - // Validate that base denominations are equal if the trace info is not provided - if trace.Trace == "" { - if trace.BaseDenom != denom { - return Wrapf( - ErrInvalidDenomForTransfer, - "token denom must match the trace base denom (%s ≠ %s)", - denom, trace.BaseDenom, - ) - } - return nil - } - +// ValidateIBCDenom validates that the given denomination is either: +// +// - A valid base denomination (eg: 'uatom') +// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-001-coin-source-tracing.md +func ValidateIBCDenom(denom string) error { denomSplit := strings.SplitN(denom, "/", 2) switch { - case denomSplit[0] != "ibc": - return Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) - case len(denomSplit) == 2: - return tmtypes.ValidateHash([]byte(denomSplit[1])) + case strings.TrimSpace(denom) == "", + len(denomSplit) == 1 && denomSplit[0] == "ibc", + len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""): + return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + + case denomSplit[0] == denom && strings.TrimSpace(denom) != "": + return sdk.ValidateDenom(denom) } - denomTraceHash := denomSplit[1] - traceHash := trace.Hash() - if !bytes.Equal(traceHash.Bytes(), denomTraceHash.Bytes()) { - return Errorf("token denomination trace hash mismatch, expected %s got %s", traceHash, denomTraceHash) + if _, err := ParseHexHash(denomSplit[1]); err != nil { + return Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) } return nil @@ -266,6 +226,46 @@ The denomination trace info only needs to be updated when token is received: - Receiver is **source** chain: The receiver created the token and must have the trace lookup already stored (if necessary _ie_ native token case wouldn't need a lookup). - Receiver is **not source** chain: Store the received info. For example, during step 1, when chain `B` receives `transfer/channelToA/denom`. +```golang +// SendTransfer +// ... + + fullDenomPath := token.Denom + +// 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) + if err != nil { + return err + } +} + +if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { +//... +``` + +```golang +// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash +// component. +func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) { + hexHash := denom[4:] + hash, err := ParseHexHash(hexHash) + if err != nil { + return "", Wrap(ErrInvalidDenomForTransfer, err.Error()) + } + + denomTrace, found := k.GetDenomTrace(ctx, hash) + if !found { + return "", Wrap(ErrTraceNotFound, hexHash) + } + + fullDenomPath := denomTrace.GetFullDenomPath() + return fullDenomPath, nil +} +``` + + ```golang // OnRecvPacket // ... @@ -277,19 +277,13 @@ The denomination trace info only needs to be updated when token is received: // NOTE: We use SourcePort and SourceChannel here, because the counterparty // chain would have prefixed with DestPort and DestChannel when originally // receiving this coin as seen in the "sender chain is the source" condition. -voucherPrefix := GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) - if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { // sender chain is not the source, unescrow tokens // remove prefix added by sender chain - if err := denomTrace.RemovePrefix(); err != nil { - return err - } - - // NOTE: since the sender is a sink chain, we already know the unprefixed denomination trace info - - token := sdk.NewCoin(denomTrace.IBCDenom(), sdk.NewIntFromUint64(data.Amount)) + voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) + unprefixedDenom := data.Denom[len(voucherPrefix):] + token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount)) // unescrow tokens escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) @@ -298,11 +292,13 @@ if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data // sender chain is the source, mint vouchers -// construct the denomination trace from the full raw denomination -denomTrace := NewDenomTraceFromRawDenom(data.Denom) +// since SendPacket did not prefix the denomination, we must prefix denomination here +sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) +// NOTE: sourcePrefix contains the trailing "/" +prefixedDenom := sourcePrefix + data.Denom -// since SendPacket did not prefix the denomination with the voucherPrefix, we must add it here -denomTrace.AddPrefix(packet.GetDestPort(), packet.GetDestChannel()) +// construct the denomination trace from the full raw denomination +denomTrace := types.ParseDenomTrace(prefixedDenom) // set the value to the lookup table if not stored already traceHash := denomTrace.Hash() @@ -310,7 +306,8 @@ if !k.HasDenomTrace(ctx, traceHash) { k.SetDenomTrace(ctx, traceHash, denomTrace) } -voucher := sdk.NewCoin(denomTrace.IBCDenom(), sdk.NewIntFromUint64(data.Amount)) +voucherDenom := denomTrace.IBCDenom() +voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount)) // mint new tokens if the source of the transfer is the same chain if err := k.bankKeeper.MintCoins( @@ -347,7 +344,8 @@ The coin denomination validation will need to be updated to reflect these change function will now: - Accept slash separators (`"/"`) and uppercase characters (due to the `HexBytes` format) -- Bump the maximum character length to 64 +- Bump the maximum character length to 128, as the hex representation used by Tendermint's + `HexBytes` type contains 64 characters. Additional validation logic, such as verifying the length of the hash, the may be added to the bank module in the future if the [custom base denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) is integrated into the SDK. diff --git a/docs/architecture/adr-template.md b/docs/architecture/adr-template.md index e3c6fa8fa004..33cd412df6cb 100644 --- a/docs/architecture/adr-template.md +++ b/docs/architecture/adr-template.md @@ -7,7 +7,7 @@ ## Status > A decision may be "proposed" if the project stakeholders haven't agreed with it yet, or "accepted" once it is agreed. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. -> {Deprecated|Proposed|Accepted} +> {Deprecated|Proposed|Accepted} {Implemented|Not Implemented} ## Context diff --git a/proto/ibc/transfer/genesis.proto b/proto/ibc/transfer/genesis.proto index ace356ea3e09..c093a84b83df 100644 --- a/proto/ibc/transfer/genesis.proto +++ b/proto/ibc/transfer/genesis.proto @@ -4,11 +4,16 @@ package ibc.transfer; option go_package = "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"; import "gogoproto/gogo.proto"; +import "ibc/transfer/transfer.proto"; -// GenesisState is currently only used to ensure that the InitGenesis gets run -// by the module manager -message GenesisState { - string port_id = 1 [ - (gogoproto.moretags) = "yaml:\"port_id\"" - ]; +// GenesisState defines the ibc-transfer genesis state +message GenesisState{ + string port_id = 1 [ + (gogoproto.moretags) = "yaml:\"port_id\"" + ]; + repeated DenomTrace denom_traces = 2 [ + (gogoproto.castrepeated) = "Traces", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"denom_traces\"" + ]; } diff --git a/proto/ibc/transfer/query.proto b/proto/ibc/transfer/query.proto new file mode 100644 index 000000000000..5476e3b2a311 --- /dev/null +++ b/proto/ibc/transfer/query.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; +package ibc.transfer; + +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/transfer/transfer.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"; + +// Query provides defines the gRPC querier service. +service Query { + // DenomTrace queries a denomination trace information. + rpc DenomTrace(QueryDenomTraceRequest) returns (QueryDenomTraceResponse) { + option (google.api.http).get = "/ibc_transfer/v1beta1/denom_traces/{hash}"; + } + + // DenomTraces queries all denomination traces. + rpc DenomTraces(QueryDenomTracesRequest) returns (QueryDenomTracesResponse) { + option (google.api.http).get = "/ibc_transfer/v1beta1/denom_traces"; + } +} + +// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC method +message QueryDenomTraceRequest { + // hash (in hex format) of the denomination trace information. + string hash = 1; +} + +// QueryDenomTraceResponse is the response type for the Query/DenomTrace RPC method. +message QueryDenomTraceResponse { + // denom_trace returns the requested denomination trace information. + DenomTrace denom_trace = 1; +} + +// QueryConnectionsRequest is the request type for the Query/DenomTraces RPC method +message QueryDenomTracesRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryConnectionsResponse is the response type for the Query/DenomTraces RPC method. +message QueryDenomTracesResponse { + // denom_traces returns all denominations trace information. + repeated DenomTrace denom_traces = 1 [ + (gogoproto.castrepeated) = "Traces", + (gogoproto.nullable) = false + ]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/proto/ibc/transfer/transfer.proto b/proto/ibc/transfer/transfer.proto index cb5b06ca9c90..b49e8b3dcf20 100644 --- a/proto/ibc/transfer/transfer.proto +++ b/proto/ibc/transfer/transfer.proto @@ -17,8 +17,7 @@ message MsgTransfer { // the tokens to be transferred cosmos.base.v1beta1.Coin token = 3 [(gogoproto.nullable) = false]; // the sender address - bytes sender = 4 - [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes sender = 4 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; // the recipient address on the destination chain string receiver = 5; // Timeout height relative to the current block height. @@ -26,8 +25,7 @@ message MsgTransfer { uint64 timeout_height = 6 [(gogoproto.moretags) = "yaml:\"timeout_height\""]; // Timeout timestamp (in nanoseconds) relative to the current block timestamp. // The timeout is disabled when set to 0. - uint64 timeout_timestamp = 7 - [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; + uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; } // FungibleTokenPacketData defines a struct for the packet payload @@ -52,3 +50,13 @@ message FungibleTokenPacketAcknowledgement { bool success = 1; string error = 2; } + +// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing +// information path. +message DenomTrace { + // path defines the chain of port/channel identifiers used for tracing the source of the fungible + // token. + string path = 1; + // base denomination of the relayed fungible token. + string base_denom = 2; +} diff --git a/types/coin_test.go b/types/coin_test.go index 9042093bfb42..f16bd682e5ef 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -76,6 +76,7 @@ func TestCoinIsValid(t *testing.T) { {Coin{"ATOM", OneInt()}, true}, {Coin{"a", OneInt()}, false}, {Coin{loremIpsum, OneInt()}, false}, + {Coin{"ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", OneInt()}, true}, {Coin{"atOm", OneInt()}, true}, {Coin{" ", OneInt()}, false}, } @@ -632,6 +633,8 @@ func TestParse(t *testing.T) { {"1.2btc", false, nil}, // amount must be integer {"5foo:bar", false, nil}, // invalid separator {"10atom10", true, Coins{{"atom10", NewInt(10)}}}, + {"200transfer/channelToA/uatom", true, Coins{{"transfer/channelToA/uatom", NewInt(200)}}}, + {"50ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", true, Coins{{"ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", NewInt(50)}}}, } for tcIndex, tc := range cases { diff --git a/x/ibc-transfer/client/cli/cli.go b/x/ibc-transfer/client/cli/cli.go index e8850d9eff5b..9686afaa189a 100644 --- a/x/ibc-transfer/client/cli/cli.go +++ b/x/ibc-transfer/client/cli/cli.go @@ -6,6 +6,23 @@ import ( "github.com/cosmos/cosmos-sdk/client" ) +// GetQueryCmd returns the query commands for IBC connections +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "ibc-transfer", + Short: "IBC fungible token transfer query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + queryCmd.AddCommand( + GetCmdQueryDenomTrace(), + GetCmdQueryDenomTraces(), + ) + + return queryCmd +} + // NewTxCmd returns the transaction commands for IBC fungible token transfer func NewTxCmd() *cobra.Command { txCmd := &cobra.Command{ diff --git a/x/ibc-transfer/client/cli/query.go b/x/ibc-transfer/client/cli/query.go new file mode 100644 index 000000000000..c794367901e3 --- /dev/null +++ b/x/ibc-transfer/client/cli/query.go @@ -0,0 +1,87 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" +) + +// GetCmdQueryDenomTrace defines the command to query a a denomination trace from a given hash. +func GetCmdQueryDenomTrace() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom-trace [hash]", + Short: "Query the denom trace info from a given trace hash", + Long: "Query the denom trace info from a given trace hash", + Example: fmt.Sprintf("%s query ibc-transfer denom-trace [hash]", version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + clientCtx, err := client.ReadQueryCommandFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryDenomTraceRequest{ + Hash: args[0], + } + + res, err := queryClient.DenomTrace(context.Background(), req) + if err != nil { + return err + } + + return clientCtx.PrintOutput(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryDenomTraces defines the command to query all the denomination trace infos +// that this chain mantains. +func GetCmdQueryDenomTraces() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom-traces", + Short: "Query the trace info for all token denominations", + Long: "Query the trace info for all token denominations", + Example: fmt.Sprintf("%s query ibc-transfer denom-traces", version.AppName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + clientCtx, err := client.ReadQueryCommandFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryDenomTracesRequest{ + Pagination: pageReq, + } + + res, err := queryClient.DenomTraces(context.Background(), req) + if err != nil { + return err + } + + return clientCtx.PrintOutput(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "denominations trace") + + return cmd +} diff --git a/x/ibc-transfer/client/cli/tx.go b/x/ibc-transfer/client/cli/tx.go index 47b031703ff8..01b17527c852 100644 --- a/x/ibc-transfer/client/cli/tx.go +++ b/x/ibc-transfer/client/cli/tx.go @@ -49,6 +49,11 @@ to the counterparty channel. Any timeout set to 0 is disabled.`), return err } + if !strings.HasPrefix(coin.Denom, "ibc/") { + denomTrace := types.ParseDenomTrace(coin.Denom) + coin.Denom = denomTrace.IBCDenom() + } + timeoutHeight, err := cmd.Flags().GetUint64(flagPacketTimeoutHeight) if err != nil { return err diff --git a/x/ibc-transfer/client/rest/rest.go b/x/ibc-transfer/client/rest/rest.go index 833a6c739a84..a2fb586f8b93 100644 --- a/x/ibc-transfer/client/rest/rest.go +++ b/x/ibc-transfer/client/rest/rest.go @@ -21,7 +21,7 @@ func RegisterRoutes(clientCtx client.Context, r *mux.Router) { // TransferTxReq defines the properties of a transfer tx request's body. type TransferTxReq struct { BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - Amount sdk.Coin `json:"amount" yaml:"amount"` + Token sdk.Coin `json:"token" yaml:"token"` Receiver string `json:"receiver" yaml:"receiver"` TimeoutHeight uint64 `json:"timeout_height" yaml:"timeout_height"` TimeoutTimestamp uint64 `json:"timeout_timestamp" yaml:"timeout_timestamp"` diff --git a/x/ibc-transfer/client/rest/tx.go b/x/ibc-transfer/client/rest/tx.go index 2a64acbccff4..0d7cdd549a91 100644 --- a/x/ibc-transfer/client/rest/tx.go +++ b/x/ibc-transfer/client/rest/tx.go @@ -56,7 +56,7 @@ func transferHandlerFn(clientCtx client.Context) http.HandlerFunc { msg := types.NewMsgTransfer( portID, channelID, - req.Amount, + req.Token, fromAddr, req.Receiver, req.TimeoutHeight, diff --git a/x/ibc-transfer/genesis.go b/x/ibc-transfer/genesis.go deleted file mode 100644 index cb26107a0cbb..000000000000 --- a/x/ibc-transfer/genesis.go +++ /dev/null @@ -1,38 +0,0 @@ -package transfer - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/ibc-transfer/keeper" - "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" -) - -// InitGenesis binds to portid from genesis state -func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { - keeper.SetPort(ctx, state.PortId) - - // Only try to bind to port if it is not already bound, since we may already own - // port capability from capability InitGenesis - if !keeper.IsBound(ctx, state.PortId) { - // transfer module binds to the transfer port on InitChain - // and claims the returned capability - err := keeper.BindPort(ctx, state.PortId) - if err != nil { - panic(fmt.Sprintf("could not claim port capability: %v", err)) - } - } - - // check if the module account exists - moduleAcc := keeper.GetTransferAccount(ctx) - if moduleAcc == nil { - panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) - } -} - -// ExportGenesis exports transfer module's portID into its geneis state -func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { - return &types.GenesisState{ - PortId: keeper.GetPort(ctx), - } -} diff --git a/x/ibc-transfer/handler.go b/x/ibc-transfer/handler.go index dd8cabb54e27..9c4a6807aba7 100644 --- a/x/ibc-transfer/handler.go +++ b/x/ibc-transfer/handler.go @@ -29,7 +29,7 @@ func handleMsgTransfer(ctx sdk.Context, k keeper.Keeper, msg *types.MsgTransfer) return nil, err } - k.Logger(ctx).Info("IBC transfer: %s from %s to %s", msg.Token, msg.Sender, msg.Receiver) + k.Logger(ctx).Info("IBC fungible token transfer", "token", msg.Token, "sender", msg.Sender, "receiver", msg.Receiver) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( diff --git a/x/ibc-transfer/handler_test.go b/x/ibc-transfer/handler_test.go index 6545a5884f01..344080cb1725 100644 --- a/x/ibc-transfer/handler_test.go +++ b/x/ibc-transfer/handler_test.go @@ -48,9 +48,9 @@ func (suite *HandlerTestSuite) TestHandleMsgTransfer() { err = suite.coordinator.RelayPacket(suite.chainA, suite.chainB, clientA, clientB, packet, ack.GetBytes()) suite.Require().NoError(err) // relay committed - // check that voucher exists on chain - voucherDenom := types.GetPrefixedDenom(packet.GetDestPort(), packet.GetDestChannel(), sdk.DefaultBondDenom) - balance := suite.chainB.App.BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), voucherDenom) + // check that voucher exists on chain B + voucherDenomTrace := types.ParseDenomTrace(types.GetPrefixedDenom(packet.GetDestPort(), packet.GetDestChannel(), sdk.DefaultBondDenom)) + balance := suite.chainB.App.BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), voucherDenomTrace.IBCDenom()) coinToSendBackToA := types.GetTransferCoin(channelB.PortID, channelB.ID, sdk.DefaultBondDenom, 100) suite.Require().Equal(coinToSendBackToA, balance) @@ -62,7 +62,9 @@ func (suite *HandlerTestSuite) TestHandleMsgTransfer() { suite.Require().NoError(err) // message committed // relay send - fungibleTokenPacket = types.NewFungibleTokenPacketData(coinToSendBackToA.Denom, coinToSendBackToA.Amount.Uint64(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) + // NOTE: fungible token is prefixed with the full trace in order to verify the packet commitment + voucherDenom := voucherDenomTrace.GetPrefix() + voucherDenomTrace.BaseDenom + fungibleTokenPacket = types.NewFungibleTokenPacketData(voucherDenom, coinToSendBackToA.Amount.Uint64(), suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) packet = channeltypes.NewPacket(fungibleTokenPacket.GetBytes(), 1, channelB.PortID, channelB.ID, channelA.PortID, channelA.ID, 110, 0) err = suite.coordinator.RelayPacket(suite.chainB, suite.chainA, clientB, clientA, packet, ack.GetBytes()) suite.Require().NoError(err) // relay committed @@ -76,6 +78,10 @@ func (suite *HandlerTestSuite) TestHandleMsgTransfer() { escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) balance = suite.chainA.App.BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) suite.Require().Equal(sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt()), balance) + + // check that balance on chain B is empty + balance = suite.chainB.App.BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), voucherDenomTrace.IBCDenom()) + suite.Require().Zero(balance.Amount.Int64()) } func TestHandlerTestSuite(t *testing.T) { diff --git a/x/ibc-transfer/keeper/genesis.go b/x/ibc-transfer/keeper/genesis.go new file mode 100644 index 000000000000..223d473be28a --- /dev/null +++ b/x/ibc-transfer/keeper/genesis.go @@ -0,0 +1,42 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" +) + +// InitGenesis initializes the ibc-transfer state and binds to PortID. +func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + for _, trace := range state.DenomTraces { + k.SetDenomTrace(ctx, trace) + } + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // check if the module account exists + moduleAcc := k.GetTransferAccount(ctx) + if moduleAcc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) + } +} + +// ExportGenesis exports ibc-transfer module's portID and denom trace info into its genesis state. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return &types.GenesisState{ + PortId: k.GetPort(ctx), + DenomTraces: k.GetAllDenomTraces(ctx), + } +} diff --git a/x/ibc-transfer/keeper/genesis_test.go b/x/ibc-transfer/keeper/genesis_test.go new file mode 100644 index 000000000000..3c1d192035aa --- /dev/null +++ b/x/ibc-transfer/keeper/genesis_test.go @@ -0,0 +1,39 @@ +package keeper_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" +) + +func (suite *KeeperTestSuite) TestGenesis() { + var ( + path string + traces types.Traces + ) + + for i := 0; i < 5; i++ { + prefix := fmt.Sprintf("transfer/channelToChain%d", i) + if i == 0 { + path = prefix + } else { + path = prefix + "/" + path + } + + denomTrace := types.DenomTrace{ + BaseDenom: "uatom", + Path: path, + } + traces = append(types.Traces{denomTrace}, traces...) + suite.chainA.App.TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), denomTrace) + } + + genesis := suite.chainA.App.TransferKeeper.ExportGenesis(suite.chainA.GetContext()) + + suite.Require().Equal(types.PortID, genesis.PortId) + suite.Require().Equal(traces.Sort(), genesis.DenomTraces) + + suite.Require().NotPanics(func() { + suite.chainA.App.TransferKeeper.InitGenesis(suite.chainA.GetContext(), *genesis) + }) +} diff --git a/x/ibc-transfer/keeper/grpc_query.go b/x/ibc-transfer/keeper/grpc_query.go new file mode 100644 index 000000000000..8d11e7cdc697 --- /dev/null +++ b/x/ibc-transfer/keeper/grpc_query.go @@ -0,0 +1,73 @@ +package keeper + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" +) + +var _ types.QueryServer = Keeper{} + +// DenomTrace implements the Query/DenomTrace gRPC method +func (q Keeper) DenomTrace(c context.Context, req *types.QueryDenomTraceRequest) (*types.QueryDenomTraceResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + hash, err := types.ParseHexHash(req.Hash) + if err != nil { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid denom trace hash %s, %s", req.Hash, err)) + } + + ctx := sdk.UnwrapSDKContext(c) + denomTrace, found := q.GetDenomTrace(ctx, hash) + if !found { + return nil, status.Error( + codes.NotFound, + sdkerrors.Wrap(types.ErrTraceNotFound, req.Hash).Error(), + ) + } + + return &types.QueryDenomTraceResponse{ + DenomTrace: &denomTrace, + }, nil +} + +// DenomTraces implements the Query/DenomTraces gRPC method +func (q Keeper) DenomTraces(c context.Context, req *types.QueryDenomTracesRequest) (*types.QueryDenomTracesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(c) + + traces := types.Traces{} + store := prefix.NewStore(ctx.KVStore(q.storeKey), types.DenomTraceKey) + + pageRes, err := query.Paginate(store, req.Pagination, func(_, value []byte) error { + var result types.DenomTrace + if err := q.cdc.UnmarshalBinaryBare(value, &result); err != nil { + return err + } + + traces = append(traces, result) + return nil + }) + + if err != nil { + return nil, err + } + + return &types.QueryDenomTracesResponse{ + DenomTraces: traces, + Pagination: pageRes, + }, nil +} diff --git a/x/ibc-transfer/keeper/grpc_query_test.go b/x/ibc-transfer/keeper/grpc_query_test.go new file mode 100644 index 000000000000..c16ecc379704 --- /dev/null +++ b/x/ibc-transfer/keeper/grpc_query_test.go @@ -0,0 +1,135 @@ +package keeper_test + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" +) + +func (suite *KeeperTestSuite) TestQueryConnection() { + var ( + req *types.QueryDenomTraceRequest + expTrace types.DenomTrace + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "invalid hex hash", + func() { + req = &types.QueryDenomTraceRequest{ + Hash: "!@#!@#!", + } + }, + false, + }, + { + "not found denom trace", + func() { + expTrace.Path = "transfer/channelToA/transfer/channelToB" + expTrace.BaseDenom = "uatom" + req = &types.QueryDenomTraceRequest{ + Hash: expTrace.Hash().String(), + } + }, + false, + }, + { + "success", + func() { + expTrace.Path = "transfer/channelToA/transfer/channelToB" + expTrace.BaseDenom = "uatom" + suite.chainA.App.TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), expTrace) + + req = &types.QueryDenomTraceRequest{ + Hash: expTrace.Hash().String(), + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + + res, err := suite.queryClient.DenomTrace(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expTrace, res.DenomTrace) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnections() { + var ( + req *types.QueryDenomTracesRequest + expTraces = types.Traces(nil) + ) + + testCases := []struct { + msg string + malleate func() + expPass bool + }{ + { + "empty pagination", + func() { + req = &types.QueryDenomTracesRequest{} + }, + true, + }, + { + "success", + func() { + expTraces = append(expTraces, types.DenomTrace{Path: "", BaseDenom: "uatom"}) + expTraces = append(expTraces, types.DenomTrace{Path: "transfer/channelToB", BaseDenom: "uatom"}) + expTraces = append(expTraces, types.DenomTrace{Path: "transfer/channelToA/transfer/channelToB", BaseDenom: "uatom"}) + + for _, trace := range expTraces { + suite.chainA.App.TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), trace) + } + + req = &types.QueryDenomTracesRequest{ + Pagination: &query.PageRequest{ + Limit: 5, + CountTotal: false, + }, + } + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := sdk.WrapSDKContext(suite.chainA.GetContext()) + + res, err := suite.queryClient.DenomTraces(ctx, req) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expTraces.Sort(), res.DenomTraces) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/x/ibc-transfer/keeper/keeper.go b/x/ibc-transfer/keeper/keeper.go index c9ede680091d..d39160c19c40 100644 --- a/x/ibc-transfer/keeper/keeper.go +++ b/x/ibc-transfer/keeper/keeper.go @@ -3,9 +3,11 @@ package keeper import ( "fmt" + tmbytes "github.com/tendermint/tendermint/libs/bytes" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -109,6 +111,61 @@ func (k Keeper) SetPort(ctx sdk.Context, portID string) { store.Set(types.PortKey, []byte(portID)) } +// GetDenomTrace retreives the full identifiers trace and base denomination from the store. +func (k Keeper) GetDenomTrace(ctx sdk.Context, denomTraceHash tmbytes.HexBytes) (types.DenomTrace, bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomTraceKey) + bz := store.Get(denomTraceHash) + if bz == nil { + return types.DenomTrace{}, false + } + + var denomTrace types.DenomTrace + k.cdc.MustUnmarshalBinaryBare(bz, &denomTrace) + return denomTrace, true +} + +// HasDenomTrace checks if a the key with the given denomination trace hash exists on the store. +func (k Keeper) HasDenomTrace(ctx sdk.Context, denomTraceHash tmbytes.HexBytes) bool { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomTraceKey) + return store.Has(denomTraceHash) +} + +// SetDenomTrace sets a new {trace hash -> denom trace} pair to the store. +func (k Keeper) SetDenomTrace(ctx sdk.Context, denomTrace types.DenomTrace) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.DenomTraceKey) + bz := k.cdc.MustMarshalBinaryBare(&denomTrace) + store.Set(denomTrace.Hash(), bz) +} + +// GetAllDenomTraces returns the trace information for all the denominations. +func (k Keeper) GetAllDenomTraces(ctx sdk.Context) types.Traces { + traces := types.Traces{} + k.IterateDenomTraces(ctx, func(denomTrace types.DenomTrace) bool { + traces = append(traces, denomTrace) + return false + }) + + return traces.Sort() +} + +// IterateDenomTraces iterates over the denomination traces in the store +// and performs a callback function. +func (k Keeper) IterateDenomTraces(ctx sdk.Context, cb func(denomTrace types.DenomTrace) bool) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.DenomTraceKey) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + + var denomTrace types.DenomTrace + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &denomTrace) + + if cb(denomTrace) { + break + } + } +} + // ClaimCapability allows the transfer module that can claim a capability that IBC module // passes to it func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { diff --git a/x/ibc-transfer/keeper/keeper_test.go b/x/ibc-transfer/keeper/keeper_test.go index b8f90634542c..b589d51c03ca 100644 --- a/x/ibc-transfer/keeper/keeper_test.go +++ b/x/ibc-transfer/keeper/keeper_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/tendermint/tendermint/crypto" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" @@ -19,12 +20,18 @@ type KeeperTestSuite struct { // testing chains used for convenience and readability chainA *ibctesting.TestChain chainB *ibctesting.TestChain + + queryClient types.QueryClient } func (suite *KeeperTestSuite) SetupTest() { suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), suite.chainA.App.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, suite.chainA.App.TransferKeeper) + suite.queryClient = types.NewQueryClient(queryHelper) } func (suite *KeeperTestSuite) TestGetTransferAccount() { diff --git a/x/ibc-transfer/keeper/relay.go b/x/ibc-transfer/keeper/relay.go index 1b7f6dd33201..4a9388e5def2 100644 --- a/x/ibc-transfer/keeper/relay.go +++ b/x/ibc-transfer/keeper/relay.go @@ -1,6 +1,8 @@ package keeper import ( + "strings" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" @@ -40,7 +42,6 @@ import ( // 4. A -> C : sender chain is sink zone. Denom upon receiving: 'C/B/denom' // 5. C -> B : sender chain is sink zone. Denom upon receiving: 'B/denom' // 6. B -> A : sender chain is sink zone. Denom upon receiving: 'denom' - func (k Keeper) SendTransfer( ctx sdk.Context, sourcePort, @@ -70,17 +71,30 @@ func (k Keeper) SendTransfer( // begin createOutgoingPacket logic // See spec for this logic: https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer#packet-relay - channelCap, ok := k.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(sourcePort, sourceChannel)) if !ok { return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") } + // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic + fullDenomPath := token.Denom + + 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) + if err != nil { + return err + } + } + // 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. - if types.SenderChainIsSource(sourcePort, sourceChannel, token.Denom) { + if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { // create the escrow address for the tokens escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) @@ -110,7 +124,7 @@ func (k Keeper) SendTransfer( } packetData := types.NewFungibleTokenPacketData( - token.Denom, token.Amount.Uint64(), sender.String(), receiver, + fullDenomPath, token.Amount.Uint64(), sender.String(), receiver, ) packet := channeltypes.NewPacket( @@ -133,7 +147,10 @@ func (k Keeper) SendTransfer( // back tokens this chain originally transferred to it, the tokens are // unescrowed and sent to the receiving address. func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data types.FungibleTokenPacketData) error { - // NOTE: packet data type already checked in handler.go + // validate packet data upon receiving + if err := data.ValidateBasic(); err != nil { + return err + } // decode the receiver address receiver, err := sdk.AccAddressFromBech32(data.Receiver) @@ -168,7 +185,25 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) // NOTE: sourcePrefix contains the trailing "/" prefixedDenom := sourcePrefix + data.Denom - voucher := sdk.NewCoin(prefixedDenom, sdk.NewIntFromUint64(data.Amount)) + + // construct the denomination trace from the full raw denomination + denomTrace := types.ParseDenomTrace(prefixedDenom) + + traceHash := denomTrace.Hash() + if !k.HasDenomTrace(ctx, traceHash) { + k.SetDenomTrace(ctx, denomTrace) + } + + voucherDenom := denomTrace.IBCDenom() + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeDenomTrace, + sdk.NewAttribute(types.AttributeKeyTraceHash, traceHash.String()), + sdk.NewAttribute(types.AttributeKeyDenom, voucherDenom), + ), + ) + + voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount)) // mint new tokens if the source of the transfer is the same chain if err := k.bankKeeper.MintCoins( @@ -230,3 +265,21 @@ func (k Keeper) refundPacketToken(ctx sdk.Context, packet channeltypes.Packet, d return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, sdk.NewCoins(token)) } + +// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash +// component. +func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) { + hexHash := denom[4:] + hash, err := types.ParseHexHash(hexHash) + if err != nil { + return "", sdkerrors.Wrap(types.ErrInvalidDenomForTransfer, err.Error()) + } + + denomTrace, found := k.GetDenomTrace(ctx, hash) + if !found { + return "", sdkerrors.Wrap(types.ErrTraceNotFound, hexHash) + } + + fullDenomPath := denomTrace.GetFullDenomPath() + return fullDenomPath, nil +} diff --git a/x/ibc-transfer/module.go b/x/ibc-transfer/module.go index 2119c2dbc58f..5e014a41fcb1 100644 --- a/x/ibc-transfer/module.go +++ b/x/ibc-transfer/module.go @@ -75,7 +75,7 @@ func (AppModuleBasic) GetTxCmd() *cobra.Command { // GetQueryCmd implements AppModuleBasic interface func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return nil + return cli.GetQueryCmd() } // RegisterInterfaces registers module concrete types into protobuf Any. @@ -118,21 +118,23 @@ func (am AppModule) LegacyQuerierHandler(codec.JSONMarshaler) sdk.Querier { // RegisterQueryService registers a GRPC query service to respond to the // module-specific GRPC queries. -func (am AppModule) RegisterQueryService(grpc.Server) {} +func (am AppModule) RegisterQueryService(server grpc.Server) { + types.RegisterQueryServer(server, am.keeper) +} -// InitGenesis performs genesis initialization for the ibc transfer module. It returns +// InitGenesis performs genesis initialization for the ibc-transfer module. It returns // no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, data json.RawMessage) []abci.ValidatorUpdate { var genesisState types.GenesisState cdc.MustUnmarshalJSON(data, &genesisState) - - // TODO: check if the IBC transfer module account is set - InitGenesis(ctx, am.keeper, genesisState) + am.keeper.InitGenesis(ctx, genesisState) return []abci.ValidatorUpdate{} } +// ExportGenesis returns the exported genesis state as raw bytes for the ibc-transfer +// module. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { - gs := ExportGenesis(ctx, am.keeper) + gs := am.keeper.ExportGenesis(ctx) return cdc.MustMarshalJSON(gs) } @@ -288,10 +290,12 @@ func (am AppModule) OnRecvPacket( if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { return nil, nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error()) } + acknowledgement := types.FungibleTokenPacketAcknowledgement{ Success: true, Error: "", } + if err := am.keeper.OnRecvPacket(ctx, packet, data); err != nil { acknowledgement = types.FungibleTokenPacketAcknowledgement{ Success: false, diff --git a/x/ibc-transfer/spec/04_messages.md b/x/ibc-transfer/spec/04_messages.md index 1f81a0001b29..54458cc1324e 100644 --- a/x/ibc-transfer/spec/04_messages.md +++ b/x/ibc-transfer/spec/04_messages.md @@ -4,7 +4,7 @@ order: 4 # Messages -### MsgTransfer +## MsgTransfer A fungible token cross chain transfer is achieved by using the `MsgTransfer`: @@ -29,6 +29,7 @@ This message is expected to fail if: - `Sender` is empty - `Receiver` is empty - `TimeoutHeight` and `TimeoutTimestamp` are both zero +- `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](./../../../docs/architecture/adr-001-coin-source-tracing.md). 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 @@ -37,4 +38,3 @@ by the counterparty Channel End connected to the Channel End with the identifier 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. - diff --git a/x/ibc-transfer/spec/05_events.md b/x/ibc-transfer/spec/05_events.md index 15a694e5a217..626a98a4f1a8 100644 --- a/x/ibc-transfer/spec/05_events.md +++ b/x/ibc-transfer/spec/05_events.md @@ -21,6 +21,7 @@ order: 5 | fungible_token_packet | receiver | {receiver} | | fungible_token_packet | denom | {denom} | | fungible_token_packet | amount | {amount} | +| denomination_trace | trace_hash | {hex_hash} | ## OnAcknowledgePacket callback @@ -40,8 +41,3 @@ order: 5 | fungible_token_packet | refund_receiver | {receiver} | | fungible_token_packet | denom | {denom} | | fungible_token_packet | amount | {amount} | - - - - - diff --git a/x/ibc-transfer/types/coin.go b/x/ibc-transfer/types/coin.go index 65b4e91d1998..e2fe7dc5ade2 100644 --- a/x/ibc-transfer/types/coin.go +++ b/x/ibc-transfer/types/coin.go @@ -48,5 +48,6 @@ func GetPrefixedCoin(portID, channelID string, coin sdk.Coin) sdk.Coin { // GetTransferCoin creates a transfer coin with the port ID and channel ID // prefixed to the base denom. func GetTransferCoin(portID, channelID, baseDenom string, amount int64) sdk.Coin { - return sdk.NewInt64Coin(GetPrefixedDenom(portID, channelID, baseDenom), amount) + denomTrace := ParseDenomTrace(GetPrefixedDenom(portID, channelID, baseDenom)) + return sdk.NewInt64Coin(denomTrace.IBCDenom(), amount) } diff --git a/x/ibc-transfer/types/errors.go b/x/ibc-transfer/types/errors.go index 6c1da2fb4675..f7cc697d1ce3 100644 --- a/x/ibc-transfer/types/errors.go +++ b/x/ibc-transfer/types/errors.go @@ -10,4 +10,5 @@ var ( ErrInvalidDenomForTransfer = sdkerrors.Register(ModuleName, 3, "invalid denomination for cross-chain transfer") ErrInvalidVersion = sdkerrors.Register(ModuleName, 4, "invalid ICS20 version") ErrInvalidAmount = sdkerrors.Register(ModuleName, 5, "invalid token amount") + ErrTraceNotFound = sdkerrors.Register(ModuleName, 6, "denomination trace not found") ) diff --git a/x/ibc-transfer/types/events.go b/x/ibc-transfer/types/events.go index 900460a50591..68832f937ded 100644 --- a/x/ibc-transfer/types/events.go +++ b/x/ibc-transfer/types/events.go @@ -6,6 +6,7 @@ const ( EventTypePacket = "fungible_token_packet" EventTypeTransfer = "ibc_transfer" EventTypeChannelClose = "channel_closed" + EventTypeDenomTrace = "denomination_trace" AttributeKeyReceiver = "receiver" AttributeKeyDenom = "denom" @@ -15,4 +16,5 @@ const ( AttributeKeyRefundAmount = "refund_amount" AttributeKeyAckSuccess = "success" AttributeKeyAckError = "error" + AttributeKeyTraceHash = "trace_hash" ) diff --git a/x/ibc-transfer/types/genesis.go b/x/ibc-transfer/types/genesis.go index 41aaf27a601d..5c0845ba9280 100644 --- a/x/ibc-transfer/types/genesis.go +++ b/x/ibc-transfer/types/genesis.go @@ -4,15 +4,27 @@ import ( host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) +// NewGenesisState creates a new ibc-transfer GenesisState instance. +func NewGenesisState(portID string, denomTraces Traces) *GenesisState { + return &GenesisState{ + PortId: portID, + DenomTraces: denomTraces, + } +} + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. func DefaultGenesisState() *GenesisState { return &GenesisState{ - PortId: PortID, + PortId: PortID, + DenomTraces: Traces{}, } } // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { - return host.PortIdentifierValidator(gs.PortId) + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + return gs.DenomTraces.Validate() } diff --git a/x/ibc-transfer/types/genesis.pb.go b/x/ibc-transfer/types/genesis.pb.go index bd0f3ca4621e..9bdde33ad191 100644 --- a/x/ibc-transfer/types/genesis.pb.go +++ b/x/ibc-transfer/types/genesis.pb.go @@ -23,10 +23,10 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// GenesisState is currently only used to ensure that the InitGenesis gets run -// by the module manager +// GenesisState defines the ibc-transfer genesis state type GenesisState struct { - PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty" yaml:"port_id"` + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty" yaml:"port_id"` + DenomTraces Traces `protobuf:"bytes,2,rep,name=denom_traces,json=denomTraces,proto3,castrepeated=Traces" json:"denom_traces" yaml:"denom_traces"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -69,6 +69,13 @@ func (m *GenesisState) GetPortId() string { return "" } +func (m *GenesisState) GetDenomTraces() Traces { + if m != nil { + return m.DenomTraces + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "ibc.transfer.GenesisState") } @@ -76,20 +83,24 @@ func init() { func init() { proto.RegisterFile("ibc/transfer/genesis.proto", fileDescriptor_c13b8463155e05c2) } var fileDescriptor_c13b8463155e05c2 = []byte{ - // 194 bytes of a gzipped FileDescriptorProto + // 265 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x4c, 0x4a, 0xd6, 0x2f, 0x29, 0x4a, 0xcc, 0x2b, 0x4e, 0x4b, 0x2d, 0xd2, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xc9, 0x4c, 0x4a, 0xd6, 0x83, 0xc9, 0x49, 0x89, - 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x25, 0xf4, 0x41, 0x2c, 0x88, 0x1a, 0x25, 0x6b, 0x2e, 0x1e, 0x77, - 0x88, 0xa6, 0xe0, 0x92, 0xc4, 0x92, 0x54, 0x21, 0x6d, 0x2e, 0xf6, 0x82, 0xfc, 0xa2, 0x92, 0xf8, - 0xcc, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0xa1, 0x4f, 0xf7, 0xe4, 0xf9, 0x2a, 0x13, - 0x73, 0x73, 0xac, 0x94, 0xa0, 0x12, 0x4a, 0x41, 0x6c, 0x20, 0x96, 0x67, 0x8a, 0x93, 0xf7, 0x89, - 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, - 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x19, 0xa6, 0x67, 0x96, 0x64, 0x94, 0x26, - 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x27, 0xe7, 0x17, 0xe7, 0xe6, 0x17, 0x43, 0x29, 0xdd, 0xe2, 0x94, - 0x6c, 0xfd, 0x0a, 0xfd, 0xcc, 0xa4, 0x64, 0x5d, 0xb8, 0xab, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, - 0xd8, 0xc0, 0x0e, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x68, 0xf3, 0x81, 0x61, 0xd2, 0x00, - 0x00, 0x00, + 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x25, 0xf4, 0x41, 0x2c, 0x88, 0x1a, 0x29, 0x69, 0x14, 0xfd, 0x30, + 0x06, 0x44, 0x52, 0x69, 0x3e, 0x23, 0x17, 0x8f, 0x3b, 0xc4, 0xc8, 0xe0, 0x92, 0xc4, 0x92, 0x54, + 0x21, 0x6d, 0x2e, 0xf6, 0x82, 0xfc, 0xa2, 0x92, 0xf8, 0xcc, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, + 0x4e, 0x27, 0xa1, 0x4f, 0xf7, 0xe4, 0xf9, 0x2a, 0x13, 0x73, 0x73, 0xac, 0x94, 0xa0, 0x12, 0x4a, + 0x41, 0x6c, 0x20, 0x96, 0x67, 0x8a, 0x50, 0x12, 0x17, 0x4f, 0x4a, 0x6a, 0x5e, 0x7e, 0x6e, 0x7c, + 0x49, 0x51, 0x62, 0x72, 0x6a, 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0x84, 0x1e, 0xb2, + 0xab, 0xf4, 0x5c, 0x40, 0x2a, 0x42, 0x40, 0x0a, 0x9c, 0x54, 0x4f, 0xdc, 0x93, 0x67, 0xf8, 0x74, + 0x4f, 0x5e, 0x18, 0x62, 0x1e, 0xb2, 0x5e, 0xa5, 0x55, 0xf7, 0xe5, 0xd9, 0xc0, 0xaa, 0x8a, 0x83, + 0xb8, 0x53, 0xe0, 0x5a, 0x8a, 0x9d, 0xbc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, + 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, + 0x21, 0xca, 0x30, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x39, 0xbf, + 0x38, 0x37, 0xbf, 0x18, 0x4a, 0xe9, 0x16, 0xa7, 0x64, 0xeb, 0x57, 0xe8, 0x67, 0x26, 0x25, 0xeb, + 0x22, 0xfc, 0x5d, 0x59, 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0xf6, 0xb5, 0x31, 0x20, 0x00, 0x00, 0xff, + 0xff, 0xcf, 0x4f, 0x0f, 0xb0, 0x54, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -112,6 +123,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.DenomTraces) > 0 { + for iNdEx := len(m.DenomTraces) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DenomTraces[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } if len(m.PortId) > 0 { i -= len(m.PortId) copy(dAtA[i:], m.PortId) @@ -143,6 +168,12 @@ func (m *GenesisState) Size() (n int) { if l > 0 { n += 1 + l + sovGenesis(uint64(l)) } + if len(m.DenomTraces) > 0 { + for _, e := range m.DenomTraces { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -213,6 +244,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } m.PortId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomTraces", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DenomTraces = append(m.DenomTraces, DenomTrace{}) + if err := m.DenomTraces[len(m.DenomTraces)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/ibc-transfer/types/keys.go b/x/ibc-transfer/types/keys.go index ebeb2d9fa22a..b9d4b3085eac 100644 --- a/x/ibc-transfer/types/keys.go +++ b/x/ibc-transfer/types/keys.go @@ -27,8 +27,12 @@ const ( QuerierRoute = ModuleName ) -// PortKey defines the key to store the port ID in store -var PortKey = []byte{0x01} +var ( + // PortKey defines the key to store the port ID in store + PortKey = []byte{0x01} + // DenomTraceKey defines the key to store the denomination trace info in store + DenomTraceKey = []byte{0x02} +) // GetEscrowAddress returns the escrow address for the specified channel // diff --git a/x/ibc-transfer/types/msgs.go b/x/ibc-transfer/types/msgs.go index d12714c6d2ee..113990bf82cd 100644 --- a/x/ibc-transfer/types/msgs.go +++ b/x/ibc-transfer/types/msgs.go @@ -59,12 +59,7 @@ func (msg MsgTransfer) ValidateBasic() error { if msg.Receiver == "" { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing recipient address") } - - // sanity check that validate basic on fungible token packet passes - // NOTE: this should always pass since validation checks should be the - // same. Please open an issue if you encounter an error on this line. - packet := NewFungibleTokenPacketData(msg.Token.Denom, msg.Token.Amount.Uint64(), msg.Sender.String(), msg.Receiver) - return packet.ValidateBasic() + return ValidateIBCDenom(msg.Token.Denom) } // GetSignBytes implements sdk.Msg diff --git a/x/ibc-transfer/types/msgs_test.go b/x/ibc-transfer/types/msgs_test.go index 9c32b826d6bf..6895a874602e 100644 --- a/x/ibc-transfer/types/msgs_test.go +++ b/x/ibc-transfer/types/msgs_test.go @@ -28,8 +28,10 @@ var ( emptyAddr sdk.AccAddress coin = sdk.NewCoin("atom", sdk.NewInt(100)) + ibcCoin = sdk.NewCoin("ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", sdk.NewInt(100)) + invalidIBCCoin = sdk.NewCoin("notibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", sdk.NewInt(100)) invalidDenomCoin = sdk.Coin{Denom: "0atom", Amount: sdk.NewInt(100)} - negativeCoin = sdk.Coin{Denom: "atoms", Amount: sdk.NewInt(-100)} + zeroCoin = sdk.Coin{Denom: "atoms", Amount: sdk.NewInt(0)} ) // TestMsgTransferRoute tests Route for MsgTransfer @@ -53,7 +55,9 @@ func TestMsgTransferValidation(t *testing.T) { msg *MsgTransfer expPass bool }{ - {"valid msg", NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0), true}, + {"valid msg with base denom", NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, 10, 0), true}, + {"valid msg with trace hash", NewMsgTransfer(validPort, validChannel, ibcCoin, addr1, addr2, 10, 0), true}, + {"invalid ibc denom", NewMsgTransfer(validPort, validChannel, invalidIBCCoin, addr1, addr2, 10, 0), false}, {"too short port id", NewMsgTransfer(invalidShortPort, validChannel, coin, addr1, addr2, 10, 0), false}, {"too long port id", NewMsgTransfer(invalidLongPort, validChannel, coin, addr1, addr2, 10, 0), false}, {"port id contains non-alpha", NewMsgTransfer(invalidPort, validChannel, coin, addr1, addr2, 10, 0), false}, @@ -61,7 +65,7 @@ func TestMsgTransferValidation(t *testing.T) { {"too long channel id", NewMsgTransfer(validPort, invalidLongChannel, coin, addr1, addr2, 10, 0), false}, {"channel id contains non-alpha", NewMsgTransfer(validPort, invalidChannel, coin, addr1, addr2, 10, 0), false}, {"invalid denom", NewMsgTransfer(validPort, validChannel, invalidDenomCoin, addr1, addr2, 10, 0), false}, - {"negative coin", NewMsgTransfer(validPort, validChannel, negativeCoin, addr1, addr2, 10, 0), false}, + {"zero coin", NewMsgTransfer(validPort, validChannel, zeroCoin, addr1, addr2, 10, 0), false}, {"missing sender address", NewMsgTransfer(validPort, validChannel, coin, emptyAddr, addr2, 10, 0), false}, {"missing recipient address", NewMsgTransfer(validPort, validChannel, coin, addr1, "", 10, 0), false}, {"empty coin", NewMsgTransfer(validPort, validChannel, sdk.Coin{}, addr1, addr2, 10, 0), false}, diff --git a/x/ibc-transfer/types/packet.go b/x/ibc-transfer/types/packet.go index 4f5bd8ff7acd..63109efe8ff8 100644 --- a/x/ibc-transfer/types/packet.go +++ b/x/ibc-transfer/types/packet.go @@ -36,9 +36,6 @@ func NewFungibleTokenPacketData( // ValidateBasic is used for validating the token transfer func (ftpd FungibleTokenPacketData) ValidateBasic() error { - if strings.TrimSpace(ftpd.Denom) == "" { - return sdkerrors.Wrap(ErrInvalidDenomForTransfer, "denom cannot be empty") - } if ftpd.Amount == 0 { return sdkerrors.Wrap(ErrInvalidAmount, "amount cannot be 0") } @@ -48,7 +45,7 @@ func (ftpd FungibleTokenPacketData) ValidateBasic() error { if strings.TrimSpace(ftpd.Receiver) == "" { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "receiver address cannot be blank") } - return nil + return ValidatePrefixedDenom(ftpd.Denom) } // GetBytes is a helper for serialising diff --git a/x/ibc-transfer/types/query.pb.go b/x/ibc-transfer/types/query.pb.go new file mode 100644 index 000000000000..440f3280e1a0 --- /dev/null +++ b/x/ibc-transfer/types/query.pb.go @@ -0,0 +1,1081 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/transfer/query.proto + +package types + +import ( + context "context" + fmt "fmt" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC method +type QueryDenomTraceRequest struct { + // hash (in hex format) of the denomination trace information. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *QueryDenomTraceRequest) Reset() { *m = QueryDenomTraceRequest{} } +func (m *QueryDenomTraceRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomTraceRequest) ProtoMessage() {} +func (*QueryDenomTraceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_26b3e8b4e9dff1c1, []int{0} +} +func (m *QueryDenomTraceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomTraceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomTraceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomTraceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomTraceRequest.Merge(m, src) +} +func (m *QueryDenomTraceRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomTraceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomTraceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomTraceRequest proto.InternalMessageInfo + +func (m *QueryDenomTraceRequest) GetHash() string { + if m != nil { + return m.Hash + } + return "" +} + +// QueryDenomTraceResponse is the response type for the Query/DenomTrace RPC method. +type QueryDenomTraceResponse struct { + // denom_trace returns the requested denomination trace information. + DenomTrace *DenomTrace `protobuf:"bytes,1,opt,name=denom_trace,json=denomTrace,proto3" json:"denom_trace,omitempty"` +} + +func (m *QueryDenomTraceResponse) Reset() { *m = QueryDenomTraceResponse{} } +func (m *QueryDenomTraceResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomTraceResponse) ProtoMessage() {} +func (*QueryDenomTraceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_26b3e8b4e9dff1c1, []int{1} +} +func (m *QueryDenomTraceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomTraceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomTraceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomTraceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomTraceResponse.Merge(m, src) +} +func (m *QueryDenomTraceResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomTraceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomTraceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomTraceResponse proto.InternalMessageInfo + +func (m *QueryDenomTraceResponse) GetDenomTrace() *DenomTrace { + if m != nil { + return m.DenomTrace + } + return nil +} + +// QueryConnectionsRequest is the request type for the Query/DenomTraces RPC method +type QueryDenomTracesRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomTracesRequest) Reset() { *m = QueryDenomTracesRequest{} } +func (m *QueryDenomTracesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomTracesRequest) ProtoMessage() {} +func (*QueryDenomTracesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_26b3e8b4e9dff1c1, []int{2} +} +func (m *QueryDenomTracesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomTracesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomTracesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomTracesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomTracesRequest.Merge(m, src) +} +func (m *QueryDenomTracesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomTracesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomTracesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomTracesRequest proto.InternalMessageInfo + +func (m *QueryDenomTracesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConnectionsResponse is the response type for the Query/DenomTraces RPC method. +type QueryDenomTracesResponse struct { + // denom_traces returns all denominations trace information. + DenomTraces Traces `protobuf:"bytes,1,rep,name=denom_traces,json=denomTraces,proto3,castrepeated=Traces" json:"denom_traces"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomTracesResponse) Reset() { *m = QueryDenomTracesResponse{} } +func (m *QueryDenomTracesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomTracesResponse) ProtoMessage() {} +func (*QueryDenomTracesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_26b3e8b4e9dff1c1, []int{3} +} +func (m *QueryDenomTracesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomTracesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomTracesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomTracesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomTracesResponse.Merge(m, src) +} +func (m *QueryDenomTracesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomTracesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomTracesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomTracesResponse proto.InternalMessageInfo + +func (m *QueryDenomTracesResponse) GetDenomTraces() Traces { + if m != nil { + return m.DenomTraces + } + return nil +} + +func (m *QueryDenomTracesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func init() { + proto.RegisterType((*QueryDenomTraceRequest)(nil), "ibc.transfer.QueryDenomTraceRequest") + proto.RegisterType((*QueryDenomTraceResponse)(nil), "ibc.transfer.QueryDenomTraceResponse") + proto.RegisterType((*QueryDenomTracesRequest)(nil), "ibc.transfer.QueryDenomTracesRequest") + proto.RegisterType((*QueryDenomTracesResponse)(nil), "ibc.transfer.QueryDenomTracesResponse") +} + +func init() { proto.RegisterFile("ibc/transfer/query.proto", fileDescriptor_26b3e8b4e9dff1c1) } + +var fileDescriptor_26b3e8b4e9dff1c1 = []byte{ + // 452 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xbf, 0x8f, 0xd3, 0x30, + 0x14, 0xc7, 0xe3, 0x02, 0x27, 0xe1, 0x9c, 0x18, 0x2c, 0x04, 0x51, 0x40, 0xb9, 0x53, 0x74, 0x1c, + 0x50, 0xa8, 0xad, 0x94, 0x89, 0xb5, 0x42, 0x30, 0x74, 0x81, 0xaa, 0x13, 0x4b, 0xe5, 0xa4, 0x26, + 0x8d, 0xa0, 0x71, 0x1a, 0xbb, 0x88, 0x0a, 0xb1, 0x30, 0x31, 0x30, 0x20, 0xf1, 0x17, 0xb0, 0x32, + 0xf1, 0x67, 0x74, 0xac, 0xc4, 0xc2, 0x04, 0xa8, 0xe5, 0x0f, 0x41, 0xb1, 0x9d, 0x26, 0x55, 0x4f, + 0xcd, 0x94, 0xa7, 0xbc, 0x5f, 0x9f, 0xf7, 0xf5, 0x7b, 0xd0, 0x49, 0xc2, 0x88, 0xc8, 0x9c, 0xa6, + 0xe2, 0x15, 0xcb, 0xc9, 0x6c, 0xce, 0xf2, 0x05, 0xce, 0x72, 0x2e, 0x39, 0x3a, 0x4e, 0xc2, 0x08, + 0x97, 0x1e, 0xf7, 0x7a, 0xcc, 0x63, 0xae, 0x1c, 0xa4, 0xb0, 0x74, 0x8c, 0xdb, 0x8e, 0xb8, 0x98, + 0x72, 0x41, 0x42, 0x2a, 0x98, 0x4e, 0x26, 0x6f, 0x83, 0x90, 0x49, 0x1a, 0x90, 0x8c, 0xc6, 0x49, + 0x4a, 0x65, 0xc2, 0x53, 0x13, 0x7b, 0x6b, 0xa7, 0x53, 0x69, 0x18, 0xe7, 0xed, 0x98, 0xf3, 0xf8, + 0x0d, 0x23, 0x34, 0x4b, 0x08, 0x4d, 0x53, 0x2e, 0x55, 0xa6, 0xd0, 0x5e, 0xff, 0x21, 0xbc, 0xf1, + 0xa2, 0x28, 0xfe, 0x84, 0xa5, 0x7c, 0x3a, 0xcc, 0x69, 0xc4, 0x06, 0x6c, 0x36, 0x67, 0x42, 0x22, + 0x04, 0x2f, 0x4f, 0xa8, 0x98, 0x38, 0xe0, 0x14, 0xdc, 0xbb, 0x3a, 0x50, 0xb6, 0x3f, 0x84, 0x37, + 0xf7, 0xa2, 0x45, 0xc6, 0x53, 0xc1, 0xd0, 0x63, 0x68, 0x8f, 0x8b, 0xbf, 0x23, 0x59, 0xfc, 0x56, + 0x59, 0x76, 0xd7, 0xc1, 0xf5, 0x49, 0x71, 0x2d, 0x0d, 0x8e, 0xb7, 0xb6, 0x4f, 0xf7, 0xaa, 0x8a, + 0x12, 0xe2, 0x29, 0x84, 0xd5, 0xb4, 0xa6, 0xe8, 0x39, 0xd6, 0xd2, 0xe0, 0x42, 0x1a, 0xac, 0x75, + 0x35, 0xd2, 0xe0, 0xe7, 0x34, 0x2e, 0x07, 0x18, 0xd4, 0x32, 0xfd, 0x1f, 0x00, 0x3a, 0xfb, 0x3d, + 0x0c, 0x7a, 0x1f, 0x1e, 0xd7, 0xd0, 0x85, 0x03, 0x4e, 0x2f, 0x1d, 0x62, 0xef, 0x5d, 0x5b, 0xfe, + 0x3e, 0xb1, 0xbe, 0xff, 0x39, 0x39, 0x32, 0x75, 0xec, 0x6a, 0x16, 0x81, 0x9e, 0xed, 0x10, 0xb7, + 0x14, 0xf1, 0xdd, 0x46, 0x62, 0x4d, 0x52, 0x47, 0xee, 0x7e, 0x6b, 0xc1, 0x2b, 0x0a, 0x19, 0x7d, + 0x06, 0x10, 0x56, 0xed, 0xd1, 0xd9, 0x2e, 0xd8, 0xc5, 0xcf, 0xe7, 0xde, 0x69, 0x88, 0xd2, 0x1d, + 0xfd, 0xe0, 0xe3, 0xcf, 0x7f, 0x5f, 0x5b, 0x0f, 0xd0, 0x7d, 0x92, 0x84, 0xd1, 0x68, 0xbb, 0x43, + 0xe5, 0xaa, 0xd5, 0x75, 0x21, 0xef, 0x8b, 0x1d, 0xf8, 0x80, 0x3e, 0x01, 0x68, 0xd7, 0x64, 0x44, + 0x87, 0x3b, 0x95, 0x4f, 0xe9, 0x9e, 0x37, 0x85, 0x19, 0xa2, 0xb6, 0x22, 0x3a, 0x43, 0x7e, 0x33, + 0x51, 0xaf, 0xbf, 0x5c, 0x7b, 0x60, 0xb5, 0xf6, 0xc0, 0xdf, 0xb5, 0x07, 0xbe, 0x6c, 0x3c, 0x6b, + 0xb5, 0xf1, 0xac, 0x5f, 0x1b, 0xcf, 0x7a, 0x19, 0xc4, 0x89, 0x9c, 0xcc, 0x43, 0x1c, 0xf1, 0x29, + 0x31, 0x97, 0xa4, 0x3f, 0x1d, 0x31, 0x7e, 0x4d, 0xde, 0x15, 0xb5, 0x3b, 0xd5, 0xc5, 0x2c, 0x32, + 0x26, 0xc2, 0x23, 0x75, 0x11, 0x8f, 0xfe, 0x07, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xa6, 0x29, 0xd0, + 0xb8, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // DenomTrace queries a denomination trace information. + DenomTrace(ctx context.Context, in *QueryDenomTraceRequest, opts ...grpc.CallOption) (*QueryDenomTraceResponse, error) + // DenomTraces queries all denomination traces. + DenomTraces(ctx context.Context, in *QueryDenomTracesRequest, opts ...grpc.CallOption) (*QueryDenomTracesResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) DenomTrace(ctx context.Context, in *QueryDenomTraceRequest, opts ...grpc.CallOption) (*QueryDenomTraceResponse, error) { + out := new(QueryDenomTraceResponse) + err := c.cc.Invoke(ctx, "/ibc.transfer.Query/DenomTrace", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) DenomTraces(ctx context.Context, in *QueryDenomTracesRequest, opts ...grpc.CallOption) (*QueryDenomTracesResponse, error) { + out := new(QueryDenomTracesResponse) + err := c.cc.Invoke(ctx, "/ibc.transfer.Query/DenomTraces", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // DenomTrace queries a denomination trace information. + DenomTrace(context.Context, *QueryDenomTraceRequest) (*QueryDenomTraceResponse, error) + // DenomTraces queries all denomination traces. + DenomTraces(context.Context, *QueryDenomTracesRequest) (*QueryDenomTracesResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) DenomTrace(ctx context.Context, req *QueryDenomTraceRequest) (*QueryDenomTraceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomTrace not implemented") +} +func (*UnimplementedQueryServer) DenomTraces(ctx context.Context, req *QueryDenomTracesRequest) (*QueryDenomTracesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomTraces not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_DenomTrace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomTraceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).DenomTrace(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.transfer.Query/DenomTrace", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).DenomTrace(ctx, req.(*QueryDenomTraceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_DenomTraces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomTracesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).DenomTraces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.transfer.Query/DenomTraces", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).DenomTraces(ctx, req.(*QueryDenomTracesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.transfer.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "DenomTrace", + Handler: _Query_DenomTrace_Handler, + }, + { + MethodName: "DenomTraces", + Handler: _Query_DenomTraces_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/transfer/query.proto", +} + +func (m *QueryDenomTraceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomTraceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomTraceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomTraceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomTraceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomTraceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DenomTrace != nil { + { + size, err := m.DenomTrace.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomTracesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomTracesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomTracesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomTracesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomTracesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomTracesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.DenomTraces) > 0 { + for iNdEx := len(m.DenomTraces) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.DenomTraces[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryDenomTraceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomTraceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.DenomTrace != nil { + l = m.DenomTrace.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomTracesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomTracesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.DenomTraces) > 0 { + for _, e := range m.DenomTraces { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryDenomTraceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomTraceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomTraceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomTraceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomTraceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomTraceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomTrace", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DenomTrace == nil { + m.DenomTrace = &DenomTrace{} + } + if err := m.DenomTrace.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomTracesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomTracesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomTracesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomTracesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomTracesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomTracesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DenomTraces", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DenomTraces = append(m.DenomTraces, DenomTrace{}) + if err := m.DenomTraces[len(m.DenomTraces)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/ibc-transfer/types/query.pb.gw.go b/x/ibc-transfer/types/query.pb.gw.go new file mode 100644 index 000000000000..397bb4791ca8 --- /dev/null +++ b/x/ibc-transfer/types/query.pb.gw.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/transfer/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +func request_Query_DenomTrace_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomTraceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.DenomTrace(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_DenomTrace_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomTraceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.DenomTrace(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_DenomTraces_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_DenomTraces_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomTracesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DenomTraces_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DenomTraces(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_DenomTraces_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomTracesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_DenomTraces_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DenomTraces(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_DenomTrace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_DenomTrace_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomTrace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomTraces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_DenomTraces_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomTraces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_DenomTrace_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_DenomTrace_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomTrace_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomTraces_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_DenomTraces_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomTraces_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_DenomTrace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"ibc_transfer", "v1beta1", "denom_traces", "hash"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_DenomTraces_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"ibc_transfer", "v1beta1", "denom_traces"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_Query_DenomTrace_0 = runtime.ForwardResponseMessage + + forward_Query_DenomTraces_0 = runtime.ForwardResponseMessage +) diff --git a/x/ibc-transfer/types/trace.go b/x/ibc-transfer/types/trace.go new file mode 100644 index 000000000000..dffe00501c87 --- /dev/null +++ b/x/ibc-transfer/types/trace.go @@ -0,0 +1,195 @@ +package types + +import ( + "encoding/hex" + "errors" + fmt "fmt" + "sort" + "strings" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" +) + +// ParseDenomTrace parses a string with the ibc prefix (denom trace) and the base denomination +// into a DenomTrace type. +// +// Examples: +// +// - "portidone/channelidone/uatom" => DenomTrace{Path: "portidone/channelidone", BaseDenom: "uatom"} +// - "uatom" => DenomTrace{Path: "", BaseDenom: "uatom"} +func ParseDenomTrace(rawDenom string) DenomTrace { + denomSplit := strings.Split(rawDenom, "/") + + if denomSplit[0] == rawDenom { + return DenomTrace{ + Path: "", + BaseDenom: rawDenom, + } + } + + return DenomTrace{ + Path: strings.Join(denomSplit[:len(denomSplit)-1], "/"), + BaseDenom: denomSplit[len(denomSplit)-1], + } +} + +// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula: +// +// hash = sha256(tracePath + "/" + baseDenom) +func (dt DenomTrace) Hash() tmbytes.HexBytes { + return tmhash.Sum([]byte(dt.GetFullDenomPath())) +} + +// GetPrefix returns the receiving denomination prefix composed by the trace info and a separator. +func (dt DenomTrace) GetPrefix() string { + return dt.Path + "/" +} + +// IBCDenom a coin denomination for an ICS20 fungible token in the format +// 'ibc/{hash(tracePath + baseDenom)}'. If the trace is empty, it will return the base denomination. +func (dt DenomTrace) IBCDenom() string { + if dt.Path != "" { + return fmt.Sprintf("ibc/%s", dt.Hash()) + } + return dt.BaseDenom +} + +// GetFullDenomPath returns the full denomination according to the ICS20 specification: +// tracePath + "/" + baseDenom +func (dt DenomTrace) GetFullDenomPath() string { + return dt.GetPrefix() + dt.BaseDenom +} + +func validateTraceIdentifiers(identifiers []string) error { + if len(identifiers) == 0 || len(identifiers)%2 != 0 { + return errors.New("trace info must come in pairs of port and channel identifiers '{portID}/{channelID}'") + } + + // validate correctness of port and channel identifiers + for i := 0; i < len(identifiers); i += 2 { + if err := host.PortIdentifierValidator(identifiers[i]); err != nil { + return sdkerrors.Wrapf(err, "invalid port ID at position %d", i) + } + if err := host.ChannelIdentifierValidator(identifiers[i+1]); err != nil { + return sdkerrors.Wrapf(err, "invalid channel ID at position %d", i) + } + } + return nil +} + +// Validate performs a basic validation of the DenomTrace fields. +func (dt DenomTrace) Validate() error { + // empty trace is accepted when token lives on the original chain + switch { + case dt.Path == "" && dt.BaseDenom != "": + return nil + case strings.TrimSpace(dt.BaseDenom) == "": + return fmt.Errorf("base denomination cannot be blank") + } + + // NOTE: no base denomination validation + + identifiers := strings.Split(dt.Path, "/") + return validateTraceIdentifiers(identifiers) +} + +// Traces defines a wrapper type for a slice of DenomTrace. +type Traces []DenomTrace + +// Validate performs a basic validation of each denomination trace info. +func (t Traces) Validate() error { + seenTraces := make(map[string]bool) + for i, trace := range t { + hash := trace.Hash().String() + if seenTraces[hash] { + return fmt.Errorf("duplicated denomination trace with hash %s", trace.Hash()) + } + + if err := trace.Validate(); err != nil { + return sdkerrors.Wrapf(err, "failed denom trace %d validation", i) + } + seenTraces[hash] = true + } + return nil +} + +var _ sort.Interface = Traces{} + +// Len implements sort.Interface for Traces +func (t Traces) Len() int { return len(t) } + +// Less implements sort.Interface for Traces +func (t Traces) Less(i, j int) bool { return t[i].GetFullDenomPath() < t[j].GetFullDenomPath() } + +// Swap implements sort.Interface for Traces +func (t Traces) Swap(i, j int) { t[i], t[j] = t[j], t[i] } + +// Sort is a helper function to sort the set of denomination traces in-place +func (t Traces) Sort() Traces { + sort.Sort(t) + return t +} + +// ValidatePrefixedDenom checks that the denomination for an IBC fungible token packet denom is correctly prefixed. +// The function will return no error if the given string follows one of the two formats: +// +// - Prefixed denomination: '{portIDN}/{channelIDN}/.../{portID0}/{channelID0}/baseDenom' +// - Unprefixed denomination: 'baseDenom' +func ValidatePrefixedDenom(denom string) error { + denomSplit := strings.Split(denom, "/") + if denomSplit[0] == denom && strings.TrimSpace(denom) != "" { + // NOTE: no base denomination validation + return nil + } + + if strings.TrimSpace(denomSplit[len(denomSplit)-1]) == "" { + return sdkerrors.Wrap(ErrInvalidDenomForTransfer, "base denomination cannot be blank") + } + + identifiers := denomSplit[:len(denomSplit)-1] + return validateTraceIdentifiers(identifiers) +} + +// ValidateIBCDenom validates that the given denomination is either: +// +// - A valid base denomination (eg: 'uatom') +// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-001-coin-source-tracing.md +func ValidateIBCDenom(denom string) error { + denomSplit := strings.SplitN(denom, "/", 2) + + switch { + case strings.TrimSpace(denom) == "", + len(denomSplit) == 1 && denomSplit[0] == "ibc", + len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""): + return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + + case denomSplit[0] == denom && strings.TrimSpace(denom) != "": + return sdk.ValidateDenom(denom) + } + + if _, err := ParseHexHash(denomSplit[1]); err != nil { + return sdkerrors.Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) + } + + return nil +} + +// ParseHexHash parses a hex hash in string format to bytes and validates its correctness. +func ParseHexHash(hexHash string) (tmbytes.HexBytes, error) { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil, err + } + + if err := tmtypes.ValidateHash(hash); err != nil { + return nil, err + } + + return hash, nil +} diff --git a/x/ibc-transfer/types/trace_test.go b/x/ibc-transfer/types/trace_test.go new file mode 100644 index 000000000000..f0868d5680e5 --- /dev/null +++ b/x/ibc-transfer/types/trace_test.go @@ -0,0 +1,150 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseDenomTrace(t *testing.T) { + testCases := []struct { + name string + denom string + expTrace DenomTrace + }{ + {"empty denom", "", DenomTrace{}}, + {"base denom", "uatom", DenomTrace{BaseDenom: "uatom"}}, + {"trace info", "transfer/channelToA/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}}, + {"incomplete path", "transfer/uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer"}}, + {"invalid path (1)", "transfer//uatom", DenomTrace{BaseDenom: "uatom", Path: "transfer/"}}, + {"invalid path (2)", "transfer/channelToA/uatom/", DenomTrace{BaseDenom: "", Path: "transfer/channelToA/uatom"}}, + } + + for _, tc := range testCases { + trace := ParseDenomTrace(tc.denom) + require.Equal(t, tc.expTrace, trace, tc.name) + } +} + +func TestDenomTrace_IBCDenom(t *testing.T) { + testCases := []struct { + name string + trace DenomTrace + expDenom string + }{ + {"base denom", DenomTrace{BaseDenom: "uatom"}, "uatom"}, + {"trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}, "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2"}, + } + + for _, tc := range testCases { + denom := tc.trace.IBCDenom() + require.Equal(t, tc.expDenom, denom, tc.name) + } +} + +func TestDenomTrace_Validate(t *testing.T) { + testCases := []struct { + name string + trace DenomTrace + expError bool + }{ + {"base denom only", DenomTrace{BaseDenom: "uatom"}, false}, + {"empty DenomTrace", DenomTrace{}, true}, + {"valid single trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA"}, false}, + {"valid multiple trace info", DenomTrace{BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, false}, + {"single trace identifier", DenomTrace{BaseDenom: "uatom", Path: "transfer"}, true}, + {"invalid port ID", DenomTrace{BaseDenom: "uatom", Path: "(transfer)/channelToA"}, true}, + {"invalid channel ID", DenomTrace{BaseDenom: "uatom", Path: "transfer/(channelToA)"}, true}, + {"empty base denom with trace", DenomTrace{BaseDenom: "", Path: "transfer/channelToA"}, true}, + } + + for _, tc := range testCases { + err := tc.trace.Validate() + if tc.expError { + require.Error(t, err, tc.name) + continue + } + require.NoError(t, err, tc.name) + } +} + +func TestTraces_Validate(t *testing.T) { + testCases := []struct { + name string + traces Traces + expError bool + }{ + {"empty Traces", Traces{}, false}, + {"valid multiple trace info", Traces{{BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}}, false}, + { + "valid multiple trace info", + Traces{ + {BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, + {BaseDenom: "uatom", Path: "transfer/channelToA/transfer/channelToB"}, + }, + true, + }, + {"empty base denom with trace", Traces{{BaseDenom: "", Path: "transfer/channelToA"}}, true}, + } + + for _, tc := range testCases { + err := tc.traces.Validate() + if tc.expError { + require.Error(t, err, tc.name) + continue + } + require.NoError(t, err, tc.name) + } +} + +func TestValidatePrefixedDenom(t *testing.T) { + testCases := []struct { + name string + denom string + expError bool + }{ + {"prefixed denom", "transfer/channelToA/uatom", false}, + {"base denom", "uatom", false}, + {"empty denom", "", true}, + {"empty prefix", "/uatom", true}, + {"empty identifiers", "//uatom", true}, + {"single trace identifier", "transfer/", true}, + {"invalid port ID", "(transfer)/channelToA/uatom", true}, + {"invalid channel ID", "transfer/(channelToA)/uatom", true}, + } + + for _, tc := range testCases { + err := ValidatePrefixedDenom(tc.denom) + if tc.expError { + require.Error(t, err, tc.name) + continue + } + require.NoError(t, err, tc.name) + } +} + +func TestValidateIBCDenom(t *testing.T) { + testCases := []struct { + name string + denom string + expError bool + }{ + {"denom with trace hash", "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", false}, + {"base denom", "uatom", false}, + {"empty denom", "", true}, + {"invalid prefixed denom", "transfer/channelToA/uatom", true}, + {"denom 'ibc'", "ibc", true}, + {"denom 'ibc/'", "ibc/", true}, + {"invald prefix", "notibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", true}, + {"invald hash", "ibc/!@#$!@#", true}, + } + + for _, tc := range testCases { + err := ValidateIBCDenom(tc.denom) + if tc.expError { + require.Error(t, err, tc.name) + continue + } + require.NoError(t, err, tc.name) + } +} diff --git a/x/ibc-transfer/types/transfer.pb.go b/x/ibc-transfer/types/transfer.pb.go index 9df6167135ec..f8c0f63b147e 100644 --- a/x/ibc-transfer/types/transfer.pb.go +++ b/x/ibc-transfer/types/transfer.pb.go @@ -260,48 +260,108 @@ func (m *FungibleTokenPacketAcknowledgement) GetError() string { return "" } +// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing +// information path. +type DenomTrace struct { + // path defines the chain of port/channel identifiers used for tracing the source of the fungible + // token. + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + // base denomination of the relayed fungible token. + BaseDenom string `protobuf:"bytes,2,opt,name=base_denom,json=baseDenom,proto3" json:"base_denom,omitempty"` +} + +func (m *DenomTrace) Reset() { *m = DenomTrace{} } +func (m *DenomTrace) String() string { return proto.CompactTextString(m) } +func (*DenomTrace) ProtoMessage() {} +func (*DenomTrace) Descriptor() ([]byte, []int) { + return fileDescriptor_08134a70fd29e656, []int{3} +} +func (m *DenomTrace) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DenomTrace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DenomTrace.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DenomTrace) XXX_Merge(src proto.Message) { + xxx_messageInfo_DenomTrace.Merge(m, src) +} +func (m *DenomTrace) XXX_Size() int { + return m.Size() +} +func (m *DenomTrace) XXX_DiscardUnknown() { + xxx_messageInfo_DenomTrace.DiscardUnknown(m) +} + +var xxx_messageInfo_DenomTrace proto.InternalMessageInfo + +func (m *DenomTrace) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func (m *DenomTrace) GetBaseDenom() string { + if m != nil { + return m.BaseDenom + } + return "" +} + func init() { proto.RegisterType((*MsgTransfer)(nil), "ibc.transfer.MsgTransfer") proto.RegisterType((*FungibleTokenPacketData)(nil), "ibc.transfer.FungibleTokenPacketData") proto.RegisterType((*FungibleTokenPacketAcknowledgement)(nil), "ibc.transfer.FungibleTokenPacketAcknowledgement") + proto.RegisterType((*DenomTrace)(nil), "ibc.transfer.DenomTrace") } func init() { proto.RegisterFile("ibc/transfer/transfer.proto", fileDescriptor_08134a70fd29e656) } var fileDescriptor_08134a70fd29e656 = []byte{ - // 505 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x4f, 0x6f, 0xd3, 0x30, - 0x1c, 0x6d, 0xe8, 0x9f, 0x0d, 0x77, 0x43, 0x60, 0x8d, 0x91, 0x15, 0x94, 0x56, 0x39, 0xf5, 0xd2, - 0x44, 0x05, 0x21, 0x24, 0x4e, 0xb4, 0x43, 0x88, 0x09, 0x21, 0x4d, 0x56, 0x4f, 0x5c, 0xa6, 0xc4, - 0xf9, 0x91, 0x5a, 0x6d, 0xec, 0xca, 0x76, 0x06, 0x13, 0x5f, 0x82, 0xef, 0xc2, 0x97, 0xd8, 0x71, - 0x47, 0x4e, 0x11, 0x6a, 0xbf, 0x41, 0x8f, 0x9c, 0x50, 0x62, 0xb7, 0xac, 0xd2, 0xb4, 0x93, 0xfd, - 0xde, 0xfb, 0xf9, 0x97, 0xe7, 0xf7, 0x73, 0xd0, 0x73, 0x16, 0xd3, 0x50, 0xcb, 0x88, 0xab, 0xaf, - 0x20, 0xb7, 0x9b, 0x60, 0x21, 0x85, 0x16, 0xf8, 0x80, 0xc5, 0x34, 0xd8, 0x70, 0x9d, 0xa3, 0x54, - 0xa4, 0xa2, 0x12, 0xc2, 0x72, 0x67, 0x6a, 0x3a, 0x1e, 0x15, 0x2a, 0x13, 0x2a, 0x8c, 0x23, 0x05, - 0xe1, 0xe5, 0x30, 0x06, 0x1d, 0x0d, 0x43, 0x2a, 0x18, 0x37, 0xba, 0xff, 0xab, 0x8e, 0xda, 0x9f, - 0x55, 0x3a, 0xb1, 0x5d, 0xf0, 0x1b, 0xd4, 0x56, 0x22, 0x97, 0x14, 0x2e, 0x16, 0x42, 0x6a, 0xd7, - 0xe9, 0x39, 0xfd, 0x87, 0xe3, 0xe3, 0x75, 0xd1, 0xc5, 0x57, 0x51, 0x36, 0x7f, 0xeb, 0xdf, 0x12, - 0x7d, 0x82, 0x0c, 0x3a, 0x17, 0x52, 0xe3, 0x77, 0xe8, 0x91, 0xd5, 0xe8, 0x34, 0xe2, 0x1c, 0xe6, - 0xee, 0x83, 0xea, 0xec, 0xc9, 0xba, 0xe8, 0x3e, 0xdd, 0x39, 0x6b, 0x75, 0x9f, 0x1c, 0x1a, 0xe2, - 0xd4, 0x60, 0xfc, 0x1a, 0x35, 0xb5, 0x98, 0x01, 0x77, 0xeb, 0x3d, 0xa7, 0xdf, 0x7e, 0x79, 0x12, - 0x18, 0xeb, 0x41, 0x69, 0x3d, 0xb0, 0xd6, 0x83, 0x53, 0xc1, 0xf8, 0xb8, 0x71, 0x5d, 0x74, 0x6b, - 0xc4, 0x54, 0xe3, 0x33, 0xd4, 0x52, 0xc0, 0x13, 0x90, 0x6e, 0xa3, 0xe7, 0xf4, 0x0f, 0xc6, 0xc3, - 0xbf, 0x45, 0x77, 0x90, 0x32, 0x3d, 0xcd, 0xe3, 0x80, 0x8a, 0x2c, 0xb4, 0x01, 0x98, 0x65, 0xa0, - 0x92, 0x59, 0xa8, 0xaf, 0x16, 0xa0, 0x82, 0x11, 0xa5, 0xa3, 0x24, 0x91, 0xa0, 0x14, 0xb1, 0x0d, - 0x70, 0x07, 0xed, 0x4b, 0xa0, 0xc0, 0x2e, 0x41, 0xba, 0xcd, 0xd2, 0x3d, 0xd9, 0xe2, 0xf2, 0x7e, - 0x9a, 0x65, 0x20, 0x72, 0x7d, 0x31, 0x05, 0x96, 0x4e, 0xb5, 0xdb, 0xea, 0x39, 0xfd, 0xc6, 0xed, - 0xfb, 0xed, 0xea, 0x3e, 0x39, 0xb4, 0xc4, 0xc7, 0x0a, 0xe3, 0x33, 0xf4, 0x64, 0x53, 0x51, 0xae, - 0x4a, 0x47, 0xd9, 0xc2, 0xdd, 0xab, 0x9a, 0xbc, 0x58, 0x17, 0x5d, 0x77, 0xb7, 0xc9, 0xb6, 0xc4, - 0x27, 0x8f, 0x2d, 0x37, 0xd9, 0x52, 0x3f, 0xd0, 0xb3, 0x0f, 0x39, 0x4f, 0x59, 0x3c, 0x87, 0x49, - 0x19, 0xc2, 0x79, 0x44, 0x67, 0xa0, 0xdf, 0x47, 0x3a, 0xc2, 0x47, 0xa8, 0x99, 0x00, 0x17, 0x99, - 0x19, 0x1d, 0x31, 0x00, 0x1f, 0xa3, 0x56, 0x94, 0x89, 0x9c, 0xeb, 0x6a, 0x2a, 0x0d, 0x62, 0x51, - 0xc9, 0xdb, 0xf0, 0xea, 0x55, 0xf9, 0x5d, 0x49, 0x34, 0x76, 0x93, 0xf0, 0x27, 0xc8, 0xbf, 0xe3, - 0xe3, 0x23, 0x3a, 0xe3, 0xe2, 0xdb, 0x1c, 0x92, 0x14, 0x32, 0xe0, 0x1a, 0xbb, 0x68, 0x4f, 0xe5, - 0x94, 0x82, 0x52, 0x95, 0x93, 0x7d, 0xb2, 0x81, 0xa5, 0x43, 0x90, 0x52, 0x48, 0xf3, 0x40, 0x88, - 0x01, 0xe3, 0x4f, 0xd7, 0x4b, 0xcf, 0xb9, 0x59, 0x7a, 0xce, 0x9f, 0xa5, 0xe7, 0xfc, 0x5c, 0x79, - 0xb5, 0x9b, 0x95, 0x57, 0xfb, 0xbd, 0xf2, 0x6a, 0x5f, 0x86, 0xf7, 0x0e, 0xf3, 0x7b, 0xc8, 0x62, - 0x3a, 0xf8, 0xff, 0x8b, 0x94, 0xb3, 0x8d, 0x5b, 0xd5, 0xe3, 0x7e, 0xf5, 0x2f, 0x00, 0x00, 0xff, - 0xff, 0x0d, 0x50, 0xec, 0xf7, 0x3f, 0x03, 0x00, 0x00, + // 541 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0xc1, 0x6e, 0xd3, 0x4c, + 0x10, 0x8e, 0xff, 0xba, 0x69, 0xbb, 0x69, 0x7f, 0xc1, 0xaa, 0x14, 0x37, 0x80, 0x13, 0xf9, 0x94, + 0x4b, 0x6c, 0x05, 0x84, 0x90, 0xb8, 0x40, 0xd2, 0x0a, 0x51, 0x21, 0xa4, 0xca, 0xca, 0x89, 0x4b, + 0xb4, 0x5e, 0x0f, 0x8e, 0x95, 0x78, 0x37, 0xda, 0x5d, 0x17, 0x2a, 0x5e, 0x82, 0x77, 0xe1, 0x25, + 0x7a, 0xec, 0x91, 0x53, 0x84, 0x92, 0x37, 0xc8, 0x91, 0x13, 0x5a, 0xef, 0x26, 0x34, 0x52, 0xc5, + 0xc9, 0xfb, 0x7d, 0xdf, 0xcc, 0xf8, 0x9b, 0xd9, 0x59, 0xf4, 0x24, 0x4f, 0x68, 0xa4, 0x04, 0x61, + 0xf2, 0x33, 0x88, 0xcd, 0x21, 0x9c, 0x09, 0xae, 0x38, 0x3e, 0xcc, 0x13, 0x1a, 0xae, 0xb9, 0xe6, + 0x71, 0xc6, 0x33, 0x5e, 0x09, 0x91, 0x3e, 0x99, 0x98, 0xa6, 0x4f, 0xb9, 0x2c, 0xb8, 0x8c, 0x12, + 0x22, 0x21, 0xba, 0xea, 0x25, 0xa0, 0x48, 0x2f, 0xa2, 0x3c, 0x67, 0x46, 0x0f, 0x7e, 0xec, 0xa0, + 0xc6, 0x47, 0x99, 0x0d, 0x6d, 0x15, 0xfc, 0x0a, 0x35, 0x24, 0x2f, 0x05, 0x85, 0xd1, 0x8c, 0x0b, + 0xe5, 0x39, 0x6d, 0xa7, 0x73, 0x30, 0x38, 0x59, 0xcd, 0x5b, 0xf8, 0x9a, 0x14, 0xd3, 0xd7, 0xc1, + 0x1d, 0x31, 0x88, 0x91, 0x41, 0x97, 0x5c, 0x28, 0xfc, 0x16, 0xfd, 0x6f, 0x35, 0x3a, 0x26, 0x8c, + 0xc1, 0xd4, 0xfb, 0xaf, 0xca, 0x3d, 0x5d, 0xcd, 0x5b, 0x8f, 0xb6, 0x72, 0xad, 0x1e, 0xc4, 0x47, + 0x86, 0x38, 0x33, 0x18, 0xbf, 0x44, 0xbb, 0x8a, 0x4f, 0x80, 0x79, 0x3b, 0x6d, 0xa7, 0xd3, 0x78, + 0x7e, 0x1a, 0x1a, 0xeb, 0xa1, 0xb6, 0x1e, 0x5a, 0xeb, 0xe1, 0x19, 0xcf, 0xd9, 0xc0, 0xbd, 0x99, + 0xb7, 0x6a, 0xb1, 0x89, 0xc6, 0x17, 0xa8, 0x2e, 0x81, 0xa5, 0x20, 0x3c, 0xb7, 0xed, 0x74, 0x0e, + 0x07, 0xbd, 0xdf, 0xf3, 0x56, 0x37, 0xcb, 0xd5, 0xb8, 0x4c, 0x42, 0xca, 0x8b, 0xc8, 0x0e, 0xc0, + 0x7c, 0xba, 0x32, 0x9d, 0x44, 0xea, 0x7a, 0x06, 0x32, 0xec, 0x53, 0xda, 0x4f, 0x53, 0x01, 0x52, + 0xc6, 0xb6, 0x00, 0x6e, 0xa2, 0x7d, 0x01, 0x14, 0xf2, 0x2b, 0x10, 0xde, 0xae, 0x76, 0x1f, 0x6f, + 0xb0, 0xee, 0x4f, 0xe5, 0x05, 0xf0, 0x52, 0x8d, 0xc6, 0x90, 0x67, 0x63, 0xe5, 0xd5, 0xdb, 0x4e, + 0xc7, 0xbd, 0xdb, 0xdf, 0xb6, 0x1e, 0xc4, 0x47, 0x96, 0x78, 0x5f, 0x61, 0x7c, 0x81, 0x1e, 0xae, + 0x23, 0xf4, 0x57, 0x2a, 0x52, 0xcc, 0xbc, 0xbd, 0xaa, 0xc8, 0xd3, 0xd5, 0xbc, 0xe5, 0x6d, 0x17, + 0xd9, 0x84, 0x04, 0xf1, 0x03, 0xcb, 0x0d, 0x37, 0xd4, 0x37, 0xf4, 0xf8, 0x5d, 0xc9, 0xb2, 0x3c, + 0x99, 0xc2, 0x50, 0x0f, 0xe1, 0x92, 0xd0, 0x09, 0xa8, 0x73, 0xa2, 0x08, 0x3e, 0x46, 0xbb, 0x29, + 0x30, 0x5e, 0x98, 0xab, 0x8b, 0x0d, 0xc0, 0x27, 0xa8, 0x4e, 0x0a, 0x5e, 0x32, 0x55, 0xdd, 0x8a, + 0x1b, 0x5b, 0xa4, 0x79, 0x3b, 0xbc, 0x9d, 0x2a, 0xfc, 0xbe, 0x49, 0xb8, 0xdb, 0x93, 0x08, 0x86, + 0x28, 0xb8, 0xe7, 0xe7, 0x7d, 0x3a, 0x61, 0xfc, 0xcb, 0x14, 0xd2, 0x0c, 0x0a, 0x60, 0x0a, 0x7b, + 0x68, 0x4f, 0x96, 0x94, 0x82, 0x94, 0x95, 0x93, 0xfd, 0x78, 0x0d, 0xb5, 0x43, 0x10, 0x82, 0x0b, + 0xb3, 0x20, 0xb1, 0x01, 0xc1, 0x1b, 0x84, 0xce, 0xb5, 0xd5, 0xa1, 0x20, 0x14, 0x30, 0x46, 0xee, + 0x8c, 0xa8, 0xb1, 0x6d, 0xa2, 0x3a, 0xe3, 0x67, 0x08, 0xe9, 0x55, 0x18, 0x99, 0xf6, 0x4c, 0xf2, + 0x81, 0x66, 0xaa, 0xbc, 0xc1, 0x87, 0x9b, 0x85, 0xef, 0xdc, 0x2e, 0x7c, 0xe7, 0xd7, 0xc2, 0x77, + 0xbe, 0x2f, 0xfd, 0xda, 0xed, 0xd2, 0xaf, 0xfd, 0x5c, 0xfa, 0xb5, 0x4f, 0xbd, 0x7f, 0x6e, 0xc3, + 0xd7, 0x28, 0x4f, 0x68, 0xf7, 0xef, 0x1b, 0xd3, 0xcb, 0x91, 0xd4, 0xab, 0xd7, 0xf1, 0xe2, 0x4f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x8b, 0x58, 0xed, 0x2d, 0x80, 0x03, 0x00, 0x00, } func (m *MsgTransfer) Marshal() (dAtA []byte, err error) { @@ -464,6 +524,43 @@ func (m *FungibleTokenPacketAcknowledgement) MarshalToSizedBuffer(dAtA []byte) ( return len(dAtA) - i, nil } +func (m *DenomTrace) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DenomTrace) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DenomTrace) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BaseDenom) > 0 { + i -= len(m.BaseDenom) + copy(dAtA[i:], m.BaseDenom) + i = encodeVarintTransfer(dAtA, i, uint64(len(m.BaseDenom))) + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintTransfer(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTransfer(dAtA []byte, offset int, v uint64) int { offset -= sovTransfer(v) base := offset @@ -548,6 +645,23 @@ func (m *FungibleTokenPacketAcknowledgement) Size() (n int) { return n } +func (m *DenomTrace) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovTransfer(uint64(l)) + } + l = len(m.BaseDenom) + if l > 0 { + n += 1 + l + sovTransfer(uint64(l)) + } + return n +} + func sovTransfer(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1081,6 +1195,123 @@ func (m *FungibleTokenPacketAcknowledgement) Unmarshal(dAtA []byte) error { } return nil } +func (m *DenomTrace) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DenomTrace: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DenomTrace: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTransfer + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTransfer + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTransfer + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTransfer + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BaseDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTransfer(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTransfer + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTransfer + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTransfer(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/ibc/spec/01_concepts.md b/x/ibc/spec/01_concepts.md index 3dffeb2be180..253a5dcb7d3f 100644 --- a/x/ibc/spec/01_concepts.md +++ b/x/ibc/spec/01_concepts.md @@ -7,7 +7,7 @@ order: 1 > NOTE: if you are not familiar with the IBC terminology and concepts, please read this [document](https://github.com/cosmos/ics/blob/master/ibc/1_IBC_TERMINOLOGY.md) as prerequisite reading. -### Connection Version Negotation +## Connection Version Negotation During the handshake procedure for connections a version string is agreed upon between the two parties. This occurs during the first 3 steps of the @@ -33,7 +33,7 @@ A valid connection version is considered to be in the following format: - the version tuple must be enclosed in parentheses - the feature set must be enclosed in brackets -- there should be no space between the comma separting the identifier and the +- there should be no space between the comma separating the identifier and the feature set - the version identifier must no contain any commas - each feature must not contain any commas @@ -46,7 +46,7 @@ with regards to version selection in `ConnOpenTry`. Each version in a set of versions should have a unique version identifier. ::: -### Channel Version Negotation +## Channel Version Negotation During the channel handshake procedure a version must be agreed upon between the two parties. The selection process is largely left to the callers and diff --git a/x/ibc/spec/README.md b/x/ibc/spec/README.md index 7e8350c99f51..9a85d85dbdb2 100644 --- a/x/ibc/spec/README.md +++ b/x/ibc/spec/README.md @@ -55,6 +55,8 @@ which call each ICS submodule's handlers (i.e `x/ibc/{XX-ICS}/handler.go`). The following ADR provide the design and architecture decision of IBC-related components. +* [ADR 001 - Coin Source Tracing](../../../docs/architecture/adr-001-coin-source-tracing.md): standard to hash the ICS20's fungible token +denomination trace path in order to support special characters and limit the maximum denomination length. * [ADR 17 - Historical Header Module](../../../docs/architecture/adr-017-historical-header-module.md): Introduces the ability to introspect past consensus states in order to verify their membership in the counterparty clients. * [ADR 19 - Protobuf State Encoding](../../../docs/architecture/adr-019-protobuf-state-encoding.md): Migration from Amino to Protobuf for state encoding. diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index 8148097aa27c..9d79910fe612 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -50,7 +50,7 @@ const ( // Default params variables used to create a TM client var ( DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel - TestHash = []byte("TESTING HASH") + TestHash = tmhash.Sum([]byte("TESTING HASH")) TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100)) ConnectionVersion = connectiontypes.GetCompatibleEncodedVersions()[0]