From a091bddc61a612d407f41b5146adc5215dec9494 Mon Sep 17 00:00:00 2001 From: Riccardo Montagnin Date: Thu, 26 May 2022 12:37:41 +0200 Subject: [PATCH] feat: ADR-010: Posts module (#847) ## Description This PR implements the `x/posts` module as per ADR-010. Implementation checklist - [x] Types - [x] Keeper - [x] CLI - [x] Simulation tests - [x] Invariants - [ ] `spec` folder --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/desmos-labs/desmos/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://docs.cosmos.network/v0.44/building-modules/intro.html) - [ ] included the necessary unit and integration [tests](https://github.com/desmos-labs/desmos/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [x] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [x] confirmed all author checklist items have been addressed - [x] reviewed state machine logic - [x] reviewed API design and naming - [ ] reviewed documentation is accurate - [x] reviewed tests and test coverage - [ ] manually tested (if applicable) --- .changeset/config.yaml | 6 +- ...db471dcdd82f056579158b3baec1d444fdd6e.yaml | 6 + app/app.go | 44 +- app/params/weights.go | 7 + app/sim_test.go | 3 + client/docs/config.json | 8 + client/docs/swagger-ui/swagger.yaml | 8481 +++++++++++------ docs/architecture/adr-010-posts-module.md | 304 +- proto/desmos/posts/v1/client/cli.proto | 46 + proto/desmos/posts/v1/genesis.proto | 43 + proto/desmos/posts/v1/models.proto | 256 + proto/desmos/posts/v1/msgs.proto | 186 + proto/desmos/posts/v1/query.proto | 123 + x/posts/abci.go | 48 + x/posts/abci_test.go | 153 + x/posts/client/cli/cli_test.go | 803 ++ x/posts/client/cli/query.go | 239 + x/posts/client/cli/tx.go | 341 + x/posts/client/utils/cli.pb.go | 886 ++ x/posts/client/utils/utils.go | 60 + x/posts/keeper/alias_functions.go | 241 + x/posts/keeper/attachments.go | 94 + x/posts/keeper/attachments_test.go | 457 + x/posts/keeper/common_test.go | 85 + x/posts/keeper/genesis.go | 104 + x/posts/keeper/genesis_test.go | 438 + x/posts/keeper/grpc_query.go | 153 + x/posts/keeper/grpc_query_test.go | 454 + x/posts/keeper/hooks.go | 61 + x/posts/keeper/invariants.go | 254 + x/posts/keeper/invariants_test.go | 668 ++ x/posts/keeper/keeper.go | 52 + x/posts/keeper/migrations.go | 29 + x/posts/keeper/msg_server.go | 478 + x/posts/keeper/msg_server_test.go | 1958 ++++ x/posts/keeper/params.go | 18 + x/posts/keeper/params_test.go | 80 + x/posts/keeper/polls.go | 114 + x/posts/keeper/polls_test.go | 407 + x/posts/keeper/posts.go | 183 + x/posts/keeper/posts_test.go | 1028 ++ x/posts/keeper/subspace_hooks_test.go | 127 + x/posts/keeper/subspaces_hooks.go | 56 + x/posts/legacy/v2/store.go | 26 + x/posts/legacy/v2/store_test.go | 91 + x/posts/module.go | 217 + x/posts/simulation/decoder.go | 58 + x/posts/simulation/decoder_test.go | 112 + x/posts/simulation/genesis.go | 157 + x/posts/simulation/operations.go | 108 + x/posts/simulation/operations_attachments.go | 165 + x/posts/simulation/operations_polls.go | 88 + x/posts/simulation/operations_posts.go | 245 + x/posts/simulation/params.go | 25 + x/posts/simulation/utils.go | 147 + x/posts/types/codec.go | 61 + x/posts/types/errors.go | 10 + x/posts/types/events.go | 21 + x/posts/types/expected_keepers.go | 28 + x/posts/types/genesis.go | 199 + x/posts/types/genesis.pb.go | 1087 +++ x/posts/types/genesis_test.go | 295 + x/posts/types/hooks.go | 27 + x/posts/types/keys.go | 162 + x/posts/types/keys_test.go | 91 + x/posts/types/models.go | 675 ++ x/posts/types/models.pb.go | 4544 +++++++++ x/posts/types/models_test.go | 1144 +++ x/posts/types/msgs.go | 413 + x/posts/types/msgs.pb.go | 3625 +++++++ x/posts/types/msgs_test.go | 689 ++ x/posts/types/params.go | 68 + x/posts/types/params_test.go | 41 + x/posts/types/query.go | 66 + x/posts/types/query.pb.go | 2620 +++++ x/posts/types/query.pb.gw.go | 682 ++ x/profiles/types/models_chain_links_test.go | 26 +- x/subspaces/legacy/v2/store_test.go | 4 +- x/subspaces/simulation/genesis.go | 26 +- x/subspaces/simulation/operations_groups.go | 11 +- .../simulation/operations_permissions.go | 7 +- x/subspaces/simulation/utils.go | 10 + x/subspaces/types/permissions.go | 8 +- x/subspaces/types/permissions_test.go | 4 +- 84 files changed, 34710 insertions(+), 2955 deletions(-) create mode 100644 .changeset/entries/72bc29b6f6aec4b3f39cd78f067db471dcdd82f056579158b3baec1d444fdd6e.yaml create mode 100644 proto/desmos/posts/v1/client/cli.proto create mode 100644 proto/desmos/posts/v1/genesis.proto create mode 100644 proto/desmos/posts/v1/models.proto create mode 100644 proto/desmos/posts/v1/msgs.proto create mode 100644 proto/desmos/posts/v1/query.proto create mode 100644 x/posts/abci.go create mode 100644 x/posts/abci_test.go create mode 100644 x/posts/client/cli/cli_test.go create mode 100644 x/posts/client/cli/query.go create mode 100644 x/posts/client/cli/tx.go create mode 100644 x/posts/client/utils/cli.pb.go create mode 100644 x/posts/client/utils/utils.go create mode 100644 x/posts/keeper/alias_functions.go create mode 100644 x/posts/keeper/attachments.go create mode 100644 x/posts/keeper/attachments_test.go create mode 100644 x/posts/keeper/common_test.go create mode 100644 x/posts/keeper/genesis.go create mode 100644 x/posts/keeper/genesis_test.go create mode 100644 x/posts/keeper/grpc_query.go create mode 100644 x/posts/keeper/grpc_query_test.go create mode 100644 x/posts/keeper/hooks.go create mode 100644 x/posts/keeper/invariants.go create mode 100644 x/posts/keeper/invariants_test.go create mode 100644 x/posts/keeper/keeper.go create mode 100644 x/posts/keeper/migrations.go create mode 100644 x/posts/keeper/msg_server.go create mode 100644 x/posts/keeper/msg_server_test.go create mode 100644 x/posts/keeper/params.go create mode 100644 x/posts/keeper/params_test.go create mode 100644 x/posts/keeper/polls.go create mode 100644 x/posts/keeper/polls_test.go create mode 100644 x/posts/keeper/posts.go create mode 100644 x/posts/keeper/posts_test.go create mode 100644 x/posts/keeper/subspace_hooks_test.go create mode 100644 x/posts/keeper/subspaces_hooks.go create mode 100644 x/posts/legacy/v2/store.go create mode 100644 x/posts/legacy/v2/store_test.go create mode 100644 x/posts/module.go create mode 100644 x/posts/simulation/decoder.go create mode 100644 x/posts/simulation/decoder_test.go create mode 100644 x/posts/simulation/genesis.go create mode 100644 x/posts/simulation/operations.go create mode 100644 x/posts/simulation/operations_attachments.go create mode 100644 x/posts/simulation/operations_polls.go create mode 100644 x/posts/simulation/operations_posts.go create mode 100644 x/posts/simulation/params.go create mode 100644 x/posts/simulation/utils.go create mode 100644 x/posts/types/codec.go create mode 100644 x/posts/types/errors.go create mode 100644 x/posts/types/events.go create mode 100644 x/posts/types/expected_keepers.go create mode 100644 x/posts/types/genesis.go create mode 100644 x/posts/types/genesis.pb.go create mode 100644 x/posts/types/genesis_test.go create mode 100644 x/posts/types/hooks.go create mode 100644 x/posts/types/keys.go create mode 100644 x/posts/types/keys_test.go create mode 100644 x/posts/types/models.go create mode 100644 x/posts/types/models.pb.go create mode 100644 x/posts/types/models_test.go create mode 100644 x/posts/types/msgs.go create mode 100644 x/posts/types/msgs.pb.go create mode 100644 x/posts/types/msgs_test.go create mode 100644 x/posts/types/params.go create mode 100644 x/posts/types/params_test.go create mode 100644 x/posts/types/query.go create mode 100644 x/posts/types/query.pb.go create mode 100644 x/posts/types/query.pb.gw.go diff --git a/.changeset/config.yaml b/.changeset/config.yaml index 67a9e84857..38f122c950 100644 --- a/.changeset/config.yaml +++ b/.changeset/config.yaml @@ -60,10 +60,12 @@ types: modules: - id: x/profiles description: Profiles - - id: x/subspaces - description: Subspaces - id: x/relationships description: Relationships + - id: x/subspaces + description: Subspaces + - id: x/posts + description: Posts - id: x/fees description: Fees - id: x/supply diff --git a/.changeset/entries/72bc29b6f6aec4b3f39cd78f067db471dcdd82f056579158b3baec1d444fdd6e.yaml b/.changeset/entries/72bc29b6f6aec4b3f39cd78f067db471dcdd82f056579158b3baec1d444fdd6e.yaml new file mode 100644 index 0000000000..9686a684a6 --- /dev/null +++ b/.changeset/entries/72bc29b6f6aec4b3f39cd78f067db471dcdd82f056579158b3baec1d444fdd6e.yaml @@ -0,0 +1,6 @@ +type: feat +module: x/posts +pull_request: 847 +description: Added the new x/posts module +backward_compatible: false +date: 2022-05-13T11:12:11.336881249Z diff --git a/app/app.go b/app/app.go index 0613e0d8ec..4a48e4ecf3 100644 --- a/app/app.go +++ b/app/app.go @@ -8,17 +8,21 @@ import ( "path/filepath" "strings" + postskeeper "github.com/desmos-labs/desmos/v3/x/posts/keeper" + poststypes "github.com/desmos-labs/desmos/v3/x/posts/types" + "github.com/desmos-labs/desmos/v3/app/upgrades" v300 "github.com/desmos-labs/desmos/v3/app/upgrades/v300" v310 "github.com/desmos-labs/desmos/v3/app/upgrades/v310" profilesv4 "github.com/desmos-labs/desmos/v3/x/profiles/legacy/v4" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/desmos-labs/desmos/v3/x/posts" "github.com/desmos-labs/desmos/v3/x/relationships" relationshipstypes "github.com/desmos-labs/desmos/v3/x/relationships/types" - "github.com/cosmos/cosmos-sdk/version" - "github.com/desmos-labs/desmos/v3/x/fees" feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" feestypes "github.com/desmos-labs/desmos/v3/x/fees/types" @@ -233,6 +237,7 @@ var ( profiles.AppModuleBasic{}, relationships.AppModuleBasic{}, subspaces.AppModuleBasic{}, + posts.AppModuleBasic{}, fees.AppModuleBasic{}, supply.AppModuleBasic{}, ) @@ -298,6 +303,7 @@ type DesmosApp struct { SubspacesKeeper subspaceskeeper.Keeper ProfileKeeper profileskeeper.Keeper RelationshipsKeeper relationshipskeeper.Keeper + PostsKeeper postskeeper.Keeper SupplyKeeper supplykeeper.Keeper // Module Manager @@ -346,6 +352,7 @@ func NewDesmosApp( // Custom modules profilestypes.StoreKey, relationshipstypes.StoreKey, subspacestypes.StoreKey, + poststypes.StoreKey, feestypes.StoreKey, supplytypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) @@ -469,10 +476,22 @@ func NewDesmosApp( app.FeesKeeper, ) + // Create posts keeper and module + app.PostsKeeper = postskeeper.NewKeeper( + app.appCodec, + keys[poststypes.StoreKey], + app.GetSubspace(poststypes.ModuleName), + &subspacesKeeper, + app.RelationshipsKeeper, + ) + // Register the subspaces hooks // NOTE: subspacesKeeper above is passed by reference, so that it will contain these hooks app.SubspacesKeeper = *subspacesKeeper.SetHooks( - subspacestypes.NewMultiSubspacesHooks(app.RelationshipsKeeper.Hooks()), + subspacestypes.NewMultiSubspacesHooks( + app.RelationshipsKeeper.Hooks(), + app.PostsKeeper.Hooks(), + ), ) app.SupplyKeeper = supplykeeper.NewKeeper(app.appCodec, app.AccountKeeper, app.BankKeeper, app.DistrKeeper) @@ -576,11 +595,8 @@ func NewDesmosApp( fees.NewAppModule(app.appCodec, app.FeesKeeper), subspaces.NewAppModule(appCodec, app.SubspacesKeeper, app.AccountKeeper, app.BankKeeper, app.FeesKeeper), profilesModule, - relationships.NewAppModule( - appCodec, app.RelationshipsKeeper, app.SubspacesKeeper, - profilesv4.NewKeeper(keys[profilestypes.StoreKey], appCodec), app.AccountKeeper, - app.BankKeeper, app.FeesKeeper, - ), + relationships.NewAppModule(appCodec, app.RelationshipsKeeper, app.SubspacesKeeper, profilesv4.NewKeeper(keys[profilestypes.StoreKey], appCodec), app.AccountKeeper, app.BankKeeper, app.FeesKeeper), + posts.NewAppModule(appCodec, app.PostsKeeper, app.SubspacesKeeper, app.AccountKeeper, app.BankKeeper, app.FeesKeeper), supply.NewAppModule(appCodec, legacyAmino, app.SupplyKeeper), ) @@ -614,6 +630,7 @@ func NewDesmosApp( subspacestypes.ModuleName, relationshipstypes.ModuleName, profilestypes.ModuleName, + poststypes.ModuleName, supplytypes.ModuleName, ) app.mm.SetOrderEndBlockers( @@ -641,6 +658,7 @@ func NewDesmosApp( subspacestypes.ModuleName, relationshipstypes.ModuleName, profilestypes.ModuleName, + poststypes.ModuleName, supplytypes.ModuleName, ) @@ -675,6 +693,7 @@ func NewDesmosApp( subspacestypes.ModuleName, profilestypes.ModuleName, relationshipstypes.ModuleName, + poststypes.ModuleName, supplytypes.ModuleName, crisistypes.ModuleName, @@ -708,6 +727,7 @@ func NewDesmosApp( subspacestypes.ModuleName, relationshipstypes.ModuleName, profilestypes.ModuleName, + poststypes.ModuleName, supplytypes.ModuleName, crisistypes.ModuleName, @@ -749,11 +769,8 @@ func NewDesmosApp( supply.NewAppModule(appCodec, legacyAmino, app.SupplyKeeper), subspaces.NewAppModule(appCodec, app.SubspacesKeeper, app.AccountKeeper, app.BankKeeper, app.FeesKeeper), profilesModule, - relationships.NewAppModule( - appCodec, app.RelationshipsKeeper, app.SubspacesKeeper, - profilesv4.NewKeeper(keys[profilestypes.StoreKey], appCodec), app.AccountKeeper, - app.BankKeeper, app.FeesKeeper, - ), + relationships.NewAppModule(appCodec, app.RelationshipsKeeper, app.SubspacesKeeper, profilesv4.NewKeeper(keys[profilestypes.StoreKey], appCodec), app.AccountKeeper, app.BankKeeper, app.FeesKeeper), + posts.NewAppModule(appCodec, app.PostsKeeper, app.SubspacesKeeper, app.AccountKeeper, app.BankKeeper, app.FeesKeeper), ) app.sm.RegisterStoreDecoders() @@ -1005,6 +1022,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(feestypes.ModuleName) paramsKeeper.Subspace(subspacestypes.ModuleName) paramsKeeper.Subspace(profilestypes.ModuleName) + paramsKeeper.Subspace(poststypes.ModuleName) return paramsKeeper } diff --git a/app/params/weights.go b/app/params/weights.go index f970c887ec..1358838671 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -24,4 +24,11 @@ const ( DefaultWeightMsgAddUserToUserGroup int = 7 DefaultWeightMsgRemoveUserFromUserGroup int = 3 DefaultWeightMsgSetUserPermissions int = 85 + + DefaultWeightMsgCreatePost int = 80 + DefaultWeightMsgEditPost int = 40 + DefaultWeightMsgDeletePost int = 20 + DefaultWeightMsgAddPostAttachment int = 50 + DefaultWeightMsgRemovePostAttachment int = 50 + DefaultWeightMsgAnswerPoll int = 50 ) diff --git a/app/sim_test.go b/app/sim_test.go index 9d2bd71c0b..f5f6b6d1a5 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -8,6 +8,8 @@ import ( "path/filepath" "testing" + poststypes "github.com/desmos-labs/desmos/v3/x/posts/types" + feestypes "github.com/desmos-labs/desmos/v3/x/fees/types" relationshipstypes "github.com/desmos-labs/desmos/v3/x/relationships/types" @@ -235,6 +237,7 @@ func TestAppImportExport(t *testing.T) { {app.keys[subspacestypes.StoreKey], newApp.keys[subspacestypes.StoreKey], [][]byte{}}, {app.keys[profilestypes.StoreKey], newApp.keys[profilestypes.StoreKey], [][]byte{}}, {app.keys[relationshipstypes.StoreKey], newApp.keys[relationshipstypes.StoreKey], [][]byte{}}, + {app.keys[poststypes.StoreKey], newApp.keys[poststypes.StoreKey], [][]byte{}}, } for _, skp := range storeKeysPrefixes { diff --git a/client/docs/config.json b/client/docs/config.json index de53a69b40..8e1a29033c 100644 --- a/client/docs/config.json +++ b/client/docs/config.json @@ -20,6 +20,14 @@ { "url": "./tmp-swagger-gen/desmos/subspaces/v1/query.swagger.json" }, + { + "url": "./tmp-swagger-gen/desmos/posts/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "PostsParams" + } + } + }, { "url": "./tmp-swagger-gen/desmos/fees/v1/query.swagger.json", "operationIds": { diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index c31d01ab0d..1371bc9fed 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -4732,10 +4732,10 @@ paths: type: string tags: - Query - /desmos/fees/v1/params: + /desmos/posts/v1/params: get: - summary: Params queries the fees module params - operationId: FeesParams + summary: Params queries the module parameters + operationId: PostsParams responses: '200': description: A successful response. @@ -4745,82 +4745,13 @@ paths: params: type: object properties: - min_fees: - type: array - items: - type: object - properties: - message_type: - type: string - amount: - type: array - items: - type: object - properties: - denom: - type: string - amount: - type: string - description: >- - Coin defines a token with a denomination and an - amount. - - - NOTE: The amount field is an Int which implements - the custom method - - signatures required by gogoproto. - title: >- - MinFee contains the minimum amount of coins that should - be paid as a fee for - - each message of the specific type sent - title: Params contains the parameters for the fees module - title: QueryParamsResponse is the response type for the Query/Params RPC - default: - description: An unexpected error response - schema: - type: object - properties: - error: - type: string - code: - type: integer - format: int32 - message: - type: string - details: - type: array - items: - type: object - properties: - type_url: - type: string - value: - type: string - format: byte - tags: - - Query - '/desmos/supply/v1/circulating/{denom}': - get: - summary: >- - Circulating queries the amount of tokens circulating in the market of - the - - given denom - operationId: Circulating - responses: - '200': - description: A successful response. - schema: - type: object - properties: - circulating_supply: - type: string + max_text_length: + type: integer + format: int64 + title: Maximum length of the post text + title: Params contains the parameters for the posts module title: >- - QueryCirculatingResponse is the response type for the - Query/Circulating RPC - + QueryParamsResponse is the response type for the Query/Params RPC method default: description: An unexpected error response @@ -5011,38 +4942,203 @@ paths: "@type": "type.googleapis.com/google.protobuf.Duration", "value": "1.212s" } - parameters: - - name: denom - description: coin denom to query the circulating supply for - in: path - required: true - type: string - - name: divider_exponent - description: >- - divider_exponent is a factor used to power the divider used to - convert the - - supply to the desired representation. - in: query - required: false - type: string - format: uint64 tags: - Query - '/desmos/supply/v1/total/{denom}': + '/desmos/posts/v1/{subspace_id}/posts': get: - summary: Total queries the total supply of the given denom - operationId: Total + summary: Posts queries all the posts inside a given subspace + operationId: Posts responses: '200': description: A successful response. schema: type: object properties: - total_supply: - type: string + posts: + type: array + items: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: >- + Id of the subspace inside which the post has been + created + id: + type: string + format: uint64 + title: Unique id of the post + external_id: + type: string + title: (optional) External id for this post + text: + type: string + title: (optional) Text of the post + entities: + title: (optional) Entities connected to this post + type: object + properties: + hashtags: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the tag ends + tag: + type: string + title: >- + Tag reference (user address, hashtag value, + etc) + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the tag ends + tag: + type: string + title: >- + Tag reference (user address, hashtag value, + etc) + title: Tag represents a generic tag + urls: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the URL starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at + which the URL ends + url: + type: string + title: >- + Value of the URL where the user should be + redirected to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + author: + type: string + title: Author of the post + conversation_id: + type: string + format: uint64 + title: (optional) Id of the original post of the conversation + referenced_posts: + type: array + items: + type: object + properties: + type: + title: Type of reference + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference + title: >- + A list this posts references (either as a reply, repost + or quote) + reply_settings: + title: Reply settings of this post + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + creation_date: + type: string + format: date-time + title: Creation date of the post + last_edited_date: + type: string + format: date-time + title: (optional) Last edited time of the post + title: Post contains all the information about a single post + pagination: + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } title: >- - QueryTotalResponse is the response type for the Query/Total RPC + QueryPostsResponse is the response type for the Query/Posts RPC method default: description: An unexpected error response @@ -5234,247 +5330,2830 @@ paths: "value": "1.212s" } parameters: - - name: denom - description: coin denom to query the circulating supply for + - name: subspace_id + description: Id of the subspace to query the posts for in: path required: true type: string - - name: divider_exponent + format: uint64 + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset description: >- - divider_exponent is a factor used to power the divider used to - convert the + offset is a numeric offset that can be used when key is unavailable. - supply to the desired representation. + It is less efficient than using key. Only one of offset or key + should + + be set. in: query required: false type: string format: uint64 - tags: - - Query -definitions: - cosmos.base.query.v1beta1.PageRequest: - type: object - properties: - key: - type: string - format: byte - description: |- - key is a value returned in PageResponse.next_key to begin - querying the next page most efficiently. Only one of offset or key - should be set. - offset: - type: string - format: uint64 - description: |- - offset is a numeric offset that can be used when key is unavailable. - It is less efficient than using key. Only one of offset or key should - be set. - limit: - type: string - format: uint64 - description: >- - limit is the total number of results to be returned in the result - page. + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. - If left empty it will default to a value to be set by each app. - count_total: - type: boolean - format: boolean - description: >- - count_total is set to true to indicate that the result set should - include + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include - a count of the total number of items available for pagination in UIs. + a count of the total number of items available for pagination in + UIs. - count_total is only respected when offset is used. It is ignored when - key + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + format: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + format: boolean + tags: + - Query + '/desmos/posts/v1/{subspace_id}/posts/{post_id}': + get: + summary: Post queries for a single post inside a given subspace + operationId: Post + responses: + '200': + description: A successful response. + schema: + type: object + properties: + post: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: Id of the subspace inside which the post has been created + id: + type: string + format: uint64 + title: Unique id of the post + external_id: + type: string + title: (optional) External id for this post + text: + type: string + title: (optional) Text of the post + entities: + title: (optional) Entities connected to this post + type: object + properties: + hashtags: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + urls: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the URL starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which + the URL ends + url: + type: string + title: >- + Value of the URL where the user should be + redirected to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + author: + type: string + title: Author of the post + conversation_id: + type: string + format: uint64 + title: (optional) Id of the original post of the conversation + referenced_posts: + type: array + items: + type: object + properties: + type: + title: Type of reference + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference + title: >- + A list this posts references (either as a reply, repost or + quote) + reply_settings: + title: Reply settings of this post + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + creation_date: + type: string + format: date-time + title: Creation date of the post + last_edited_date: + type: string + format: date-time + title: (optional) Last edited time of the post + title: Post contains all the information about a single post + title: >- + QueryPostResponse is the response type for the Query/Post RPC + method + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: subspace_id + description: Id of the subspace inside which the post lies + in: path + required: true + type: string + format: uint64 + - name: post_id + description: Id of the post to query for + in: path + required: true + type: string + format: uint64 + tags: + - Query + '/desmos/posts/v1/{subspace_id}/posts/{post_id}/attachments': + get: + summary: PostAttachments queries the attachments of the post having the given id + operationId: PostAttachments + responses: + '200': + description: A successful response. + schema: + type: object + properties: + attachments: + type: array + items: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: >- + Id of the subspace inside which the post to which this + attachment should be + + connected is + post_id: + type: string + format: uint64 + title: >- + Id of the post to which this attachment should be + connected + id: + type: integer + format: int64 + title: If of this attachment + content: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the + type of the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's + path must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be + in a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the + binary all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can + optionally set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results + based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available + in the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty + scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the + above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any + values in the form + + of utility functions or additional generated methods of + the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and + the unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will + yield type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the + regular + + representation of the deserialized, embedded message, + with an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a + custom JSON + + representation, that representation will be embedded + adding a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message + [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Content of the attachment + title: Attachment contains the data of a single post attachment + pagination: + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryPostAttachmentsResponse is the response type for the + Query/PostAttachments RPC method + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: subspace_id + description: Id of the subspace where the post is stored + in: path + required: true + type: string + format: uint64 + - name: post_id + description: Id of the post to query the attachments for + in: path + required: true + type: string + format: uint64 + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + format: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + format: boolean + tags: + - Query + '/desmos/posts/v1/{subspace_id}/posts/{post_id}/polls/{poll_id}/answers': + get: + summary: PollAnswers queries the answers for the poll having the given id + operationId: PollAnswers + responses: + '200': + description: A successful response. + schema: + type: object + properties: + answers: + type: array + items: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: >- + Subspace id inside which the post related to this + attachment is located + post_id: + type: string + format: uint64 + title: Id of the post associated to this attachment + poll_id: + type: integer + format: int64 + title: Id of the poll to which this answer is associated + answers_indexes: + type: array + items: + type: integer + format: int64 + title: Indexes of the answers inside the ProvidedAnswers array + user: + type: string + title: Address of the user answering the poll + title: UserAnswer represents a user answer to a poll + pagination: + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: >- + QueryPollAnswersResponse is the response type for the + Query/PollAnswers RPC + + method + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: subspace_id + description: Id of the subspace where the post is stored + in: path + required: true + type: string + format: uint64 + - name: post_id + description: Id of the post that holds the poll + in: path + required: true + type: string + format: uint64 + - name: poll_id + description: Id of the poll to query the answers for + in: path + required: true + type: integer + format: int64 + - name: user + description: (Optional) Address of the user to query the responses for. + in: query + required: false + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + format: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + format: boolean + tags: + - Query + /desmos/fees/v1/params: + get: + summary: Params queries the fees module params + operationId: FeesParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + type: object + properties: + min_fees: + type: array + items: + type: object + properties: + message_type: + type: string + amount: + type: array + items: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an + amount. + + + NOTE: The amount field is an Int which implements + the custom method + + signatures required by gogoproto. + title: >- + MinFee contains the minimum amount of coins that should + be paid as a fee for + + each message of the specific type sent + title: Params contains the parameters for the fees module + title: QueryParamsResponse is the response type for the Query/Params RPC + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Query + '/desmos/supply/v1/circulating/{denom}': + get: + summary: >- + Circulating queries the amount of tokens circulating in the market of + the + + given denom + operationId: Circulating + responses: + '200': + description: A successful response. + schema: + type: object + properties: + circulating_supply: + type: string + title: >- + QueryCirculatingResponse is the response type for the + Query/Circulating RPC + + method + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: denom + description: coin denom to query the circulating supply for + in: path + required: true + type: string + - name: divider_exponent + description: >- + divider_exponent is a factor used to power the divider used to + convert the + + supply to the desired representation. + in: query + required: false + type: string + format: uint64 + tags: + - Query + '/desmos/supply/v1/total/{denom}': + get: + summary: Total queries the total supply of the given denom + operationId: Total + responses: + '200': + description: A successful response. + schema: + type: object + properties: + total_supply: + type: string + title: >- + QueryTotalResponse is the response type for the Query/Total RPC + method + default: + description: An unexpected error response + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: denom + description: coin denom to query the circulating supply for + in: path + required: true + type: string + - name: divider_exponent + description: >- + divider_exponent is a factor used to power the divider used to + convert the + + supply to the desired representation. + in: query + required: false + type: string + format: uint64 + tags: + - Query +definitions: + cosmos.base.query.v1beta1.PageRequest: + type: object + properties: + key: + type: string + format: byte + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + offset: + type: string + format: uint64 + description: |- + offset is a numeric offset that can be used when key is unavailable. + It is less efficient than using key. Only one of offset or key should + be set. + limit: + type: string + format: uint64 + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + count_total: + type: boolean + format: boolean + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in UIs. + + count_total is only respected when offset is used. It is ignored when + key + + is set. + reverse: + type: boolean + format: boolean + description: >- + reverse is set to true if results are to be returned in the descending + order. + + + Since: cosmos-sdk 0.43 + description: |- + message SomeRequest { + Foo some_parameter = 1; + PageRequest pagination = 2; + } + title: |- + PageRequest is to be embedded in gRPC request messages for efficient + pagination. Ex: + cosmos.base.query.v1beta1.PageResponse: + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: |- + total is total number of results available if PageRequest.count_total + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + cosmos.base.v1beta1.Coin: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. + desmos.profiles.v2.ApplicationLink: + type: object + properties: + user: + type: string + title: User to which the link is associated + data: + title: Data contains the details of this specific link + type: object + properties: + application: + type: string + title: 'The application name (eg. Twitter, GitHub, etc)' + username: + type: string + title: 'Username on the application (eg. Twitter tag, GitHub profile, etc)' + state: + title: State of the link + type: string + enum: + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + - APPLICATION_LINK_STATE_VERIFICATION_STARTED + - APPLICATION_LINK_STATE_VERIFICATION_ERROR + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS + - APPLICATION_LINK_STATE_TIMED_OUT + default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + description: >- + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just been + initialized + - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified + - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully + - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification + oracle_request: + title: OracleRequest represents the request that has been made to the oracle + type: object + properties: + id: + type: string + format: uint64 + title: ID is the ID of the request + oracle_script_id: + type: string + format: uint64 + title: OracleScriptID is ID of an oracle script + call_data: + title: CallData contains the data used to perform the oracle request + type: object + properties: + application: + type: string + title: The application for which the ownership should be verified + call_data: + type: string + title: >- + The hex encoded call data that should be used to verify the + application + + account ownership + client_id: + type: string + title: >- + ClientID represents the ID of the client that has called the + oracle script + result: + title: |- + Data coming from the result of the verification. + Only available when the state is STATE_SUCCESS + type: object + properties: + success: + title: Success represents a successful verification + type: object + properties: + value: + type: string + title: Hex-encoded value that has be signed by the profile + signature: + type: string + title: >- + Hex-encoded signature that has been produced by signing the + value + failed: + title: Failed represents a failed verification + type: object + properties: + error: + type: string + title: Error that is associated with the failure + creation_time: + type: string + format: date-time + title: CreationTime represents the time in which the link was created + title: ApplicationLink contains the data of a link to a centralized application + desmos.profiles.v2.ApplicationLinkState: + type: string + enum: + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + - APPLICATION_LINK_STATE_VERIFICATION_STARTED + - APPLICATION_LINK_STATE_VERIFICATION_ERROR + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS + - APPLICATION_LINK_STATE_TIMED_OUT + default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + description: >- + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just been + initialized + - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified + - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully + - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification + title: |- + ApplicationLinkState defines if an application link is in the following + states: STARTED, ERRORED, SUCCESSFUL, TIMED_OUT + desmos.profiles.v2.BioParams: + type: object + properties: + max_length: + type: string + format: byte + title: BioParams defines the parameters related to profile biography + desmos.profiles.v2.ChainConfig: + type: object + properties: + name: + type: string + description: ChainConfig contains the data of the chain with which the link is made. + desmos.profiles.v2.ChainLink: + type: object + properties: + user: + type: string + title: User defines the destination profile address to link + address: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + Address contains the data of the external chain address to be + connected + + with the Desmos profile + proof: + title: Proof contains the ownership proof of the external chain address + type: object + properties: + pub_key: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might + be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + PubKey represents the public key associated with the address for + which to + + prove the ownership + signature: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. - is set. - reverse: - type: boolean - format: boolean + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might + be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + Signature represents the hex-encoded signature of the PlainText + value + plain_text: + type: string + title: >- + PlainText represents the hex-encoded value signed in order to + produce the + + Signature + chain_config: + title: ChainConfig contains the configuration of the external chain + type: object + properties: + name: + type: string description: >- - reverse is set to true if results are to be returned in the descending - order. + ChainConfig contains the data of the chain with which the link is + made. + creation_time: + type: string + format: date-time + title: CreationTime represents the time in which the link has been created + title: |- + ChainLink contains the data representing either an inter- or cross- chain + link + desmos.profiles.v2.DTagParams: + type: object + properties: + reg_ex: + type: string + min_length: + type: string + format: byte + max_length: + type: string + format: byte + title: DTagParams defines the parameters related to profile DTags + desmos.profiles.v2.DTagTransferRequest: + type: object + properties: + dtag_to_trade: + type: string + title: >- + DTagToTrade contains the value of the DTag that should be transferred + from + + the receiver of the request to the sender + sender: + type: string + title: Sender represents the address of the account that sent the request + receiver: + type: string + title: >- + Receiver represents the receiver of the request that, if accepted, + will + + give to the sender their DTag + title: DTagTransferRequest represent a DTag transfer request between two users + desmos.profiles.v2.Data: + type: object + properties: + application: + type: string + title: 'The application name (eg. Twitter, GitHub, etc)' + username: + type: string + title: 'Username on the application (eg. Twitter tag, GitHub profile, etc)' + title: |- + Data contains the data associated to a specific user of a + generic centralized application + desmos.profiles.v2.NicknameParams: + type: object + properties: + min_length: + type: string + format: byte + max_length: + type: string + format: byte + title: NicknameParams defines the parameters related to the profiles nicknames + desmos.profiles.v2.OracleParams: + type: object + properties: + script_id: + type: string + format: uint64 + title: >- + ScriptID represents the ID of the oracle script to be called to verify + the + + data + ask_count: + type: string + format: uint64 + title: >- + AskCount represents the number of oracles to which ask to verify the + data + min_count: + type: string + format: uint64 + title: >- + MinCount represents the minimum count of oracles that should complete + the + + verification successfully + prepare_gas: + type: string + format: uint64 + title: >- + PrepareGas represents the amount of gas to be used during the + preparation + + stage of the oracle script + execute_gas: + type: string + format: uint64 + title: >- + ExecuteGas represents the amount of gas to be used during the + execution of + + the oracle script + fee_amount: + type: array + items: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. + title: >- + FeeAmount represents the amount of fees to be payed in order to + execute the - Since: cosmos-sdk 0.43 - description: |- - message SomeRequest { - Foo some_parameter = 1; - PageRequest pagination = 2; - } + oracle script title: |- - PageRequest is to be embedded in gRPC request messages for efficient - pagination. Ex: - cosmos.base.query.v1beta1.PageResponse: + OracleParams defines the parameters related to the oracle + that will be used to verify the ownership of a centralized + application account by a Desmos profile + desmos.profiles.v2.OracleRequest: type: object properties: - next_key: + id: type: string - format: byte - title: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: + format: uint64 + title: ID is the ID of the request + oracle_script_id: type: string format: uint64 - title: |- - total is total number of results available if PageRequest.count_total - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. + title: OracleScriptID is ID of an oracle script + call_data: + title: CallData contains the data used to perform the oracle request + type: object + properties: + application: + type: string + title: The application for which the ownership should be verified + call_data: + type: string + title: >- + The hex encoded call data that should be used to verify the + application - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - cosmos.base.v1beta1.Coin: + account ownership + client_id: + type: string + title: >- + ClientID represents the ID of the client that has called the oracle + script + title: |- + OracleRequest represents a generic oracle request used to + verify the ownership of a centralized application account + desmos.profiles.v2.OracleRequest.CallData: type: object properties: - denom: + application: type: string - amount: + title: The application for which the ownership should be verified + call_data: type: string - description: |- - Coin defines a token with a denomination and an amount. + title: >- + The hex encoded call data that should be used to verify the + application - NOTE: The amount field is an Int which implements the custom method - signatures required by gogoproto. - desmos.profiles.v2.ApplicationLink: + account ownership + title: |- + CallData contains the data sent to a single oracle request in order to + verify the ownership of a centralized application by a Desmos profile + desmos.profiles.v2.Params: type: object properties: - user: - type: string - title: User to which the link is associated - data: - title: Data contains the details of this specific link + nickname: type: object properties: - application: + min_length: type: string - title: 'The application name (eg. Twitter, GitHub, etc)' - username: + format: byte + max_length: type: string - title: 'Username on the application (eg. Twitter tag, GitHub profile, etc)' - state: - title: State of the link - type: string - enum: - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - - APPLICATION_LINK_STATE_VERIFICATION_STARTED - - APPLICATION_LINK_STATE_VERIFICATION_ERROR - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS - - APPLICATION_LINK_STATE_TIMED_OUT - default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - description: >- - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just been - initialized - - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified - - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully - - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification - oracle_request: - title: OracleRequest represents the request that has been made to the oracle + format: byte + title: >- + NicknameParams defines the parameters related to the profiles + nicknames + dtag: type: object properties: - id: + reg_ex: + type: string + min_length: + type: string + format: byte + max_length: + type: string + format: byte + title: DTagParams defines the parameters related to profile DTags + bio: + type: object + properties: + max_length: + type: string + format: byte + title: BioParams defines the parameters related to profile biography + oracle: + type: object + properties: + script_id: type: string format: uint64 - title: ID is the ID of the request - oracle_script_id: + title: >- + ScriptID represents the ID of the oracle script to be called to + verify the + + data + ask_count: type: string format: uint64 - title: OracleScriptID is ID of an oracle script - call_data: - title: CallData contains the data used to perform the oracle request - type: object - properties: - application: - type: string - title: The application for which the ownership should be verified - call_data: - type: string - title: >- - The hex encoded call data that should be used to verify the - application + title: >- + AskCount represents the number of oracles to which ask to verify + the data + min_count: + type: string + format: uint64 + title: >- + MinCount represents the minimum count of oracles that should + complete the - account ownership - client_id: + verification successfully + prepare_gas: type: string + format: uint64 title: >- - ClientID represents the ID of the client that has called the - oracle script - result: - title: |- - Data coming from the result of the verification. - Only available when the state is STATE_SUCCESS - type: object - properties: - success: - title: Success represents a successful verification - type: object - properties: - value: - type: string - title: Hex-encoded value that has be signed by the profile - signature: - type: string - title: >- - Hex-encoded signature that has been produced by signing the - value - failed: - title: Failed represents a failed verification - type: object - properties: - error: - type: string - title: Error that is associated with the failure - creation_time: - type: string - format: date-time - title: CreationTime represents the time in which the link was created - title: ApplicationLink contains the data of a link to a centralized application - desmos.profiles.v2.ApplicationLinkState: - type: string - enum: - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - - APPLICATION_LINK_STATE_VERIFICATION_STARTED - - APPLICATION_LINK_STATE_VERIFICATION_ERROR - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS - - APPLICATION_LINK_STATE_TIMED_OUT - default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - description: >- - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just been - initialized - - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified - - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully - - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification - title: |- - ApplicationLinkState defines if an application link is in the following - states: STARTED, ERRORED, SUCCESSFUL, TIMED_OUT - desmos.profiles.v2.BioParams: - type: object - properties: - max_length: - type: string - format: byte - title: BioParams defines the parameters related to profile biography - desmos.profiles.v2.ChainConfig: - type: object - properties: - name: - type: string - description: ChainConfig contains the data of the chain with which the link is made. - desmos.profiles.v2.ChainLink: + PrepareGas represents the amount of gas to be used during the + preparation + + stage of the oracle script + execute_gas: + type: string + format: uint64 + title: >- + ExecuteGas represents the amount of gas to be used during the + execution of + + the oracle script + fee_amount: + type: array + items: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + title: >- + FeeAmount represents the amount of fees to be payed in order to + execute the + + oracle script + title: |- + OracleParams defines the parameters related to the oracle + that will be used to verify the ownership of a centralized + application account by a Desmos profile + title: Params contains the parameters for the profiles module + desmos.profiles.v2.Proof: type: object properties: - user: - type: string - title: User defines the destination profile address to link - address: + pub_key: type: object properties: type_url: @@ -5634,1108 +8313,1118 @@ definitions: "value": "1.212s" } title: >- - Address contains the data of the external chain address to be - connected + PubKey represents the public key associated with the address for which + to - with the Desmos profile - proof: - title: Proof contains the ownership proof of the external chain address + prove the ownership + signature: type: object properties: - pub_key: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - protocol buffer message. This string must contain at least + protocol buffer message. This string must contain at least - one "/" character. The last segment of the URL's path must - represent + one "/" character. The last segment of the URL's path must + represent - the fully qualified name of the type (as in + the fully qualified name of the type (as in - `path/google.protobuf.Duration`). The name should be in a - canonical form + `path/google.protobuf.Duration`). The name should be in a + canonical form - (e.g., leading "." is not accepted). + (e.g., leading "." is not accepted). - In practice, teams usually precompile into the binary all - types that they + In practice, teams usually precompile into the binary all types + that they - expect it to use in the context of Any. However, for URLs - which use the + expect it to use in the context of Any. However, for URLs which + use the - scheme `http`, `https`, or no scheme, one can optionally set - up a type + scheme `http`, `https`, or no scheme, one can optionally set up a + type - server that maps type URLs to message definitions as follows: + server that maps type URLs to message definitions as follows: - * If no scheme is provided, `https` is assumed. + * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on - the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - Note: this functionality is not currently available in the - official + Note: this functionality is not currently available in the + official - protobuf release, and it is not used for type URLs beginning - with + protobuf release, and it is not used for type URLs beginning with - type.googleapis.com. + type.googleapis.com. - Schemes other than `http`, `https` (or the empty scheme) might - be + Schemes other than `http`, `https` (or the empty scheme) might be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. + used with implementation specific semantics. + value: + type: string + format: byte description: >- - `Any` contains an arbitrary serialized protocol buffer message - along with a - - URL that describes the type of the serialized message. - - - Protobuf library provides support to pack/unpack Any values in the - form - - of utility functions or additional generated methods of the Any + Must be a valid serialized protocol buffer of the above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + URL that describes the type of the serialized message. - Example 1: Pack and unpack a message in C++. - - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } - - Example 2: Pack and unpack a message in Java. - - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } - - Example 3: Pack and unpack a message in Python. - - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - - Example 4: Pack and unpack a message in Go - - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } - The pack methods provided by protobuf library will by default use + Protobuf library provides support to pack/unpack Any values in the + form - 'type.googleapis.com/full.type.name' as the type URL and the - unpack + of utility functions or additional generated methods of the Any type. - methods only use the fully qualified type name after the last '/' - in the type URL, for example "foo.bar.com/x/y.z" will yield type + Example 1: Pack and unpack a message in C++. - name "y.z". + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + Example 2: Pack and unpack a message in Java. + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - JSON + Example 3: Pack and unpack a message in Python. - ==== + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - The JSON representation of an `Any` value uses the regular + Example 4: Pack and unpack a message in Go - representation of the deserialized, embedded message, with an + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - additional field `@type` which contains the type URL. Example: + The pack methods provided by protobuf library will by default use - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + 'type.googleapis.com/full.type.name' as the type URL and the unpack - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + methods only use the fully qualified type name after the last '/' - If the embedded message type is well-known and has a custom JSON + in the type URL, for example "foo.bar.com/x/y.z" will yield type - representation, that representation will be embedded adding a - field + name "y.z". - `value` which holds the custom JSON in addition to the `@type` - field. Example (for message [google.protobuf.Duration][]): - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - PubKey represents the public key associated with the address for - which to + JSON - prove the ownership - signature: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + ==== - protocol buffer message. This string must contain at least + The JSON representation of an `Any` value uses the regular - one "/" character. The last segment of the URL's path must - represent + representation of the deserialized, embedded message, with an - the fully qualified name of the type (as in + additional field `@type` which contains the type URL. Example: - `path/google.protobuf.Duration`). The name should be in a - canonical form + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - (e.g., leading "." is not accepted). + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + If the embedded message type is well-known and has a custom JSON - In practice, teams usually precompile into the binary all - types that they + representation, that representation will be embedded adding a field - expect it to use in the context of Any. However, for URLs - which use the + `value` which holds the custom JSON in addition to the `@type` - scheme `http`, `https`, or no scheme, one can optionally set - up a type + field. Example (for message [google.protobuf.Duration][]): - server that maps type URLs to message definitions as follows: + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Signature represents the hex-encoded signature of the PlainText value + plain_text: + type: string + title: >- + PlainText represents the hex-encoded value signed in order to produce + the + Signature + title: |- + Proof contains all the data used to verify a signature when linking an + account to a profile + desmos.profiles.v2.QueryApplicationLinkByClientIDResponse: + type: object + properties: + link: + type: object + properties: + user: + type: string + title: User to which the link is associated + data: + title: Data contains the details of this specific link + type: object + properties: + application: + type: string + title: 'The application name (eg. Twitter, GitHub, etc)' + username: + type: string + title: >- + Username on the application (eg. Twitter tag, GitHub profile, + etc) + state: + title: State of the link + type: string + enum: + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + - APPLICATION_LINK_STATE_VERIFICATION_STARTED + - APPLICATION_LINK_STATE_VERIFICATION_ERROR + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS + - APPLICATION_LINK_STATE_TIMED_OUT + default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + description: >- + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just + been initialized + - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified + - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully + - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification + oracle_request: + title: >- + OracleRequest represents the request that has been made to the + oracle + type: object + properties: + id: + type: string + format: uint64 + title: ID is the ID of the request + oracle_script_id: + type: string + format: uint64 + title: OracleScriptID is ID of an oracle script + call_data: + title: CallData contains the data used to perform the oracle request + type: object + properties: + application: + type: string + title: The application for which the ownership should be verified + call_data: + type: string + title: >- + The hex encoded call data that should be used to verify + the application - * If no scheme is provided, `https` is assumed. + account ownership + client_id: + type: string + title: >- + ClientID represents the ID of the client that has called the + oracle script + result: + title: |- + Data coming from the result of the verification. + Only available when the state is STATE_SUCCESS + type: object + properties: + success: + title: Success represents a successful verification + type: object + properties: + value: + type: string + title: Hex-encoded value that has be signed by the profile + signature: + type: string + title: >- + Hex-encoded signature that has been produced by signing + the value + failed: + title: Failed represents a failed verification + type: object + properties: + error: + type: string + title: Error that is associated with the failure + creation_time: + type: string + format: date-time + title: CreationTime represents the time in which the link was created + title: >- + ApplicationLink contains the data of a link to a centralized + application + title: |- + QueryApplicationLinkByClientIDResponse contains the data returned by the + request allowing to get an application link using a client id + desmos.profiles.v2.QueryApplicationLinkOwnersResponse: + type: object + properties: + owners: + type: array + items: + type: object + properties: + user: + type: string + application: + type: string + username: + type: string + title: >- + ApplicationLinkOwnerDetails contains the details of a single + application - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on - the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + link owner + title: Addresses of the application links owners + pagination: + title: Pagination defines the pagination response + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - Note: this functionality is not currently available in the - official + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: >- + QueryApplicationLinkOwnersResponse contains the data returned by the + request + + allowing to get application link owners. + desmos.profiles.v2.QueryApplicationLinkOwnersResponse.ApplicationLinkOwnerDetails: + type: object + properties: + user: + type: string + application: + type: string + username: + type: string + title: |- + ApplicationLinkOwnerDetails contains the details of a single application + link owner + desmos.profiles.v2.QueryApplicationLinksResponse: + type: object + properties: + links: + type: array + items: + type: object + properties: + user: + type: string + title: User to which the link is associated + data: + title: Data contains the details of this specific link + type: object + properties: + application: + type: string + title: 'The application name (eg. Twitter, GitHub, etc)' + username: + type: string + title: >- + Username on the application (eg. Twitter tag, GitHub + profile, etc) + state: + title: State of the link + type: string + enum: + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + - APPLICATION_LINK_STATE_VERIFICATION_STARTED + - APPLICATION_LINK_STATE_VERIFICATION_ERROR + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS + - APPLICATION_LINK_STATE_TIMED_OUT + default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED + description: >- + - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has + just been initialized + - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified + - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process + - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully + - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification + oracle_request: + title: >- + OracleRequest represents the request that has been made to the + oracle + type: object + properties: + id: + type: string + format: uint64 + title: ID is the ID of the request + oracle_script_id: + type: string + format: uint64 + title: OracleScriptID is ID of an oracle script + call_data: + title: >- + CallData contains the data used to perform the oracle + request + type: object + properties: + application: + type: string + title: >- + The application for which the ownership should be + verified + call_data: + type: string + title: >- + The hex encoded call data that should be used to verify + the application - protobuf release, and it is not used for type URLs beginning - with + account ownership + client_id: + type: string + title: >- + ClientID represents the ID of the client that has called the + oracle script + result: + title: |- + Data coming from the result of the verification. + Only available when the state is STATE_SUCCESS + type: object + properties: + success: + title: Success represents a successful verification + type: object + properties: + value: + type: string + title: Hex-encoded value that has be signed by the profile + signature: + type: string + title: >- + Hex-encoded signature that has been produced by signing + the value + failed: + title: Failed represents a failed verification + type: object + properties: + error: + type: string + title: Error that is associated with the failure + creation_time: + type: string + format: date-time + title: CreationTime represents the time in which the link was created + title: >- + ApplicationLink contains the data of a link to a centralized + application + pagination: + title: Pagination defines the pagination response + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - type.googleapis.com. + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryApplicationLinksResponse represents the response to the query used + to get the application links for a specific user + desmos.profiles.v2.QueryChainLinkOwnersResponse: + type: object + properties: + owners: + type: array + items: + type: object + properties: + user: + type: string + chain_name: + type: string + target: + type: string + title: >- + ChainLinkOwnerDetails contains the details of a single chain link + owner + title: Addresses of the chain links owners + pagination: + title: Pagination defines the pagination response + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - Schemes other than `http`, `https` (or the empty scheme) might - be + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message - along with a + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: |- + QueryChainLinkOwnersResponse contains the data returned by the request + allowing to get chain link owners. + desmos.profiles.v2.QueryChainLinkOwnersResponse.ChainLinkOwnerDetails: + type: object + properties: + user: + type: string + chain_name: + type: string + target: + type: string + title: ChainLinkOwnerDetails contains the details of a single chain link owner + desmos.profiles.v2.QueryChainLinksResponse: + type: object + properties: + links: + type: array + items: + type: object + properties: + user: + type: string + title: User defines the destination profile address to link + address: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - URL that describes the type of the serialized message. + protocol buffer message. This string must contain at least + one "/" character. The last segment of the URL's path must + represent - Protobuf library provides support to pack/unpack Any values in the - form + the fully qualified name of the type (as in - of utility functions or additional generated methods of the Any - type. + `path/google.protobuf.Duration`). The name should be in a + canonical form + (e.g., leading "." is not accepted). - Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + In practice, teams usually precompile into the binary all + types that they - Example 2: Pack and unpack a message in Java. + expect it to use in the context of Any. However, for URLs + which use the - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + scheme `http`, `https`, or no scheme, one can optionally set + up a type - Example 3: Pack and unpack a message in Python. + server that maps type URLs to message definitions as + follows: - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - Example 4: Pack and unpack a message in Go + * If no scheme is provided, `https` is assumed. - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - The pack methods provided by protobuf library will by default use + Note: this functionality is not currently available in the + official - 'type.googleapis.com/full.type.name' as the type URL and the - unpack + protobuf release, and it is not used for type URLs beginning + with - methods only use the fully qualified type name after the last '/' + type.googleapis.com. - in the type URL, for example "foo.bar.com/x/y.z" will yield type - name "y.z". + Schemes other than `http`, `https` (or the empty scheme) + might be + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + URL that describes the type of the serialized message. - JSON - ==== + Protobuf library provides support to pack/unpack Any values in + the form - The JSON representation of an `Any` value uses the regular + of utility functions or additional generated methods of the Any + type. - representation of the deserialized, embedded message, with an - additional field `@type` which contains the type URL. Example: + Example 1: Pack and unpack a message in C++. - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + Example 2: Pack and unpack a message in Java. - If the embedded message type is well-known and has a custom JSON + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - representation, that representation will be embedded adding a - field + Example 3: Pack and unpack a message in Python. - `value` which holds the custom JSON in addition to the `@type` + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - field. Example (for message [google.protobuf.Duration][]): + Example 4: Pack and unpack a message in Go - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - Signature represents the hex-encoded signature of the PlainText - value - plain_text: - type: string - title: >- - PlainText represents the hex-encoded value signed in order to - produce the + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - Signature - chain_config: - title: ChainConfig contains the configuration of the external chain - type: object - properties: - name: - type: string - description: >- - ChainConfig contains the data of the chain with which the link is - made. - creation_time: - type: string - format: date-time - title: CreationTime represents the time in which the link has been created - title: |- - ChainLink contains the data representing either an inter- or cross- chain - link - desmos.profiles.v2.DTagParams: - type: object - properties: - reg_ex: - type: string - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: DTagParams defines the parameters related to profile DTags - desmos.profiles.v2.DTagTransferRequest: - type: object - properties: - dtag_to_trade: - type: string - title: >- - DTagToTrade contains the value of the DTag that should be transferred - from + The pack methods provided by protobuf library will by default + use - the receiver of the request to the sender - sender: - type: string - title: Sender represents the address of the account that sent the request - receiver: - type: string - title: >- - Receiver represents the receiver of the request that, if accepted, - will + 'type.googleapis.com/full.type.name' as the type URL and the + unpack - give to the sender their DTag - title: DTagTransferRequest represent a DTag transfer request between two users - desmos.profiles.v2.Data: - type: object - properties: - application: - type: string - title: 'The application name (eg. Twitter, GitHub, etc)' - username: - type: string - title: 'Username on the application (eg. Twitter tag, GitHub profile, etc)' - title: |- - Data contains the data associated to a specific user of a - generic centralized application - desmos.profiles.v2.NicknameParams: - type: object - properties: - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: NicknameParams defines the parameters related to the profiles nicknames - desmos.profiles.v2.OracleParams: - type: object - properties: - script_id: - type: string - format: uint64 - title: >- - ScriptID represents the ID of the oracle script to be called to verify - the + methods only use the fully qualified type name after the last + '/' - data - ask_count: - type: string - format: uint64 - title: >- - AskCount represents the number of oracles to which ask to verify the - data - min_count: - type: string - format: uint64 - title: >- - MinCount represents the minimum count of oracles that should complete - the + in the type URL, for example "foo.bar.com/x/y.z" will yield type - verification successfully - prepare_gas: - type: string - format: uint64 - title: >- - PrepareGas represents the amount of gas to be used during the - preparation + name "y.z". - stage of the oracle script - execute_gas: - type: string - format: uint64 - title: >- - ExecuteGas represents the amount of gas to be used during the - execution of - the oracle script - fee_amount: - type: array - items: - type: object - properties: - denom: - type: string - amount: - type: string - description: |- - Coin defines a token with a denomination and an amount. - NOTE: The amount field is an Int which implements the custom method - signatures required by gogoproto. - title: >- - FeeAmount represents the amount of fees to be payed in order to - execute the + JSON - oracle script - title: |- - OracleParams defines the parameters related to the oracle - that will be used to verify the ownership of a centralized - application account by a Desmos profile - desmos.profiles.v2.OracleRequest: - type: object - properties: - id: - type: string - format: uint64 - title: ID is the ID of the request - oracle_script_id: - type: string - format: uint64 - title: OracleScriptID is ID of an oracle script - call_data: - title: CallData contains the data used to perform the oracle request - type: object - properties: - application: - type: string - title: The application for which the ownership should be verified - call_data: - type: string - title: >- - The hex encoded call data that should be used to verify the - application + ==== - account ownership - client_id: - type: string - title: >- - ClientID represents the ID of the client that has called the oracle - script - title: |- - OracleRequest represents a generic oracle request used to - verify the ownership of a centralized application account - desmos.profiles.v2.OracleRequest.CallData: - type: object - properties: - application: - type: string - title: The application for which the ownership should be verified - call_data: - type: string - title: >- - The hex encoded call data that should be used to verify the - application + The JSON representation of an `Any` value uses the regular - account ownership - title: |- - CallData contains the data sent to a single oracle request in order to - verify the ownership of a centralized application by a Desmos profile - desmos.profiles.v2.Params: - type: object - properties: - nickname: - type: object - properties: - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: >- - NicknameParams defines the parameters related to the profiles - nicknames - dtag: - type: object - properties: - reg_ex: - type: string - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: DTagParams defines the parameters related to profile DTags - bio: - type: object - properties: - max_length: - type: string - format: byte - title: BioParams defines the parameters related to profile biography - oracle: - type: object - properties: - script_id: - type: string - format: uint64 - title: >- - ScriptID represents the ID of the oracle script to be called to - verify the + representation of the deserialized, embedded message, with an - data - ask_count: - type: string - format: uint64 - title: >- - AskCount represents the number of oracles to which ask to verify - the data - min_count: - type: string - format: uint64 - title: >- - MinCount represents the minimum count of oracles that should - complete the + additional field `@type` which contains the type URL. Example: - verification successfully - prepare_gas: - type: string - format: uint64 - title: >- - PrepareGas represents the amount of gas to be used during the - preparation + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - stage of the oracle script - execute_gas: - type: string - format: uint64 - title: >- - ExecuteGas represents the amount of gas to be used during the - execution of + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - the oracle script - fee_amount: - type: array - items: - type: object - properties: - denom: - type: string - amount: - type: string - description: >- - Coin defines a token with a denomination and an amount. + If the embedded message type is well-known and has a custom JSON + representation, that representation will be embedded adding a + field - NOTE: The amount field is an Int which implements the custom - method + `value` which holds the custom JSON in addition to the `@type` - signatures required by gogoproto. - title: >- - FeeAmount represents the amount of fees to be payed in order to - execute the + field. Example (for message [google.protobuf.Duration][]): - oracle script - title: |- - OracleParams defines the parameters related to the oracle - that will be used to verify the ownership of a centralized - application account by a Desmos profile - title: Params contains the parameters for the profiles module - desmos.profiles.v2.Proof: - type: object - properties: - pub_key: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + Address contains the data of the external chain address to be + connected - protocol buffer message. This string must contain at least + with the Desmos profile + proof: + title: Proof contains the ownership proof of the external chain address + type: object + properties: + pub_key: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized - one "/" character. The last segment of the URL's path must - represent + protocol buffer message. This string must contain at + least - the fully qualified name of the type (as in + one "/" character. The last segment of the URL's path + must represent - `path/google.protobuf.Duration`). The name should be in a - canonical form + the fully qualified name of the type (as in - (e.g., leading "." is not accepted). + `path/google.protobuf.Duration`). The name should be in + a canonical form + (e.g., leading "." is not accepted). - In practice, teams usually precompile into the binary all types - that they - expect it to use in the context of Any. However, for URLs which - use the + In practice, teams usually precompile into the binary + all types that they - scheme `http`, `https`, or no scheme, one can optionally set up a - type + expect it to use in the context of Any. However, for + URLs which use the - server that maps type URLs to message definitions as follows: + scheme `http`, `https`, or no scheme, one can optionally + set up a type + server that maps type URLs to message definitions as + follows: - * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + * If no scheme is provided, `https` is assumed. - Note: this functionality is not currently available in the - official + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - protobuf release, and it is not used for type URLs beginning with + Note: this functionality is not currently available in + the official - type.googleapis.com. + protobuf release, and it is not used for type URLs + beginning with + type.googleapis.com. - Schemes other than `http`, `https` (or the empty scheme) might be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above specified - type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message along - with a + Schemes other than `http`, `https` (or the empty scheme) + might be - URL that describes the type of the serialized message. + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + URL that describes the type of the serialized message. - Protobuf library provides support to pack/unpack Any values in the - form - of utility functions or additional generated methods of the Any type. + Protobuf library provides support to pack/unpack Any values + in the form + of utility functions or additional generated methods of the + Any type. - Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + Example 1: Pack and unpack a message in C++. - Example 2: Pack and unpack a message in Java. + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Example 2: Pack and unpack a message in Java. - Example 3: Pack and unpack a message in Python. + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + Example 3: Pack and unpack a message in Python. - Example 4: Pack and unpack a message in Go + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + Example 4: Pack and unpack a message in Go - The pack methods provided by protobuf library will by default use + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - 'type.googleapis.com/full.type.name' as the type URL and the unpack + The pack methods provided by protobuf library will by + default use - methods only use the fully qualified type name after the last '/' + 'type.googleapis.com/full.type.name' as the type URL and the + unpack - in the type URL, for example "foo.bar.com/x/y.z" will yield type + methods only use the fully qualified type name after the + last '/' - name "y.z". + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + name "y.z". - JSON - ==== + JSON - The JSON representation of an `Any` value uses the regular + ==== - representation of the deserialized, embedded message, with an + The JSON representation of an `Any` value uses the regular - additional field `@type` which contains the type URL. Example: + representation of the deserialized, embedded message, with + an - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + additional field `@type` which contains the type URL. + Example: - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - If the embedded message type is well-known and has a custom JSON + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - representation, that representation will be embedded adding a field + If the embedded message type is well-known and has a custom + JSON - `value` which holds the custom JSON in addition to the `@type` + representation, that representation will be embedded adding + a field - field. Example (for message [google.protobuf.Duration][]): + `value` which holds the custom JSON in addition to the + `@type` - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - PubKey represents the public key associated with the address for which - to + field. Example (for message [google.protobuf.Duration][]): - prove the ownership - signature: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + PubKey represents the public key associated with the address + for which to - protocol buffer message. This string must contain at least + prove the ownership + signature: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized - one "/" character. The last segment of the URL's path must - represent + protocol buffer message. This string must contain at + least - the fully qualified name of the type (as in + one "/" character. The last segment of the URL's path + must represent - `path/google.protobuf.Duration`). The name should be in a - canonical form + the fully qualified name of the type (as in - (e.g., leading "." is not accepted). + `path/google.protobuf.Duration`). The name should be in + a canonical form + (e.g., leading "." is not accepted). - In practice, teams usually precompile into the binary all types - that they - expect it to use in the context of Any. However, for URLs which - use the + In practice, teams usually precompile into the binary + all types that they - scheme `http`, `https`, or no scheme, one can optionally set up a - type + expect it to use in the context of Any. However, for + URLs which use the - server that maps type URLs to message definitions as follows: + scheme `http`, `https`, or no scheme, one can optionally + set up a type + server that maps type URLs to message definitions as + follows: - * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + * If no scheme is provided, `https` is assumed. - Note: this functionality is not currently available in the - official + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - protobuf release, and it is not used for type URLs beginning with + Note: this functionality is not currently available in + the official - type.googleapis.com. + protobuf release, and it is not used for type URLs + beginning with + type.googleapis.com. - Schemes other than `http`, `https` (or the empty scheme) might be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above specified - type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message along - with a + Schemes other than `http`, `https` (or the empty scheme) + might be - URL that describes the type of the serialized message. + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + URL that describes the type of the serialized message. - Protobuf library provides support to pack/unpack Any values in the - form - of utility functions or additional generated methods of the Any type. + Protobuf library provides support to pack/unpack Any values + in the form + of utility functions or additional generated methods of the + Any type. - Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + Example 1: Pack and unpack a message in C++. - Example 2: Pack and unpack a message in Java. + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Example 2: Pack and unpack a message in Java. - Example 3: Pack and unpack a message in Python. + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + Example 3: Pack and unpack a message in Python. - Example 4: Pack and unpack a message in Go + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + Example 4: Pack and unpack a message in Go - The pack methods provided by protobuf library will by default use + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - 'type.googleapis.com/full.type.name' as the type URL and the unpack + The pack methods provided by protobuf library will by + default use - methods only use the fully qualified type name after the last '/' + 'type.googleapis.com/full.type.name' as the type URL and the + unpack - in the type URL, for example "foo.bar.com/x/y.z" will yield type + methods only use the fully qualified type name after the + last '/' - name "y.z". + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + name "y.z". - JSON - ==== + JSON - The JSON representation of an `Any` value uses the regular + ==== - representation of the deserialized, embedded message, with an + The JSON representation of an `Any` value uses the regular - additional field `@type` which contains the type URL. Example: + representation of the deserialized, embedded message, with + an - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + additional field `@type` which contains the type URL. + Example: - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - If the embedded message type is well-known and has a custom JSON + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - representation, that representation will be embedded adding a field + If the embedded message type is well-known and has a custom + JSON - `value` which holds the custom JSON in addition to the `@type` + representation, that representation will be embedded adding + a field - field. Example (for message [google.protobuf.Duration][]): + `value` which holds the custom JSON in addition to the + `@type` - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: Signature represents the hex-encoded signature of the PlainText value - plain_text: - type: string - title: >- - PlainText represents the hex-encoded value signed in order to produce - the + field. Example (for message [google.protobuf.Duration][]): - Signature - title: |- - Proof contains all the data used to verify a signature when linking an - account to a profile - desmos.profiles.v2.QueryApplicationLinkByClientIDResponse: - type: object - properties: - link: - type: object - properties: - user: - type: string - title: User to which the link is associated - data: - title: Data contains the details of this specific link - type: object - properties: - application: - type: string - title: 'The application name (eg. Twitter, GitHub, etc)' - username: - type: string - title: >- - Username on the application (eg. Twitter tag, GitHub profile, - etc) - state: - title: State of the link - type: string - enum: - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - - APPLICATION_LINK_STATE_VERIFICATION_STARTED - - APPLICATION_LINK_STATE_VERIFICATION_ERROR - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS - - APPLICATION_LINK_STATE_TIMED_OUT - default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - description: >- - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has just - been initialized - - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified - - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully - - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification - oracle_request: - title: >- - OracleRequest represents the request that has been made to the - oracle - type: object - properties: - id: - type: string - format: uint64 - title: ID is the ID of the request - oracle_script_id: - type: string - format: uint64 - title: OracleScriptID is ID of an oracle script - call_data: - title: CallData contains the data used to perform the oracle request - type: object - properties: - application: - type: string - title: The application for which the ownership should be verified - call_data: - type: string - title: >- - The hex encoded call data that should be used to verify - the application + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + Signature represents the hex-encoded signature of the + PlainText value + plain_text: + type: string + title: >- + PlainText represents the hex-encoded value signed in order + to produce the - account ownership - client_id: - type: string - title: >- - ClientID represents the ID of the client that has called the - oracle script - result: - title: |- - Data coming from the result of the verification. - Only available when the state is STATE_SUCCESS - type: object - properties: - success: - title: Success represents a successful verification - type: object - properties: - value: - type: string - title: Hex-encoded value that has be signed by the profile - signature: - type: string - title: >- - Hex-encoded signature that has been produced by signing - the value - failed: - title: Failed represents a failed verification - type: object - properties: - error: - type: string - title: Error that is associated with the failure - creation_time: - type: string - format: date-time - title: CreationTime represents the time in which the link was created - title: >- - ApplicationLink contains the data of a link to a centralized - application - title: |- - QueryApplicationLinkByClientIDResponse contains the data returned by the - request allowing to get an application link using a client id - desmos.profiles.v2.QueryApplicationLinkOwnersResponse: - type: object - properties: - owners: - type: array - items: - type: object - properties: - user: - type: string - application: - type: string - username: + Signature + chain_config: + title: ChainConfig contains the configuration of the external chain + type: object + properties: + name: + type: string + description: >- + ChainConfig contains the data of the chain with which the link + is made. + creation_time: type: string + format: date-time + title: >- + CreationTime represents the time in which the link has been + created title: >- - ApplicationLinkOwnerDetails contains the details of a single - application + ChainLink contains the data representing either an inter- or cross- + chain - link owner - title: Addresses of the application links owners + link pagination: title: Pagination defines the pagination response type: object @@ -6762,132 +9451,44 @@ definitions: repeated Bar results = 1; PageResponse page = 2; } - description: >- - QueryApplicationLinkOwnersResponse contains the data returned by the - request - - allowing to get application link owners. - desmos.profiles.v2.QueryApplicationLinkOwnersResponse.ApplicationLinkOwnerDetails: - type: object - properties: - user: - type: string - application: - type: string - username: - type: string - title: |- - ApplicationLinkOwnerDetails contains the details of a single application - link owner - desmos.profiles.v2.QueryApplicationLinksResponse: + description: |- + QueryChainLinksResponse is the response type for the + Query/ChainLinks RPC method. + desmos.profiles.v2.QueryIncomingDTagTransferRequestsResponse: type: object properties: - links: + requests: type: array items: type: object properties: - user: - type: string - title: User to which the link is associated - data: - title: Data contains the details of this specific link - type: object - properties: - application: - type: string - title: 'The application name (eg. Twitter, GitHub, etc)' - username: - type: string - title: >- - Username on the application (eg. Twitter tag, GitHub - profile, etc) - state: - title: State of the link + dtag_to_trade: type: string - enum: - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - - APPLICATION_LINK_STATE_VERIFICATION_STARTED - - APPLICATION_LINK_STATE_VERIFICATION_ERROR - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS - - APPLICATION_LINK_STATE_TIMED_OUT - default: APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED - description: >- - - APPLICATION_LINK_STATE_INITIALIZED_UNSPECIFIED: A link has - just been initialized - - APPLICATION_LINK_STATE_VERIFICATION_STARTED: A link has just started being verified - - APPLICATION_LINK_STATE_VERIFICATION_ERROR: A link has errored during the verification process - - APPLICATION_LINK_STATE_VERIFICATION_SUCCESS: A link has being verified successfully - - APPLICATION_LINK_STATE_TIMED_OUT: A link has timed out while waiting for the verification - oracle_request: title: >- - OracleRequest represents the request that has been made to the - oracle - type: object - properties: - id: - type: string - format: uint64 - title: ID is the ID of the request - oracle_script_id: - type: string - format: uint64 - title: OracleScriptID is ID of an oracle script - call_data: - title: >- - CallData contains the data used to perform the oracle - request - type: object - properties: - application: - type: string - title: >- - The application for which the ownership should be - verified - call_data: - type: string - title: >- - The hex encoded call data that should be used to verify - the application + DTagToTrade contains the value of the DTag that should be + transferred from - account ownership - client_id: - type: string - title: >- - ClientID represents the ID of the client that has called the - oracle script - result: - title: |- - Data coming from the result of the verification. - Only available when the state is STATE_SUCCESS - type: object - properties: - success: - title: Success represents a successful verification - type: object - properties: - value: - type: string - title: Hex-encoded value that has be signed by the profile - signature: - type: string - title: >- - Hex-encoded signature that has been produced by signing - the value - failed: - title: Failed represents a failed verification - type: object - properties: - error: - type: string - title: Error that is associated with the failure - creation_time: + the receiver of the request to the sender + sender: type: string - format: date-time - title: CreationTime represents the time in which the link was created + title: >- + Sender represents the address of the account that sent the + request + receiver: + type: string + title: >- + Receiver represents the receiver of the request that, if + accepted, will + + give to the sender their DTag title: >- - ApplicationLink contains the data of a link to a centralized - application + DTagTransferRequest represent a DTag transfer request between two + users + title: >- + Requests represent the list of all the DTag transfer requests made + towards + + the user pagination: title: Pagination defines the pagination response type: object @@ -6910,710 +9511,937 @@ definitions: PageResponse is to be embedded in gRPC response messages where the corresponding request message has used PageRequest. - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - title: |- - QueryApplicationLinksResponse represents the response to the query used - to get the application links for a specific user - desmos.profiles.v2.QueryChainLinkOwnersResponse: + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: |- + QueryIncomingDTagTransferRequestsResponse is the response type for the + Query/IncomingDTagTransferRequests RPC method. + desmos.profiles.v2.QueryParamsResponse: + type: object + properties: + params: + type: object + properties: + nickname: + type: object + properties: + min_length: + type: string + format: byte + max_length: + type: string + format: byte + title: >- + NicknameParams defines the parameters related to the profiles + nicknames + dtag: + type: object + properties: + reg_ex: + type: string + min_length: + type: string + format: byte + max_length: + type: string + format: byte + title: DTagParams defines the parameters related to profile DTags + bio: + type: object + properties: + max_length: + type: string + format: byte + title: BioParams defines the parameters related to profile biography + oracle: + type: object + properties: + script_id: + type: string + format: uint64 + title: >- + ScriptID represents the ID of the oracle script to be called + to verify the + + data + ask_count: + type: string + format: uint64 + title: >- + AskCount represents the number of oracles to which ask to + verify the data + min_count: + type: string + format: uint64 + title: >- + MinCount represents the minimum count of oracles that should + complete the + + verification successfully + prepare_gas: + type: string + format: uint64 + title: >- + PrepareGas represents the amount of gas to be used during the + preparation + + stage of the oracle script + execute_gas: + type: string + format: uint64 + title: >- + ExecuteGas represents the amount of gas to be used during the + execution of + + the oracle script + fee_amount: + type: array + items: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + title: >- + FeeAmount represents the amount of fees to be payed in order + to execute the + + oracle script + title: |- + OracleParams defines the parameters related to the oracle + that will be used to verify the ownership of a centralized + application account by a Desmos profile + title: Params contains the parameters for the profiles module + description: QueryParamsResponse is the response type for the Query/Params RPC method. + desmos.profiles.v2.QueryProfileResponse: type: object properties: - owners: - type: array - items: - type: object - properties: - user: - type: string - chain_name: - type: string - target: - type: string - title: >- - ChainLinkOwnerDetails contains the details of a single chain link - owner - title: Addresses of the chain links owners - pagination: - title: Pagination defines the pagination response + profile: type: object properties: - next_key: - type: string - format: byte - title: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: + type_url: type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. - - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - description: |- - QueryChainLinkOwnersResponse contains the data returned by the request - allowing to get chain link owners. - desmos.profiles.v2.QueryChainLinkOwnersResponse.ChainLinkOwnerDetails: - type: object - properties: - user: - type: string - chain_name: - type: string - target: - type: string - title: ChainLinkOwnerDetails contains the details of a single chain link owner - desmos.profiles.v2.QueryChainLinksResponse: - type: object - properties: - links: - type: array - items: - type: object - properties: - user: - type: string - title: User defines the destination profile address to link - address: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - protocol buffer message. This string must contain at least + protocol buffer message. This string must contain at least - one "/" character. The last segment of the URL's path must - represent + one "/" character. The last segment of the URL's path must + represent - the fully qualified name of the type (as in + the fully qualified name of the type (as in - `path/google.protobuf.Duration`). The name should be in a - canonical form + `path/google.protobuf.Duration`). The name should be in a + canonical form - (e.g., leading "." is not accepted). + (e.g., leading "." is not accepted). - In practice, teams usually precompile into the binary all - types that they + In practice, teams usually precompile into the binary all types + that they - expect it to use in the context of Any. However, for URLs - which use the + expect it to use in the context of Any. However, for URLs which + use the - scheme `http`, `https`, or no scheme, one can optionally set - up a type + scheme `http`, `https`, or no scheme, one can optionally set up a + type - server that maps type URLs to message definitions as - follows: + server that maps type URLs to message definitions as follows: - * If no scheme is provided, `https` is assumed. + * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a - [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on - the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - Note: this functionality is not currently available in the - official + Note: this functionality is not currently available in the + official - protobuf release, and it is not used for type URLs beginning - with + protobuf release, and it is not used for type URLs beginning with - type.googleapis.com. + type.googleapis.com. - Schemes other than `http`, `https` (or the empty scheme) - might be + Schemes other than `http`, `https` (or the empty scheme) might be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message - along with a + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a - URL that describes the type of the serialized message. + URL that describes the type of the serialized message. - Protobuf library provides support to pack/unpack Any values in - the form + Protobuf library provides support to pack/unpack Any values in the + form - of utility functions or additional generated methods of the Any - type. + of utility functions or additional generated methods of the Any type. - Example 1: Pack and unpack a message in C++. + Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - Example 2: Pack and unpack a message in Java. + Example 2: Pack and unpack a message in Java. - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - Example 3: Pack and unpack a message in Python. + Example 3: Pack and unpack a message in Python. - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - Example 4: Pack and unpack a message in Go + Example 4: Pack and unpack a message in Go - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - The pack methods provided by protobuf library will by default - use + The pack methods provided by protobuf library will by default use - 'type.googleapis.com/full.type.name' as the type URL and the - unpack + 'type.googleapis.com/full.type.name' as the type URL and the unpack - methods only use the fully qualified type name after the last - '/' + methods only use the fully qualified type name after the last '/' - in the type URL, for example "foo.bar.com/x/y.z" will yield type + in the type URL, for example "foo.bar.com/x/y.z" will yield type - name "y.z". + name "y.z". - JSON + JSON - ==== + ==== - The JSON representation of an `Any` value uses the regular + The JSON representation of an `Any` value uses the regular - representation of the deserialized, embedded message, with an + representation of the deserialized, embedded message, with an - additional field `@type` which contains the type URL. Example: + additional field `@type` which contains the type URL. Example: - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - If the embedded message type is well-known and has a custom JSON + If the embedded message type is well-known and has a custom JSON - representation, that representation will be embedded adding a - field + representation, that representation will be embedded adding a field - `value` which holds the custom JSON in addition to the `@type` + `value` which holds the custom JSON in addition to the `@type` - field. Example (for message [google.protobuf.Duration][]): + field. Example (for message [google.protobuf.Duration][]): - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - Address contains the data of the external chain address to be - connected + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + description: >- + QueryProfileResponse is the response type for the Query/Profile RPC + method. + desmos.profiles.v2.Result: + type: object + properties: + success: + title: Success represents a successful verification + type: object + properties: + value: + type: string + title: Hex-encoded value that has be signed by the profile + signature: + type: string + title: Hex-encoded signature that has been produced by signing the value + failed: + title: Failed represents a failed verification + type: object + properties: + error: + type: string + title: Error that is associated with the failure + title: Result represents a verification result + desmos.profiles.v2.Result.Failed: + type: object + properties: + error: + type: string + title: Error that is associated with the failure + title: |- + Failed is the result of an application link that has not been verified + successfully + desmos.profiles.v2.Result.Success: + type: object + properties: + value: + type: string + title: Hex-encoded value that has be signed by the profile + signature: + type: string + title: Hex-encoded signature that has been produced by signing the value + title: |- + Success is the result of an application link that has been successfully + verified + google.protobuf.Any: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - with the Desmos profile - proof: - title: Proof contains the ownership proof of the external chain address - type: object - properties: - pub_key: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of - the serialized + protocol buffer message. This string must contain at least - protocol buffer message. This string must contain at - least + one "/" character. The last segment of the URL's path must represent - one "/" character. The last segment of the URL's path - must represent + the fully qualified name of the type (as in - the fully qualified name of the type (as in + `path/google.protobuf.Duration`). The name should be in a canonical + form - `path/google.protobuf.Duration`). The name should be in - a canonical form + (e.g., leading "." is not accepted). - (e.g., leading "." is not accepted). + In practice, teams usually precompile into the binary all types that + they - In practice, teams usually precompile into the binary - all types that they + expect it to use in the context of Any. However, for URLs which use + the - expect it to use in the context of Any. However, for - URLs which use the + scheme `http`, `https`, or no scheme, one can optionally set up a type - scheme `http`, `https`, or no scheme, one can optionally - set up a type + server that maps type URLs to message definitions as follows: - server that maps type URLs to message definitions as - follows: + * If no scheme is provided, `https` is assumed. - * If no scheme is provided, `https` is assumed. + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - * An HTTP GET on the URL must yield a - [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based - on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + Note: this functionality is not currently available in the official - Note: this functionality is not currently available in - the official + protobuf release, and it is not used for type URLs beginning with - protobuf release, and it is not used for type URLs - beginning with + type.googleapis.com. - type.googleapis.com. + Schemes other than `http`, `https` (or the empty scheme) might be - Schemes other than `http`, `https` (or the empty scheme) - might be + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along with + a - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. - description: >- - `Any` contains an arbitrary serialized protocol buffer - message along with a + URL that describes the type of the serialized message. - URL that describes the type of the serialized message. + Protobuf library provides support to pack/unpack Any values in the form - Protobuf library provides support to pack/unpack Any values - in the form + of utility functions or additional generated methods of the Any type. - of utility functions or additional generated methods of the - Any type. + Example 1: Pack and unpack a message in C++. - Example 1: Pack and unpack a message in C++. + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + Example 2: Pack and unpack a message in Java. - Example 2: Pack and unpack a message in Java. + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Example 3: Pack and unpack a message in Python. - Example 3: Pack and unpack a message in Python. + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - Example 4: Pack and unpack a message in Go + The pack methods provided by protobuf library will by default use - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + 'type.googleapis.com/full.type.name' as the type URL and the unpack - The pack methods provided by protobuf library will by - default use + methods only use the fully qualified type name after the last '/' - 'type.googleapis.com/full.type.name' as the type URL and the - unpack + in the type URL, for example "foo.bar.com/x/y.z" will yield type - methods only use the fully qualified type name after the - last '/' + name "y.z". - in the type URL, for example "foo.bar.com/x/y.z" will yield - type - name "y.z". + JSON + ==== - JSON + The JSON representation of an `Any` value uses the regular - ==== + representation of the deserialized, embedded message, with an - The JSON representation of an `Any` value uses the regular + additional field `@type` which contains the type URL. Example: - representation of the deserialized, embedded message, with - an + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - additional field `@type` which contains the type URL. - Example: + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + If the embedded message type is well-known and has a custom JSON - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + representation, that representation will be embedded adding a field - If the embedded message type is well-known and has a custom - JSON + `value` which holds the custom JSON in addition to the `@type` - representation, that representation will be embedded adding - a field + field. Example (for message [google.protobuf.Duration][]): - `value` which holds the custom JSON in addition to the - `@type` + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + grpc.gateway.runtime.Error: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - field. Example (for message [google.protobuf.Duration][]): + protocol buffer message. This string must contain at least - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - PubKey represents the public key associated with the address - for which to + one "/" character. The last segment of the URL's path must + represent - prove the ownership - signature: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of - the serialized + the fully qualified name of the type (as in - protocol buffer message. This string must contain at - least + `path/google.protobuf.Duration`). The name should be in a + canonical form - one "/" character. The last segment of the URL's path - must represent + (e.g., leading "." is not accepted). - the fully qualified name of the type (as in - `path/google.protobuf.Duration`). The name should be in - a canonical form + In practice, teams usually precompile into the binary all types + that they - (e.g., leading "." is not accepted). + expect it to use in the context of Any. However, for URLs which + use the + scheme `http`, `https`, or no scheme, one can optionally set up + a type - In practice, teams usually precompile into the binary - all types that they + server that maps type URLs to message definitions as follows: - expect it to use in the context of Any. However, for - URLs which use the - scheme `http`, `https`, or no scheme, one can optionally - set up a type + * If no scheme is provided, `https` is assumed. - server that maps type URLs to message definitions as - follows: + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + Note: this functionality is not currently available in the + official - * If no scheme is provided, `https` is assumed. + protobuf release, and it is not used for type URLs beginning + with - * An HTTP GET on the URL must yield a - [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based - on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + type.googleapis.com. - Note: this functionality is not currently available in - the official - protobuf release, and it is not used for type URLs - beginning with + Schemes other than `http`, `https` (or the empty scheme) might + be - type.googleapis.com. + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + URL that describes the type of the serialized message. - Schemes other than `http`, `https` (or the empty scheme) - might be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. - description: >- - `Any` contains an arbitrary serialized protocol buffer - message along with a + Protobuf library provides support to pack/unpack Any values in the + form - URL that describes the type of the serialized message. + of utility functions or additional generated methods of the Any + type. - Protobuf library provides support to pack/unpack Any values - in the form + Example 1: Pack and unpack a message in C++. - of utility functions or additional generated methods of the - Any type. + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + Example 2: Pack and unpack a message in Java. - Example 1: Pack and unpack a message in C++. + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } + Example 3: Pack and unpack a message in Python. - Example 2: Pack and unpack a message in Java. + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Example 4: Pack and unpack a message in Go - Example 3: Pack and unpack a message in Python. + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... + The pack methods provided by protobuf library will by default use - Example 4: Pack and unpack a message in Go + 'type.googleapis.com/full.type.name' as the type URL and the unpack - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + methods only use the fully qualified type name after the last '/' - The pack methods provided by protobuf library will by - default use + in the type URL, for example "foo.bar.com/x/y.z" will yield type - 'type.googleapis.com/full.type.name' as the type URL and the - unpack + name "y.z". - methods only use the fully qualified type name after the - last '/' - in the type URL, for example "foo.bar.com/x/y.z" will yield - type - name "y.z". + JSON + ==== + The JSON representation of an `Any` value uses the regular - JSON + representation of the deserialized, embedded message, with an - ==== + additional field `@type` which contains the type URL. Example: - The JSON representation of an `Any` value uses the regular + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - representation of the deserialized, embedded message, with - an + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - additional field `@type` which contains the type URL. - Example: + If the embedded message type is well-known and has a custom JSON - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + representation, that representation will be embedded adding a field - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + `value` which holds the custom JSON in addition to the `@type` - If the embedded message type is well-known and has a custom - JSON + field. Example (for message [google.protobuf.Duration][]): - representation, that representation will be embedded adding - a field + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + desmos.relationships.v1.QueryBlocksResponse: + type: object + properties: + blocks: + type: array + items: + type: object + properties: + blocker: + type: string + title: Blocker represents the address of the user blocking another one + blocked: + type: string + title: Blocked represents the address of the blocked user + reason: + type: string + description: >- + Reason represents the optional reason the user has been blocked + for. + subspace_id: + type: string + format: uint64 + title: >- + SubspaceID represents the ID of the subspace inside which the + user should - `value` which holds the custom JSON in addition to the - `@type` + be blocked + description: >- + UserBlock represents the fact that the Blocker has blocked the given + Blocked - field. Example (for message [google.protobuf.Duration][]): + user. + pagination: + type: object + properties: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - title: >- - Signature represents the hex-encoded signature of the - PlainText value - plain_text: - type: string - title: >- - PlainText represents the hex-encoded value signed in order - to produce the + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. - Signature - chain_config: - title: ChainConfig contains the configuration of the external chain - type: object - properties: - name: - type: string - description: >- - ChainConfig contains the data of the chain with which the link - is made. - creation_time: + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: |- + QueryBlocksResponse is the response type for the Query/Blocks RPC + method. + desmos.relationships.v1.QueryRelationshipsResponse: + type: object + properties: + relationships: + type: array + items: + type: object + properties: + creator: + type: string + title: Creator represents the creator of the relationship + counterparty: type: string - format: date-time title: >- - CreationTime represents the time in which the link has been - created - title: >- - ChainLink contains the data representing either an inter- or cross- - chain + Counterparty represents the other user involved in the + relationship + subspace_id: + type: string + format: uint64 + title: >- + SubspaceID represents the id of the subspace for which the + relationship is - link + valid + description: |- + Relationship is the struct of a relationship. + It represent the concept of "follow" of traditional social networks. pagination: - title: Pagination defines the pagination response type: object properties: - next_key: + next_key: + type: string + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: |- + QueryRelationshipsResponse is the response type for the + Query/Relationships RPC method. + desmos.relationships.v1.Relationship: + type: object + properties: + creator: + type: string + title: Creator represents the creator of the relationship + counterparty: + type: string + title: Counterparty represents the other user involved in the relationship + subspace_id: + type: string + format: uint64 + title: >- + SubspaceID represents the id of the subspace for which the + relationship is + + valid + description: |- + Relationship is the struct of a relationship. + It represent the concept of "follow" of traditional social networks. + desmos.relationships.v1.UserBlock: + type: object + properties: + blocker: + type: string + title: Blocker represents the address of the user blocking another one + blocked: + type: string + title: Blocked represents the address of the blocked user + reason: + type: string + description: Reason represents the optional reason the user has been blocked for. + subspace_id: + type: string + format: uint64 + title: >- + SubspaceID represents the ID of the subspace inside which the user + should + + be blocked + description: >- + UserBlock represents the fact that the Blocker has blocked the given + Blocked + + user. + desmos.subspaces.v1.PermissionDetail: + type: object + properties: + user: + title: User represents a user permission + type: object + properties: + user: type: string - format: byte - title: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: + title: User for which the permission was set + permission: + type: integer + format: int64 + title: Permission set to the user + group: + title: Group represents a group permission + type: object + properties: + group_id: + type: integer + format: int64 + title: Unique id of the group + permission: + type: integer + format: int64 + title: Permission set to the group + title: PermissionDetail contains the details data of a permission + desmos.subspaces.v1.PermissionDetail.Group: + type: object + properties: + group_id: + type: integer + format: int64 + title: Unique id of the group + permission: + type: integer + format: int64 + title: Permission set to the group + title: Group is a permission that has been set to a user group + desmos.subspaces.v1.PermissionDetail.User: + type: object + properties: + user: + type: string + title: User for which the permission was set + permission: + type: integer + format: int64 + title: Permission set to the user + title: User is a permission that has been set to a specific user + desmos.subspaces.v1.QuerySubspaceResponse: + type: object + properties: + subspace: + type: object + properties: + id: type: string format: uint64 + title: Unique id that identifies the subspace + name: + type: string + title: Human-readable name of the subspace + description: + type: string + title: Optional description of this subspace + treasury: + type: string title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. + Represents the account that is associated with the subspace and - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - description: |- - QueryChainLinksResponse is the response type for the - Query/ChainLinks RPC method. - desmos.profiles.v2.QueryIncomingDTagTransferRequestsResponse: + should be used to connect external applications to verify this + subspace + owner: + type: string + title: Address of the user that owns the subspace + creator: + type: string + title: Address of the subspace creator + creation_time: + type: string + format: date-time + title: the creation time of the subspace + title: Subspace contains all the data of a Desmos subspace + title: QuerySubspaceResponse is the response type for the Query/Subspace method + desmos.subspaces.v1.QuerySubspacesResponse: type: object properties: - requests: + subspaces: type: array items: type: object properties: - dtag_to_trade: + id: type: string - title: >- - DTagToTrade contains the value of the DTag that should be - transferred from - - the receiver of the request to the sender - sender: + format: uint64 + title: Unique id that identifies the subspace + name: type: string - title: >- - Sender represents the address of the account that sent the - request - receiver: + title: Human-readable name of the subspace + description: + type: string + title: Optional description of this subspace + treasury: type: string title: >- - Receiver represents the receiver of the request that, if - accepted, will - - give to the sender their DTag - title: >- - DTagTransferRequest represent a DTag transfer request between two - users - title: >- - Requests represent the list of all the DTag transfer requests made - towards + Represents the account that is associated with the subspace and - the user + should be used to connect external applications to verify this + subspace + owner: + type: string + title: Address of the user that owns the subspace + creator: + type: string + title: Address of the subspace creator + creation_time: + type: string + format: date-time + title: the creation time of the subspace + title: Subspace contains all the data of a Desmos subspace pagination: - title: Pagination defines the pagination response type: object properties: next_key: @@ -7621,949 +10449,703 @@ definitions: format: byte title: |- next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: - type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. - - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - description: |- - QueryIncomingDTagTransferRequestsResponse is the response type for the - Query/IncomingDTagTransferRequests RPC method. - desmos.profiles.v2.QueryParamsResponse: - type: object - properties: - params: - type: object - properties: - nickname: - type: object - properties: - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: >- - NicknameParams defines the parameters related to the profiles - nicknames - dtag: - type: object - properties: - reg_ex: - type: string - min_length: - type: string - format: byte - max_length: - type: string - format: byte - title: DTagParams defines the parameters related to profile DTags - bio: - type: object - properties: - max_length: - type: string - format: byte - title: BioParams defines the parameters related to profile biography - oracle: - type: object - properties: - script_id: - type: string - format: uint64 - title: >- - ScriptID represents the ID of the oracle script to be called - to verify the - - data - ask_count: - type: string - format: uint64 - title: >- - AskCount represents the number of oracles to which ask to - verify the data - min_count: - type: string - format: uint64 - title: >- - MinCount represents the minimum count of oracles that should - complete the - - verification successfully - prepare_gas: - type: string - format: uint64 - title: >- - PrepareGas represents the amount of gas to be used during the - preparation - - stage of the oracle script - execute_gas: - type: string - format: uint64 - title: >- - ExecuteGas represents the amount of gas to be used during the - execution of - - the oracle script - fee_amount: - type: array - items: - type: object - properties: - denom: - type: string - amount: - type: string - description: >- - Coin defines a token with a denomination and an amount. - - - NOTE: The amount field is an Int which implements the custom - method + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - signatures required by gogoproto. - title: >- - FeeAmount represents the amount of fees to be payed in order - to execute the + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. - oracle script - title: |- - OracleParams defines the parameters related to the oracle - that will be used to verify the ownership of a centralized - application account by a Desmos profile - title: Params contains the parameters for the profiles module - description: QueryParamsResponse is the response type for the Query/Params RPC method. - desmos.profiles.v2.QueryProfileResponse: + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QuerySubspacesResponse is the response type for the Query/Subspaces RPC + method + desmos.subspaces.v1.QueryUserGroupMembersResponse: type: object properties: - profile: + members: + type: array + items: + type: string + pagination: type: object properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized - - protocol buffer message. This string must contain at least - - one "/" character. The last segment of the URL's path must - represent - - the fully qualified name of the type (as in - - `path/google.protobuf.Duration`). The name should be in a - canonical form - - (e.g., leading "." is not accepted). - - - In practice, teams usually precompile into the binary all types - that they - - expect it to use in the context of Any. However, for URLs which - use the - - scheme `http`, `https`, or no scheme, one can optionally set up a - type - - server that maps type URLs to message definitions as follows: - - - * If no scheme is provided, `https` is assumed. - - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) - - Note: this functionality is not currently available in the - official - - protobuf release, and it is not used for type URLs beginning with - - type.googleapis.com. - - - Schemes other than `http`, `https` (or the empty scheme) might be - - used with implementation specific semantics. - value: + next_key: type: string format: byte - description: >- - Must be a valid serialized protocol buffer of the above specified - type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message along - with a - - URL that describes the type of the serialized message. - - - Protobuf library provides support to pack/unpack Any values in the - form - - of utility functions or additional generated methods of the Any type. - - - Example 1: Pack and unpack a message in C++. - - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } - - Example 2: Pack and unpack a message in Java. - - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } - - Example 3: Pack and unpack a message in Python. - - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - - Example 4: Pack and unpack a message in Go - - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } - - The pack methods provided by protobuf library will by default use - - 'type.googleapis.com/full.type.name' as the type URL and the unpack - - methods only use the fully qualified type name after the last '/' - - in the type URL, for example "foo.bar.com/x/y.z" will yield type - - name "y.z". - - - - JSON - - ==== - - The JSON representation of an `Any` value uses the regular - - representation of the deserialized, embedded message, with an - - additional field `@type` which contains the type URL. Example: - - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } - - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } - - If the embedded message type is well-known and has a custom JSON - - representation, that representation will be embedded adding a field - - `value` which holds the custom JSON in addition to the `@type` + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total - field. Example (for message [google.protobuf.Duration][]): + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - description: >- - QueryProfileResponse is the response type for the Query/Profile RPC - method. - desmos.profiles.v2.Result: + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryUserGroupMembersResponse is the response type for the + Query/UserGroupMembers RPC method + desmos.subspaces.v1.QueryUserGroupResponse: type: object properties: - success: - title: Success represents a successful verification + group: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: ID of the subspace inside which this group exists + id: + type: integer + format: int64 + title: Unique id that identifies the group + name: + type: string + title: Human-readable name of the user group + description: + type: string + title: Optional description of this group + permissions: + type: integer + format: int64 + title: >- + Permissions that will be granted to all the users part of this + group + title: UserGroup represents a group of users + title: |- + QueryUserGroupResponse is the response type for the Query/UserGroup RPC + method + desmos.subspaces.v1.QueryUserGroupsResponse: + type: object + properties: + groups: + type: array + items: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: ID of the subspace inside which this group exists + id: + type: integer + format: int64 + title: Unique id that identifies the group + name: + type: string + title: Human-readable name of the user group + description: + type: string + title: Optional description of this group + permissions: + type: integer + format: int64 + title: >- + Permissions that will be granted to all the users part of this + group + title: UserGroup represents a group of users + pagination: type: object properties: - value: - type: string - title: Hex-encoded value that has be signed by the profile - signature: + next_key: type: string - title: Hex-encoded signature that has been produced by signing the value - failed: - title: Failed represents a failed verification - type: object - properties: - error: + format: byte + title: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently + total: type: string - title: Error that is associated with the failure - title: Result represents a verification result - desmos.profiles.v2.Result.Failed: - type: object - properties: - error: - type: string - title: Error that is associated with the failure + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } title: |- - Failed is the result of an application link that has not been verified - successfully - desmos.profiles.v2.Result.Success: + QueryUserGroupsResponse is the response type for the Query/UserGroups RPC + method + desmos.subspaces.v1.QueryUserPermissionsResponse: type: object properties: - value: - type: string - title: Hex-encoded value that has be signed by the profile - signature: - type: string - title: Hex-encoded signature that has been produced by signing the value + permissions: + type: integer + format: int64 + details: + type: array + items: + type: object + properties: + user: + title: User represents a user permission + type: object + properties: + user: + type: string + title: User for which the permission was set + permission: + type: integer + format: int64 + title: Permission set to the user + group: + title: Group represents a group permission + type: object + properties: + group_id: + type: integer + format: int64 + title: Unique id of the group + permission: + type: integer + format: int64 + title: Permission set to the group + title: PermissionDetail contains the details data of a permission title: |- - Success is the result of an application link that has been successfully - verified - google.protobuf.Any: + QueryUserPermissionsRequest is the response type for the + Query/UserPermissions method + desmos.subspaces.v1.Subspace: type: object properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized - - protocol buffer message. This string must contain at least - - one "/" character. The last segment of the URL's path must represent - - the fully qualified name of the type (as in - - `path/google.protobuf.Duration`). The name should be in a canonical - form - - (e.g., leading "." is not accepted). - - - In practice, teams usually precompile into the binary all types that - they - - expect it to use in the context of Any. However, for URLs which use - the - - scheme `http`, `https`, or no scheme, one can optionally set up a type - - server that maps type URLs to message definitions as follows: - - - * If no scheme is provided, `https` is assumed. - - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) - - Note: this functionality is not currently available in the official - - protobuf release, and it is not used for type URLs beginning with - - type.googleapis.com. - - - Schemes other than `http`, `https` (or the empty scheme) might be - - used with implementation specific semantics. - value: + id: type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above specified - type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message along with - a - - URL that describes the type of the serialized message. - - - Protobuf library provides support to pack/unpack Any values in the form - - of utility functions or additional generated methods of the Any type. - - - Example 1: Pack and unpack a message in C++. - - Foo foo = ...; - Any any; - any.PackFrom(foo); - ... - if (any.UnpackTo(&foo)) { - ... - } - - Example 2: Pack and unpack a message in Java. - - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } - - Example 3: Pack and unpack a message in Python. - - foo = Foo(...) - any = Any() - any.Pack(foo) - ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - - Example 4: Pack and unpack a message in Go - - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) - ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } - - The pack methods provided by protobuf library will by default use - - 'type.googleapis.com/full.type.name' as the type URL and the unpack - - methods only use the fully qualified type name after the last '/' - - in the type URL, for example "foo.bar.com/x/y.z" will yield type - - name "y.z". - - - - JSON - - ==== - - The JSON representation of an `Any` value uses the regular - - representation of the deserialized, embedded message, with an - - additional field `@type` which contains the type URL. Example: - - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } - - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } - - If the embedded message type is well-known and has a custom JSON - - representation, that representation will be embedded adding a field - - `value` which holds the custom JSON in addition to the `@type` - - field. Example (for message [google.protobuf.Duration][]): + format: uint64 + title: Unique id that identifies the subspace + name: + type: string + title: Human-readable name of the subspace + description: + type: string + title: Optional description of this subspace + treasury: + type: string + title: >- + Represents the account that is associated with the subspace and - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - grpc.gateway.runtime.Error: + should be used to connect external applications to verify this + subspace + owner: + type: string + title: Address of the user that owns the subspace + creator: + type: string + title: Address of the subspace creator + creation_time: + type: string + format: date-time + title: the creation time of the subspace + title: Subspace contains all the data of a Desmos subspace + desmos.subspaces.v1.UserGroup: type: object properties: - error: + subspace_id: type: string - code: + format: uint64 + title: ID of the subspace inside which this group exists + id: type: integer - format: int32 - message: + format: int64 + title: Unique id that identifies the group + name: type: string - details: - type: array - items: - type: object - properties: - type_url: - type: string - description: >- - A URL/resource name that uniquely identifies the type of the - serialized + title: Human-readable name of the user group + description: + type: string + title: Optional description of this group + permissions: + type: integer + format: int64 + title: Permissions that will be granted to all the users part of this group + title: UserGroup represents a group of users + desmos.posts.v1.Attachment: + type: object + properties: + subspace_id: + type: string + format: uint64 + title: >- + Id of the subspace inside which the post to which this attachment + should be - protocol buffer message. This string must contain at least + connected is + post_id: + type: string + format: uint64 + title: Id of the post to which this attachment should be connected + id: + type: integer + format: int64 + title: If of this attachment + content: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized - one "/" character. The last segment of the URL's path must - represent + protocol buffer message. This string must contain at least - the fully qualified name of the type (as in + one "/" character. The last segment of the URL's path must + represent - `path/google.protobuf.Duration`). The name should be in a - canonical form + the fully qualified name of the type (as in - (e.g., leading "." is not accepted). + `path/google.protobuf.Duration`). The name should be in a + canonical form + (e.g., leading "." is not accepted). - In practice, teams usually precompile into the binary all types - that they - expect it to use in the context of Any. However, for URLs which - use the + In practice, teams usually precompile into the binary all types + that they - scheme `http`, `https`, or no scheme, one can optionally set up - a type + expect it to use in the context of Any. However, for URLs which + use the - server that maps type URLs to message definitions as follows: + scheme `http`, `https`, or no scheme, one can optionally set up a + type + server that maps type URLs to message definitions as follows: - * If no scheme is provided, `https` is assumed. - * An HTTP GET on the URL must yield a [google.protobuf.Type][] - value in binary format, or produce an error. - * Applications are allowed to cache lookup results based on the - URL, or have them precompiled into a binary to avoid any - lookup. Therefore, binary compatibility needs to be preserved - on changes to types. (Use versioned type names to manage - breaking changes.) + * If no scheme is provided, `https` is assumed. - Note: this functionality is not currently available in the - official + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) - protobuf release, and it is not used for type URLs beginning - with + Note: this functionality is not currently available in the + official - type.googleapis.com. + protobuf release, and it is not used for type URLs beginning with + type.googleapis.com. - Schemes other than `http`, `https` (or the empty scheme) might - be - used with implementation specific semantics. - value: - type: string - format: byte - description: >- - Must be a valid serialized protocol buffer of the above - specified type. - description: >- - `Any` contains an arbitrary serialized protocol buffer message along - with a + Schemes other than `http`, `https` (or the empty scheme) might be - URL that describes the type of the serialized message. + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + URL that describes the type of the serialized message. - Protobuf library provides support to pack/unpack Any values in the - form - of utility functions or additional generated methods of the Any - type. + Protobuf library provides support to pack/unpack Any values in the + form + of utility functions or additional generated methods of the Any type. - Example 1: Pack and unpack a message in C++. - Foo foo = ...; - Any any; - any.PackFrom(foo); + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { ... - if (any.UnpackTo(&foo)) { - ... - } + } - Example 2: Pack and unpack a message in Java. + Example 2: Pack and unpack a message in Java. - Foo foo = ...; - Any any = Any.pack(foo); - ... - if (any.is(Foo.class)) { - foo = any.unpack(Foo.class); - } + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } - Example 3: Pack and unpack a message in Python. + Example 3: Pack and unpack a message in Python. - foo = Foo(...) - any = Any() - any.Pack(foo) + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) ... - if any.Is(Foo.DESCRIPTOR): - any.Unpack(foo) - ... - Example 4: Pack and unpack a message in Go + Example 4: Pack and unpack a message in Go - foo := &pb.Foo{...} - any, err := ptypes.MarshalAny(foo) + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { ... - foo := &pb.Foo{} - if err := ptypes.UnmarshalAny(any, foo); err != nil { - ... - } + } - The pack methods provided by protobuf library will by default use + The pack methods provided by protobuf library will by default use - 'type.googleapis.com/full.type.name' as the type URL and the unpack + 'type.googleapis.com/full.type.name' as the type URL and the unpack - methods only use the fully qualified type name after the last '/' + methods only use the fully qualified type name after the last '/' - in the type URL, for example "foo.bar.com/x/y.z" will yield type + in the type URL, for example "foo.bar.com/x/y.z" will yield type - name "y.z". + name "y.z". - JSON + JSON - ==== + ==== - The JSON representation of an `Any` value uses the regular + The JSON representation of an `Any` value uses the regular - representation of the deserialized, embedded message, with an + representation of the deserialized, embedded message, with an - additional field `@type` which contains the type URL. Example: + additional field `@type` which contains the type URL. Example: - package google.profile; - message Person { - string first_name = 1; - string last_name = 2; - } + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } - { - "@type": "type.googleapis.com/google.profile.Person", - "firstName": , - "lastName": - } + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } - If the embedded message type is well-known and has a custom JSON + If the embedded message type is well-known and has a custom JSON - representation, that representation will be embedded adding a field + representation, that representation will be embedded adding a field - `value` which holds the custom JSON in addition to the `@type` + `value` which holds the custom JSON in addition to the `@type` - field. Example (for message [google.protobuf.Duration][]): + field. Example (for message [google.protobuf.Duration][]): - { - "@type": "type.googleapis.com/google.protobuf.Duration", - "value": "1.212s" - } - desmos.relationships.v1.QueryBlocksResponse: + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Content of the attachment + title: Attachment contains the data of a single post attachment + desmos.posts.v1.Entities: type: object properties: - blocks: + hashtags: type: array items: type: object properties: - blocker: + start: type: string - title: Blocker represents the address of the user blocking another one - blocked: + format: uint64 + title: Index of the character inside the text at which the tag starts + end: type: string - title: Blocked represents the address of the blocked user - reason: + format: uint64 + title: Index of the character inside the text at which the tag ends + tag: type: string - description: >- - Reason represents the optional reason the user has been blocked - for. - subspace_id: + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: type: string format: uint64 - title: >- - SubspaceID represents the ID of the subspace inside which the - user should - - be blocked - description: >- - UserBlock represents the fact that the Blocker has blocked the given - Blocked - - user. - pagination: - type: object - properties: - next_key: - type: string - format: byte - title: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: - type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. - - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - description: |- - QueryBlocksResponse is the response type for the Query/Blocks RPC - method. - desmos.relationships.v1.QueryRelationshipsResponse: - type: object - properties: - relationships: + title: Index of the character inside the text at which the tag starts + end: + type: string + format: uint64 + title: Index of the character inside the text at which the tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + urls: type: array items: type: object properties: - creator: - type: string - title: Creator represents the creator of the relationship - counterparty: + start: type: string - title: >- - Counterparty represents the other user involved in the - relationship - subspace_id: + format: uint64 + title: Index of the character inside the text at which the URL starts + end: type: string format: uint64 - title: >- - SubspaceID represents the id of the subspace for which the - relationship is - - valid - description: |- - Relationship is the struct of a relationship. - It represent the concept of "follow" of traditional social networks. - pagination: - type: object - properties: - next_key: - type: string - format: byte - title: |- - next_key is the key to be passed to PageRequest.key to - query the next page most efficiently - total: - type: string - format: uint64 - title: >- - total is total number of results available if - PageRequest.count_total - - was set, its value is undefined otherwise - description: |- - PageResponse is to be embedded in gRPC response messages where the - corresponding request message has used PageRequest. - - message SomeResponse { - repeated Bar results = 1; - PageResponse page = 2; - } - description: |- - QueryRelationshipsResponse is the response type for the - Query/Relationships RPC method. - desmos.relationships.v1.Relationship: + title: Index of the character inside the text at which the URL ends + url: + type: string + title: Value of the URL where the user should be redirected to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + title: Contains the details of entities parsed out of the post text + desmos.posts.v1.Params: type: object properties: - creator: - type: string - title: Creator represents the creator of the relationship - counterparty: - type: string - title: Counterparty represents the other user involved in the relationship - subspace_id: - type: string - format: uint64 - title: >- - SubspaceID represents the id of the subspace for which the - relationship is - - valid - description: |- - Relationship is the struct of a relationship. - It represent the concept of "follow" of traditional social networks. - desmos.relationships.v1.UserBlock: + max_text_length: + type: integer + format: int64 + title: Maximum length of the post text + title: Params contains the parameters for the posts module + desmos.posts.v1.Post: type: object properties: - blocker: + subspace_id: type: string - title: Blocker represents the address of the user blocking another one - blocked: + format: uint64 + title: Id of the subspace inside which the post has been created + id: type: string - title: Blocked represents the address of the blocked user - reason: + format: uint64 + title: Unique id of the post + external_id: type: string - description: Reason represents the optional reason the user has been blocked for. - subspace_id: + title: (optional) External id for this post + text: type: string - format: uint64 - title: >- - SubspaceID represents the ID of the subspace inside which the user - should - - be blocked - description: >- - UserBlock represents the fact that the Blocker has blocked the given - Blocked - - user. - desmos.subspaces.v1.PermissionDetail: - type: object - properties: - user: - title: User represents a user permission - type: object - properties: - user: - type: string - title: User for which the permission was set - permission: - type: integer - format: int64 - title: Permission set to the user - group: - title: Group represents a group permission + title: (optional) Text of the post + entities: + title: (optional) Entities connected to this post type: object properties: - group_id: - type: integer - format: int64 - title: Unique id of the group - permission: - type: integer - format: int64 - title: Permission set to the group - title: PermissionDetail contains the details data of a permission - desmos.subspaces.v1.PermissionDetail.Group: - type: object - properties: - group_id: - type: integer - format: int64 - title: Unique id of the group - permission: - type: integer - format: int64 - title: Permission set to the group - title: Group is a permission that has been set to a user group - desmos.subspaces.v1.PermissionDetail.User: + hashtags: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + starts + end: + type: string + format: uint64 + title: Index of the character inside the text at which the tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + starts + end: + type: string + format: uint64 + title: Index of the character inside the text at which the tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + urls: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the URL + starts + end: + type: string + format: uint64 + title: Index of the character inside the text at which the URL ends + url: + type: string + title: Value of the URL where the user should be redirected to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + author: + type: string + title: Author of the post + conversation_id: + type: string + format: uint64 + title: (optional) Id of the original post of the conversation + referenced_posts: + type: array + items: + type: object + properties: + type: + title: Type of reference + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference + title: 'A list this posts references (either as a reply, repost or quote)' + reply_settings: + title: Reply settings of this post + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + creation_date: + type: string + format: date-time + title: Creation date of the post + last_edited_date: + type: string + format: date-time + title: (optional) Last edited time of the post + title: Post contains all the information about a single post + desmos.posts.v1.PostReference: type: object properties: - user: + type: + title: Type of reference type: string - title: User for which the permission was set - permission: - type: integer - format: int64 - title: Permission set to the user - title: User is a permission that has been set to a specific user - desmos.subspaces.v1.QuerySubspaceResponse: + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference + desmos.posts.v1.PostReference.Type: + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + title: Type represents the different types of references + desmos.posts.v1.QueryParamsResponse: type: object properties: - subspace: + params: type: object properties: - id: - type: string - format: uint64 - title: Unique id that identifies the subspace - name: - type: string - title: Human-readable name of the subspace - description: - type: string - title: Optional description of this subspace - treasury: - type: string - title: >- - Represents the account that is associated with the subspace and - - should be used to connect external applications to verify this - subspace - owner: - type: string - title: Address of the user that owns the subspace - creator: - type: string - title: Address of the subspace creator - creation_time: - type: string - format: date-time - title: the creation time of the subspace - title: Subspace contains all the data of a Desmos subspace - title: QuerySubspaceResponse is the response type for the Query/Subspace method - desmos.subspaces.v1.QuerySubspacesResponse: + max_text_length: + type: integer + format: int64 + title: Maximum length of the post text + title: Params contains the parameters for the posts module + title: QueryParamsResponse is the response type for the Query/Params RPC method + desmos.posts.v1.QueryPollAnswersResponse: type: object properties: - subspaces: + answers: type: array items: type: object properties: - id: + subspace_id: type: string format: uint64 - title: Unique id that identifies the subspace - name: - type: string - title: Human-readable name of the subspace - description: - type: string - title: Optional description of this subspace - treasury: - type: string title: >- - Represents the account that is associated with the subspace and - - should be used to connect external applications to verify this - subspace - owner: - type: string - title: Address of the user that owns the subspace - creator: + Subspace id inside which the post related to this attachment is + located + post_id: type: string - title: Address of the subspace creator - creation_time: + format: uint64 + title: Id of the post associated to this attachment + poll_id: + type: integer + format: int64 + title: Id of the poll to which this answer is associated + answers_indexes: + type: array + items: + type: integer + format: int64 + title: Indexes of the answers inside the ProvidedAnswers array + user: type: string - format: date-time - title: the creation time of the subspace - title: Subspace contains all the data of a Desmos subspace + title: Address of the user answering the poll + title: UserAnswer represents a user answer to a poll pagination: type: object properties: @@ -8589,16 +11171,206 @@ definitions: repeated Bar results = 1; PageResponse page = 2; } - title: |- - QuerySubspacesResponse is the response type for the Query/Subspaces RPC + title: >- + QueryPollAnswersResponse is the response type for the Query/PollAnswers + RPC + method - desmos.subspaces.v1.QueryUserGroupMembersResponse: + desmos.posts.v1.QueryPostAttachmentsResponse: type: object properties: - members: + attachments: type: array items: - type: string + type: object + properties: + subspace_id: + type: string + format: uint64 + title: >- + Id of the subspace inside which the post to which this + attachment should be + + connected is + post_id: + type: string + format: uint64 + title: Id of the post to which this attachment should be connected + id: + type: integer + format: int64 + title: If of this attachment + content: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := ptypes.MarshalAny(foo) + ... + foo := &pb.Foo{} + if err := ptypes.UnmarshalAny(any, foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + + JSON + + ==== + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Content of the attachment + title: Attachment contains the data of a single post attachment pagination: type: object properties: @@ -8625,42 +11397,160 @@ definitions: PageResponse page = 2; } title: |- - QueryUserGroupMembersResponse is the response type for the - Query/UserGroupMembers RPC method - desmos.subspaces.v1.QueryUserGroupResponse: + QueryPostAttachmentsResponse is the response type for the + Query/PostAttachments RPC method + desmos.posts.v1.QueryPostResponse: type: object properties: - group: + post: type: object properties: subspace_id: type: string format: uint64 - title: ID of the subspace inside which this group exists + title: Id of the subspace inside which the post has been created id: - type: integer - format: int64 - title: Unique id that identifies the group - name: type: string - title: Human-readable name of the user group - description: + format: uint64 + title: Unique id of the post + external_id: type: string - title: Optional description of this group - permissions: - type: integer - format: int64 - title: >- - Permissions that will be granted to all the users part of this - group - title: UserGroup represents a group of users - title: |- - QueryUserGroupResponse is the response type for the Query/UserGroup RPC - method - desmos.subspaces.v1.QueryUserGroupsResponse: + title: (optional) External id for this post + text: + type: string + title: (optional) Text of the post + entities: + title: (optional) Entities connected to this post + type: object + properties: + hashtags: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the tag + ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + urls: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the URL + starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the URL + ends + url: + type: string + title: Value of the URL where the user should be redirected to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + author: + type: string + title: Author of the post + conversation_id: + type: string + format: uint64 + title: (optional) Id of the original post of the conversation + referenced_posts: + type: array + items: + type: object + properties: + type: + title: Type of reference + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference + title: 'A list this posts references (either as a reply, repost or quote)' + reply_settings: + title: Reply settings of this post + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + creation_date: + type: string + format: date-time + title: Creation date of the post + last_edited_date: + type: string + format: date-time + title: (optional) Last edited time of the post + title: Post contains all the information about a single post + title: QueryPostResponse is the response type for the Query/Post RPC method + desmos.posts.v1.QueryPostsResponse: type: object properties: - groups: + posts: type: array items: type: object @@ -8668,24 +11558,148 @@ definitions: subspace_id: type: string format: uint64 - title: ID of the subspace inside which this group exists + title: Id of the subspace inside which the post has been created id: - type: integer - format: int64 - title: Unique id that identifies the group - name: type: string - title: Human-readable name of the user group - description: + format: uint64 + title: Unique id of the post + external_id: type: string - title: Optional description of this group - permissions: - type: integer - format: int64 + title: (optional) External id for this post + text: + type: string + title: (optional) Text of the post + entities: + title: (optional) Entities connected to this post + type: object + properties: + hashtags: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + mentions: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + tag starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + tag ends + tag: + type: string + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + urls: + type: array + items: + type: object + properties: + start: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + URL starts + end: + type: string + format: uint64 + title: >- + Index of the character inside the text at which the + URL ends + url: + type: string + title: >- + Value of the URL where the user should be redirected + to + display_url: + type: string + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + author: + type: string + title: Author of the post + conversation_id: + type: string + format: uint64 + title: (optional) Id of the original post of the conversation + referenced_posts: + type: array + items: + type: object + properties: + type: + title: Type of reference + type: string + enum: + - TYPE_UNSPECIFIED + - TYPE_REPLIED_TO + - TYPE_QUOTED + - TYPE_REPOSTED + default: TYPE_UNSPECIFIED + description: |- + - TYPE_UNSPECIFIED: No reference specified + - TYPE_REPLIED_TO: This post is a reply to the referenced post + - TYPE_QUOTED: This post is a quote to the referenced post + - TYPE_REPOSTED: This post is a report of the referenced post + post_id: + type: string + format: uint64 + title: Id of the referenced post + title: PostReference contains the details of a post reference title: >- - Permissions that will be granted to all the users part of this - group - title: UserGroup represents a group of users + A list this posts references (either as a reply, repost or + quote) + reply_settings: + title: Reply settings of this post + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + creation_date: + type: string + format: date-time + title: Creation date of the post + last_edited_date: + type: string + format: date-time + title: (optional) Last edited time of the post + title: Post contains all the information about a single post pagination: type: object properties: @@ -8711,100 +11725,83 @@ definitions: repeated Bar results = 1; PageResponse page = 2; } - title: |- - QueryUserGroupsResponse is the response type for the Query/UserGroups RPC - method - desmos.subspaces.v1.QueryUserPermissionsResponse: - type: object - properties: - permissions: - type: integer - format: int64 - details: - type: array - items: - type: object - properties: - user: - title: User represents a user permission - type: object - properties: - user: - type: string - title: User for which the permission was set - permission: - type: integer - format: int64 - title: Permission set to the user - group: - title: Group represents a group permission - type: object - properties: - group_id: - type: integer - format: int64 - title: Unique id of the group - permission: - type: integer - format: int64 - title: Permission set to the group - title: PermissionDetail contains the details data of a permission - title: |- - QueryUserPermissionsRequest is the response type for the - Query/UserPermissions method - desmos.subspaces.v1.Subspace: + title: QueryPostsResponse is the response type for the Query/Posts RPC method + desmos.posts.v1.ReplySetting: + type: string + enum: + - REPLY_SETTING_UNSPECIFIED + - REPLY_SETTING_EVERYONE + - REPLY_SETTING_FOLLOWERS + - REPLY_SETTING_MUTUAL + - REPLY_SETTING_MENTIONS + default: REPLY_SETTING_UNSPECIFIED + description: |- + - REPLY_SETTING_UNSPECIFIED: No reply setting specified + - REPLY_SETTING_EVERYONE: Everyone will be able to reply to this post + - REPLY_SETTING_FOLLOWERS: Only followers of the author will be able to reply to this post + - REPLY_SETTING_MUTUAL: Only the author mutual followers will be able to reply to this post + - REPLY_SETTING_MENTIONS: Only people mentioned inside this post will be able to reply + title: ReplySetting contains the possible reply settings that a post can have + desmos.posts.v1.Tag: type: object properties: - id: + start: type: string format: uint64 - title: Unique id that identifies the subspace - name: + title: Index of the character inside the text at which the tag starts + end: type: string - title: Human-readable name of the subspace - description: + format: uint64 + title: Index of the character inside the text at which the tag ends + tag: type: string - title: Optional description of this subspace - treasury: + title: 'Tag reference (user address, hashtag value, etc)' + title: Tag represents a generic tag + desmos.posts.v1.Url: + type: object + properties: + start: type: string - title: >- - Represents the account that is associated with the subspace and - - should be used to connect external applications to verify this - subspace - owner: + format: uint64 + title: Index of the character inside the text at which the URL starts + end: type: string - title: Address of the user that owns the subspace - creator: + format: uint64 + title: Index of the character inside the text at which the URL ends + url: type: string - title: Address of the subspace creator - creation_time: + title: Value of the URL where the user should be redirected to + display_url: type: string - format: date-time - title: the creation time of the subspace - title: Subspace contains all the data of a Desmos subspace - desmos.subspaces.v1.UserGroup: + title: (optional) Display value of the URL + title: Url contains the details of a generic URL + desmos.posts.v1.UserAnswer: type: object properties: subspace_id: type: string format: uint64 - title: ID of the subspace inside which this group exists - id: - type: integer - format: int64 - title: Unique id that identifies the group - name: - type: string - title: Human-readable name of the user group - description: + title: >- + Subspace id inside which the post related to this attachment is + located + post_id: type: string - title: Optional description of this group - permissions: + format: uint64 + title: Id of the post associated to this attachment + poll_id: type: integer format: int64 - title: Permissions that will be granted to all the users part of this group - title: UserGroup represents a group of users + title: Id of the poll to which this answer is associated + answers_indexes: + type: array + items: + type: integer + format: int64 + title: Indexes of the answers inside the ProvidedAnswers array + user: + type: string + title: Address of the user answering the poll + title: UserAnswer represents a user answer to a poll desmos.fees.v1.MinFee: type: object properties: diff --git a/docs/architecture/adr-010-posts-module.md b/docs/architecture/adr-010-posts-module.md index 9c4e0790e7..27996fea8e 100644 --- a/docs/architecture/adr-010-posts-module.md +++ b/docs/architecture/adr-010-posts-module.md @@ -6,10 +6,11 @@ - April 06th, 2022: First review; - April 11th, 2022: Second review; - April 12th, 2022: Third review; +- May 26th, 2022: Fourth review; ## Status -PROPOSED +PROPOSED Implemented ## Abstract @@ -40,54 +41,57 @@ Optionally, a post can also have a series of _entities_ that have been parsed ou #### Post ```protobuf +syntax = "proto3"; + // Post contains all the information about a single post message Post { // Id of the subspace inside which the post has been created - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Unique id of the post - required uint64 id = 2; + uint64 id = 2; // External id for this post - optional string external_id = 3; + string external_id = 3; // Text of the post - optional string text = 4; + string text = 4; // Entities connected to this post - optional Entities entities = 5; + Entities entities = 5; // Author of the post - required string author = 6; + string author = 6; // Id of the original post of the conversation - optional uint64 conversation_id = 7 [default = 0]; + uint64 conversation_id = 7 [default = 0]; // A list this posts references (either as a reply, repost or quote) repeated PostReference referenced_posts = 8; // Reply settings of this post - required ReplySetting reply_settings = 9; + ReplySetting reply_settings = 9; // Creation date of the post - required google.protobuf.Timestamp creation_date = 10; + google.protobuf.Timestamp creation_date = 10; // Last edited time of the post - optional google.protobuf.Timestamp last_edited_date = 11; + google.protobuf.Timestamp last_edited_date = 11; } // PostReference contains the details of a post reference message PostReference { // Type of reference - required Type type = 1; + Type type = 1; // Id of the referenced post - required uint64 post_id = 2; + uint64 post_id = 2; enum Type { - REPLIED_TO = 1; - QUOTED = 2; - REPOSTED = 3; + TYPE_UNSPECIFIED = 0; + TYPE_REPLIED_TO = 1; + TYPE_QUOTED = 2; + TYPE_REPOSTED = 3; } } @@ -100,92 +104,125 @@ message Entities { // ReplySetting contains the possible reply settings that a post can have enum ReplySetting { + // No reply setting specified + REPLY_SETTING_UNSPECIFIED = 0; + // Everyone will be able to reply to this post - EVERYONE = 1; + REPLY_SETTING_EVERYONE = 1; // Only followers of the author will be able to reply to this post - FOLLOWERS = 2; + REPLY_SETTING_FOLLOWERS = 2; // Only the author mutual followers will be able to reply to this post - MUTUALS = 3; + REPLY_SETTING_MUTUAL = 3; // Only people mentioned inside this post will be able to reply - MENTIONS = 4; + REPLY_SETTING_MENTIONS = 4; } // Tag represents a generic tag message Tag { // Index of the character inside the text at which the tag starts - required uint64 start = 1; + uint64 start = 1; // Index of the character inside the text at which the tag ends - required uint64 end = 2; + uint64 end = 2; // Tag reference (user address, hashtag value, etc) - required string tag = 3; + string tag = 3; } // Url contains the details of a generic URL message Url { // Index of the character inside the text at which the URL starts - required uint64 start = 1; + uint64 start = 1; // Index of the character inside the text at which the URL ends - required uint64 end = 2; + uint64 end = 2; // Value of the URL where the user should be redirected to - required string url = 3; + string url = 3; // Display value of the URL - optional string display_url = 4; + string display_url = 4; } ``` #### Attachments ```protobuf +syntax = "proto3"; + // Attachment contains the data of a single post attachment message Attachment { - required uint32 id = 1; - oneof sum { - Poll poll = 2; - Media media = 3; - } + // Id of the subspace inside which the post to which this attachment should be + // connected is + uint64 subspace_id = 1; + + // Id of the post to which this attachment should be connected + uint64 post_id = 2; + + // If of this attachment + uint32 id = 3; + + // Content of the attachment + google.protobuf.Any content = 4; } // Media represents a media attachment message Media { - required string uri = 2; - required string mime_type = 3; + string uri = 2; + string mime_type = 3; } // Poll represents a poll attachment message Poll { // Question of the poll - required string question = 2; + string question = 1; // Answers the users can choose from - repeated ProvidedAnswer provided_answers = 3; + repeated ProvidedAnswer provided_answers = 2; // Date at which the poll will close - required google.protobuf.Timestamp end_date = 4; + google.protobuf.Timestamp end_date = 3; // Whether the poll allows multiple choices from the same user or not - optional bool allows_multiple_answers = 5 [default = false]; + bool allows_multiple_answers = 4; // Whether the poll allows to edit an answer or not - optional bool allows_answer_edits = 6 [default = false]; + bool allows_answer_edits = 5; + + // Final poll results + PollTallyResults final_tally_results = 6; // Provided answer contains the details of a possible poll answer message ProvidedAnswer { // Text of the answer - optional string text = 1; + string text = 1; // Attachments of the answer repeated Attachment attachments = 2; } } +// UserAnswer represents a user answer to a poll +message UserAnswer { + // Subspace id inside which the post related to this attachment is located + uint64 subspace_id = 1; + + // Id of the post associated to this attachment + uint64 post_id = 2; + + // Id of the poll to which this answer is associated + uint32 poll_id = 3; + + // Indexes of the answers inside the ProvidedAnswers array + repeated uint32 answers_indexes = 4; + + // Address of the user answering the poll + string user = 5; +} + // PollTallyResults contains the tally results for a poll message PollTallyResults { repeated AnswerResult results = 1; @@ -193,10 +230,10 @@ message PollTallyResults { // AnswerResult contains the result of a single poll provided answer message AnswerResult { // Index of the answer inside the poll's ProvidedAnswers slice - required uint32 answer_index = 1; + uint32 answer_index = 1; // Number of votes the answer has received - required uint64 votes = 2; + uint64 votes = 2; } } ``` @@ -206,7 +243,7 @@ message PollTallyResults { // Params contains the parameters for the posts module message Params { // Maximum length of the post text - required uint64 max_text_length = 1; + uint64 max_text_length = 1; } ``` @@ -230,12 +267,17 @@ We will allow the following operations to be performed. > As per their nature, _attachments_ are **immutable**. This means that the only operations allowed on a post attachments are either adding an attachment or deleting an existing attachment. No edit on the attachment itself is permitted. ```protobuf +syntax = "proto3"; + service Msg { // CreatePost allows to create a new post rpc CreatePost(MsgCreatePost) returns (MsgCreatePostResponse); // EditPost allows to edit an existing post rpc EditPost(MsgEditPost) returns (MsgEditPostResponse); + + // DeletePost allows to delete an existing post + rpc DeletePost(MsgDeletePost) returns (MsgDeletePostResponse); // AddPostAttachment allows to add a new attachment to a post rpc AddPostAttachment(MsgAddPostAttachment) returns (MsgAddPostAttachmentResponse); @@ -243,9 +285,6 @@ service Msg { // RemovePostAttachment allows to remove an attachment from a post rpc RemovePostAttachment(MsgRemovePostAttachment) returns (MsgRemovePostAttachmentResponse); - // DeletePost allows to delete an existing post - rpc DeletePost(MsgDeletePost) returns (MsgDeletePostResponse); - // AnswerPoll allows to answer a post poll rpc AnswerPoll(MsgAnswerPoll) returns (MsgAnswerPollResponse); } @@ -253,163 +292,148 @@ service Msg { // MsgCreatePost represents the message to be used to create a post. message MsgCreatePost { // Id of the subspace inside which the post must be created - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // External id for this post - optional string external_id = 2; + string external_id = 2; // Text of the post - optional string text = 3; + string text = 3; // Entities connected to this post - optional Entities entities = 4; + Entities entities = 4; // Attachments of the post - repeated Attachment attachments = 5; + repeated google.protobuf.Any attachments = 5; // Author of the post - required string author = 6; + string author = 6; // Id of the original post of the conversation - optional uint64 conversation_id = 7 [default = 0]; + uint64 conversation_id = 7; // Reply settings of this post - required ReplySetting reply_settings = 8; + ReplySetting reply_settings = 8; // A list this posts references (either as a reply, repost or quote) repeated PostReference referenced_posts = 9; - - // Attachment contains the data of a single post attachment - message Attachment { - oneof sum { - Poll poll = 1; - Media media = 2; - } - } } // MsgCreatePostResponse defines the Msg/CreatePost response type. message MsgCreatePostResponse { // Id of the newly created post - required uint64 post_id = 1; + uint64 post_id = 1; // Creation date of the post - required google.protobuf.Timestamp creation_date = 2; + google.protobuf.Timestamp creation_date = 2; } // MsgEditPost represents the message to be used to edit a post. message MsgEditPost { // Id of the subspace inside which the post is - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post to edit - required uint64 id = 2; + uint64 id = 2; // New text of the post - required string text = 3; + string text = 3; // Editor of the post - required string editor = 4; + string editor = 4; // New entities connected to this post - optional Entities entities = 5; + Entities entities = 5; // Author of the post - required string author = 6; + string author = 6; } // MsgCreatePostResponse defines the Msg/EditPost response type. message MsgEditPostResponse { // Edit date of the post - required google.protobuf.Timestamp edit_date = 1; + google.protobuf.Timestamp edit_date = 1; +} + +// MsgDeletePost represents the message used when deleting a post. +message MsgDeletePost { + // Id of the subspace containing the post + uint64 subspace_id = 1; + + // Id of the post to be deleted + uint64 post_id = 2; + + // User that is deleting the post + string signer = 3; } +// MsgDeletePostResponse represents the Msg/DeletePost response type +message MsgDeletePostResponse {} + // MsgAddPostAttachment represents the message that should be // used when adding an attachment to post message MsgAddPostAttachment { // Id of the subspace containing the post - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post to which to add the attachment - required uint64 post_id = 2; - - // Attachment to be added to the post - required Attachment attachment = 3; + uint64 post_id = 2; - // Author of the post - required string author = 4; - - message Attachment { - oneof sum { - Poll poll = 1; - Media media = 2; - } - } + // Content of the attachment + google.protobuf.Any content = 3; + + // Editor of the post + string editor = 4; } // MsgAddPostAttachmentResponse defines the Msg/AddPostAttachment response type. message MsgAddPostAttachmentResponse { // New id of the uploaded attachment - required uint32 attachment_id = 1; + uint32 attachment_id = 1; // Edit date of the post - required google.protobuf.Timestamp edit_date = 2; + google.protobuf.Timestamp edit_date = 2; } // MsgRemovePostAttachment represents the message to be used when // removing an attachment from a post message MsgRemovePostAttachment { // Id of the subspace containing the post - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post from which to remove the attachment - required uint64 post_id = 2; + uint64 post_id = 2; // Id of the attachment to be removed - required uint32 attachment_id = 3; + uint32 attachment_id = 3; // User that is removing the attachment - required string signer = 4; + string editor = 4; } // MsgRemovePostAttachmentResponse defines the // Msg/RemovePostAttachment response type. message MsgRemovePostAttachmentResponse { // Edit date of the post - required google.protobuf.Timestamp edit_date = 1; + google.protobuf.Timestamp edit_date = 1; } -// MsgDeletePost represents the message used when deleting a post. -message MsgDeletePost { - // Id of the subspace containing the post - required uint64 subspace_id = 1; - - // Id of the post to be deleted - required uint64 post_id = 2; - - // User that is deleting the post - required string signer = 3; -} - -// MsgDeletePostResponse represents the Msg/DeletePost response type -message MsgDeletePostResponse {} - // MsgAnswerPoll represents the message used to answer a poll message MsgAnswerPoll { // Id of the subspace containing the post - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post that contains the poll to be answered - required uint64 post_id = 2; + uint64 post_id = 2; // Id of the poll to be answered - required uint32 poll_id = 3; + uint32 poll_id = 3; // Indexes of the answer inside the ProvidedAnswers array repeated uint32 answers_indexes = 4; // Address of the user answering the poll - required string signer = 5; + string signer = 5; } // MsgAnswerPollResponse represents the MSg/AnswerPoll response type @@ -418,6 +442,8 @@ message MsgAnswerPollResponse {} ### `Query` Service ```protobuf +syntax = "proto3"; + // Query defines the gRPC querier service service Query { // Posts queries all the posts inside a given subspace @@ -440,11 +466,6 @@ service Query { option (google.api.http).get = "/desmos/posts/v1/{subspace_id}/posts/{post_id}/polls/{poll_id}/answers"; } - // PollTallyResults queries the tally results for an ended poll - rpc PollTallyResults(QueryPollTallyResultRequest) returns (QueryPollTallyResultResponse) { - option (google.api.http).get = "/desmos/posts/v1/{subspace_id}/posts/{post_id}/polls/{poll_id}/results"; - } - // Params queries the module parameters rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/desmos/posts/v1/params"; @@ -454,98 +475,86 @@ service Query { // QueryPostsRequest is the request type for the Query/Posts RPC method message QueryPostsRequest { // Id of the subspace to query the posts for - required uint64 subspace_id = 1; + uint64 subspace_id = 1; - // pagination defines an optional pagination for the request. - optional cosmos.base.query.v1beta1.PageRequest pagination = 2; + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; } // QueryPostsResponse is the response type for the Query/Posts RPC method message QueryPostsResponse { repeated Post posts = 1; - optional cosmos.base.query.v1beta1.PageResponse pagination = 2; + cosmos.base.query.v1beta1.PageResponse pagination = 2; } // QueryPostRequest is the request type for the Query/Post RPC method message QueryPostRequest { - required uint64 subspace_id = 1; - required uint64 post_id = 2; + uint64 subspace_id = 1; + uint64 post_id = 2; } // QueryPostResponse is the response type for the Query/Post RPC method message QueryPostResponse { - required Post post = 1; + Post post = 1; } // QueryPostsRequest is the request type for the Query/PostAttachments RPC method message QueryPostAttachmentsRequest { // Id of the subspace where the post is stored - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post to query the attachments for - required uint64 post_id = 2; + uint64 post_id = 2; - // pagination defines an optional pagination for the request. + // pagination defines an pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 3; } // QueryPostAttachmentsResponse is the response type for the Query/PostAttachments RPC method message QueryPostAttachmentsResponse { repeated Attachment attachments = 1; - optional cosmos.base.query.v1beta1.PageResponse pagination = 2; + cosmos.base.query.v1beta1.PageResponse pagination = 2; } // QueryPollAnswersRequest is the request type for the Query/PollAnswers RPC method message QueryPollAnswersRequest { // Id of the subspace where the post is stored - required uint64 subspace_id = 1; + uint64 subspace_id = 1; // Id of the post that holds the poll - required uint64 post_id = 2; + uint64 post_id = 2; // Id of the poll to query the answers for - required uint32 poll_id = 3; + uint32 poll_id = 3; // (Optional) Address of the user to query the responses for - optional string user = 4; + string user = 4; - // pagination defines an optional pagination for the request. + // pagination defines an pagination for the request. cosmos.base.query.v1beta1.PageRequest pagination = 5; } // QueryPollAnswersResponse is the response type for the Query/PollAnswers RPC method message QueryPollAnswersResponse { repeated Answer answers = 1; - optional cosmos.base.query.v1beta1.PageResponse pagination = 2; + cosmos.base.query.v1beta1.PageResponse pagination = 2; // Answer contains the details about a single user answer to a poll message Answer { // Address of the user that input this - required string user = 1; + string user = 1; // Indexes of the answers inside the ProvidedAnswers array repeated uint32 answers_indexes = 2; } } -// QueryPollTallyResultRequest is the request type for the Query/PollTallyResults RPC method -message QueryPollTallyResultRequest { - required uint64 subspace_id = 1; - required uint64 post_id = 2; - required uint32 poll_id = 3; -} - -// QueryPollTallyResultResponse is the response type for the Query/PollTallyResults RPC method -message QueryPollTallyResultResponse { - repeated PollTallyResults results = 1; -} - // QueryParamsRequest is the request type for the Query/Params RPC method message QueryParamsRequest {} // QueryParamsResponse is the response type for the Query/Params RPC method message QueryParamsResponse { - required Params params = 1; + Params params = 1; } ``` @@ -567,7 +576,6 @@ The changes described inside this ADR are **not** backward compatible. To solve - Required the `x/subspaces` to implement two new permissions: - `PermissionManageContent` to allow moderators to remove post attachment and posts from a subspace; - - `PermissionCreateContent` to allow users to create a content inside a subspace; - `PermissionEditContent` to allow users to edit a content inside a subspace. ## Further Discussions diff --git a/proto/desmos/posts/v1/client/cli.proto b/proto/desmos/posts/v1/client/cli.proto new file mode 100644 index 0000000000..10099ab13e --- /dev/null +++ b/proto/desmos/posts/v1/client/cli.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; +package desmos.posts.v1.client; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +import "cosmos_proto/cosmos.proto"; + +import "desmos/posts/v1/models.proto"; + +option go_package = "github.com/desmos-labs/desmos/v3/x/posts/client/utils"; + +// CreatePostJSON contains the data that can be specified when creating a Post +// using the CLi command +message CreatePostJSON { + // (optional) External id for this post + string external_id = 1 [ (gogoproto.customname) = "ExternalID" ]; + + // (optional) Text of the post + string text = 2; + + // (optional) Entities connected to this post + Entities entities = 3; + + // Attachments of the post + repeated google.protobuf.Any attachments = 4; + + // (optional) Id of the original post of the conversation + uint64 conversation_id = 5 [ (gogoproto.customname) = "ConversationID" ]; + + // Reply settings of this post + ReplySetting reply_settings = 6; + + // A list this posts references (either as a reply, repost or quote) + repeated PostReference referenced_posts = 7 [ (gogoproto.nullable) = false ]; +} + +// EditPostJSON contains the data that can be specified when editing a Post +// using the CLI command +message EditPostJSON { + // New text of the post + string text = 1; + + // New entities connected to this post + Entities entities = 2; +} \ No newline at end of file diff --git a/proto/desmos/posts/v1/genesis.proto b/proto/desmos/posts/v1/genesis.proto new file mode 100644 index 0000000000..a72dbccb0a --- /dev/null +++ b/proto/desmos/posts/v1/genesis.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +package desmos.posts.v1; + +import "gogoproto/gogo.proto"; +import "desmos/posts/v1/models.proto"; + +option go_package = "github.com/desmos-labs/desmos/v3/x/posts/types"; + +// GenesisState contains the data of the genesis state for the posts module +message GenesisState { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + repeated SubspaceDataEntry subspaces_data = 1 + [ (gogoproto.nullable) = false ]; + + repeated GenesisPost genesis_posts = 2 [ (gogoproto.nullable) = false ]; + + repeated Attachment attachments = 3 [ (gogoproto.nullable) = false ]; + + repeated UserAnswer user_answers = 4 [ (gogoproto.nullable) = false ]; + + Params params = 5 [ (gogoproto.nullable) = false ]; +} + +// SubspaceDataEntry contains the data for a given subspace +message SubspaceDataEntry { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + uint64 initial_post_id = 2 [ (gogoproto.customname) = "InitialPostID" ]; +} + +// GenesisPost contains the genesis data for a single post +message GenesisPost { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + Post post = 1 [ (gogoproto.nullable) = false, (gogoproto.embed) = true ]; + uint32 initial_attachment_id = 2 + [ (gogoproto.customname) = "InitialAttachmentID" ]; +} diff --git a/proto/desmos/posts/v1/models.proto b/proto/desmos/posts/v1/models.proto new file mode 100644 index 0000000000..7de8b0bfc4 --- /dev/null +++ b/proto/desmos/posts/v1/models.proto @@ -0,0 +1,256 @@ +syntax = "proto3"; +package desmos.posts.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/desmos-labs/desmos/v3/x/posts/types"; + +// Post contains all the information about a single post +message Post { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Id of the subspace inside which the post has been created + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Unique id of the post + uint64 id = 2 [ (gogoproto.customname) = "ID" ]; + + // (optional) External id for this post + string external_id = 3 [ (gogoproto.customname) = "ExternalID" ]; + + // (optional) Text of the post + string text = 4; + + // (optional) Entities connected to this post + Entities entities = 5; + + // Author of the post + string author = 6; + + // (optional) Id of the original post of the conversation + uint64 conversation_id = 7 [ (gogoproto.customname) = "ConversationID" ]; + + // A list this posts references (either as a reply, repost or quote) + repeated PostReference referenced_posts = 8 [ (gogoproto.nullable) = false ]; + + // Reply settings of this post + ReplySetting reply_settings = 9; + + // Creation date of the post + google.protobuf.Timestamp creation_date = 10 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; + + // (optional) Last edited time of the post + google.protobuf.Timestamp last_edited_date = 11 + [ (gogoproto.stdtime) = true ]; +} + +// PostReference contains the details of a post reference +message PostReference { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Type of reference + Type type = 1; + + // Id of the referenced post + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // Type represents the different types of references + enum Type { + option (gogoproto.goproto_enum_prefix) = false; + + // No reference specified + TYPE_UNSPECIFIED = 0; + + // This post is a reply to the referenced post + TYPE_REPLIED_TO = 1; + + // This post is a quote to the referenced post + TYPE_QUOTED = 2; + + // This post is a report of the referenced post + TYPE_REPOSTED = 3; + } +} + +// Contains the details of entities parsed out of the post text +message Entities { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + repeated Tag hashtags = 1 [ (gogoproto.nullable) = false ]; + repeated Tag mentions = 2 [ (gogoproto.nullable) = false ]; + repeated Url urls = 3 [ (gogoproto.nullable) = false ]; +} + +// ReplySetting contains the possible reply settings that a post can have +enum ReplySetting { + option (gogoproto.goproto_enum_prefix) = false; + + // No reply setting specified + REPLY_SETTING_UNSPECIFIED = 0; + + // Everyone will be able to reply to this post + REPLY_SETTING_EVERYONE = 1; + + // Only followers of the author will be able to reply to this post + REPLY_SETTING_FOLLOWERS = 2; + + // Only the author mutual followers will be able to reply to this post + REPLY_SETTING_MUTUAL = 3; + + // Only people mentioned inside this post will be able to reply + REPLY_SETTING_MENTIONS = 4; +} + +// Tag represents a generic tag +message Tag { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Index of the character inside the text at which the tag starts + uint64 start = 1; + + // Index of the character inside the text at which the tag ends + uint64 end = 2; + + // Tag reference (user address, hashtag value, etc) + string tag = 3; +} + +// Url contains the details of a generic URL +message Url { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Index of the character inside the text at which the URL starts + uint64 start = 1; + + // Index of the character inside the text at which the URL ends + uint64 end = 2; + + // Value of the URL where the user should be redirected to + string url = 3; + + // (optional) Display value of the URL + string display_url = 4; +} + +// Attachment contains the data of a single post attachment +message Attachment { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Id of the subspace inside which the post to which this attachment should be + // connected is + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post to which this attachment should be connected + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // If of this attachment + uint32 id = 3 [ (gogoproto.customname) = "ID" ]; + + // Content of the attachment + google.protobuf.Any content = 4; +} + +// Media represents a media attachment +message Media { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + string uri = 2; + string mime_type = 3; +} + +// Poll represents a poll attachment +message Poll { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Question of the poll + string question = 1; + + // Answers the users can choose from + repeated ProvidedAnswer provided_answers = 2 [ (gogoproto.nullable) = false ]; + + // Date at which the poll will close + google.protobuf.Timestamp end_date = 3 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; + + // Whether the poll allows multiple choices from the same user or not + bool allows_multiple_answers = 4; + + // Whether the poll allows to edit an answer or not + bool allows_answer_edits = 5; + + // Final poll results + PollTallyResults final_tally_results = 6; + + // Provided answer contains the details of a possible poll answer + message ProvidedAnswer { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // (optional) Text of the answer + string text = 1; + + // Attachments of the answer + repeated Attachment attachments = 2 [ (gogoproto.nullable) = false ]; + } +} + +// UserAnswer represents a user answer to a poll +message UserAnswer { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Subspace id inside which the post related to this attachment is located + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post associated to this attachment + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // Id of the poll to which this answer is associated + uint32 poll_id = 3 [ (gogoproto.customname) = "PollID" ]; + + // Indexes of the answers inside the ProvidedAnswers array + repeated uint32 answers_indexes = 4; + + // Address of the user answering the poll + string user = 5; +} + +// PollTallyResults contains the tally results for a poll +message PollTallyResults { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + repeated AnswerResult results = 1 [ (gogoproto.nullable) = false ]; + + // AnswerResult contains the result of a single poll provided answer + message AnswerResult { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Index of the answer inside the poll's ProvidedAnswers slice + uint32 answer_index = 1; + + // Number of votes the answer has received + uint64 votes = 2; + } +} + +// Params contains the parameters for the posts module +message Params { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // Maximum length of the post text + uint32 max_text_length = 1; +} \ No newline at end of file diff --git a/proto/desmos/posts/v1/msgs.proto b/proto/desmos/posts/v1/msgs.proto new file mode 100644 index 0000000000..298cec3eeb --- /dev/null +++ b/proto/desmos/posts/v1/msgs.proto @@ -0,0 +1,186 @@ +syntax = "proto3"; +package desmos.posts.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +import "desmos/posts/v1/models.proto"; + +option go_package = "github.com/desmos-labs/desmos/v3/x/posts/types"; + +// Msg defines the posts Msg service. +service Msg { + // CreatePost allows to create a new post + rpc CreatePost(MsgCreatePost) returns (MsgCreatePostResponse); + + // EditPost allows to edit an existing post + rpc EditPost(MsgEditPost) returns (MsgEditPostResponse); + + // DeletePost allows to delete an existing post + rpc DeletePost(MsgDeletePost) returns (MsgDeletePostResponse); + + // AddPostAttachment allows to add a new attachment to a post + rpc AddPostAttachment(MsgAddPostAttachment) + returns (MsgAddPostAttachmentResponse); + + // RemovePostAttachment allows to remove an attachment from a post + rpc RemovePostAttachment(MsgRemovePostAttachment) + returns (MsgRemovePostAttachmentResponse); + + // AnswerPoll allows to answer a post poll + rpc AnswerPoll(MsgAnswerPoll) returns (MsgAnswerPollResponse); +} + +// MsgCreatePost represents the message to be used to create a post. +message MsgCreatePost { + // Id of the subspace inside which the post must be created + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // (optional) External id for this post + string external_id = 2 [ (gogoproto.customname) = "ExternalID" ]; + + // (optional) Text of the post + string text = 3; + + // (optional) Entities connected to this post + Entities entities = 4; + + // Attachments of the post + repeated google.protobuf.Any attachments = 5; + + // Author of the post + string author = 6; + + // (optional) Id of the original post of the conversation + uint64 conversation_id = 7 [ (gogoproto.customname) = "ConversationID" ]; + + // Reply settings of this post + ReplySetting reply_settings = 8; + + // A list this posts references (either as a reply, repost or quote) + repeated PostReference referenced_posts = 9 [ (gogoproto.nullable) = false ]; +} + +// MsgCreatePostResponse defines the Msg/CreatePost response type. +message MsgCreatePostResponse { + // Id of the newly created post + uint64 post_id = 1 [ (gogoproto.customname) = "PostID" ]; + + // Creation date of the post + google.protobuf.Timestamp creation_date = 2 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; +} + +// MsgEditPost represents the message to be used to edit a post. +message MsgEditPost { + // Id of the subspace inside which the post is + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post to edit + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // New text of the post. If set to [do-not-modify] it will change the current + // post's text. + string text = 3; + + // New entities connected to this post. These will always replace the current + // post's entities + Entities entities = 4; + + // Editor of the post + string editor = 5; +} + +// MsgCreatePostResponse defines the Msg/EditPost response type. +message MsgEditPostResponse { + // Edit date of the post + google.protobuf.Timestamp edit_date = 1 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; +} + +// MsgDeletePost represents the message used when deleting a post. +message MsgDeletePost { + // Id of the subspace containing the post + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post to be deleted + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // User that is deleting the post + string signer = 3; +} + +// MsgDeletePostResponse represents the Msg/DeletePost response type +message MsgDeletePostResponse {} + +// MsgAddPostAttachment represents the message that should be +// used when adding an attachment to post +message MsgAddPostAttachment { + // Id of the subspace containing the post + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post to which to add the attachment + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // Content of the attachment + google.protobuf.Any content = 3; + + // Editor of the post + string editor = 4; +} + +// MsgAddPostAttachmentResponse defines the Msg/AddPostAttachment response type. +message MsgAddPostAttachmentResponse { + // New id of the uploaded attachment + uint32 attachment_id = 1 [ (gogoproto.customname) = "AttachmentID" ]; + + // Edit date of the post + google.protobuf.Timestamp edit_date = 2 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; +} + +// MsgRemovePostAttachment represents the message to be used when +// removing an attachment from a post +message MsgRemovePostAttachment { + // Id of the subspace containing the post + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post from which to remove the attachment + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // Id of the attachment to be removed + uint32 attachment_id = 3 [ (gogoproto.customname) = "AttachmentID" ]; + + // User that is removing the attachment + string editor = 4; +} + +// MsgRemovePostAttachmentResponse defines the +// Msg/RemovePostAttachment response type. +message MsgRemovePostAttachmentResponse { + // Edit date of the post + google.protobuf.Timestamp edit_date = 1 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; +} + +// MsgAnswerPoll represents the message used to answer a poll +message MsgAnswerPoll { + // Id of the subspace containing the post + uint64 subspace_id = 1 [ (gogoproto.customname) = "SubspaceID" ]; + + // Id of the post that contains the poll to be answered + uint64 post_id = 2 [ (gogoproto.customname) = "PostID" ]; + + // Id of the poll to be answered + uint32 poll_id = 3 [ (gogoproto.customname) = "PollID" ]; + + // Indexes of the answer inside the ProvidedAnswers array + repeated uint32 answers_indexes = 4; + + // Address of the user answering the poll + string signer = 5; +} + +// MsgAnswerPollResponse represents the MSg/AnswerPoll response type +message MsgAnswerPollResponse {} \ No newline at end of file diff --git a/proto/desmos/posts/v1/query.proto b/proto/desmos/posts/v1/query.proto new file mode 100644 index 0000000000..782e9d5352 --- /dev/null +++ b/proto/desmos/posts/v1/query.proto @@ -0,0 +1,123 @@ +syntax = "proto3"; +package desmos.posts.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; + +import "desmos/posts/v1/models.proto"; + +option go_package = "github.com/desmos-labs/desmos/v3/x/posts/types"; + +// Query defines the gRPC querier service +service Query { + // Posts queries all the posts inside a given subspace + rpc Posts(QueryPostsRequest) returns (QueryPostsResponse) { + option (google.api.http).get = "/desmos/posts/v1/{subspace_id}/posts"; + } + + // Post queries for a single post inside a given subspace + rpc Post(QueryPostRequest) returns (QueryPostResponse) { + option (google.api.http).get = + "/desmos/posts/v1/{subspace_id}/posts/{post_id}"; + } + + // PostAttachments queries the attachments of the post having the given id + rpc PostAttachments(QueryPostAttachmentsRequest) + returns (QueryPostAttachmentsResponse) { + option (google.api.http).get = + "/desmos/posts/v1/{subspace_id}/posts/{post_id}/attachments"; + } + + // PollAnswers queries the answers for the poll having the given id + rpc PollAnswers(QueryPollAnswersRequest) returns (QueryPollAnswersResponse) { + option (google.api.http).get = "/desmos/posts/v1/{subspace_id}/posts/" + "{post_id}/polls/{poll_id}/answers"; + } + + // Params queries the module parameters + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/desmos/posts/v1/params"; + } +} + +// QueryPostsRequest is the request type for the Query/Posts RPC method +message QueryPostsRequest { + // Id of the subspace to query the posts for + uint64 subspace_id = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryPostsResponse is the response type for the Query/Posts RPC method +message QueryPostsResponse { + repeated Post posts = 1 [ (gogoproto.nullable) = false ]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryPostRequest is the request type for the Query/Post RPC method +message QueryPostRequest { + // Id of the subspace inside which the post lies + uint64 subspace_id = 1; + + // Id of the post to query for + uint64 post_id = 2; +} + +// QueryPostResponse is the response type for the Query/Post RPC method +message QueryPostResponse { Post post = 1 [ (gogoproto.nullable) = false ]; } + +// QueryPostsRequest is the request type for the Query/PostAttachments RPC +// method +message QueryPostAttachmentsRequest { + // Id of the subspace where the post is stored + uint64 subspace_id = 1; + + // Id of the post to query the attachments for + uint64 post_id = 2; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 3; +} + +// QueryPostAttachmentsResponse is the response type for the +// Query/PostAttachments RPC method +message QueryPostAttachmentsResponse { + repeated Attachment attachments = 1 [ (gogoproto.nullable) = false ]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryPollAnswersRequest is the request type for the Query/PollAnswers RPC +// method +message QueryPollAnswersRequest { + // Id of the subspace where the post is stored + uint64 subspace_id = 1; + + // Id of the post that holds the poll + uint64 post_id = 2; + + // Id of the poll to query the answers for + uint32 poll_id = 3; + + // (Optional) Address of the user to query the responses for + string user = 4; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 5; +} + +// QueryPollAnswersResponse is the response type for the Query/PollAnswers RPC +// method +message QueryPollAnswersResponse { + repeated UserAnswer answers = 1 [ (gogoproto.nullable) = false ]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method +message QueryParamsResponse { + Params params = 1 [ (gogoproto.nullable) = false ]; +} \ No newline at end of file diff --git a/x/posts/abci.go b/x/posts/abci.go new file mode 100644 index 0000000000..08b8b2f198 --- /dev/null +++ b/x/posts/abci.go @@ -0,0 +1,48 @@ +package posts + +import ( + "fmt" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// EndBlocker called every block, process ended polls +func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) { + // Iterate over all the active polls that have been ended by the current block time + keeper.IterateActivePollsQueue(ctx, ctx.BlockTime(), func(index int64, poll types.Attachment) (stop bool) { + // Compute the poll results + results := keeper.Tally(ctx, poll.SubspaceID, poll.PostID, poll.ID) + + // Update the content with the results + content := poll.Content.GetCachedValue().(*types.Poll) + content.FinalTallyResults = results + + contentAny, err := codectypes.NewAnyWithValue(content) + if err != nil { + panic(err) + } + poll.Content = contentAny + + keeper.SaveAttachment(ctx, poll) + keeper.RemoveFromActivePollQueue(ctx, poll) + + // Emit an event + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeTallyPoll, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", poll.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", poll.PostID)), + sdk.NewAttribute(types.AttributeKeyPollID, fmt.Sprintf("%d", poll.ID)), + ), + ) + + // When poll ends + keeper.AfterPollVotingPeriodEnded(ctx, poll.SubspaceID, poll.PostID, poll.ID) + + return false + }) +} diff --git a/x/posts/abci_test.go b/x/posts/abci_test.go new file mode 100644 index 0000000000..4c65ddd221 --- /dev/null +++ b/x/posts/abci_test.go @@ -0,0 +1,153 @@ +package posts_test + +import ( + "testing" + "time" + + relationshipskeeper "github.com/desmos-labs/desmos/v3/x/relationships/keeper" + relationshipstypes "github.com/desmos-labs/desmos/v3/x/relationships/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + db "github.com/tendermint/tm-db" + + "github.com/desmos-labs/desmos/v3/app" + "github.com/desmos-labs/desmos/v3/x/posts" + postskeeper "github.com/desmos-labs/desmos/v3/x/posts/keeper" + poststypes "github.com/desmos-labs/desmos/v3/x/posts/types" + "github.com/desmos-labs/desmos/v3/x/profiles/types" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func TestEndBlocker(t *testing.T) { + // Define store keys + keys := sdk.NewMemoryStoreKeys(poststypes.StoreKey, subspacestypes.StoreKey, relationshipstypes.StoreKey, paramstypes.StoreKey) + tKeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + + // Create an in-memory db + memDB := db.NewMemDB() + ms := store.NewCommitMultiStore(memDB) + for _, key := range keys { + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, memDB) + } + for _, tKey := range tKeys { + ms.MountStoreWithDB(tKey, sdk.StoreTypeTransient, memDB) + } + + err := ms.LoadLatestVersion() + require.NoError(t, err) + + ctx := sdk.NewContext(ms, tmproto.Header{ChainID: "test-chain"}, false, log.NewNopLogger()) + cdc, legacyAmino := app.MakeCodecs() + pk := paramskeeper.NewKeeper(cdc, legacyAmino, keys[paramstypes.StoreKey], tKeys[paramstypes.TStoreKey]) + sk := subspaceskeeper.NewKeeper(cdc, keys[subspacestypes.StoreKey]) + rk := relationshipskeeper.NewKeeper(cdc, keys[relationshipstypes.StoreKey], sk) + keeper := postskeeper.NewKeeper(cdc, keys[poststypes.StoreKey], pk.Subspace(types.DefaultParamsSpace), sk, rk) + + testCases := []struct { + name string + setupCtx func(ctx sdk.Context) sdk.Context + store func(ctx sdk.Context) + check func(ctx sdk.Context) + }{ + { + name: "active poll is not tallied before time", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + attachment := poststypes.NewAttachment(1, 1, 1, poststypes.NewPoll( + "What animal is best?", + []poststypes.Poll_ProvidedAnswer{ + poststypes.NewProvidedAnswer("Cat", nil), + poststypes.NewProvidedAnswer("Dog", nil), + poststypes.NewProvidedAnswer("No one of the above", nil), + }, + + // Just 1 nanosecond before time + time.Date(2020, 1, 1, 12, 00, 00, 001, time.UTC), + + true, + false, + nil, + )) + keeper.SaveAttachment(ctx, attachment) + keeper.InsertActivePollQueue(ctx, attachment) + + keeper.SaveUserAnswer(ctx, poststypes.NewUserAnswer(1, 1, 1, []uint32{0, 1}, "cosmos1pmklwgqjqmgc4ynevmtset85uwm0uau90jdtfn")) + keeper.SaveUserAnswer(ctx, poststypes.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1zmqjufkg44ngswgf4vmn7evp8k6h07erdyxefd")) + }, + check: func(ctx sdk.Context) { + kvStore := ctx.KVStore(keys[poststypes.StoreKey]) + endTime := time.Date(2020, 1, 1, 12, 00, 00, 001, time.UTC) + require.True(t, kvStore.Has(poststypes.ActivePollQueueKey(1, 1, 1, endTime))) + }, + }, + { + name: "active poll is tallied after time", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 001, time.UTC)) + }, + store: func(ctx sdk.Context) { + attachment := poststypes.NewAttachment(1, 1, 1, poststypes.NewPoll( + "What animal is best?", + []poststypes.Poll_ProvidedAnswer{ + poststypes.NewProvidedAnswer("Cat", nil), + poststypes.NewProvidedAnswer("Dog", nil), + poststypes.NewProvidedAnswer("No one of the above", nil), + }, + + // Just 1 nanosecond before time + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + + true, + false, + nil, + )) + keeper.SaveAttachment(ctx, attachment) + keeper.InsertActivePollQueue(ctx, attachment) + + keeper.SaveUserAnswer(ctx, poststypes.NewUserAnswer(1, 1, 1, []uint32{0, 1}, "cosmos1pmklwgqjqmgc4ynevmtset85uwm0uau90jdtfn")) + keeper.SaveUserAnswer(ctx, poststypes.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1zmqjufkg44ngswgf4vmn7evp8k6h07erdyxefd")) + }, + check: func(ctx sdk.Context) { + poll, found := keeper.GetPoll(ctx, 1, 1, 1) + require.True(t, found) + require.Equal(t, poststypes.NewPollTallyResults([]poststypes.PollTallyResults_AnswerResult{ + poststypes.NewAnswerResult(0, 1), + poststypes.NewAnswerResult(1, 2), + poststypes.NewAnswerResult(2, 0), + }), poll.FinalTallyResults) + + kvStore := ctx.KVStore(keys[poststypes.StoreKey]) + endTime := time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC) + require.False(t, kvStore.Has(poststypes.ActivePollQueueKey(1, 1, 1, endTime))) + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + ctx, _ := ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + posts.EndBlocker(ctx, keeper) + + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/client/cli/cli_test.go b/x/posts/client/cli/cli_test.go new file mode 100644 index 0000000000..8aaecedbc3 --- /dev/null +++ b/x/posts/client/cli/cli_test.go @@ -0,0 +1,803 @@ +package cli_test + +import ( + "fmt" + "io/ioutil" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/client/flags" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + tmcli "github.com/tendermint/tendermint/libs/cli" + + "github.com/desmos-labs/desmos/v3/app" + "github.com/desmos-labs/desmos/v3/testutil" + "github.com/desmos-labs/desmos/v3/x/posts/client/cli" + cliutils "github.com/desmos-labs/desmos/v3/x/posts/client/utils" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := testutil.DefaultConfig() + genesisState := cfg.GenesisState + cfg.NumValidators = 2 + + // Initialize the subspaces module genesis state + subspacesGenesis := subspacestypes.NewGenesisState( + 2, + []subspacestypes.GenesisSubspace{ + subspacestypes.NewGenesisSubspace(subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + ), 1), + }, + nil, nil, nil, + ) + subspacesDataBz, err := cfg.Codec.MarshalJSON(subspacesGenesis) + s.Require().NoError(err) + genesisState[subspacestypes.ModuleName] = subspacesDataBz + + // Initialize the module genesis data + postsGenesis := types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(2, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + []types.PostReference{}, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + true, + false, + nil, + )), + }, + []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + types.NewUserAnswer(1, 1, 1, []uint32{0, 1}, "cosmos1u65w3xnhga8ngyg44eudh07zdxmkzny6uaudfc"), + }, + types.DefaultParams(), + ) + postsDataBz, err := cfg.Codec.MarshalJSON(postsGenesis) + s.Require().NoError(err) + genesisState[types.ModuleName] = postsDataBz + + // Store the genesis data + cfg.GenesisState = genesisState + + s.cfg = cfg + s.network = network.New(s.T(), cfg) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +// -------------------------------------------------------------------------------------------------------------------- + +func (s *IntegrationTestSuite) TestCmdQueryPost() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + expResponse types.QueryPostResponse + }{ + { + name: "non existing post returns error", + args: []string{ + "1", "2", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: true, + }, + { + name: "existing post is returned correctly", + args: []string{ + "1", "1", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryPostResponse{ + Post: types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + []types.PostReference{}, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryPost() + clientCtx := val.ClientCtx + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + var response types.QueryPostResponse + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), &response), out.String()) + s.Require().Equal(tc.expResponse.Post, response.Post) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdQueryPosts() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + expResponse types.QueryPostsResponse + }{ + { + name: "posts are returned correctly", + args: []string{ + "1", + fmt.Sprintf("--%s=%d", flags.FlagLimit, 1), + fmt.Sprintf("--%s=%d", flags.FlagPage, 1), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryPostsResponse{ + Posts: []types.Post{ + types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + []types.PostReference{}, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryPosts() + clientCtx := val.ClientCtx + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + var response types.QueryPostsResponse + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), &response), out.String()) + s.Require().Equal(tc.expResponse.Posts, response.Posts) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdQueryPostAttachments() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + expResponse types.QueryPostAttachmentsResponse + }{ + { + name: "attachments are returned correctly", + args: []string{ + "1", "1", + fmt.Sprintf("--%s=%d", flags.FlagLimit, 1), + fmt.Sprintf("--%s=%d", flags.FlagPage, 1), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryPostAttachmentsResponse{ + Attachments: []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + true, + false, + nil, + )), + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryPostAttachments() + clientCtx := val.ClientCtx + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + var response types.QueryPostAttachmentsResponse + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), &response), out.String()) + s.Require().Equal(tc.expResponse.Attachments, response.Attachments) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdQueryPollAnswers() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + expResponse types.QueryPollAnswersResponse + }{ + { + name: "answers are returned correctly if no user is specified", + args: []string{ + "1", "1", "1", "", + fmt.Sprintf("--%s=%d", flags.FlagLimit, 2), + fmt.Sprintf("--%s=%d", flags.FlagPage, 1), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryPollAnswersResponse{ + Answers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{0, 1}, "cosmos1u65w3xnhga8ngyg44eudh07zdxmkzny6uaudfc"), + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + }, + { + name: "answer is returned correctly if a user is specified", + args: []string{ + "1", "1", "1", "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryPollAnswersResponse{ + Answers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryPollAnswers() + clientCtx := val.ClientCtx + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + var response types.QueryPollAnswersResponse + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), &response), out.String()) + s.Require().Equal(tc.expResponse.Answers, response.Answers) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdQueryParams() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + expResponse types.QueryParamsResponse + }{ + { + name: "params are returned correctly", + args: []string{ + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + shouldErr: false, + expResponse: types.QueryParamsResponse{ + Params: types.DefaultParams(), + }, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryParams() + clientCtx := val.ClientCtx + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + + var response types.QueryParamsResponse + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), &response), out.String()) + s.Require().Equal(tc.expResponse.Params, response.Params) + } + }) + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +func (s *IntegrationTestSuite) writeCreatePostJSONFile() (filePath string) { + attachments, err := types.PackAttachments([]types.AttachmentContent{}) + s.Require().NoError(err) + + jsonData := cliutils.CreatePostJSON{ + ExternalID: "This is my external id", + Text: "This is my post text", + Entities: types.NewEntities(nil, nil, []types.Url{ + types.NewURL(0, 3, "https://example.com", "This"), + }), + Attachments: attachments, + ConversationID: 1, + ReplySettings: types.REPLY_SETTING_EVERYONE, + ReferencedPosts: []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + } + + cdc, _ := app.MakeCodecs() + jsonBz := cdc.MustMarshalJSON(&jsonData) + + // Write the JSON to a temp file + f, err := ioutil.TempFile(s.T().TempDir(), "create-post") + s.Require().NoError(err) + defer f.Close() + + err = ioutil.WriteFile(f.Name(), jsonBz, 0644) + s.Require().NoError(err) + return f.Name() +} + +func (s *IntegrationTestSuite) TestCmdCreatePost() { + filePath := s.writeCreatePostJSONFile() + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "", filePath, + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", filePath, + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdCreatePost() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) writeEditPostJSONFile() (filePath string) { + jsonData := cliutils.EditPostJSON{ + Text: "This is my edited text", + Entities: types.NewEntities(nil, nil, []types.Url{ + types.NewURL(0, 3, "https://example.com", "This"), + }), + } + + cdc, _ := app.MakeCodecs() + jsonBz := cdc.MustMarshalJSON(&jsonData) + + // Write the JSON to a temp file + f, err := ioutil.TempFile(s.T().TempDir(), "edit-post") + s.Require().NoError(err) + defer f.Close() + + err = ioutil.WriteFile(f.Name(), jsonBz, 0644) + s.Require().NoError(err) + return f.Name() +} + +func (s *IntegrationTestSuite) TestCmdEditPost() { + filePath := s.writeEditPostJSONFile() + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "0", "1", filePath, + }, + shouldErr: true, + }, + { + name: "invalid post id returns error", + args: []string{ + "1", "0", filePath, + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", "1", filePath, + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdEditPost() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdDeletePost() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "0", "1", + }, + shouldErr: true, + }, + { + name: "invalid post id returns error", + args: []string{ + "1", "0", + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", "1", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdDeletePost() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) writeAttachmentJSONFile() (filePath string) { + attachmentAny, err := codectypes.NewAnyWithValue(types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )) + s.Require().NoError(err) + + cdc, _ := app.MakeCodecs() + jsonBz := cdc.MustMarshalJSON(attachmentAny) + + // Write the JSON to a temp file + f, err := ioutil.TempFile(s.T().TempDir(), "attachment") + s.Require().NoError(err) + defer f.Close() + + err = ioutil.WriteFile(f.Name(), jsonBz, 0644) + s.Require().NoError(err) + return f.Name() +} + +func (s *IntegrationTestSuite) TestCmdAddPostAttachment() { + filePath := s.writeAttachmentJSONFile() + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "0", "1", filePath, + }, + shouldErr: true, + }, + { + name: "invalid post id returns error", + args: []string{ + "1", "0", filePath, + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", "1", filePath, + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdAddPostAttachment() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdRemovePostAttachment() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "0", "1", "1", + }, + shouldErr: true, + }, + { + name: "invalid post id returns error", + args: []string{ + "1", "0", "1", + }, + shouldErr: true, + }, + { + name: "invalid attachment id returns error", + args: []string{ + "1", "1", "0", + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", "1", "1", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdRemovePostAttachment() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdAnswerPoll() { + val := s.network.Validators[0] + testCases := []struct { + name string + args []string + shouldErr bool + respType proto.Message + }{ + { + name: "invalid subspace id returns error", + args: []string{ + "0", "1", "1", "0,1", + }, + shouldErr: true, + }, + { + name: "invalid post id returns error", + args: []string{ + "1", "0", "1", "0,1", + }, + shouldErr: true, + }, + { + name: "invalid poll id returns error", + args: []string{ + "1", "1", "0", "0,1", + }, + shouldErr: true, + }, + { + name: "invalid answer returns error", + args: []string{ + "1", "1", "1", "", + }, + shouldErr: true, + }, + { + name: "valid data returns no error", + args: []string{ + "1", "1", "1", "0,1,2", + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + }, + shouldErr: false, + respType: &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := cli.GetCmdAnswerPoll() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.shouldErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONCodec.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + } + }) + } +} diff --git a/x/posts/client/cli/query.go b/x/posts/client/cli/query.go new file mode 100644 index 0000000000..3574aec4e6 --- /dev/null +++ b/x/posts/client/cli/query.go @@ -0,0 +1,239 @@ +package cli + +// DONTCOVER + +import ( + "context" + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + "github.com/spf13/cobra" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// GetQueryCmd returns the command allowing to perform queries +func GetQueryCmd() *cobra.Command { + subspaceQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the posts module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + subspaceQueryCmd.AddCommand( + GetCmdQueryPost(), + GetCmdQueryPosts(), + GetCmdQueryPostAttachments(), + GetCmdQueryPollAnswers(), + GetCmdQueryParams(), + ) + return subspaceQueryCmd +} + +// GetCmdQueryPost returns the command to query the post having the given id +func GetCmdQueryPost() *cobra.Command { + cmd := &cobra.Command{ + Use: "post [subspace-id] [post-id]", + Short: "Query the post with the given id", + Example: fmt.Sprintf(`%s query posts post 1 1`, version.AppName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + res, err := queryClient.Post(context.Background(), types.NewQueryPostRequest(subspaceID, postID)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryPosts returns the command to query all the posts inside a subspace +func GetCmdQueryPosts() *cobra.Command { + cmd := &cobra.Command{ + Use: "posts [subspace-id]", + Short: "Query the posts inside a specific subspace", + Example: fmt.Sprintf(`%s query posts posts 1 --page=2 --limit=100`, version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.Posts(context.Background(), types.NewQueryPostsRequest(subspaceID, pageReq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "posts") + + return cmd +} + +// GetCmdQueryPostAttachments returns the command to query all the attachments for the post having the given id +func GetCmdQueryPostAttachments() *cobra.Command { + cmd := &cobra.Command{ + Use: "attachments [subspace-id] [post-id]", + Short: "Query the attachments for the post having the given id", + Example: fmt.Sprintf(`%s query posts attachments 1 1 --page=2 --limit=100`, version.AppName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.PostAttachments(context.Background(), types.NewQueryPostAttachmentsRequest(subspaceID, postID, pageReq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "post attachments") + + return cmd +} + +// GetCmdQueryPollAnswers returns the command to query all the answers to a given poll +func GetCmdQueryPollAnswers() *cobra.Command { + cmd := &cobra.Command{ + Use: "answers [subspace-id] [post-id] [poll-id] [[user]]", + Short: "Query the answers related to a specific poll with an optional user", + Long: `Query the answers related to the poll having the given id, and associated to the specified post inside the specified subspace. +If a user address is provided, only the answer of that user will be returned (if any). +`, + Example: fmt.Sprintf(` +%s query posts answers 1 1 1 --page=2 --limit=100 +%s query posts answers 1 1 1 desmos1mc0mrx23aawryc6gztvdyrupph00yz8lk42v40 --page=2 --limit=100 +`, version.AppName, version.AppName), + Args: cobra.RangeArgs(3, 4), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + pollID, err := types.ParseAttachmentID(args[2]) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.PollAnswers(context.Background(), types.NewQueryPollAnswersRequest(subspaceID, postID, pollID, args[3], pageReq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "poll answers") + + return cmd +} + +// GetCmdQueryParams returns the command to query the module params +func GetCmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the module parameters", + Example: fmt.Sprintf(`%s query posts params`, version.AppName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(context.Background(), types.NewQueryParamsRequest()) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/posts/client/cli/tx.go b/x/posts/client/cli/tx.go new file mode 100644 index 0000000000..5d97ae95e2 --- /dev/null +++ b/x/posts/client/cli/tx.go @@ -0,0 +1,341 @@ +package cli + +// DONTCOVER + +import ( + "fmt" + "strconv" + "strings" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/version" + "github.com/spf13/cobra" + + cliutils "github.com/desmos-labs/desmos/v3/x/posts/client/utils" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// NewTxCmd returns a new command to perform subspaces transactions +func NewTxCmd() *cobra.Command { + subspacesTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Posts transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + subspacesTxCmd.AddCommand( + GetCmdCreatePost(), + GetCmdEditPost(), + GetCmdDeletePost(), + GetCmdAddPostAttachment(), + GetCmdRemovePostAttachment(), + GetCmdAnswerPoll(), + ) + + return subspacesTxCmd +} + +// GetCmdCreatePost returns the command allowing to create a new post +func GetCmdCreatePost() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [subspace-id] [json-file-path]", + Args: cobra.ExactArgs(2), + Short: "Create a new post", + Long: `Create a new post containing the data specified inside the JSON file located at the provided path.`, + Example: fmt.Sprintf(`%s tx posts create 1 /path/to/my/file.json --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + data, err := cliutils.ParseCreatePostJSON(clientCtx.Codec, args[1]) + if err != nil { + return err + } + + author := clientCtx.FromAddress.String() + + attachments, err := types.UnpackAttachments(clientCtx.Codec, data.Attachments) + if err != nil { + return err + } + + msg := types.NewMsgCreatePost( + subspaceID, + data.ExternalID, + data.Text, + data.ConversationID, + data.ReplySettings, + data.Entities, + attachments, + data.ReferencedPosts, + author, + ) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdEditPost returns the command allowing to edit an existing post +func GetCmdEditPost() *cobra.Command { + cmd := &cobra.Command{ + Use: "edit [subspace-id] [post-id] [json-file-path]", + Args: cobra.ExactArgs(3), + Short: "Edit an existing post", + Long: `Edit an existing post by using the data specified inside the JSON file located at the provided path.`, + Example: fmt.Sprintf(`%s tx posts edit 1 1 /path/to/my/file.json --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + data, err := cliutils.ParseEditPostJSON(clientCtx.Codec, args[2]) + if err != nil { + return err + } + + editor := clientCtx.FromAddress.String() + + msg := types.NewMsgEditPost( + subspaceID, + postID, + data.Text, + data.Entities, + editor, + ) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdDeletePost returns the command allowing to delete an existing post +func GetCmdDeletePost() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete [subspace-id] [post-id]", + Args: cobra.ExactArgs(2), + Short: "Delete an existing post", + Example: fmt.Sprintf(`%s tx posts delete 1 1 --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + signer := clientCtx.FromAddress.String() + + msg := types.NewMsgDeletePost(subspaceID, postID, signer) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdAddPostAttachment returns the command allowing to add an attachment to an existing post +func GetCmdAddPostAttachment() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-attachment [subspace-id] [post-id] [json-file-path]", + Args: cobra.ExactArgs(3), + Short: "Add an attachment to an existing post", + Long: `Add an attachment to an existing post by using the data specified inside the JSON file located at the provided path.`, + Example: fmt.Sprintf(`%s tx posts add-attachment 1 1 /path/to/my/file.json --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + content, err := cliutils.ParseAttachmentContent(clientCtx.Codec, args[2]) + if err != nil { + return err + } + + editor := clientCtx.FromAddress.String() + + msg := types.NewMsgAddPostAttachment(subspaceID, postID, content, editor) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdRemovePostAttachment returns the command allowing to delete an existing post attachment +func GetCmdRemovePostAttachment() *cobra.Command { + cmd := &cobra.Command{ + Use: "remove-attachment [subspace-id] [post-id] [attachment-id]", + Args: cobra.ExactArgs(3), + Short: "Remove an existing post attachment", + Example: fmt.Sprintf(`%s tx posts remove-attachment 1 1 1 --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + attachmentID, err := types.ParseAttachmentID(args[2]) + if err != nil { + return err + } + + signer := clientCtx.FromAddress.String() + + msg := types.NewMsgRemovePostAttachment(subspaceID, postID, attachmentID, signer) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdAnswerPoll returns the command allowing to answer an existing poll +func GetCmdAnswerPoll() *cobra.Command { + cmd := &cobra.Command{ + Use: "answer-poll [subspace-id] [post-id] [poll-id] [answers-indexes]", + Args: cobra.ExactArgs(4), + Short: "Answer an existing poll", + Long: `Answer an existing poll with the provided answers index. +If you want to specify multiple answers, separate them using a comma. + +Answer indexes must be the indexes that each of your answer has inside the poll's provided answer array. +E.g. Suppose you have a poll with the following provided answer array: +- Question: What animal is the best? + Provided answers: + - Cat + - Dog + +Then, the "Cat" answer has index 0 and and the "Dog" answer has index 1. +`, + Example: fmt.Sprintf(`%s tx posts answer-poll 1 1 1 0,1,2 --from alice`, version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + subspaceID, err := subspacestypes.ParseSubspaceID(args[0]) + if err != nil { + return err + } + + postID, err := types.ParsePostID(args[1]) + if err != nil { + return err + } + + pollID, err := types.ParseAttachmentID(args[2]) + if err != nil { + return err + } + + indexes := strings.Split(args[3], ",") + answers := make([]uint32, len(indexes)) + for i, answer := range indexes { + answerIndex, err := strconv.ParseUint(answer, 10, 32) + if err != nil { + return err + } + answers[i] = uint32(answerIndex) + } + + signer := clientCtx.FromAddress.String() + + msg := types.NewMsgAnswerPoll(subspaceID, postID, pollID, answers, signer) + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("message validation failed: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/posts/client/utils/cli.pb.go b/x/posts/client/utils/cli.pb.go new file mode 100644 index 0000000000..381704053f --- /dev/null +++ b/x/posts/client/utils/cli.pb.go @@ -0,0 +1,886 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: desmos/posts/v1/client/cli.proto + +package utils + +import ( + fmt "fmt" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + types "github.com/desmos-labs/desmos/v3/x/posts/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + _ "github.com/regen-network/cosmos-proto" + _ "google.golang.org/protobuf/types/known/timestamppb" + 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 + +// CreatePostJSON contains the data that can be specified when creating a Post +// using the CLi command +type CreatePostJSON struct { + // (optional) External id for this post + ExternalID string `protobuf:"bytes,1,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + // (optional) Text of the post + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` + // (optional) Entities connected to this post + Entities *types.Entities `protobuf:"bytes,3,opt,name=entities,proto3" json:"entities,omitempty"` + // Attachments of the post + Attachments []*types1.Any `protobuf:"bytes,4,rep,name=attachments,proto3" json:"attachments,omitempty"` + // (optional) Id of the original post of the conversation + ConversationID uint64 `protobuf:"varint,5,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + // Reply settings of this post + ReplySettings types.ReplySetting `protobuf:"varint,6,opt,name=reply_settings,json=replySettings,proto3,enum=desmos.posts.v1.ReplySetting" json:"reply_settings,omitempty"` + // A list this posts references (either as a reply, repost or quote) + ReferencedPosts []types.PostReference `protobuf:"bytes,7,rep,name=referenced_posts,json=referencedPosts,proto3" json:"referenced_posts"` +} + +func (m *CreatePostJSON) Reset() { *m = CreatePostJSON{} } +func (m *CreatePostJSON) String() string { return proto.CompactTextString(m) } +func (*CreatePostJSON) ProtoMessage() {} +func (*CreatePostJSON) Descriptor() ([]byte, []int) { + return fileDescriptor_905ce8d49fe91e44, []int{0} +} +func (m *CreatePostJSON) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CreatePostJSON) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CreatePostJSON.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 *CreatePostJSON) XXX_Merge(src proto.Message) { + xxx_messageInfo_CreatePostJSON.Merge(m, src) +} +func (m *CreatePostJSON) XXX_Size() int { + return m.Size() +} +func (m *CreatePostJSON) XXX_DiscardUnknown() { + xxx_messageInfo_CreatePostJSON.DiscardUnknown(m) +} + +var xxx_messageInfo_CreatePostJSON proto.InternalMessageInfo + +func (m *CreatePostJSON) GetExternalID() string { + if m != nil { + return m.ExternalID + } + return "" +} + +func (m *CreatePostJSON) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *CreatePostJSON) GetEntities() *types.Entities { + if m != nil { + return m.Entities + } + return nil +} + +func (m *CreatePostJSON) GetAttachments() []*types1.Any { + if m != nil { + return m.Attachments + } + return nil +} + +func (m *CreatePostJSON) GetConversationID() uint64 { + if m != nil { + return m.ConversationID + } + return 0 +} + +func (m *CreatePostJSON) GetReplySettings() types.ReplySetting { + if m != nil { + return m.ReplySettings + } + return types.REPLY_SETTING_UNSPECIFIED +} + +func (m *CreatePostJSON) GetReferencedPosts() []types.PostReference { + if m != nil { + return m.ReferencedPosts + } + return nil +} + +// EditPostJSON contains the data that can be specified when editing a Post +// using the CLI command +type EditPostJSON struct { + // New text of the post + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + // New entities connected to this post + Entities *types.Entities `protobuf:"bytes,2,opt,name=entities,proto3" json:"entities,omitempty"` +} + +func (m *EditPostJSON) Reset() { *m = EditPostJSON{} } +func (m *EditPostJSON) String() string { return proto.CompactTextString(m) } +func (*EditPostJSON) ProtoMessage() {} +func (*EditPostJSON) Descriptor() ([]byte, []int) { + return fileDescriptor_905ce8d49fe91e44, []int{1} +} +func (m *EditPostJSON) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EditPostJSON) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EditPostJSON.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 *EditPostJSON) XXX_Merge(src proto.Message) { + xxx_messageInfo_EditPostJSON.Merge(m, src) +} +func (m *EditPostJSON) XXX_Size() int { + return m.Size() +} +func (m *EditPostJSON) XXX_DiscardUnknown() { + xxx_messageInfo_EditPostJSON.DiscardUnknown(m) +} + +var xxx_messageInfo_EditPostJSON proto.InternalMessageInfo + +func (m *EditPostJSON) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *EditPostJSON) GetEntities() *types.Entities { + if m != nil { + return m.Entities + } + return nil +} + +func init() { + proto.RegisterType((*CreatePostJSON)(nil), "desmos.posts.v1.client.CreatePostJSON") + proto.RegisterType((*EditPostJSON)(nil), "desmos.posts.v1.client.EditPostJSON") +} + +func init() { proto.RegisterFile("desmos/posts/v1/client/cli.proto", fileDescriptor_905ce8d49fe91e44) } + +var fileDescriptor_905ce8d49fe91e44 = []byte{ + // 459 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xe3, 0x26, 0x14, 0xd8, 0x80, 0x8b, 0x56, 0x15, 0x72, 0x2b, 0x70, 0xac, 0x9e, 0x7c, + 0x61, 0x57, 0x4d, 0x55, 0x2e, 0x9c, 0x48, 0x93, 0x43, 0x38, 0x10, 0xb4, 0x3d, 0xc1, 0x25, 0x72, + 0xec, 0xa9, 0xbb, 0x92, 0xbd, 0x1b, 0x79, 0x27, 0x51, 0xf2, 0x16, 0x1c, 0x79, 0xa4, 0x1e, 0x7b, + 0xe4, 0x14, 0x21, 0xe7, 0x45, 0x90, 0xff, 0x95, 0x28, 0x15, 0x52, 0x4f, 0x9e, 0xf1, 0xf7, 0x9b, + 0xd9, 0xd9, 0x6f, 0x87, 0x78, 0x11, 0x98, 0x54, 0x1b, 0x3e, 0xd7, 0x06, 0x0d, 0x5f, 0x9e, 0xf3, + 0x30, 0x91, 0xa0, 0xb0, 0xf8, 0xb0, 0x79, 0xa6, 0x51, 0xd3, 0xb7, 0x15, 0xc1, 0x4a, 0x82, 0x2d, + 0xcf, 0x59, 0x45, 0x9c, 0x1e, 0xc7, 0x3a, 0xd6, 0x25, 0xc2, 0x8b, 0xa8, 0xa2, 0x4f, 0x4f, 0x62, + 0xad, 0xe3, 0x04, 0x78, 0x99, 0xcd, 0x16, 0x37, 0x3c, 0x50, 0xeb, 0x5a, 0xea, 0xed, 0x4b, 0x28, + 0x53, 0x30, 0x18, 0xa4, 0xf3, 0xa6, 0x36, 0xd4, 0xc5, 0x49, 0xd3, 0xaa, 0x69, 0x95, 0xd4, 0xd2, + 0xbb, 0xfd, 0x31, 0x53, 0x1d, 0x41, 0x52, 0xab, 0x67, 0xbf, 0xda, 0xc4, 0xbe, 0xca, 0x20, 0x40, + 0xf8, 0xa6, 0x0d, 0x7e, 0xb9, 0x9e, 0x7c, 0xa5, 0x9c, 0x74, 0x61, 0x85, 0x90, 0xa9, 0x20, 0x99, + 0xca, 0xc8, 0xb1, 0x3c, 0xcb, 0x7f, 0x39, 0xb0, 0xf3, 0x4d, 0x8f, 0x8c, 0xea, 0xdf, 0xe3, 0xa1, + 0x20, 0x0d, 0x32, 0x8e, 0x28, 0x25, 0x1d, 0x84, 0x15, 0x3a, 0x07, 0x05, 0x29, 0xca, 0x98, 0x5e, + 0x92, 0x17, 0xa0, 0x50, 0xa2, 0x04, 0xe3, 0xb4, 0x3d, 0xcb, 0xef, 0xf6, 0x4f, 0xd8, 0xbe, 0x1b, + 0xa3, 0x1a, 0x10, 0x0f, 0x28, 0xfd, 0x48, 0xba, 0x01, 0x62, 0x10, 0xde, 0xa6, 0xa0, 0xd0, 0x38, + 0x1d, 0xaf, 0xed, 0x77, 0xfb, 0xc7, 0xac, 0xba, 0x3e, 0x6b, 0xae, 0xcf, 0x3e, 0xab, 0xb5, 0xd8, + 0x05, 0xe9, 0x27, 0x72, 0x14, 0x6a, 0xb5, 0x84, 0xcc, 0x04, 0x28, 0xb5, 0x2a, 0xe6, 0x7e, 0xe6, + 0x59, 0x7e, 0x67, 0x40, 0xf3, 0x4d, 0xcf, 0xbe, 0xda, 0x91, 0xc6, 0x43, 0x61, 0xef, 0xa2, 0xe3, + 0x88, 0x0e, 0x89, 0x9d, 0xc1, 0x3c, 0x59, 0x4f, 0x0d, 0x20, 0x4a, 0x15, 0x1b, 0xe7, 0xd0, 0xb3, + 0x7c, 0xbb, 0xff, 0xfe, 0xd1, 0xc4, 0xa2, 0xc0, 0xae, 0x2b, 0x4a, 0xbc, 0xce, 0x76, 0x32, 0x43, + 0x27, 0xe4, 0x4d, 0x06, 0x37, 0x90, 0x81, 0x0a, 0x21, 0x9a, 0x96, 0x25, 0xce, 0xf3, 0x72, 0x7e, + 0xf7, 0x51, 0x9f, 0xc2, 0x6b, 0xd1, 0xc0, 0x83, 0xce, 0xdd, 0xa6, 0xd7, 0x12, 0x47, 0xff, 0xaa, + 0x0b, 0xd9, 0x9c, 0x7d, 0x27, 0xaf, 0x46, 0x91, 0xc4, 0x87, 0x77, 0x69, 0x6c, 0xb6, 0xfe, 0x63, + 0xf3, 0xc1, 0x93, 0x6d, 0x1e, 0x4c, 0xee, 0x72, 0xd7, 0xba, 0xcf, 0x5d, 0xeb, 0x4f, 0xee, 0x5a, + 0x3f, 0xb7, 0x6e, 0xeb, 0x7e, 0xeb, 0xb6, 0x7e, 0x6f, 0xdd, 0xd6, 0x8f, 0xcb, 0x58, 0xe2, 0xed, + 0x62, 0xc6, 0x42, 0x9d, 0xf2, 0xaa, 0xd1, 0x87, 0x24, 0x98, 0x99, 0x3a, 0xe6, 0xcb, 0x0b, 0xbe, + 0xaa, 0x37, 0xa9, 0xde, 0xf6, 0x05, 0xca, 0xc4, 0xcc, 0x0e, 0xcb, 0xa7, 0xb9, 0xf8, 0x1b, 0x00, + 0x00, 0xff, 0xff, 0x81, 0x3d, 0x38, 0xcf, 0x14, 0x03, 0x00, 0x00, +} + +func (m *CreatePostJSON) 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 *CreatePostJSON) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CreatePostJSON) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ReferencedPosts) > 0 { + for iNdEx := len(m.ReferencedPosts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ReferencedPosts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCli(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } + if m.ReplySettings != 0 { + i = encodeVarintCli(dAtA, i, uint64(m.ReplySettings)) + i-- + dAtA[i] = 0x30 + } + if m.ConversationID != 0 { + i = encodeVarintCli(dAtA, i, uint64(m.ConversationID)) + i-- + dAtA[i] = 0x28 + } + if len(m.Attachments) > 0 { + for iNdEx := len(m.Attachments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attachments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCli(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if m.Entities != nil { + { + size, err := m.Entities.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCli(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintCli(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExternalID) > 0 { + i -= len(m.ExternalID) + copy(dAtA[i:], m.ExternalID) + i = encodeVarintCli(dAtA, i, uint64(len(m.ExternalID))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EditPostJSON) 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 *EditPostJSON) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EditPostJSON) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Entities != nil { + { + size, err := m.Entities.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCli(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintCli(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintCli(dAtA []byte, offset int, v uint64) int { + offset -= sovCli(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *CreatePostJSON) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExternalID) + if l > 0 { + n += 1 + l + sovCli(uint64(l)) + } + l = len(m.Text) + if l > 0 { + n += 1 + l + sovCli(uint64(l)) + } + if m.Entities != nil { + l = m.Entities.Size() + n += 1 + l + sovCli(uint64(l)) + } + if len(m.Attachments) > 0 { + for _, e := range m.Attachments { + l = e.Size() + n += 1 + l + sovCli(uint64(l)) + } + } + if m.ConversationID != 0 { + n += 1 + sovCli(uint64(m.ConversationID)) + } + if m.ReplySettings != 0 { + n += 1 + sovCli(uint64(m.ReplySettings)) + } + if len(m.ReferencedPosts) > 0 { + for _, e := range m.ReferencedPosts { + l = e.Size() + n += 1 + l + sovCli(uint64(l)) + } + } + return n +} + +func (m *EditPostJSON) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Text) + if l > 0 { + n += 1 + l + sovCli(uint64(l)) + } + if m.Entities != nil { + l = m.Entities.Size() + n += 1 + l + sovCli(uint64(l)) + } + return n +} + +func sovCli(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCli(x uint64) (n int) { + return sovCli(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *CreatePostJSON) 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 ErrIntOverflowCli + } + 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: CreatePostJSON: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CreatePostJSON: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExternalID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + 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 ErrInvalidLengthCli + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExternalID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + 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 ErrInvalidLengthCli + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCli + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Entities == nil { + m.Entities = &types.Entities{} + } + if err := m.Entities.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attachments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCli + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attachments = append(m.Attachments, &types1.Any{}) + if err := m.Attachments[len(m.Attachments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConversationID", wireType) + } + m.ConversationID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConversationID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReplySettings", wireType) + } + m.ReplySettings = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReplySettings |= types.ReplySetting(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReferencedPosts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCli + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReferencedPosts = append(m.ReferencedPosts, types.PostReference{}) + if err := m.ReferencedPosts[len(m.ReferencedPosts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCli(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCli + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EditPostJSON) 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 ErrIntOverflowCli + } + 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: EditPostJSON: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EditPostJSON: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + 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 ErrInvalidLengthCli + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCli + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCli + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCli + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Entities == nil { + m.Entities = &types.Entities{} + } + if err := m.Entities.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCli(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCli + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCli(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, ErrIntOverflowCli + } + 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, ErrIntOverflowCli + } + 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, ErrIntOverflowCli + } + 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, ErrInvalidLengthCli + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCli + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCli + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCli = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCli = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCli = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/posts/client/utils/utils.go b/x/posts/client/utils/utils.go new file mode 100644 index 0000000000..4991d2e3da --- /dev/null +++ b/x/posts/client/utils/utils.go @@ -0,0 +1,60 @@ +package utils + +import ( + "io/ioutil" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// ParseCreatePostJSON reads and parses a CreatePostJSON from file. +func ParseCreatePostJSON(cdc codec.Codec, dataFile string) (CreatePostJSON, error) { + var data CreatePostJSON + + contents, err := ioutil.ReadFile(dataFile) + if err != nil { + return data, err + } + + err = cdc.UnmarshalJSON(contents, &data) + if err != nil { + return data, err + } + + return data, nil +} + +// ParseEditPostJSON reads and parses a EditPostJSON from file. +func ParseEditPostJSON(cdc codec.Codec, dataFile string) (EditPostJSON, error) { + var data EditPostJSON + + contents, err := ioutil.ReadFile(dataFile) + if err != nil { + return data, err + } + + err = cdc.UnmarshalJSON(contents, &data) + if err != nil { + return data, err + } + + return data, nil +} + +// ParseAttachmentContent reads and parses a AttachmentContent from file. +func ParseAttachmentContent(cdc codec.Codec, dataFile string) (types.AttachmentContent, error) { + var data types.AttachmentContent + + contents, err := ioutil.ReadFile(dataFile) + if err != nil { + return data, err + } + + err = cdc.UnmarshalInterfaceJSON(contents, &data) + if err != nil { + return data, err + } + + return data, nil +} diff --git a/x/posts/keeper/alias_functions.go b/x/posts/keeper/alias_functions.go new file mode 100644 index 0000000000..e0fd00eb4c --- /dev/null +++ b/x/posts/keeper/alias_functions.go @@ -0,0 +1,241 @@ +package keeper + +import ( + "bytes" + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + subspacetypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// HasSubspace checks whether the given subspace exists or not +func (k Keeper) HasSubspace(ctx sdk.Context, subspaceID uint64) bool { + return k.sk.HasSubspace(ctx, subspaceID) +} + +// HasPermission checks whether the given user has the provided permissions or not +func (k Keeper) HasPermission(ctx sdk.Context, subspaceID uint64, user sdk.AccAddress, permission subspacetypes.Permission) bool { + return k.sk.HasPermission(ctx, subspaceID, user, permission) +} + +// HasUserBlocked tells whether the given blocker has blocked the user inside the provided subspace +func (k Keeper) HasUserBlocked(ctx sdk.Context, blocker, user string, subspaceID uint64) bool { + return k.rk.HasUserBlocked(ctx, blocker, user, subspaceID) +} + +// HasRelationship tells whether the relationship between the user and counterparty exists for the given subspace +func (k Keeper) HasRelationship(ctx sdk.Context, user, counterparty string, subspaceID uint64) bool { + return k.rk.HasRelationship(ctx, user, counterparty, subspaceID) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// IteratePostIDs iterates over all the next post ids and performs the provided function +func (k Keeper) IteratePostIDs(ctx sdk.Context, fn func(index int64, subspaceID uint64, postID uint64) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.NextPostIDPrefix) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + subspaceID := subspacetypes.GetSubspaceIDFromBytes(bytes.TrimPrefix(iterator.Key(), types.NextPostIDPrefix)) + postID := types.GetPostIDFromBytes(iterator.Value()) + stop := fn(i, subspaceID, postID) + if stop { + break + } + i++ + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +// IteratePosts iterates over all the posts stored inside the context and performs the provided function +func (k Keeper) IteratePosts(ctx sdk.Context, fn func(index int64, post types.Post) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.PostPrefix) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var post types.Post + k.cdc.MustUnmarshal(iterator.Value(), &post) + stop := fn(i, post) + if stop { + break + } + i++ + } +} + +// GetPosts returns all the posts stored inside the given context +func (k Keeper) GetPosts(ctx sdk.Context) []types.Post { + var posts []types.Post + k.IteratePosts(ctx, func(index int64, post types.Post) (stop bool) { + posts = append(posts, post) + return false + }) + return posts +} + +// IterateSubspacePosts iterates over all the posts stored inside the given subspace and performs the provided function +func (k Keeper) IterateSubspacePosts(ctx sdk.Context, subspaceID uint64, fn func(index int64, post types.Post) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.SubspacePostsPrefix(subspaceID)) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var post types.Post + k.cdc.MustUnmarshal(iterator.Value(), &post) + stop := fn(i, post) + if stop { + break + } + i++ + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +// IterateActivePolls iterates over the polls in the active polls queue and performs the provided function +func (k Keeper) IterateActivePolls(ctx sdk.Context, fn func(index int64, poll types.Attachment) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.ActivePollQueuePrefix) + defer iterator.Close() + + index := int64(0) + for ; iterator.Valid(); iterator.Next() { + subspaceID, postID, pollID, _ := types.SplitActivePollQueueKey(iterator.Key()) + attachment, found := k.GetAttachment(ctx, subspaceID, postID, pollID) + if !found || !types.IsPoll(attachment) { + panic(fmt.Sprintf("poll %d %d %d does not exist", subspaceID, postID, pollID)) + } + + stop := fn(index, attachment) + if stop { + break + } + index++ + } +} + +// IterateActivePollsQueue iterates over the polls that are still active by the time given performs the provided function +func (k Keeper) IterateActivePollsQueue(ctx sdk.Context, endTime time.Time, fn func(index int64, poll types.Attachment) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := store.Iterator(types.ActivePollQueuePrefix, sdk.PrefixEndBytes(types.ActivePollByTimeKey(endTime))) + defer iterator.Close() + + index := int64(0) + for ; iterator.Valid(); iterator.Next() { + subspaceID, postID, pollID, _ := types.SplitActivePollQueueKey(iterator.Key()) + attachment, found := k.GetAttachment(ctx, subspaceID, postID, pollID) + if !found || !types.IsPoll(attachment) { + panic(fmt.Sprintf("poll %d %d %d does not exist", subspaceID, postID, pollID)) + } + + stop := fn(index, attachment) + if stop { + break + } + index++ + } +} + +// IterateAttachments iterates over all the attachments in the given context and performs the provided function +func (k Keeper) IterateAttachments(ctx sdk.Context, fn func(index int64, attachment types.Attachment) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.AttachmentPrefix) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var attachment types.Attachment + k.cdc.MustUnmarshal(iterator.Value(), &attachment) + stop := fn(i, attachment) + if stop { + break + } + i++ + } +} + +// IteratePostAttachments iterates through the attachments associated with the provided post and performs the given function +func (k Keeper) IteratePostAttachments(ctx sdk.Context, subspaceID uint64, postID uint64, fn func(index int64, attachment types.Attachment) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.PostAttachmentsPrefix(subspaceID, postID)) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var attachment types.Attachment + k.cdc.MustUnmarshal(iterator.Value(), &attachment) + stop := fn(i, attachment) + if stop { + break + } + i++ + } +} + +// GetPostAttachments returns all the attachments associated to the given post +func (k Keeper) GetPostAttachments(ctx sdk.Context, subspaceID uint64, postID uint64) []types.Attachment { + var attachments []types.Attachment + k.IteratePostAttachments(ctx, subspaceID, postID, func(index int64, attachment types.Attachment) (stop bool) { + attachments = append(attachments, attachment) + return false + }) + return attachments +} + +// -------------------------------------------------------------------------------------------------------------------- + +// IterateUserAnswers iterates over all the polls user answers and performs the provided function +func (k Keeper) IterateUserAnswers(ctx sdk.Context, fn func(index int64, answer types.UserAnswer) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.UserAnswerPrefix) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var answer types.UserAnswer + k.cdc.MustUnmarshal(iterator.Value(), &answer) + stop := fn(i, answer) + if stop { + break + } + i++ + } +} + +// IteratePollUserAnswers iterates through the answers to the given poll and performs the provided function +func (k Keeper) IteratePollUserAnswers(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, fn func(index int64, answer types.UserAnswer) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.PollAnswersPrefix(subspaceID, postID, pollID)) + defer iterator.Close() + + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + var answer types.UserAnswer + k.cdc.MustUnmarshal(iterator.Value(), &answer) + stop := fn(i, answer) + if stop { + break + } + i++ + } +} + +// GetPollUserAnswers returns all the user answers for the given poll +func (k Keeper) GetPollUserAnswers(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) []types.UserAnswer { + var answers []types.UserAnswer + k.IteratePollUserAnswers(ctx, subspaceID, postID, pollID, func(index int64, answer types.UserAnswer) (stop bool) { + answers = append(answers, answer) + return false + }) + return answers +} diff --git a/x/posts/keeper/attachments.go b/x/posts/keeper/attachments.go new file mode 100644 index 0000000000..f35de29ade --- /dev/null +++ b/x/posts/keeper/attachments.go @@ -0,0 +1,94 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// SetNextAttachmentID sets the new attachment id for the given post to the store +func (k Keeper) SetNextAttachmentID(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) { + store := ctx.KVStore(k.storeKey) + store.Set(types.NextAttachmentIDStoreKey(subspaceID, postID), types.GetAttachmentIDBytes(attachmentID)) +} + +// HasNextAttachmentID checks whether the given post already has an attachment id +func (k Keeper) HasNextAttachmentID(ctx sdk.Context, subspaceID uint64, postID uint64) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.NextAttachmentIDStoreKey(subspaceID, postID)) +} + +// GetNextAttachmentID gets the highest attachment id for the given post +func (k Keeper) GetNextAttachmentID(ctx sdk.Context, subspaceID uint64, postID uint64) (attachmentID uint32, err error) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.NextAttachmentIDStoreKey(subspaceID, postID)) + if bz == nil { + return 0, sdkerrors.Wrapf(types.ErrInvalidGenesis, "initial attachment ID hasn't been set for post %d within subspace %d", postID, subspaceID) + } + + attachmentID = types.GetAttachmentIDFromBytes(bz) + return attachmentID, nil +} + +// DeleteNextAttachmentID deletes the store key used to store the next attachment id for the post having the given id +func (k Keeper) DeleteNextAttachmentID(ctx sdk.Context, subspaceID uint64, postID uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.NextAttachmentIDStoreKey(subspaceID, postID)) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// SaveAttachment saves the given attachment inside the current context +func (k Keeper) SaveAttachment(ctx sdk.Context, attachment types.Attachment) { + store := ctx.KVStore(k.storeKey) + + // Store the attachment + store.Set(types.AttachmentStoreKey(attachment.SubspaceID, attachment.PostID, attachment.ID), k.cdc.MustMarshal(&attachment)) + + k.AfterAttachmentSaved(ctx, attachment.SubspaceID, attachment.PostID, attachment.ID) +} + +// HasAttachment tells whether the given attachment exists or not +func (k Keeper) HasAttachment(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.AttachmentStoreKey(subspaceID, postID, attachmentID)) +} + +// GetAttachment returns the attachment associated with the given id. +// If there is no attachment associated with the given id the function will return an empty attachment and false. +func (k Keeper) GetAttachment(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) (attachment types.Attachment, found bool) { + store := ctx.KVStore(k.storeKey) + + key := types.AttachmentStoreKey(subspaceID, postID, attachmentID) + if !store.Has(key) { + return types.Attachment{}, false + } + + k.cdc.MustUnmarshal(store.Get(key), &attachment) + return attachment, true +} + +// DeleteAttachment deletes the given attachment from the current context +func (k Keeper) DeleteAttachment(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) { + store := ctx.KVStore(k.storeKey) + + attachment, found := k.GetAttachment(ctx, subspaceID, postID, attachmentID) + if !found { + return + } + + // Delete the attachment + store.Delete(types.AttachmentStoreKey(attachment.SubspaceID, attachment.PostID, attachment.ID)) + + // Remove the poll from the active queue + if types.IsPoll(attachment) { + // Remove the poll from the active queue (if it was there) + k.RemoveFromActivePollQueue(ctx, attachment) + + // Delete the poll user answers + for _, answer := range k.GetPollUserAnswers(ctx, attachment.SubspaceID, attachment.PostID, attachment.ID) { + k.DeleteUserAnswer(ctx, attachment.SubspaceID, attachment.PostID, attachment.ID, answer.User) + } + } +} diff --git a/x/posts/keeper/attachments_test.go b/x/posts/keeper/attachments_test.go new file mode 100644 index 0000000000..706fe11dd2 --- /dev/null +++ b/x/posts/keeper/attachments_test.go @@ -0,0 +1,457 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestKeeper_SetNextAttachmentID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + attachmentID uint32 + check func(ctx sdk.Context) + }{ + { + name: "non existing attachment id is set properly", + subspaceID: 1, + postID: 1, + attachmentID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + attachmentID := types.GetAttachmentIDFromBytes(store.Get(types.NextAttachmentIDStoreKey(1, 1))) + suite.Require().Equal(uint32(1), attachmentID) + }, + }, + { + name: "existing attachment id is overridden properly", + store: func(ctx sdk.Context) { + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + }, + subspaceID: 1, + postID: 1, + attachmentID: 2, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + attachmentID := types.GetAttachmentIDFromBytes(store.Get(types.NextAttachmentIDStoreKey(1, 1))) + suite.Require().Equal(uint32(2), attachmentID) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SetNextAttachmentID(ctx, tc.subspaceID, tc.postID, tc.attachmentID) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_HasNextAttachmentID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + expResult bool + }{ + { + name: "not found attachment id returns false", + subspaceID: 1, + postID: 1, + expResult: false, + }, + { + name: "found attachment id returns true", + store: func(ctx sdk.Context) { + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + }, + subspaceID: 1, + postID: 1, + expResult: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + result := suite.k.HasNextAttachmentID(ctx, tc.subspaceID, tc.postID) + suite.Require().Equal(tc.expResult, result) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetNextAttachmentID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + shouldErr bool + expAttachmentID uint32 + }{ + { + name: "non existing attachment id returns error", + subspaceID: 1, + postID: 1, + shouldErr: true, + }, + { + name: "existing attachment id returns correct value", + store: func(ctx sdk.Context) { + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + }, + subspaceID: 1, + postID: 1, + shouldErr: false, + expAttachmentID: 1, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + attachmentID, err := suite.k.GetNextAttachmentID(ctx, tc.subspaceID, tc.postID) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAttachmentID, attachmentID) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_DeleteNextAttachmentID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + check func(ctx sdk.Context) + }{ + { + name: "non existing attachment id is deleted properly", + subspaceID: 1, + postID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + suite.Require().False(store.Has(types.NextAttachmentIDStoreKey(1, 1))) + }, + }, + { + name: "existing post id is deleted properly", + store: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + store.Set(types.NextAttachmentIDStoreKey(1, 1), types.GetAttachmentIDBytes(1)) + }, + subspaceID: 1, + postID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + suite.Require().False(store.Has(types.NextAttachmentIDStoreKey(1, 1))) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.DeleteNextAttachmentID(ctx, tc.subspaceID, tc.postID) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +func (suite *KeeperTestsuite) TestKeeper_SaveAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + attachment types.Attachment + check func(ctx sdk.Context) + }{ + { + name: "non existing attachment is stored properly", + attachment: types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + check: func(ctx sdk.Context) { + stored, found := suite.k.GetAttachment(ctx, 1, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), stored) + }, + }, + { + name: "existing attachment is overridden properly", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + attachment: types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 1), + types.NewAnswerResult(2, 5), + }), + )), + check: func(ctx sdk.Context) { + stored, found := suite.k.GetAttachment(ctx, 1, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 1), + types.NewAnswerResult(2, 5), + }), + )), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SaveAttachment(ctx, tc.attachment) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_HasAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + attachmentID uint32 + expResult bool + }{ + { + name: "not found attachment returns false", + subspaceID: 1, + postID: 1, + attachmentID: 1, + expResult: false, + }, + { + name: "found attachment returns true", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 1, + attachmentID: 1, + expResult: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + result := suite.k.HasAttachment(ctx, tc.subspaceID, tc.postID, tc.attachmentID) + suite.Require().Equal(tc.expResult, result) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + attachmentID uint32 + expFound bool + expAttachment types.Attachment + }{ + { + name: "non existing attachment returns false and empty attachment", + subspaceID: 1, + postID: 1, + attachmentID: 1, + expFound: false, + expAttachment: types.Attachment{}, + }, + { + name: "existing attachment returns true and the correct value", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 1, + attachmentID: 1, + expFound: true, + expAttachment: types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + attachment, found := suite.k.GetAttachment(ctx, tc.subspaceID, tc.postID, tc.attachmentID) + suite.Require().Equal(tc.expFound, found) + suite.Require().Equal(tc.expAttachment, attachment) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_DeleteAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + attachmentID uint32 + check func(ctx sdk.Context) + }{ + { + name: "non existing attachment is deleted properly", + subspaceID: 1, + postID: 1, + attachmentID: 1, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasAttachment(ctx, 1, 1, 1)) + }, + }, + { + name: "exiting media attachment is deleted properly", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 1, + attachmentID: 1, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasAttachment(ctx, 1, 1, 1)) + }, + }, + { + name: "existing poll attachment is deleted along with all the data", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer( + 1, + 1, + 1, + []uint32{1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + )) + }, + subspaceID: 1, + postID: 1, + attachmentID: 1, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasAttachment(ctx, 1, 1, 1)) + + suite.Require().Empty(suite.k.GetPollUserAnswers(ctx, 1, 1, 1)) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.DeleteAttachment(ctx, tc.subspaceID, tc.postID, tc.attachmentID) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/keeper/common_test.go b/x/posts/keeper/common_test.go new file mode 100644 index 0000000000..048d010a75 --- /dev/null +++ b/x/posts/keeper/common_test.go @@ -0,0 +1,85 @@ +package keeper_test + +import ( + "testing" + + relationshipskeeper "github.com/desmos-labs/desmos/v3/x/relationships/keeper" + relationshipstypes "github.com/desmos-labs/desmos/v3/x/relationships/types" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + db "github.com/tendermint/tm-db" + + "github.com/desmos-labs/desmos/v3/app" +) + +type KeeperTestsuite struct { + suite.Suite + + cdc codec.Codec + legacyAminoCdc *codec.LegacyAmino + ctx sdk.Context + + storeKey sdk.StoreKey + k keeper.Keeper + + sk subspaceskeeper.Keeper + rk relationshipskeeper.Keeper + pk paramskeeper.Keeper +} + +func (suite *KeeperTestsuite) SetupTest() { + // Define store keys + keys := sdk.NewMemoryStoreKeys(types.StoreKey, relationshipstypes.StoreKey, subspacestypes.StoreKey, paramstypes.StoreKey) + tKeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + suite.storeKey = keys[types.StoreKey] + + // Create an in-memory db + memDB := db.NewMemDB() + ms := store.NewCommitMultiStore(memDB) + for _, key := range keys { + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, memDB) + } + for _, tKey := range tKeys { + ms.MountStoreWithDB(tKey, sdk.StoreTypeTransient, memDB) + } + + if err := ms.LoadLatestVersion(); err != nil { + panic(err) + } + + suite.ctx = sdk.NewContext(ms, tmproto.Header{ChainID: "test-chain"}, false, log.NewNopLogger()) + suite.cdc, suite.legacyAminoCdc = app.MakeCodecs() + + suite.pk = paramskeeper.NewKeeper( + suite.cdc, suite.legacyAminoCdc, keys[paramstypes.StoreKey], tKeys[paramstypes.TStoreKey], + ) + + // Define k + suite.sk = subspaceskeeper.NewKeeper(suite.cdc, keys[subspacestypes.StoreKey]) + suite.rk = relationshipskeeper.NewKeeper(suite.cdc, keys[relationshipstypes.StoreKey], suite.sk) + suite.k = keeper.NewKeeper( + suite.cdc, + suite.storeKey, + suite.pk.Subspace(types.DefaultParamsSpace), + suite.sk, + suite.rk, + ) +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestsuite)) +} diff --git a/x/posts/keeper/genesis.go b/x/posts/keeper/genesis.go new file mode 100644 index 0000000000..e9a3a91462 --- /dev/null +++ b/x/posts/keeper/genesis.go @@ -0,0 +1,104 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// ExportGenesis returns the GenesisState associated with the given context +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return types.NewGenesisState( + k.getSubspaceDataEntries(ctx), + k.getPostData(ctx), + k.getAllAttachments(ctx), + k.getAllUserAnswers(ctx), + k.GetParams(ctx), + ) +} + +// getSubspaceDataEntries returns the subspaces data entries stored in the given context +func (k Keeper) getSubspaceDataEntries(ctx sdk.Context) []types.SubspaceDataEntry { + var entries []types.SubspaceDataEntry + k.sk.IterateSubspaces(ctx, func(index int64, subspace subspacestypes.Subspace) (stop bool) { + nextPostID, err := k.GetNextPostID(ctx, subspace.ID) + if err != nil { + panic(err) + } + + entries = append(entries, types.NewSubspaceDataEntry(subspace.ID, nextPostID)) + return false + }) + return entries +} + +// getPostData returns the posts data stored in the given context +func (k Keeper) getPostData(ctx sdk.Context) []types.GenesisPost { + var posts []types.GenesisPost + k.IteratePosts(ctx, func(index int64, post types.Post) (stop bool) { + attachmentID, err := k.GetNextAttachmentID(ctx, post.SubspaceID, post.ID) + if err != nil { + panic(err) + } + + posts = append(posts, types.NewGenesisPost(attachmentID, post)) + return false + }) + return posts +} + +// getAllAttachments returns all the attachments stored inside the given context +func (k Keeper) getAllAttachments(ctx sdk.Context) []types.Attachment { + var attachments []types.Attachment + k.IterateAttachments(ctx, func(index int64, attachment types.Attachment) (stop bool) { + attachments = append(attachments, attachment) + return false + }) + return attachments +} + +// getAllUserAnswers returns all the user answers stored inside the given context +func (k Keeper) getAllUserAnswers(ctx sdk.Context) []types.UserAnswer { + var answers []types.UserAnswer + k.IterateUserAnswers(ctx, func(index int64, answer types.UserAnswer) (stop bool) { + answers = append(answers, answer) + return false + }) + return answers +} + +// -------------------------------------------------------------------------------------------------------------------- + +// InitGenesis initializes the chain state based on the given GenesisState +func (k Keeper) InitGenesis(ctx sdk.Context, data types.GenesisState) { + // Initialize the initial post id for each subspace + for _, entry := range data.SubspacesData { + k.SetNextPostID(ctx, entry.SubspaceID, entry.InitialPostID) + } + + // Initialize all the posts + for _, post := range data.GenesisPosts { + k.SetNextAttachmentID(ctx, post.SubspaceID, post.ID, post.InitialAttachmentID) + k.SavePost(ctx, post.Post) + } + + // Initialize the attachments + for _, attachment := range data.Attachments { + k.SaveAttachment(ctx, attachment) + if poll, ok := attachment.Content.GetCachedValue().(*types.Poll); ok { + if poll.EndDate.After(ctx.BlockTime()) { + k.InsertActivePollQueue(ctx, attachment) + } + } + } + + // Initialize the user answers + for _, answer := range data.UserAnswers { + k.SaveUserAnswer(ctx, answer) + } + + // Initialize the params + k.SetParams(ctx, data.Params) +} diff --git a/x/posts/keeper/genesis_test.go b/x/posts/keeper/genesis_test.go new file mode 100644 index 0000000000..5d5ab71dfb --- /dev/null +++ b/x/posts/keeper/genesis_test.go @@ -0,0 +1,438 @@ +package keeper_test + +import ( + "time" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestKeeper_ExportGenesis() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expGenesis *types.GenesisState + }{ + { + name: "subspaces data is exported properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.Params{}) + + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 1, 1) + + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 2, + "Another text subspace", + "This is another test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 2, 2) + }, + expGenesis: types.NewGenesisState([]types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 1), + types.NewSubspaceDataEntry(2, 2), + }, nil, nil, nil, types.Params{}), + }, + { + name: "posts are exported properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.Params{}) + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SetNextAttachmentID(ctx, 1, 2, 3) + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + expGenesis: types.NewGenesisState(nil, []types.GenesisPost{ + types.NewGenesisPost(1, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + types.NewGenesisPost(3, types.NewPost( + 1, + 2, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, nil, nil, types.Params{}), + }, + { + name: "attachments are exported properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.Params{}) + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 2, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + expGenesis: types.NewGenesisState(nil, nil, []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + types.NewAttachment(1, 1, 2, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, nil, types.Params{}), + }, + { + name: "user answers are exported properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.Params{}) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1, 2, 3}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expGenesis: types.NewGenesisState(nil, nil, nil, []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + types.NewUserAnswer(1, 1, 2, []uint32{1, 2, 3}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, types.Params{}), + }, + { + name: "params are exported properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.NewParams(20)) + }, + expGenesis: types.NewGenesisState(nil, nil, nil, nil, types.NewParams(20)), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + genesis := suite.k.ExportGenesis(ctx) + suite.Require().Equal(tc.expGenesis, genesis) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_ImportGenesis() { + testCases := []struct { + name string + store func(ctx sdk.Context) + data types.GenesisState + check func(ctx sdk.Context) + }{ + { + name: "subspace data is imported properly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + data: types.GenesisState{ + SubspacesData: []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 1), + }, + }, + check: func(ctx sdk.Context) { + stored, err := suite.k.GetNextPostID(ctx, 1) + suite.Require().NoError(err) + suite.Require().Equal(uint64(1), stored) + }, + }, + { + name: "genesis post is imported correctly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + data: types.GenesisState{ + GenesisPosts: []types.GenesisPost{ + types.NewGenesisPost(2, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + }, + check: func(ctx sdk.Context) { + post, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), post) + + attachmentID, err := suite.k.GetNextAttachmentID(ctx, 1, 1) + suite.Require().NoError(err) + suite.Require().Equal(uint32(2), attachmentID) + }, + }, + { + name: "attachment is imported correctly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + data: types.GenesisState{ + Attachments: []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + }, + check: func(ctx sdk.Context) { + stored, found := suite.k.GetAttachment(ctx, 1, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), stored) + }, + }, + { + name: "poll attachment is added to active poll queue properly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + data: types.GenesisState{ + Attachments: []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(3000, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + )), + }, + }, + check: func(ctx sdk.Context) { + stored, found := suite.k.GetAttachment(ctx, 1, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(3000, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + )), stored) + + store := ctx.KVStore(suite.storeKey) + endDate := time.Date(3000, 1, 1, 12, 00, 00, 000, time.UTC) + suite.Require().True(store.Has(types.ActivePollQueueKey(1, 1, 1, endDate))) + }, + }, + { + name: "user answer is imported properly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + }, + data: types.GenesisState{ + UserAnswers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + check: func(ctx sdk.Context) { + stored, found := suite.k.GetUserAnswer(ctx, 1, 1, 1, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st") + suite.Require().True(found) + suite.Require().Equal(types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), stored) + }, + }, + { + name: "params are imported properly", + data: types.GenesisState{ + Params: types.NewParams(200), + }, + check: func(ctx sdk.Context) { + stored := suite.k.GetParams(ctx) + suite.Require().Equal(types.NewParams(200), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.InitGenesis(ctx, tc.data) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/keeper/grpc_query.go b/x/posts/keeper/grpc_query.go new file mode 100644 index 0000000000..79d3da2ee9 --- /dev/null +++ b/x/posts/keeper/grpc_query.go @@ -0,0 +1,153 @@ +package keeper + +import ( + "context" + + "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" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +var _ types.QueryServer = &Keeper{} + +// Posts implements the QueryPosts gRPC method +func (k Keeper) Posts(ctx context.Context, request *types.QueryPostsRequest) (*types.QueryPostsResponse, error) { + if request.SubspaceId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + store := sdkCtx.KVStore(k.storeKey) + postsSubspace := prefix.NewStore(store, types.SubspacePostsPrefix(request.SubspaceId)) + + var posts []types.Post + pageRes, err := query.Paginate(postsSubspace, request.Pagination, func(key []byte, value []byte) error { + var post types.Post + if err := k.cdc.Unmarshal(value, &post); err != nil { + return status.Error(codes.Internal, err.Error()) + } + + posts = append(posts, post) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryPostsResponse{ + Posts: posts, + Pagination: pageRes, + }, nil +} + +// Post implements the QueryPost gRPC method +func (k Keeper) Post(ctx context.Context, request *types.QueryPostRequest) (*types.QueryPostResponse, error) { + if request.SubspaceId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id") + } + if request.PostId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + post, found := k.GetPost(sdkCtx, request.SubspaceId, request.PostId) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "post with id %d not found", request.PostId) + } + + return &types.QueryPostResponse{ + Post: post, + }, nil +} + +// PostAttachments implements the QueryPostAttachments gRPC method +func (k Keeper) PostAttachments(ctx context.Context, request *types.QueryPostAttachmentsRequest) (*types.QueryPostAttachmentsResponse, error) { + if request.SubspaceId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id") + } + if request.PostId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + store := sdkCtx.KVStore(k.storeKey) + attachmentsStore := prefix.NewStore(store, types.PostAttachmentsPrefix(request.SubspaceId, request.PostId)) + + var attachments []types.Attachment + pageRes, err := query.Paginate(attachmentsStore, request.Pagination, func(key []byte, value []byte) error { + var attachment types.Attachment + if err := k.cdc.Unmarshal(value, &attachment); err != nil { + return status.Error(codes.Internal, err.Error()) + } + + attachments = append(attachments, attachment) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryPostAttachmentsResponse{ + Attachments: attachments, + Pagination: pageRes, + }, nil +} + +// PollAnswers implements the QueryPollAnswers gRPC method +func (k Keeper) PollAnswers(ctx context.Context, request *types.QueryPollAnswersRequest) (*types.QueryPollAnswersResponse, error) { + if request.SubspaceId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id") + } + if request.PostId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id") + } + if request.PollId == 0 { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid poll id") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + store := sdkCtx.KVStore(k.storeKey) + answersPrefix := types.PollAnswersPrefix(request.SubspaceId, request.PostId, request.PollId) + if request.User != "" { + answersPrefix = types.PollAnswerStoreKey(request.SubspaceId, request.PostId, request.PollId, request.User) + } + answersStore := prefix.NewStore(store, answersPrefix) + + var answers []types.UserAnswer + pageRes, err := query.Paginate(answersStore, request.Pagination, func(key []byte, value []byte) error { + var answer types.UserAnswer + if err := k.cdc.Unmarshal(value, &answer); err != nil { + return status.Error(codes.Internal, err.Error()) + } + + answers = append(answers, answer) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryPollAnswersResponse{ + Answers: answers, + Pagination: pageRes, + }, nil +} + +// Params implements the QueryParams gRPC method +func (k Keeper) Params(ctx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + params := k.GetParams(sdkCtx) + return &types.QueryParamsResponse{Params: params}, nil +} diff --git a/x/posts/keeper/grpc_query_test.go b/x/posts/keeper/grpc_query_test.go new file mode 100644 index 0000000000..8382567635 --- /dev/null +++ b/x/posts/keeper/grpc_query_test.go @@ -0,0 +1,454 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestQueryServer_Posts() { + testCases := []struct { + name string + store func(ctx sdk.Context) + request *types.QueryPostsRequest + shouldErr bool + expPosts []types.Post + }{ + { + name: "invalid subspace id returns error", + request: types.NewQueryPostsRequest(0, nil), + shouldErr: true, + }, + { + name: "valid request without pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "First post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "", + "Second post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 13, 00, 00, 000, time.UTC), + nil, + )) + }, + request: types.NewQueryPostsRequest(1, nil), + shouldErr: false, + expPosts: []types.Post{ + types.NewPost( + 1, + 1, + "", + "First post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + types.NewPost( + 1, + 2, + "", + "Second post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 13, 00, 00, 000, time.UTC), + nil, + ), + }, + }, + { + name: "valid request with pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "First post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "", + "Second post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 13, 00, 00, 000, time.UTC), + nil, + )) + }, + request: types.NewQueryPostsRequest(1, &query.PageRequest{ + Limit: 1, + }), + shouldErr: false, + expPosts: []types.Post{ + types.NewPost( + 1, + 1, + "", + "First post!", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + res, err := suite.k.Posts(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expPosts, res.Posts) + } + }) + } +} + +func (suite *KeeperTestsuite) TestQueryServer_Post() { + testCases := []struct { + name string + store func(ctx sdk.Context) + request *types.QueryPostRequest + shouldErr bool + expPost types.Post + }{ + { + name: "invalid subspace id returns error", + request: types.NewQueryPostRequest(0, 1), + shouldErr: true, + }, + { + name: "invalid post id returns error", + request: types.NewQueryPostRequest(1, 0), + shouldErr: true, + }, + { + name: "not found post returns error", + request: types.NewQueryPostRequest(1, 1), + shouldErr: true, + }, + { + name: "existing post is returned properly", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + request: types.NewQueryPostRequest(1, 1), + shouldErr: false, + expPost: types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + res, err := suite.k.Post(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expPost, res.Post) + } + }) + } +} + +func (suite *KeeperTestsuite) TestQueryServer_PostAttachments() { + testCases := []struct { + name string + store func(ctx sdk.Context) + request *types.QueryPostAttachmentsRequest + shouldErr bool + expAttachments []types.Attachment + }{ + { + name: "invalid subspace id returns error", + request: types.NewQueryPostAttachmentsRequest(0, 1, nil), + shouldErr: true, + }, + { + name: "invalid post id returns error", + request: types.NewQueryPostAttachmentsRequest(1, 0, nil), + shouldErr: true, + }, + { + name: "valid request without pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 2, types.NewMedia( + "ftp://user:password@example.com/second-image.png", + "image/png", + ))) + }, + request: types.NewQueryPostAttachmentsRequest(1, 1, nil), + expAttachments: []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + types.NewAttachment(1, 1, 2, types.NewMedia( + "ftp://user:password@example.com/second-image.png", + "image/png", + )), + }, + }, + { + name: "valid request with pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 2, types.NewMedia( + "ftp://user:password@example.com/second-image.png", + "image/png", + ))) + }, + request: types.NewQueryPostAttachmentsRequest(1, 1, &query.PageRequest{ + Limit: 1, + }), + expAttachments: []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + res, err := suite.k.PostAttachments(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAttachments, res.Attachments) + } + }) + } +} + +func (suite *KeeperTestsuite) TestQueryServer_PollAnswers() { + testCases := []struct { + name string + store func(ctx sdk.Context) + request *types.QueryPollAnswersRequest + shouldErr bool + expAnswers []types.UserAnswer + }{ + { + name: "invalid subspace id returns error", + request: types.NewQueryPollAnswersRequest(0, 1, 1, "", nil), + shouldErr: true, + }, + { + name: "invalid post id returns error", + request: types.NewQueryPollAnswersRequest(1, 0, 1, "", nil), + shouldErr: true, + }, + { + name: "invalid poll id returns error", + request: types.NewQueryPollAnswersRequest(1, 1, 0, "", nil), + shouldErr: true, + }, + { + name: "valid request without user and without pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + }, + request: types.NewQueryPollAnswersRequest(1, 1, 1, "", nil), + shouldErr: false, + expAnswers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea"), + }, + }, + { + name: "valid request without user and with pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + }, + request: types.NewQueryPollAnswersRequest(1, 1, 1, "", &query.PageRequest{ + Limit: 1, + }), + shouldErr: false, + expAnswers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + { + name: "valid request with user and without pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + }, + request: types.NewQueryPollAnswersRequest(1, 1, 1, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st", nil), + shouldErr: false, + expAnswers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + { + name: "valid request with user and with pagination returns properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 2, []uint32{1}, "cosmos1xnjndlr28kuymqexyk9m9m3kqyvm8fge0edxea")) + }, + request: types.NewQueryPollAnswersRequest(1, 1, 1, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st", &query.PageRequest{ + Limit: 1, + }), + shouldErr: false, + expAnswers: []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + res, err := suite.k.PollAnswers(sdk.WrapSDKContext(ctx), tc.request) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expAnswers, res.Answers) + } + }) + } +} + +func (suite *KeeperTestsuite) TestQueryServer_Params() { + testCases := []struct { + name string + store func(ctx sdk.Context) + request *types.QueryParamsRequest + expParams types.Params + }{ + { + name: "params are returned properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.NewParams(200)) + }, + request: types.NewQueryParamsRequest(), + expParams: types.NewParams(200), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + res, err := suite.k.Params(sdk.WrapSDKContext(ctx), tc.request) + suite.Require().NoError(err) + suite.Require().Equal(tc.expParams, res.Params) + }) + } +} diff --git a/x/posts/keeper/hooks.go b/x/posts/keeper/hooks.go new file mode 100644 index 0000000000..af76551b7f --- /dev/null +++ b/x/posts/keeper/hooks.go @@ -0,0 +1,61 @@ +package keeper + +// DONTCOVER + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// Implement PostsHooks interface +var _ types.PostsHooks = Keeper{} + +// AfterPostSaved implements types.PostsHooks +func (k Keeper) AfterPostSaved(ctx sdk.Context, subspaceID uint64, postID uint64) { + if k.hooks != nil { + k.hooks.AfterPostSaved(ctx, subspaceID, postID) + } +} + +// AfterPostDeleted implements types.PostsHooks +func (k Keeper) AfterPostDeleted(ctx sdk.Context, subspaceID uint64, postID uint64) { + if k.hooks != nil { + k.hooks.AfterPostDeleted(ctx, subspaceID, postID) + } +} + +// AfterAttachmentSaved implements types.PostsHooks +func (k Keeper) AfterAttachmentSaved(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) { + if k.hooks != nil { + k.hooks.AfterAttachmentSaved(ctx, subspaceID, postID, attachmentID) + } +} + +// AfterAttachmentDeleted implements types.PostsHooks +func (k Keeper) AfterAttachmentDeleted(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) { + if k.hooks != nil { + k.hooks.AfterAttachmentDeleted(ctx, subspaceID, postID, attachmentID) + } +} + +// AfterPollAnswerSaved implements types.PostsHooks +func (k Keeper) AfterPollAnswerSaved(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) { + if k.hooks != nil { + k.hooks.AfterPollAnswerSaved(ctx, subspaceID, postID, pollID, user) + } +} + +// AfterPollAnswerDeleted implements types.PostsHooks +func (k Keeper) AfterPollAnswerDeleted(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) { + if k.hooks != nil { + k.hooks.AfterPollAnswerDeleted(ctx, subspaceID, postID, pollID, user) + } +} + +// AfterPollVotingPeriodEnded implements types.PostsHooks +func (k Keeper) AfterPollVotingPeriodEnded(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) { + if k.hooks != nil { + k.hooks.AfterPollVotingPeriodEnded(ctx, subspaceID, postID, pollID) + } +} diff --git a/x/posts/keeper/invariants.go b/x/posts/keeper/invariants.go new file mode 100644 index 0000000000..dabbd136c9 --- /dev/null +++ b/x/posts/keeper/invariants.go @@ -0,0 +1,254 @@ +package keeper + +import ( + "fmt" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// RegisterInvariants registers all posts invariants +func RegisterInvariants(ir sdk.InvariantRegistry, keeper Keeper) { + ir.RegisterRoute(types.ModuleName, "valid-subspaces", + ValidSubspacesInvariant(keeper)) + ir.RegisterRoute(types.ModuleName, "valid-posts", + ValidPostsInvariant(keeper)) + ir.RegisterRoute(types.ModuleName, "valid-attachments", + ValidAttachmentsInvariant(keeper)) + ir.RegisterRoute(types.ModuleName, "valid-user-answers", + ValidUserAnswersInvariant(keeper)) + ir.RegisterRoute(types.ModuleName, "valid-active-polls", + ValidActivePollsInvariant(keeper)) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidSubspacesInvariant checks that all the subspaces have a valid post id to them +func ValidSubspacesInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (message string, broken bool) { + var invalidSubspaces []subspacestypes.Subspace + k.sk.IterateSubspaces(ctx, func(index int64, subspace subspacestypes.Subspace) (stop bool) { + + // Make sure the next post id exists for the subspace + if !k.HasNextPostID(ctx, subspace.ID) { + invalidSubspaces = append(invalidSubspaces, subspace) + } + + return false + }) + + return sdk.FormatInvariant(types.ModuleName, "invalid subspaces", + fmt.Sprintf("the following subspaces are invalid:\n %s", formatOutputSubspaces(invalidSubspaces)), + ), invalidSubspaces != nil + } +} + +// formatOutputPosts concatenates the given subspaces information into a string +func formatOutputSubspaces(subspaces []subspacestypes.Subspace) (output string) { + for _, subspace := range subspaces { + output += fmt.Sprintf("%d\n", subspace.ID) + } + return output +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidPostsInvariant checks that all the posts are valid +func ValidPostsInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (message string, broken bool) { + var invalidPosts []types.Post + k.IteratePosts(ctx, func(_ int64, post types.Post) (stop bool) { + invalid := false + + // The only check we need to perform here is if the subspace still exists. + // All referenced posts might have been deleted, and params might have changed, so we can't use k.ValidatePost + if !k.HasSubspace(ctx, post.SubspaceID) { + invalid = true + } + + nextPostID, err := k.GetNextPostID(ctx, post.SubspaceID) + if err != nil { + invalid = true + } + + // Make sure the post id is always less than the next one + if post.ID >= nextPostID { + invalid = true + } + + // Make sure the attachment id exists + if !k.HasNextAttachmentID(ctx, post.SubspaceID, post.ID) { + invalid = true + } + + // Validate the post + err = post.Validate() + if err != nil { + invalid = true + } + + if invalid { + invalidPosts = append(invalidPosts, post) + } + + return false + }) + + return sdk.FormatInvariant(types.ModuleName, "invalid posts", + fmt.Sprintf("the following posts are invalid:\n%s", formatOutputPosts(invalidPosts)), + ), invalidPosts != nil + } +} + +// formatOutputPosts concatenates the given posts information into a string +func formatOutputPosts(posts []types.Post) (output string) { + for _, post := range posts { + output += fmt.Sprintf("SubspaceID: %d, PostID: %d\n", + post.SubspaceID, post.ID) + } + return output +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidAttachmentsInvariant checks that all the attachments are valid +func ValidAttachmentsInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (message string, broken bool) { + var invalidAttachments []types.Attachment + k.IterateAttachments(ctx, func(_ int64, attachment types.Attachment) (stop bool) { + invalid := false + + // Check subspace + if !k.HasSubspace(ctx, attachment.SubspaceID) { + invalid = true + } + + // Check associated post + if !k.HasPost(ctx, attachment.SubspaceID, attachment.PostID) { + invalid = true + } + + nextAttachmentID, err := k.GetNextAttachmentID(ctx, attachment.SubspaceID, attachment.PostID) + if err != nil { + invalid = true + } + + // Make sure the attachment id is always less than the next one + if attachment.ID >= nextAttachmentID { + invalid = true + } + + // Validate attachment + err = attachment.Validate() + if err != nil { + invalid = true + } + + if invalid { + invalidAttachments = append(invalidAttachments, attachment) + } + + return false + }) + + return sdk.FormatInvariant(types.ModuleName, "invalid attachments", + fmt.Sprintf("the following attachments are invalid:\n%s", formatOutputAttachments(invalidAttachments)), + ), invalidAttachments != nil + } +} + +// formatOutputAttachments concatenates the given attachment information into a string +func formatOutputAttachments(attachments []types.Attachment) (output string) { + for _, attachment := range attachments { + output += fmt.Sprintf("SubspaceID: %d, PostID: %d, AttachmentID: %d\n", + attachment.SubspaceID, attachment.PostID, attachment.ID) + } + return output +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidUserAnswersInvariant checks that all the user answers are valid +func ValidUserAnswersInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (message string, broken bool) { + var invalidUserAnswers []types.UserAnswer + k.IterateUserAnswers(ctx, func(_ int64, answer types.UserAnswer) (stop bool) { + invalid := false + + // Check subspace + if !k.HasSubspace(ctx, answer.SubspaceID) { + invalid = true + } + + // Check post + if !k.HasPost(ctx, answer.SubspaceID, answer.PostID) { + invalid = true + } + + // Check the associated poll + if !k.HasPoll(ctx, answer.SubspaceID, answer.PostID, answer.PollID) { + invalid = true + } + + // Validate the answer + err := answer.Validate() + if err != nil { + invalid = true + } + + if invalid { + invalidUserAnswers = append(invalidUserAnswers, answer) + } + + return false + }) + + return sdk.FormatInvariant(types.ModuleName, "invalid user answers", + fmt.Sprintf("the following user answers are invalid:\n%s", formatOutputUserAnswers(invalidUserAnswers)), + ), invalidUserAnswers != nil + } +} + +// formatOutputUserAnswers concatenates the given user answers information into a string +func formatOutputUserAnswers(answers []types.UserAnswer) (output string) { + for _, answer := range answers { + output += fmt.Sprintf("SubspaceID: %d, PostID: %d, PollID: %d, User: %s\n", + answer.SubspaceID, answer.PostID, answer.PollID, answer.User) + } + return output +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidActivePollsInvariant checks that all the active polls are valid +func ValidActivePollsInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (message string, broken bool) { + var invalidActivePolls []types.Attachment + k.IterateActivePolls(ctx, func(_ int64, attachment types.Attachment) (stop bool) { + poll := attachment.Content.GetCachedValue().(*types.Poll) + + // Make sure active polls do not have tally results yet + if poll.FinalTallyResults != nil { + invalidActivePolls = append(invalidActivePolls, attachment) + } + + return false + }) + + return sdk.FormatInvariant(types.ModuleName, "invalid active polls", + fmt.Sprintf("the following active polls are invalid:\n%s", formatOutputActivePolls(invalidActivePolls)), + ), invalidActivePolls != nil + } +} + +// formatOutputActivePolls concatenates the given polls information into a string +func formatOutputActivePolls(polls []types.Attachment) (output string) { + for _, poll := range polls { + output += fmt.Sprintf("SubspaceID: %d, PostID: %d, PollID: %d", + poll.SubspaceID, poll.PostID, poll.ID) + } + return output +} diff --git a/x/posts/keeper/invariants_test.go b/x/posts/keeper/invariants_test.go new file mode 100644 index 0000000000..f3b26b0946 --- /dev/null +++ b/x/posts/keeper/invariants_test.go @@ -0,0 +1,668 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func (suite *KeeperTestsuite) TestValidSubspacesInvariant() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expBroken bool + }{ + { + name: "not found next post id breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + expBroken: true, + }, + { + name: "valid data does not break invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SetNextPostID(ctx, 1, 1) + }, + expBroken: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + _, broken := keeper.ValidSubspacesInvariant(suite.k)(ctx) + suite.Require().Equal(tc.expBroken, broken) + }) + } +} + +func (suite *KeeperTestsuite) TestValidPostsInvariant() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expBroken bool + }{ + { + name: "not found subspace breaks invariant", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + expBroken: true, + }, + { + name: "not found next post id breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + expBroken: true, + }, + { + name: "invalid post id compared to next post id breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 1, 1) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + expBroken: true, + }, + { + name: "not found next attachment id breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 1, 2) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.DeleteNextAttachmentID(ctx, 1, 1) + }, + expBroken: true, + }, + { + name: "invalid post breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 1, 2) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + }, + expBroken: true, + }, + { + name: "valid data does not break invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + suite.k.SetNextPostID(ctx, 1, 2) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + }, + expBroken: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + _, broken := keeper.ValidPostsInvariant(suite.k)(ctx) + suite.Require().Equal(tc.expBroken, broken) + }) + } +} + +func (suite *KeeperTestsuite) TestValidAttachmentsInvariant() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expBroken bool + }{ + { + name: "not found subspace breaks invariant", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + expBroken: true, + }, + { + name: "not found post breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + expBroken: true, + }, + { + name: "not found next attachment id returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + expBroken: true, + }, + { + name: "invalid attachment id compared to next attachment id returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextAttachmentID(ctx, 1, 1, 1) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + expBroken: true, + }, + { + name: "invalid attachment breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextAttachmentID(ctx, 1, 1, 2) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("", ""), + )) + }, + expBroken: true, + }, + { + name: "valid data returns no error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextAttachmentID(ctx, 1, 1, 2) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + expBroken: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + _, broken := keeper.ValidAttachmentsInvariant(suite.k)(ctx) + suite.Require().Equal(tc.expBroken, broken) + }) + } +} + +func (suite *KeeperTestsuite) TestValidUserAnswersInvariant() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expBroken bool + }{ + { + name: "not found subspace breaks invariant", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expBroken: true, + }, + { + name: "not found post breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expBroken: true, + }, + { + name: "not found poll breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expBroken: true, + }, + { + name: "invalid user answer breaks invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, nil, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expBroken: true, + }, + { + name: "valid data does not break invariant", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test subspace", + "This is a test subspace", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + "cosmos1m0czrla04f7rp3zg7dsgc4kla54q7pc4xt00l5", + "cosmos1qzskhrcjnkdz2ln4yeafzsdwht8ch08j4wed69", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st")) + }, + expBroken: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + _, broken := keeper.ValidUserAnswersInvariant(suite.k)(ctx) + suite.Require().Equal(tc.expBroken, broken) + }) + } +} + +func (suite *KeeperTestsuite) TestValidActivePollsInvariant() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expBroken bool + }{ + { + name: "non nil final results breaks invariant", + store: func(ctx sdk.Context) { + poll := types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 10), + }), + )) + suite.k.SaveAttachment(ctx, poll) + suite.k.InsertActivePollQueue(ctx, poll) + }, + expBroken: true, + }, + { + name: "valid data returns no error", + store: func(ctx sdk.Context) { + poll := types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + )) + suite.k.SaveAttachment(ctx, poll) + suite.k.InsertActivePollQueue(ctx, poll) + }, + expBroken: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + _, broken := keeper.ValidActivePollsInvariant(suite.k)(ctx) + suite.Require().Equal(tc.expBroken, broken) + }) + } +} diff --git a/x/posts/keeper/keeper.go b/x/posts/keeper/keeper.go new file mode 100644 index 0000000000..f91a6772e1 --- /dev/null +++ b/x/posts/keeper/keeper.go @@ -0,0 +1,52 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/tendermint/tendermint/libs/log" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramsSubspace paramstypes.Subspace + hooks types.PostsHooks + + sk types.SubspacesKeeper + rk types.RelationshipsKeeper +} + +// NewKeeper creates a new instance of the Posts Keeper. +func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey, paramsSubspace paramstypes.Subspace, sk types.SubspacesKeeper, rk types.RelationshipsKeeper) Keeper { + if !paramsSubspace.HasKeyTable() { + paramsSubspace = paramsSubspace.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + storeKey: storeKey, + cdc: cdc, + paramsSubspace: paramsSubspace, + + sk: sk, + rk: rk, + } +} + +// Logger returns a module-specific logger +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+types.ModuleName) +} + +// SetHooks allows to set the posts hooks +func (k *Keeper) SetHooks(sh types.PostsHooks) *Keeper { + if k.hooks != nil { + panic("cannot set posts hooks twice") + } + + k.hooks = sh + return k +} diff --git a/x/posts/keeper/migrations.go b/x/posts/keeper/migrations.go new file mode 100644 index 0000000000..c9bd7c96f3 --- /dev/null +++ b/x/posts/keeper/migrations.go @@ -0,0 +1,29 @@ +package keeper + +// DONTCOVER + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + v2 "github.com/desmos-labs/desmos/v3/x/posts/legacy/v2" + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + k Keeper + sk types.SubspacesKeeper +} + +// NewMigrator returns a new Migrator +func NewMigrator(keeper Keeper, sk types.SubspacesKeeper) Migrator { + return Migrator{ + k: keeper, + sk: sk, + } +} + +// Migrate1to2 migrates from version 1 to 2. +func (m Migrator) Migrate1to2(ctx sdk.Context) error { + return v2.MigrateStore(ctx, m.k.storeKey, m.sk) +} diff --git a/x/posts/keeper/msg_server.go b/x/posts/keeper/msg_server.go new file mode 100644 index 0000000000..5452285d2f --- /dev/null +++ b/x/posts/keeper/msg_server.go @@ -0,0 +1,478 @@ +package keeper + +import ( + "context" + "fmt" + "sort" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the stored MsgServer interface +// for the provided k +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +var _ types.MsgServer = &msgServer{} + +// CreatePost defines the rpc method for Msg/CreatePost +func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + author, err := sdk.AccAddressFromBech32(msg.Author) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid author address: %s", msg.Author) + } + + // Check the permission to create content + if !k.HasPermission(ctx, msg.SubspaceID, author, subspacestypes.PermissionWrite) { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot create content inside this subspace") + } + + // Get the next post id + postID, err := k.GetNextPostID(ctx, msg.SubspaceID) + if err != nil { + return nil, err + } + + // Create and validate the post + post := types.NewPost( + msg.SubspaceID, + postID, + msg.ExternalID, + msg.Text, + msg.Author, + msg.ConversationID, + msg.Entities, + msg.ReferencedPosts, + msg.ReplySettings, + ctx.BlockTime(), + nil, + ) + err = k.ValidatePost(ctx, post) + if err != nil { + return nil, err + } + + // Store the post + k.SavePost(ctx, post) + + // Update the id for the next post + k.SetNextPostID(ctx, msg.SubspaceID, post.ID+1) + + // Unpack the attachments + attachments, err := types.UnpackAttachments(k.cdc, msg.Attachments) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid attachments: %s", err) + } + + // Store the attachments + for _, content := range attachments { + _, err = k.storePostAttachment(ctx, post.SubspaceID, post.ID, content) + if err != nil { + return nil, err + } + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Author), + ), + sdk.NewEvent( + types.EventTypeCreatePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", post.ID)), + sdk.NewAttribute(types.AttributeKeyAuthor, msg.Author), + sdk.NewAttribute(types.AttributeKeyCreationTime, post.CreationDate.Format(time.RFC3339)), + ), + }) + + return &types.MsgCreatePostResponse{ + PostID: post.ID, + CreationDate: post.CreationDate, + }, nil +} + +// EditPost defines the rpc method for Msg/EditPost +func (k msgServer) EditPost(goCtx context.Context, msg *types.MsgEditPost) (*types.MsgEditPostResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + // Get the post + post, found := k.GetPost(ctx, msg.SubspaceID, msg.PostID) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d not found", msg.PostID) + } + + // Make sure the editor matches the author + if post.Author != msg.Editor { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "you are not the author of this post") + } + + editor, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid editor address: %s", msg.Editor) + } + + // Check the permission to create content + if !k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionEditOwnContent) { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot edit content inside this subspace") + } + + // Update the post and validate it + updateTime := ctx.BlockTime() + update := types.NewPostUpdate(msg.Text, msg.Entities, updateTime) + updatedPost := post.Update(update) + err = k.ValidatePost(ctx, updatedPost) + if err != nil { + return nil, err + } + + // Store the update + k.SavePost(ctx, updatedPost) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Editor), + ), + sdk.NewEvent( + types.EventTypeEditPost, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", msg.PostID)), + sdk.NewAttribute(types.AttributeKeyLastEditTime, updateTime.Format(time.RFC3339)), + ), + }) + + return &types.MsgEditPostResponse{ + EditDate: updateTime, + }, nil +} + +// DeletePost defines the rpc method for Msg/DeletePost +func (k msgServer) DeletePost(goCtx context.Context, msg *types.MsgDeletePost) (*types.MsgDeletePostResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + // Get the post + post, found := k.GetPost(ctx, msg.SubspaceID, msg.PostID) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", msg.PostID) + } + + editor, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid signer address: %s", msg.Signer) + } + + // Check the permission to remove the post + isModerator := k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionModerateContent) + canEdit := post.Author == msg.Signer && k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionEditOwnContent) + if !isModerator && !canEdit { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot edit content inside this subspace") + } + + // Delete the post + k.Keeper.DeletePost(ctx, msg.SubspaceID, msg.PostID) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer), + ), + sdk.NewEvent( + types.EventTypeDeletePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", msg.PostID)), + ), + }) + + return &types.MsgDeletePostResponse{}, nil +} + +// storePostAttachment allows to easily store a post attachment, returning the attachment id used and any error +func (k msgServer) storePostAttachment(ctx sdk.Context, subspaceID uint64, postID uint64, content types.AttachmentContent) (attachmentID uint32, err error) { + // Perform poll checks + if poll, ok := content.(*types.Poll); ok { + // Make sure no tally results are provided + if poll.FinalTallyResults != nil { + return 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "poll tally results must be nil") + } + + // Make sure the end date is in the future + if poll.EndDate.Before(ctx.BlockTime()) { + return 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "poll end date must be in the future") + } + } + + // Get the next attachment id + attachmentID, err = k.GetNextAttachmentID(ctx, subspaceID, postID) + if err != nil { + return + } + + // Create the attachment and validate it + attachment := types.NewAttachment(subspaceID, postID, attachmentID, content) + err = attachment.Validate() + if err != nil { + return 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid attachment content: %s", err) + } + + // Save the attachment + k.SaveAttachment(ctx, attachment) + + // Store the poll inside the active queue + if types.IsPoll(attachment) { + k.InsertActivePollQueue(ctx, attachment) + } + + // Update the id for the next attachment + k.SetNextAttachmentID(ctx, subspaceID, postID, attachment.ID+1) + + return attachmentID, nil +} + +// AddPostAttachment defines the rpc method for Msg/AddPostAttachment +func (k msgServer) AddPostAttachment(goCtx context.Context, msg *types.MsgAddPostAttachment) (*types.MsgAddPostAttachmentResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + // Get the post + post, found := k.GetPost(ctx, msg.SubspaceID, msg.PostID) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", msg.PostID) + } + + // Make sure the editor matches the author + if post.Author != msg.Editor { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "you are not the author of this post") + } + + editor, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid editor address: %s", msg.Editor) + } + + // Check the permission to edit content + if !k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionEditOwnContent) { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot edit content inside this subspace") + } + + // Unpack the content + var content types.AttachmentContent + err = k.cdc.UnpackAny(msg.Content, &content) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid attachment content: %s", err) + } + + // Save the attachment + attachmentID, err := k.storePostAttachment(ctx, msg.SubspaceID, msg.PostID, msg.Content.GetCachedValue().(types.AttachmentContent)) + if err != nil { + return nil, err + } + + // Update the post edit time + updateTime := ctx.BlockTime() + post.LastEditedDate = &updateTime + err = k.ValidatePost(ctx, post) + if err != nil { + return nil, err + } + k.SavePost(ctx, post) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Editor), + ), + sdk.NewEvent( + types.EventTypeAddPostAttachment, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", msg.PostID)), + sdk.NewAttribute(types.AttributeKeyAttachmentID, fmt.Sprintf("%d", attachmentID)), + sdk.NewAttribute(types.AttributeKeyLastEditTime, post.LastEditedDate.Format(time.RFC3339)), + ), + }) + + return &types.MsgAddPostAttachmentResponse{ + AttachmentID: attachmentID, + EditDate: updateTime, + }, nil +} + +// RemovePostAttachment defines the rpc method for Msg/RemovePostAttachment +func (k msgServer) RemovePostAttachment(goCtx context.Context, msg *types.MsgRemovePostAttachment) (*types.MsgRemovePostAttachmentResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + // Get the post + post, found := k.GetPost(ctx, msg.SubspaceID, msg.PostID) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", msg.PostID) + } + + editor, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid editor address: %s", msg.Editor) + } + + // Check the permission to remove the attachment + isModerator := k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionModerateContent) + canEdit := post.Author == msg.Editor && k.HasPermission(ctx, msg.SubspaceID, editor, subspacestypes.PermissionEditOwnContent) + if !isModerator && !canEdit { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot edit content inside this subspace") + } + + // Check if the attachment exists + if !k.HasAttachment(ctx, msg.SubspaceID, msg.PostID, msg.AttachmentID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "attachment with id %d not found", msg.AttachmentID) + } + + // Remove the post attachment + k.DeleteAttachment(ctx, msg.SubspaceID, msg.PostID, msg.AttachmentID) + + // Update the post edit time + updateTime := ctx.BlockTime() + post.LastEditedDate = &updateTime + err = k.ValidatePost(ctx, post) + if err != nil { + return nil, err + } + k.SavePost(ctx, post) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Editor), + ), + sdk.NewEvent( + types.EventTypeRemovePostAttachment, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", msg.PostID)), + sdk.NewAttribute(types.AttributeKeyAttachmentID, fmt.Sprintf("%d", msg.AttachmentID)), + sdk.NewAttribute(types.AttributeKeyLastEditTime, post.LastEditedDate.Format(time.RFC3339)), + ), + }) + + return &types.MsgRemovePostAttachmentResponse{ + EditDate: updateTime, + }, nil +} + +// AnswerPoll defines the rpc method for Msg/AnswerPoll +func (k msgServer) AnswerPoll(goCtx context.Context, msg *types.MsgAnswerPoll) (*types.MsgAnswerPollResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check the if the subspace exists + if !k.HasSubspace(ctx, msg.SubspaceID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace with id %d not found", msg.SubspaceID) + } + + signer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid signer address: %s", msg.Signer) + } + + // Check the permission to interact with content + if !k.HasPermission(ctx, msg.SubspaceID, signer, subspacestypes.PermissionInteractWithContent) { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot interact with content inside this subspace") + } + + // Make sure the post exists + if !k.HasPost(ctx, msg.SubspaceID, msg.PostID) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", msg.PostID) + } + + // Get the poll + poll, found := k.GetPoll(ctx, msg.SubspaceID, msg.PostID, msg.PollID) + if !found { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "poll with id %d does not exist", msg.PollID) + } + + alreadyAnswered := k.HasUserAnswer(ctx, msg.SubspaceID, msg.PostID, msg.PollID, signer.String()) + + // Make sure the user is not trying to edit the answer when the poll does not allow it + if alreadyAnswered && !poll.AllowsAnswerEdits { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "you cannot edit this poll's answer") + } + + // Make sure the user not answering with multiple options when the poll does not allow it + if len(msg.AnswersIndexes) > 1 && !poll.AllowsMultipleAnswers { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only one answer is allowed on this post") + } + + // Sort the answers indexes + sort.Slice(msg.AnswersIndexes, func(i, j int) bool { + return msg.AnswersIndexes[i] < msg.AnswersIndexes[j] + }) + + // Make sure the answer indexes exist + maxProvidedIndex := uint32(len(poll.ProvidedAnswers) - 1) + maxAnswerIndex := msg.AnswersIndexes[len(msg.AnswersIndexes)-1] + if maxAnswerIndex > maxProvidedIndex { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid answer index: %d", maxAnswerIndex) + } + + // Store the user answer + answer := types.NewUserAnswer(msg.SubspaceID, msg.PostID, msg.PollID, msg.AnswersIndexes, signer.String()) + k.SaveUserAnswer(ctx, answer) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(msg)), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Signer), + ), + sdk.NewEvent( + types.EventTypeAnswerPoll, + sdk.NewAttribute(types.AttributeKeySubspaceID, fmt.Sprintf("%d", msg.SubspaceID)), + sdk.NewAttribute(types.AttributeKeyPostID, fmt.Sprintf("%d", msg.PostID)), + sdk.NewAttribute(types.AttributeKeyPollID, fmt.Sprintf("%d", msg.PollID)), + ), + }) + + return &types.MsgAnswerPollResponse{}, nil +} diff --git a/x/posts/keeper/msg_server_test.go b/x/posts/keeper/msg_server_test.go new file mode 100644 index 0000000000..42ed05886a --- /dev/null +++ b/x/posts/keeper/msg_server_test.go @@ -0,0 +1,1958 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func (suite *KeeperTestsuite) TestMsgServer_CreatePost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgCreatePost + shouldErr bool + expResponse *types.MsgCreatePostResponse + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "non existing subspace returns error", + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 1, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without permission returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 1, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid conversation id returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + + suite.k.SetParams(ctx, types.DefaultParams()) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 1, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid reference returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + + suite.k.SetParams(ctx, types.DefaultParams()) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + []types.AttachmentContent{ + types.NewMedia("", ""), + }, + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "initial post id not set returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid post returns error", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + + suite.k.SetNextPostID(ctx, 1, 1) + + // Set the max post length to 1 character + suite.k.SetParams(ctx, types.NewParams(1)) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid attachment returns error", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + + suite.k.SetNextPostID(ctx, 1, 1) + + suite.k.SetParams(ctx, types.DefaultParams()) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + []types.AttachmentContent{ + types.NewMedia("", ""), + }, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "valid post is stored correctly", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite) + + suite.k.SetNextPostID(ctx, 1, 1) + + suite.k.SetParams(ctx, types.DefaultParams()) + }, + msg: types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + []types.AttachmentContent{ + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + }, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expResponse: &types.MsgCreatePostResponse{ + PostID: 1, + CreationDate: time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgCreatePost{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeCreatePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyAuthor, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + sdk.NewAttribute(types.AttributeKeyCreationTime, time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Check the post + stored, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), stored) + + // Check the attachments + attachments := suite.k.GetPostAttachments(ctx, 1, 1) + suite.Require().Equal([]types.Attachment{ + types.NewAttachment( + 1, + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + ), + }, attachments) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + res, err := msgServer.CreatePost(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResponse, res) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} + +func (suite *KeeperTestsuite) TestMsgServer_EditPost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgEditPost + shouldErr bool + expResponse *types.MsgEditPostResponse + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "non existing subspace returns error", + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found post returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a new post", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without permission returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a new post", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid update returns error", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a new post", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + // Set max post text length to 1 character + suite.k.SetParams(ctx, types.NewParams(1)) + }, + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "post is updated correctly", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a new post", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgEditPost( + 1, + 1, + "This is my new text", + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expResponse: &types.MsgEditPostResponse{ + EditDate: time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgEditPost{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeEditPost, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyLastEditTime, time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Make sure the post is what we are expecting + stored, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + + editDate := time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC) + suite.Require().Equal(types.NewPost( + 1, + 1, + "External ID", + "This is my new text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + &editDate, + ), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + res, err := msgServer.EditPost(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResponse, res) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} + +func (suite *KeeperTestsuite) TestMsgServer_DeletePost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgDeletePost + shouldErr bool + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "non existing subspace returns error", + msg: types.NewMsgDeletePost(1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + shouldErr: true, + }, + { + name: "not found post returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionWrite&subspacestypes.PermissionEditOwnContent) + }, + msg: types.NewMsgDeletePost(1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + shouldErr: true, + }, + { + name: "user without permission returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgDeletePost(1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + shouldErr: true, + }, + { + name: "author cannot delete other user post", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgDeletePost(1, 1, "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4"), + shouldErr: true, + }, + { + name: "moderator can delete post", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionModerateContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgDeletePost(1, 1, "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4"), + shouldErr: false, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeletePost{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4"), + ), + sdk.NewEvent( + types.EventTypeDeletePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + ), + }, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasPost(ctx, 1, 1)) + }, + }, + { + name: "author can delete post", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgDeletePost(1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeletePost{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeDeletePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + ), + }, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasPost(ctx, 1, 1)) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + _, err := msgServer.DeletePost(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} + +func (suite *KeeperTestsuite) TestMsgServer_AddPostAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgAddPostAttachment + shouldErr bool + expResponse *types.MsgAddPostAttachmentResponse + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "non existing subspace returns error", + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found post returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without permissions returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid attachment returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + }, + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("", ""), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "correct data is stored properly", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + }, + msg: types.NewMsgAddPostAttachment( + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expResponse: &types.MsgAddPostAttachmentResponse{ + AttachmentID: 1, + EditDate: time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgAddPostAttachment{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeAddPostAttachment, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyAttachmentID, "1"), + sdk.NewAttribute(types.AttributeKeyLastEditTime, time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Make sure the post is updated properly + post, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + + updateDate := time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC) + suite.Require().Equal(&updateDate, post.LastEditedDate) + + // Make sure the attachment is there + stored, found := suite.k.GetAttachment(ctx, 1, 1, 1) + suite.Require().True(found) + suite.Require().Equal(types.NewAttachment( + 1, + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + ), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + res, err := msgServer.AddPostAttachment(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResponse, res) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} + +func (suite *KeeperTestsuite) TestMsgServer_RemovePostAttachment() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgRemovePostAttachment + shouldErr bool + expResponse *types.MsgRemovePostAttachmentResponse + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "not found subspace returns error", + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found post returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without permissions returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user with permissions cannot delete other author attachment", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found attachment returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "moderator can delete attachment", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionModerateContent) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + ), + shouldErr: false, + expResponse: &types.MsgRemovePostAttachmentResponse{ + EditDate: time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgRemovePostAttachment{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4"), + ), + sdk.NewEvent( + types.EventTypeRemovePostAttachment, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyAttachmentID, "1"), + sdk.NewAttribute(types.AttributeKeyLastEditTime, time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Make sure the post is updated properly + post, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + + updateDate := time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC) + suite.Require().Equal(&updateDate, post.LastEditedDate) + + // Make sure the attachment is no longer there + suite.Require().False(suite.k.HasAttachment(ctx, 1, 1, 1)) + }, + }, + { + name: "author can delete attachment", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionEditOwnContent) + + suite.k.SetParams(ctx, types.DefaultParams()) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + )) + }, + msg: types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expResponse: &types.MsgRemovePostAttachmentResponse{ + EditDate: time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgRemovePostAttachment{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeRemovePostAttachment, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyAttachmentID, "1"), + sdk.NewAttribute(types.AttributeKeyLastEditTime, time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Make sure the post is updated properly + post, found := suite.k.GetPost(ctx, 1, 1) + suite.Require().True(found) + + updateDate := time.Date(2021, 1, 1, 12, 00, 00, 000, time.UTC) + suite.Require().Equal(&updateDate, post.LastEditedDate) + + // Make sure the attachment is no longer there + suite.Require().False(suite.k.HasAttachment(ctx, 1, 1, 1)) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + res, err := msgServer.RemovePostAttachment(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expResponse, res) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} + +func (suite *KeeperTestsuite) TestMsgServer_AnswerPoll() { + testCases := []struct { + name string + store func(ctx sdk.Context) + setupCtx func(ctx sdk.Context) sdk.Context + msg *types.MsgAnswerPoll + shouldErr bool + expEvents sdk.Events + check func(ctx sdk.Context) + }{ + { + name: "not found subspace returns error", + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1, 2, 3}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without permission returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1, 2, 3}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found post returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1, 2, 3}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "not found poll returns error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1, 2, 3}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "already answered poll returns error if no answer edits are allowed", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + )) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer( + 1, + 1, + 1, + []uint32{0, 1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "multiple answers return error if they are not allowed", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{0, 1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "invalid answer indexes return error", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + true, + true, + nil, + ), + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{0, 1, 2}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "editing an answer works correctly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + true, + nil, + ), + )) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer( + 1, + 1, + 1, + []uint32{1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{0}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgAnswerPoll{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeAnswerPoll, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyPollID, "1"), + ), + }, + check: func(ctx sdk.Context) { + // Check the user answer + stored, found := suite.k.GetUserAnswer(ctx, 1, 1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().True(found) + suite.Require().Equal(types.NewUserAnswer( + 1, + 1, + 1, + []uint32{0}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), stored) + }, + }, + { + name: "new answer is stored correctly", + store: func(ctx sdk.Context) { + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + user, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().NoError(err) + suite.sk.SetUserPermissions(ctx, 1, user, subspacestypes.PermissionInteractWithContent) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SaveAttachment(ctx, types.NewAttachment( + 1, + 1, + 1, + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + true, + false, + nil, + ), + )) + }, + msg: types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{0, 1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgAnswerPoll{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeAnswerPoll, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeyPostID, "1"), + sdk.NewAttribute(types.AttributeKeyPollID, "1"), + ), + }, + check: func(ctx sdk.Context) { + // Check the user answer + stored, found := suite.k.GetUserAnswer(ctx, 1, 1, 1, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd") + suite.Require().True(found) + suite.Require().Equal(types.NewUserAnswer( + 1, + 1, + 1, + []uint32{0, 1}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.setupCtx != nil { + ctx = tc.setupCtx(ctx) + } + if tc.store != nil { + tc.store(ctx) + } + + msgServer := keeper.NewMsgServerImpl(suite.k) + _, err := msgServer.AnswerPoll(sdk.WrapSDKContext(ctx), tc.msg) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expEvents, ctx.EventManager().Events()) + + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} diff --git a/x/posts/keeper/params.go b/x/posts/keeper/params.go new file mode 100644 index 0000000000..fb76232221 --- /dev/null +++ b/x/posts/keeper/params.go @@ -0,0 +1,18 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// SetParams sets params on the store +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramsSubspace.SetParamSet(ctx, ¶ms) +} + +// GetParams returns the params from the store +func (k Keeper) GetParams(ctx sdk.Context) (p types.Params) { + k.paramsSubspace.GetParamSet(ctx, &p) + return p +} diff --git a/x/posts/keeper/params_test.go b/x/posts/keeper/params_test.go new file mode 100644 index 0000000000..3fef95ba3b --- /dev/null +++ b/x/posts/keeper/params_test.go @@ -0,0 +1,80 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestKeeper_SetParams() { + testCases := []struct { + name string + store func(ctx sdk.Context) + params types.Params + check func(ctx sdk.Context) + }{ + { + name: "default params are saved correctly", + params: types.DefaultParams(), + check: func(ctx sdk.Context) { + stored := suite.k.GetParams(ctx) + suite.Require().Equal(stored, types.DefaultParams()) + }, + }, + { + name: "params are overridden properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.DefaultParams()) + }, + params: types.NewParams(1), + check: func(ctx sdk.Context) { + stored := suite.k.GetParams(ctx) + suite.Require().Equal(stored, types.NewParams(1)) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SetParams(ctx, tc.params) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetParams() { + testCases := []struct { + name string + store func(ctx sdk.Context) + expParams types.Params + }{ + { + name: "params are returned properly", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.NewParams(1)) + }, + expParams: types.NewParams(1), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + params := suite.k.GetParams(ctx) + suite.Require().Equal(tc.expParams, params) + }) + } +} diff --git a/x/posts/keeper/polls.go b/x/posts/keeper/polls.go new file mode 100644 index 0000000000..19aa6c6b04 --- /dev/null +++ b/x/posts/keeper/polls.go @@ -0,0 +1,114 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// HasPoll tells whether the specified post contains a poll with the provided id +func (k Keeper) HasPoll(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) bool { + attachment, found := k.GetAttachment(ctx, subspaceID, postID, pollID) + if !found { + return false + } + return types.IsPoll(attachment) +} + +// GetPoll returns the poll having the given id. +// If not poll with the given id is found, the function returns nil and false. +func (k Keeper) GetPoll(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) (poll *types.Poll, found bool) { + attachment, found := k.GetAttachment(ctx, subspaceID, postID, pollID) + if !found { + return nil, false + } + + poll, ok := attachment.Content.GetCachedValue().(*types.Poll) + return poll, ok +} + +// Tally iterates over the votes and returns the tally results of a poll +func (k Keeper) Tally(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) *types.PollTallyResults { + poll, found := k.GetPoll(ctx, subspaceID, postID, pollID) + if !found { + return nil + } + + // Create the map index -> count(votes) + results := make(map[uint32]uint64, len(poll.ProvidedAnswers)) + for i := range poll.ProvidedAnswers { + results[uint32(i)] = 0 + } + + k.IteratePollUserAnswers(ctx, subspaceID, postID, pollID, func(_ int64, answer types.UserAnswer) (stop bool) { + // Update the results + for _, answerIndex := range answer.AnswersIndexes { + results[answerIndex]++ + } + + // Delete the user answer + k.DeleteUserAnswer(ctx, answer.SubspaceID, answer.PostID, answer.PollID, answer.User) + + return false + }) + + tallyResults := make([]types.PollTallyResults_AnswerResult, len(results)) + for index, count := range results { + tallyResults[int(index)] = types.NewAnswerResult(index, count) + } + + return types.NewPollTallyResults(tallyResults) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// InsertActivePollQueue inserts a poll into the active poll queue +func (k Keeper) InsertActivePollQueue(ctx sdk.Context, poll types.Attachment) { + store := ctx.KVStore(k.storeKey) + bz := types.GetPollIDBytes(poll.SubspaceID, poll.PostID, poll.ID) + content := poll.Content.GetCachedValue().(*types.Poll) + store.Set(types.ActivePollQueueKey(poll.SubspaceID, poll.PostID, poll.ID, content.EndDate), bz) +} + +// RemoveFromActivePollQueue removes a poll from the active poll queue +func (k Keeper) RemoveFromActivePollQueue(ctx sdk.Context, poll types.Attachment) { + store := ctx.KVStore(k.storeKey) + content := poll.Content.GetCachedValue().(*types.Poll) + store.Delete(types.ActivePollQueueKey(poll.SubspaceID, poll.PostID, poll.ID, content.EndDate)) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// SaveUserAnswer stores the given poll answer into the current context +func (k Keeper) SaveUserAnswer(ctx sdk.Context, answer types.UserAnswer) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PollAnswerStoreKey(answer.SubspaceID, answer.PostID, answer.PollID, answer.User), k.cdc.MustMarshal(&answer)) + + k.AfterPollAnswerSaved(ctx, answer.SubspaceID, answer.PostID, answer.PollID, answer.User) +} + +// HasUserAnswer tells whether a user answer to the specified poll exists +func (k Keeper) HasUserAnswer(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.PollAnswerStoreKey(subspaceID, postID, pollID, user)) +} + +// GetUserAnswer returns the user answer from the given user for the specified poll. +// If there is no answer result associated with the given poll and user the function will return an empty answer and false. +func (k Keeper) GetUserAnswer(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) (answer types.UserAnswer, found bool) { + store := ctx.KVStore(k.storeKey) + key := types.PollAnswerStoreKey(subspaceID, postID, pollID, user) + if !store.Has(key) { + return types.UserAnswer{}, false + } + k.cdc.MustUnmarshal(store.Get(key), &answer) + return answer, true +} + +// DeleteUserAnswer deletes the user answer from the provided poll +func (k Keeper) DeleteUserAnswer(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PollAnswerStoreKey(subspaceID, postID, pollID, user)) + + k.AfterPollAnswerDeleted(ctx, subspaceID, postID, pollID, user) +} diff --git a/x/posts/keeper/polls_test.go b/x/posts/keeper/polls_test.go new file mode 100644 index 0000000000..189f387e79 --- /dev/null +++ b/x/posts/keeper/polls_test.go @@ -0,0 +1,407 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestKeeper_HasPoll() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + expResult bool + }{ + { + name: "media attachment returns false", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + expResult: false, + }, + { + name: "poll attachment returns true", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + expResult: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + result := suite.k.HasPoll(ctx, tc.subspaceID, tc.postID, tc.pollID) + suite.Require().Equal(tc.expResult, result) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetPoll() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + expFound bool + expPoll *types.Poll + }{ + { + name: "non existing poll returns nil and false", + subspaceID: 1, + postID: 1, + pollID: 1, + expFound: false, + expPoll: nil, + }, + { + name: "media attachment returns nil and false", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + expFound: false, + expPoll: nil, + }, + { + name: "poll returns true and correct value", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ))) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + expFound: true, + expPoll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + + if tc.store != nil { + tc.store(ctx) + } + + poll, found := suite.k.GetPoll(ctx, tc.subspaceID, tc.postID, tc.pollID) + suite.Require().Equal(tc.expFound, found) + suite.Require().Equal(tc.expPoll, poll) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_Tally() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + expResult *types.PollTallyResults + check func(ctx sdk.Context) + }{ + { + name: "not found poll returns null", + expResult: nil, + }, + { + name: "existing poll returns correct results", + store: func(ctx sdk.Context) { + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + types.NewProvidedAnswer("No one of the above", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + true, + false, + nil, + ))) + + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{0, 1}, "cosmos1pmklwgqjqmgc4ynevmtset85uwm0uau90jdtfn")) + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1zmqjufkg44ngswgf4vmn7evp8k6h07erdyxefd")) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + expResult: types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 1), + types.NewAnswerResult(1, 2), + types.NewAnswerResult(2, 0), + }), + check: func(ctx sdk.Context) { + // Make sure all the answers have been deleted + answers := suite.k.GetPollUserAnswers(ctx, 1, 1, 1) + suite.Require().Empty(answers) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + results := suite.k.Tally(ctx, tc.subspaceID, tc.postID, tc.pollID) + suite.Require().Equal(tc.expResult, results) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +func (suite *KeeperTestsuite) TestKeeper_SaveUserAnswer() { + testCases := []struct { + name string + store func(ctx sdk.Context) + answer types.UserAnswer + check func(ctx sdk.Context) + }{ + { + name: "non existing answer is stored properly", + answer: types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + check: func(ctx sdk.Context) { + stored, found := suite.k.GetUserAnswer(ctx, 1, 1, 1, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw") + suite.Require().True(found) + suite.Require().Equal(types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), stored) + }, + }, + { + name: "existing answer is overridden properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + answer: types.NewUserAnswer(1, 1, 1, []uint32{1, 2}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + check: func(ctx sdk.Context) { + stored, found := suite.k.GetUserAnswer(ctx, 1, 1, 1, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw") + suite.Require().True(found) + suite.Require().Equal(types.NewUserAnswer(1, 1, 1, []uint32{1, 2}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SaveUserAnswer(ctx, tc.answer) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_HasUserAnswer() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + user string + expResult bool + }{ + { + name: "non existing answer returns false", + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + expResult: false, + }, + { + name: "existing answer returns true", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + expResult: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + result := suite.k.HasUserAnswer(ctx, tc.subspaceID, tc.postID, tc.pollID, tc.user) + suite.Require().Equal(tc.expResult, result) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetUserAnswer() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + user string + expFound bool + expAnswer types.UserAnswer + }{ + { + name: "not found answer returns false and empty answer", + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + expFound: false, + expAnswer: types.UserAnswer{}, + }, + { + name: "found answer returns true and correct data", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + expFound: true, + expAnswer: types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + answer, found := suite.k.GetUserAnswer(ctx, tc.subspaceID, tc.postID, tc.pollID, tc.user) + suite.Require().Equal(tc.expFound, found) + suite.Require().Equal(tc.expAnswer, answer) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_DeleteUserAnswer() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + pollID uint32 + user string + check func(ctx sdk.Context) + }{ + { + name: "non existing answer is deleted properly", + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasUserAnswer(ctx, 1, 1, 1, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + }, + { + name: "existing answer is deleted properly", + store: func(ctx sdk.Context) { + suite.k.SaveUserAnswer(ctx, types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + subspaceID: 1, + postID: 1, + pollID: 1, + user: "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw", + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasUserAnswer(ctx, 1, 1, 1, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw")) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.DeleteUserAnswer(ctx, tc.subspaceID, tc.postID, tc.pollID, tc.user) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/keeper/posts.go b/x/posts/keeper/posts.go new file mode 100644 index 0000000000..3efe45a667 --- /dev/null +++ b/x/posts/keeper/posts.go @@ -0,0 +1,183 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// SetNextPostID sets the new post id for the given subspace to the store +func (k Keeper) SetNextPostID(ctx sdk.Context, subspaceID uint64, postID uint64) { + store := ctx.KVStore(k.storeKey) + store.Set(types.NextPostIDStoreKey(subspaceID), types.GetPostIDBytes(postID)) +} + +// HasNextPostID tells whether the next post id key existing for the given subspace +func (k Keeper) HasNextPostID(ctx sdk.Context, subspaceID uint64) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.NextPostIDStoreKey(subspaceID)) +} + +// GetNextPostID gets the highest post id for the given subspace +func (k Keeper) GetNextPostID(ctx sdk.Context, subspaceID uint64) (postID uint64, err error) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.NextPostIDStoreKey(subspaceID)) + if bz == nil { + return 0, sdkerrors.Wrapf(types.ErrInvalidGenesis, "initial post ID hasn't been set for subspace %d", subspaceID) + } + + postID = types.GetPostIDFromBytes(bz) + return postID, nil +} + +// DeleteNextPostID removes the post id key for the given subspace +func (k Keeper) DeleteNextPostID(ctx sdk.Context, subspaceID uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.NextPostIDStoreKey(subspaceID)) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidatePostReference checks the post reference to make sure that the referenced +// post's author has not blocked the user referencing the post +func (k Keeper) ValidatePostReference(ctx sdk.Context, postAuthor string, subspaceID uint64, referenceID uint64) error { + // Make sure the referenced post exists + referencedPost, found := k.GetPost(ctx, subspaceID, referenceID) + if !found { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", referenceID) + } + + // Make sure the original author has not blocked the post author + if k.HasUserBlocked(ctx, referencedPost.Author, postAuthor, subspaceID) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "author of post %d has blocked you", referenceID) + } + + return nil +} + +// ValidatePostReply checks the original post reply settings to make sure that +// only specified users can answer to the post +func (k Keeper) ValidatePostReply(ctx sdk.Context, postAuthor string, subspaceID uint64, referenceID uint64) error { + replyPost, found := k.GetPost(ctx, subspaceID, referenceID) + if !found { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %d does not exist", referenceID) + } + + switch replyPost.ReplySettings { + case types.REPLY_SETTING_FOLLOWERS: + // We need to make sure that a relationship between post author -> original author exists + if !k.HasRelationship(ctx, postAuthor, replyPost.Author, subspaceID) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only followers of the author can reply to this post") + } + + case types.REPLY_SETTING_MUTUAL: + // We need to make sure that both relationships exist (post author -> original author and original author -> post author) + if !k.HasRelationship(ctx, postAuthor, replyPost.Author, subspaceID) || !k.HasRelationship(ctx, replyPost.Author, postAuthor, subspaceID) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only mutual connections of the author can reply to this post") + } + + case types.REPLY_SETTING_MENTIONS: + // We need to check each mention of the original post + if !replyPost.IsUserMentioned(postAuthor) { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "only mentioned users can reply to this post") + } + } + + return nil +} + +// ValidatePost validates the given post based on the current params, returning an error if anything is wrong +func (k Keeper) ValidatePost(ctx sdk.Context, post types.Post) error { + params := k.GetParams(ctx) + + // Validate the conversation reference + if post.ConversationID != 0 { + err := k.ValidatePostReference(ctx, post.Author, post.SubspaceID, post.ConversationID) + if err != nil { + return err + } + } + + // Validate the post references + for _, reference := range post.ReferencedPosts { + err := k.ValidatePostReference(ctx, post.Author, post.SubspaceID, reference.PostID) + if err != nil { + return err + } + + if reference.Type == types.TYPE_REPLIED_TO { + err = k.ValidatePostReply(ctx, post.Author, post.SubspaceID, reference.PostID) + if err != nil { + return err + } + } + } + + // Check the post text length to make sure it's not exceeding the max length + if uint32(len(post.Text)) > params.MaxTextLength { + return sdkerrors.Wrapf(types.ErrInvalidPost, "text exceed max length allowed") + } + + err := post.Validate() + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, err.Error()) + } + + return nil +} + +// SavePost saves the given post inside the current context. +func (k Keeper) SavePost(ctx sdk.Context, post types.Post) { + store := ctx.KVStore(k.storeKey) + + // Store the post + store.Set(types.PostStoreKey(post.SubspaceID, post.ID), k.cdc.MustMarshal(&post)) + + // If the initial attachment id does not exist, create it now + if !k.HasNextAttachmentID(ctx, post.SubspaceID, post.ID) { + k.SetNextAttachmentID(ctx, post.SubspaceID, post.ID, 1) + } + + k.Logger(ctx).Debug("post saved", "subspace id", post.SubspaceID, "id", post.ID) + k.AfterPostSaved(ctx, post.SubspaceID, post.ID) +} + +// HasPost tells whether the given post exists or not +func (k Keeper) HasPost(ctx sdk.Context, subspaceID uint64, postID uint64) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.PostStoreKey(subspaceID, postID)) +} + +// GetPost returns the post associated with the given id. +// If there is no post associated with the given id the function will return an empty post and false. +func (k Keeper) GetPost(ctx sdk.Context, subspaceID uint64, postID uint64) (post types.Post, found bool) { + store := ctx.KVStore(k.storeKey) + + key := types.PostStoreKey(subspaceID, postID) + if !store.Has(key) { + return types.Post{}, false + } + + k.cdc.MustUnmarshal(store.Get(key), &post) + return post, true +} + +// DeletePost deletes the given post and all its attachments from the store +func (k Keeper) DeletePost(ctx sdk.Context, subspaceID uint64, postID uint64) { + store := ctx.KVStore(k.storeKey) + + // Delete the post + store.Delete(types.PostStoreKey(subspaceID, postID)) + + // Delete all the attachments + k.IteratePostAttachments(ctx, subspaceID, postID, func(_ int64, attachment types.Attachment) (stop bool) { + k.DeleteAttachment(ctx, attachment.SubspaceID, attachment.PostID, attachment.ID) + return false + }) + + // Delete the attachment id key + k.DeleteNextAttachmentID(ctx, subspaceID, postID) + + k.AfterPostDeleted(ctx, subspaceID, postID) +} diff --git a/x/posts/keeper/posts_test.go b/x/posts/keeper/posts_test.go new file mode 100644 index 0000000000..1fd36b660f --- /dev/null +++ b/x/posts/keeper/posts_test.go @@ -0,0 +1,1028 @@ +package keeper_test + +import ( + "time" + + relationshipstypes "github.com/desmos-labs/desmos/v3/x/relationships/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func (suite *KeeperTestsuite) TestKeeper_SetNextPostID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + check func(ctx sdk.Context) + }{ + { + name: "non existing post id is set properly", + subspaceID: 1, + postID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + stored := types.GetPostIDFromBytes(store.Get(types.NextPostIDStoreKey(1))) + suite.Require().Equal(uint64(1), stored) + }, + }, + { + name: "existing post id is overridden properly", + store: func(ctx sdk.Context) { + suite.k.SetNextPostID(ctx, 1, 1) + }, + subspaceID: 1, + postID: 2, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + stored := types.GetPostIDFromBytes(store.Get(types.NextPostIDStoreKey(1))) + suite.Require().Equal(uint64(2), stored) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SetNextPostID(ctx, tc.subspaceID, tc.postID) + if tc.check != nil { + tc.check(ctx) + } + }) + } + +} + +func (suite *KeeperTestsuite) TestKeeper_GetNextPostID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + shouldErr bool + expPostID uint64 + }{ + { + name: "not found post id returns error", + subspaceID: 1, + shouldErr: true, + }, + { + name: "found post id returns no error", + store: func(ctx sdk.Context) { + suite.k.SetNextPostID(ctx, 1, 1) + }, + subspaceID: 1, + shouldErr: false, + expPostID: 1, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + postID, err := suite.k.GetNextPostID(ctx, tc.subspaceID) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + suite.Require().Equal(tc.expPostID, postID) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_DeleteNextPostID() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + check func(ctx sdk.Context) + }{ + { + name: "non existing post id is deleted properly", + subspaceID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + suite.Require().False(store.Has(types.NextPostIDStoreKey(1))) + }, + }, + { + name: "existing post id is deleted properly", + store: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + store.Set(types.NextPostIDStoreKey(1), types.GetPostIDBytes(1)) + }, + subspaceID: 1, + check: func(ctx sdk.Context) { + store := ctx.KVStore(suite.storeKey) + suite.Require().False(store.Has(types.NextPostIDStoreKey(1))) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.DeleteNextPostID(ctx, tc.subspaceID) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +func (suite *KeeperTestsuite) TestKeeper_ValidatePostReference() { + testCases := []struct { + name string + store func(ctx sdk.Context) + postAuthor string + subspaceID uint64 + referenceID uint64 + shouldErr bool + }{ + { + name: "non existing referenced post returns error", + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "blocked post author returns error", + store: func(ctx sdk.Context) { + suite.rk.SaveUserBlock(ctx, relationshipstypes.NewUserBlock( + "cosmos1fvnkn5yjhdc6sxwlph8e98udw8nsly0w9yznrk", + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + "", + 1, + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "This is a long post text to make sure tags are valid", + "cosmos1fvnkn5yjhdc6sxwlph8e98udw8nsly0w9yznrk", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "valid post reference returns no error", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "This is a long post text to make sure tags are valid", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + err := suite.k.ValidatePostReference(ctx, tc.postAuthor, tc.subspaceID, tc.referenceID) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_ValidatePostReply() { + testCases := []struct { + name string + store func(ctx sdk.Context) + postAuthor string + subspaceID uint64 + referenceID uint64 + shouldErr bool + }{ + { + name: "reply post not found returns error", + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "REPLY_SETTING_FOLLOWERS and not follower returns error", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_FOLLOWERS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "REPLY_SETTING_FOLLOWERS and follower returns no error", + store: func(ctx sdk.Context) { + suite.rk.SaveRelationship(ctx, relationshipstypes.NewRelationship( + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_FOLLOWERS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: false, + }, + { + name: "REPLY_SETTING_MUTUAL and not mutual returns error", + store: func(ctx sdk.Context) { + suite.rk.SaveRelationship(ctx, relationshipstypes.NewRelationship( + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_MENTIONS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "REPLY_SETTING_MUTUAL and mutual returns no error", + store: func(ctx sdk.Context) { + suite.rk.SaveRelationship(ctx, relationshipstypes.NewRelationship( + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + )) + + suite.rk.SaveRelationship(ctx, relationshipstypes.NewRelationship( + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + 1, + )) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_MENTIONS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "REPLY_SETTING_MENTIONS and no mention returns error", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_MENTIONS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: true, + }, + { + name: "REPLY_SETTING_MENTIONS and mention returns no error", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + "This is a test post", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + types.NewEntities(nil, []types.Tag{ + types.NewTag(0, 44, "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g"), + }, nil), + nil, + types.REPLY_SETTING_MENTIONS, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + postAuthor: "cosmos1t457f629cc3ykftepjejgzxv0vmz5dw2gn940g", + subspaceID: 1, + referenceID: 1, + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + err := suite.k.ValidatePostReply(ctx, tc.postAuthor, tc.subspaceID, tc.referenceID) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_ValidatePost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + post types.Post + shouldErr bool + }{ + { + name: "invalid text length returns error", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.NewParams(1)) + }, + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "post with invalid conversation id returns error", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.DefaultParams()) + }, + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "post with invalid reference returns error", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.DefaultParams()) + }, + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid post returns error", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.NewParams(1)) + }, + post: types.NewPost( + 1, + 0, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "valid post returns no error", + store: func(ctx sdk.Context) { + suite.k.SetParams(ctx, types.DefaultParams()) + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External id", + "This is a long post text to make sure tags are valid", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + post: types.NewPost( + 1, + 2, + "External id", + "This is a long post text to make sure tags are valid", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_REPLIED_TO, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + err := suite.k.ValidatePost(ctx, tc.post) + if tc.shouldErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_SavePost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + post types.Post + check func(ctx sdk.Context) + }{ + { + name: "non existing post is saved properly", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + check: func(ctx sdk.Context) { + // Check the post exists + stored, found := suite.k.GetPost(ctx, 1, 2) + suite.Require().True(found) + suite.Require().Equal(types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), stored) + + // Make sure the attachment it is generated properly + store := ctx.KVStore(suite.storeKey) + attachmentID := types.GetAttachmentIDFromBytes(store.Get(types.NextAttachmentIDStoreKey(1, 2))) + suite.Require().Equal(uint32(1), attachmentID) + }, + }, + { + name: "existing post is overridden properly", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + post: types.NewPost( + 1, + 2, + "External id", + "This is a new text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + check: func(ctx sdk.Context) { + // Make sure the post is saved properly + stored, found := suite.k.GetPost(ctx, 1, 2) + suite.Require().True(found) + suite.Require().Equal(types.NewPost( + 1, + 2, + "External id", + "This is a new text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), stored) + + // Make sure the attachment it is generated properly + store := ctx.KVStore(suite.storeKey) + attachmentID := types.GetAttachmentIDFromBytes(store.Get(types.NextAttachmentIDStoreKey(1, 2))) + suite.Require().Equal(uint32(1), attachmentID) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.SavePost(ctx, tc.post) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_HasPost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + expResult bool + }{ + { + name: "non existing post returns false", + subspaceID: 1, + postID: 1, + expResult: false, + }, + { + name: "existing post returns true", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + subspaceID: 1, + postID: 2, + expResult: true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + result := suite.k.HasPost(ctx, tc.subspaceID, tc.postID) + suite.Require().Equal(tc.expResult, result) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_GetPost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + expFound bool + expPost types.Post + }{ + { + name: "non existing post returns false and empty post", + subspaceID: 1, + postID: 1, + expFound: false, + expPost: types.Post{}, + }, + { + name: "existing post returns correct value and true", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + subspaceID: 1, + postID: 2, + expFound: true, + expPost: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + post, found := suite.k.GetPost(ctx, tc.subspaceID, tc.postID) + suite.Require().Equal(tc.expFound, found) + suite.Require().Equal(tc.expPost, post) + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_DeletePost() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + postID uint64 + check func(ctx sdk.Context) + }{ + { + name: "non existing post is deleted properly", + subspaceID: 1, + postID: 1, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasPost(ctx, 1, 1)) + }, + }, + { + name: "existing post is deleted along with attachments", + store: func(ctx sdk.Context) { + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + + suite.k.SetNextAttachmentID(ctx, 1, 2, 2) + + suite.k.SaveAttachment(ctx, types.NewAttachment(1, 2, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ))) + }, + subspaceID: 1, + postID: 2, + check: func(ctx sdk.Context) { + suite.Require().False(suite.k.HasPost(ctx, 1, 2)) + suite.Require().False(suite.k.HasNextAttachmentID(ctx, 1, 2)) + suite.Require().False(suite.k.HasAttachment(ctx, 1, 2, 1)) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + suite.k.DeletePost(ctx, tc.subspaceID, tc.postID) + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/keeper/subspace_hooks_test.go b/x/posts/keeper/subspace_hooks_test.go new file mode 100644 index 0000000000..8636a2ea64 --- /dev/null +++ b/x/posts/keeper/subspace_hooks_test.go @@ -0,0 +1,127 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func (suite *KeeperTestsuite) TestKeeper_AfterSubspaceSaved() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + check func(ctx sdk.Context) + }{ + { + name: "post id is set properly", + subspaceID: 1, + check: func(ctx sdk.Context) { + stored, err := suite.k.GetNextPostID(ctx, 1) + suite.Require().NoError(err) + suite.Require().Equal(uint64(1), stored) + }, + }, + } + + // Set the subspaces hooks + suite.sk.SetHooks(suite.k.Hooks()) + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + // Call the method that should call the hook + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + tc.subspaceID, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + if tc.check != nil { + tc.check(ctx) + } + }) + } +} + +func (suite *KeeperTestsuite) TestKeeper_AfterSubspaceDeleted() { + testCases := []struct { + name string + store func(ctx sdk.Context) + subspaceID uint64 + check func(ctx sdk.Context) + }{ + { + name: "subspace data are deleted properly", + store: func(ctx sdk.Context) { + suite.k.SetNextPostID(ctx, 1, 1) + suite.k.SavePost(ctx, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SavePost(ctx, types.NewPost( + 1, + 2, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + }, + subspaceID: 1, + check: func(ctx sdk.Context) { + _, err := suite.k.GetNextPostID(ctx, 1) + suite.Require().Error(err) + + suite.Require().False(suite.k.HasPost(ctx, 1, 1)) + suite.Require().False(suite.k.HasPost(ctx, 1, 2)) + }, + }, + } + + // Set the subspaces hooks + suite.sk.SetHooks(suite.k.Hooks()) + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + ctx, _ := suite.ctx.CacheContext() + if tc.store != nil { + tc.store(ctx) + } + + // Call the method that should call the hook + suite.sk.DeleteSubspace(ctx, tc.subspaceID) + + if tc.check != nil { + tc.check(ctx) + } + }) + } +} diff --git a/x/posts/keeper/subspaces_hooks.go b/x/posts/keeper/subspaces_hooks.go new file mode 100644 index 0000000000..f054f8a581 --- /dev/null +++ b/x/posts/keeper/subspaces_hooks.go @@ -0,0 +1,56 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// Hooks represents a wrapper struct +type Hooks struct { + k Keeper +} + +var _ subspacestypes.SubspacesHooks = Hooks{} + +// Hooks creates new subspaces hooks +func (k Keeper) Hooks() Hooks { return Hooks{k} } + +// AfterSubspaceSaved implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceSaved(ctx sdk.Context, subspaceID uint64) { + // Create the initial post it + h.k.SetNextPostID(ctx, subspaceID, 1) +} + +// AfterSubspaceDeleted implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceDeleted(ctx sdk.Context, subspaceID uint64) { + // Delete the post id key + h.k.DeleteNextPostID(ctx, subspaceID) + + // Delete all the posts + h.k.IterateSubspacePosts(ctx, subspaceID, func(_ int64, post types.Post) (stop bool) { + h.k.DeletePost(ctx, post.SubspaceID, post.ID) + return false + }) +} + +// AfterSubspaceGroupSaved implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceGroupSaved(sdk.Context, uint64, uint32) {} + +// AfterSubspaceGroupMemberAdded implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceGroupMemberAdded(sdk.Context, uint64, uint32, sdk.AccAddress) {} + +// AfterSubspaceGroupMemberRemoved implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceGroupMemberRemoved(sdk.Context, uint64, uint32, sdk.AccAddress) {} + +// AfterSubspaceGroupDeleted implements subspacestypes.Hooks +func (h Hooks) AfterSubspaceGroupDeleted(sdk.Context, uint64, uint32) {} + +// AfterUserPermissionSet implements subspacestypes.Hooks +func (h Hooks) AfterUserPermissionSet(sdk.Context, uint64, sdk.AccAddress, subspacestypes.Permission) { +} + +// AfterUserPermissionRemoved implements subspacestypes.Hooks +func (h Hooks) AfterUserPermissionRemoved(sdk.Context, uint64, sdk.AccAddress) {} diff --git a/x/posts/legacy/v2/store.go b/x/posts/legacy/v2/store.go new file mode 100644 index 0000000000..b1beff80f9 --- /dev/null +++ b/x/posts/legacy/v2/store.go @@ -0,0 +1,26 @@ +package v2 + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +type SubspacesKeeper interface { + IterateSubspaces(ctx sdk.Context, fn func(index int64, subspaces subspacestypes.Subspace) (stop bool)) +} + +// MigrateStore performs in-place store migrations from v1 to v2 +// The only thing that is done here is setting up the next post id key for existing subspaces. +func MigrateStore(ctx sdk.Context, storeKey sdk.StoreKey, sk SubspacesKeeper) error { + store := ctx.KVStore(storeKey) + + // Set the next post id for all the subspaces + sk.IterateSubspaces(ctx, func(index int64, subspaces subspacestypes.Subspace) (stop bool) { + store.Set(types.NextPostIDStoreKey(subspaces.ID), types.GetPostIDBytes(1)) + return false + }) + + return nil +} diff --git a/x/posts/legacy/v2/store_test.go b/x/posts/legacy/v2/store_test.go new file mode 100644 index 0000000000..0577ba500a --- /dev/null +++ b/x/posts/legacy/v2/store_test.go @@ -0,0 +1,91 @@ +package v2_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/app" + "github.com/desmos-labs/desmos/v3/testutil" + v2 "github.com/desmos-labs/desmos/v3/x/posts/legacy/v2" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +func TestMigrateStore(t *testing.T) { + cdc, _ := app.MakeCodecs() + + // Build all the necessary keys + keys := sdk.NewKVStoreKeys(subspacestypes.StoreKey, types.StoreKey) + tKeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + + sk := subspaceskeeper.NewKeeper(cdc, keys[subspacestypes.StoreKey]) + + testCases := []struct { + name string + store func(ctx sdk.Context) + shouldErr bool + check func(ctx sdk.Context) + }{ + { + name: "next post id is set for existing subspaces", + store: func(ctx sdk.Context) { + sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "This is a test subspace", + "This is a test subspace", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 2, + "This is another test subspace", + "This is anoter test subspace", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + "cosmos1s0he0z3g92zwsxdj83h0ky9w463sx7gq9mqtgn", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + shouldErr: false, + check: func(ctx sdk.Context) { + store := ctx.KVStore(keys[types.StoreKey]) + + postID := types.GetPostIDFromBytes(store.Get(types.NextPostIDStoreKey(1))) + require.Equal(t, uint64(1), postID) + + postID = types.GetPostIDFromBytes(store.Get(types.NextPostIDStoreKey(2))) + require.Equal(t, uint64(1), postID) + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + ctx := testutil.BuildContext(keys, tKeys, memKeys) + if tc.store != nil { + tc.store(ctx) + } + + err := v2.MigrateStore(ctx, keys[types.StoreKey], sk) + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + if tc.check != nil { + tc.check(ctx) + } + } + }) + } +} diff --git a/x/posts/module.go b/x/posts/module.go new file mode 100644 index 0000000000..4f9d179e70 --- /dev/null +++ b/x/posts/module.go @@ -0,0 +1,217 @@ +package posts + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + + "github.com/desmos-labs/desmos/v3/x/posts/simulation" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + + feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" + + "github.com/desmos-labs/desmos/v3/x/posts/client/cli" + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" +) + +const ( + consensusVersion = 2 +) + +// type check to ensure the interface is properly implemented +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the posts module. +type AppModuleBasic struct { + cdc codec.Codec +} + +// Name returns the posts module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the posts module's types for the given codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the posts module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the posts module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var data types.GenesisState + if err := cdc.UnmarshalJSON(bz, &data); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + return types.ValidateGenesis(&data) +} + +// RegisterRESTRoutes registers the REST routes for the posts module. +func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the posts module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) +} + +// GetTxCmd returns the root tx command for the posts module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd returns the root query command for the posts module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// RegisterInterfaces registers interfaces and implementations of the posts module. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// AppModule implements an application module for the posts module. +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper + ak authkeeper.AccountKeeper + bk bankkeeper.Keeper + fk feeskeeper.Keeper + sk subspaceskeeper.Keeper +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + m := keeper.NewMigrator(am.keeper, am.sk) + err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2) + if err != nil { + panic(err) + } +} + +// NewAppModule creates a new AppModule Object +func NewAppModule( + cdc codec.Codec, keeper keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + ak: ak, + bk: bk, + fk: fk, + sk: sk, + } +} + +// Name returns the posts module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the module invariants +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + keeper.RegisterInvariants(ir, am.keeper) +} + +// Deprecated: Route returns the module's message router and handler. +func (am AppModule) Route() sdk.Route { + return sdk.Route{} +} + +// QuerierRoute returns the posts module's querier route name. +func (am AppModule) QuerierRoute() string { + return types.QuerierRoute +} + +// LegacyQuerierHandler returns the posts module sdk.Querier. +func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier { + return nil +} + +// InitGenesis performs genesis initialization for the posts module. +// It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + am.keeper.InitGenesis(ctx, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the +// posts module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule. +func (AppModule) ConsensusVersion() uint64 { + return consensusVersion +} + +// BeginBlock returns the begin blocker for the posts module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the posts module. It returns no validator +// updates. +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + EndBlocker(ctx, am.keeper) + return []abci.ValidatorUpdate{} +} + +// -------------------------------------------------------------------------------------------------------------------- + +// AppModuleSimulation defines the module simulation functions used by the posts module. +type AppModuleSimulation struct{} + +// GenerateGenesisState creates a randomized GenState of the bank module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizeGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { + return nil +} + +// RandomizedParams creates randomized posts param changes for the simulator. +func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder performs a no-op. +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.ModuleName] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the all the posts module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.keeper, am.sk, am.ak, am.bk, am.fk) +} diff --git a/x/posts/simulation/decoder.go b/x/posts/simulation/decoder.go new file mode 100644 index 0000000000..86e0571e30 --- /dev/null +++ b/x/posts/simulation/decoder.go @@ -0,0 +1,58 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +// NewDecodeStore returns a new decoder that unmarshals the KVPair's Value +// to the corresponding subspaces type +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.HasPrefix(kvA.Key, types.NextPostIDPrefix): + var idA, idB uint64 + idA = types.GetPostIDFromBytes(kvA.Value) + idB = types.GetPostIDFromBytes(kvB.Value) + return fmt.Sprintf("PostIDA: %d\nPostIDB: %d\n", idA, idB) + + case bytes.HasPrefix(kvA.Key, types.PostPrefix): + var postA, postB types.Post + cdc.MustUnmarshal(kvA.Value, &postA) + cdc.MustUnmarshal(kvB.Value, &postB) + return fmt.Sprintf("PostA: %s\nPostB: %s\n", &postA, &postB) + + case bytes.HasPrefix(kvA.Key, types.NextAttachmentIDPrefix): + var idA, idB uint32 + idA = types.GetAttachmentIDFromBytes(kvA.Value) + idB = types.GetAttachmentIDFromBytes(kvB.Value) + return fmt.Sprintf("AttachmentIDA: %d\nAttachmentIDB: %d\n", idA, idB) + + case bytes.HasPrefix(kvA.Key, types.AttachmentPrefix): + var attachmentA, attachmentB types.Attachment + cdc.MustUnmarshal(kvA.Value, &attachmentA) + cdc.MustUnmarshal(kvB.Value, &attachmentB) + return fmt.Sprintf("AttachmentA: %s\nAttachmentB: %s\n", &attachmentA, &attachmentB) + + case bytes.HasPrefix(kvA.Key, types.UserAnswerPrefix): + var answerA, answerB types.UserAnswer + cdc.MustUnmarshal(kvA.Value, &answerA) + cdc.MustUnmarshal(kvB.Value, &answerB) + return fmt.Sprintf("UserAnswerA: %s\nUserAnswerB: %s\n", &answerA, &answerB) + + case bytes.HasPrefix(kvA.Key, types.ActivePollQueuePrefix): + subspaceIDA, postIDA, pollIDA := types.GetPollIDFromBytes(kvA.Value) + subspaceIDB, postIDB, pollIDB := types.GetPollIDFromBytes(kvB.Value) + return fmt.Sprintf("SubspaceIDA: %d, PostIDA: %d, PollIDA: %d\nSubspaceIDB: %d, PostIDB: %d, PollIDB: %d\n", + subspaceIDA, postIDA, pollIDA, subspaceIDB, postIDB, pollIDB) + + default: + panic(fmt.Sprintf("unexpected %s key %X (%s)", types.ModuleName, kvA.Key, kvA.Key)) + } + } +} diff --git a/x/posts/simulation/decoder_test.go b/x/posts/simulation/decoder_test.go new file mode 100644 index 0000000000..7d8fb7d0a7 --- /dev/null +++ b/x/posts/simulation/decoder_test.go @@ -0,0 +1,112 @@ +package simulation_test + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/app" + "github.com/desmos-labs/desmos/v3/x/posts/simulation" + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func TestDecodeStore(t *testing.T) { + cdc, _ := app.MakeCodecs() + decoder := simulation.NewDecodeStore(cdc) + + post := types.NewPost( + 1, + 2, + "External id", + "This is a post text that does not contain any useful information", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ) + attachment := types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )) + userAnswer := types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos19r59nc7wfgc5gjnu5ga5yztkvr5qssj24krx2f") + + kvPairs := kv.Pairs{Pairs: []kv.Pair{ + { + Key: types.NextPostIDStoreKey(1), + Value: types.GetPostIDBytes(1), + }, + { + Key: types.PostStoreKey(1, 1), + Value: cdc.MustMarshal(&post), + }, + { + Key: types.NextAttachmentIDStoreKey(1, 1), + Value: types.GetAttachmentIDBytes(1), + }, + { + Key: types.AttachmentStoreKey(1, 1, 1), + Value: cdc.MustMarshal(&attachment), + }, + { + Key: types.PollAnswerStoreKey(1, 1, 1, "cosmos19r59nc7wfgc5gjnu5ga5yztkvr5qssj24krx2f"), + Value: cdc.MustMarshal(&userAnswer), + }, + { + Key: types.ActivePollQueueKey(1, 1, 1, time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)), + Value: types.GetPollIDBytes(1, 1, 1), + }, + { + Key: []byte("Unknown key"), + Value: nil, + }, + }} + + testCases := []struct { + name string + expectedLog string + }{ + {"Post ID", fmt.Sprintf("PostIDA: %d\nPostIDB: %d\n", + 1, 1)}, + {"Post", fmt.Sprintf("PostA: %s\nPostB: %s\n", + &post, &post)}, + {"Attachment ID", fmt.Sprintf("AttachmentIDA: %d\nAttachmentIDB: %d\n", + 1, 1)}, + {"Attachment", fmt.Sprintf("AttachmentA: %s\nAttachmentB: %s\n", + &attachment, &attachment)}, + {"User answer", fmt.Sprintf("UserAnswerA: %s\nUserAnswerB: %s\n", + &userAnswer, &userAnswer)}, + {"Active poll queue", fmt.Sprintf("SubspaceIDA: %d, PostIDA: %d, PollIDA: %d\nSubspaceIDB: %d, PostIDB: %d, PollIDB: %d\n", + 1, 1, 1, 1, 1, 1)}, + {"other", ""}, + } + + for i, tc := range testCases { + i, tc := i, tc + t.Run(tc.name, func(t *testing.T) { + switch i { + case len(testCases) - 1: + require.Panics(t, func() { decoder(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tc.name) + default: + require.Equal(t, tc.expectedLog, decoder(kvPairs.Pairs[i], kvPairs.Pairs[i]), tc.name) + } + }) + } +} diff --git a/x/posts/simulation/genesis.go b/x/posts/simulation/genesis.go new file mode 100644 index 0000000000..27947a6e88 --- /dev/null +++ b/x/posts/simulation/genesis.go @@ -0,0 +1,157 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspacessim "github.com/desmos-labs/desmos/v3/x/subspaces/simulation" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// RandomizeGenState generates a random GenesisState for posts +func RandomizeGenState(simState *module.SimulationState) { + // Read the subspaces data + subspacesGenesisBz := simState.GenState[subspacestypes.ModuleName] + var subspacesGenesis subspacestypes.GenesisState + simState.Cdc.MustUnmarshalJSON(subspacesGenesisBz, &subspacesGenesis) + + params := types.NewParams( + RandomMaxTextLength(simState.Rand), + ) + posts := randomPosts(simState.Rand, subspacesGenesis.Subspaces, simState.Accounts, params) + subspacesDataEntries := getSubspacesData(posts) + attachments := randomAttachments(simState.Rand, posts) + genesisPosts := getGenesisPosts(posts, attachments) + userAnswers := randomUserAnswers(simState.Rand, attachments, simState.Accounts) + + // Save the genesis + postsGenesis := types.NewGenesisState(subspacesDataEntries, genesisPosts, attachments, userAnswers, params) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(postsGenesis) +} + +// randomPosts returns randomly generated genesis posts +func randomPosts(r *rand.Rand, subspaces []subspacestypes.GenesisSubspace, accs []simtypes.Account, params types.Params) (posts []types.Post) { + postsNumber := uint64(r.Intn(100)) + posts = make([]types.Post, postsNumber) + for index := uint64(0); index < postsNumber; index++ { + subspace := subspacessim.RandomGenesisSubspace(r, subspaces) + posts[index] = GenerateRandomPost(r, accs, subspace.Subspace.ID, index+1, params) + } + return posts +} + +// getSubspacesData returns the +func getSubspacesData(posts []types.Post) (entries []types.SubspaceDataEntry) { + if len(posts) == 0 { + return nil + } + + subspacesMaxPostID := map[uint64]uint64{} + for _, post := range posts { + maxPostID, ok := subspacesMaxPostID[post.SubspaceID] + if !ok || post.ID > maxPostID { + subspacesMaxPostID[post.SubspaceID] = post.ID + } + } + + entries = make([]types.SubspaceDataEntry, len(subspacesMaxPostID)) + var i = 0 + for subspaceID, maxPostID := range subspacesMaxPostID { + entries[i] = types.NewSubspaceDataEntry(subspaceID, maxPostID+1) + i++ + } + + return entries +} + +// randomAttachments returns randomly generated attachments +func randomAttachments(r *rand.Rand, posts []types.Post) (attachments []types.Attachment) { + if len(posts) == 0 { + return nil + } + + attachmentsNumber := uint32(r.Intn(50)) + attachments = make([]types.Attachment, attachmentsNumber) + for index := uint32(0); index < attachmentsNumber; index++ { + post := RandomPost(r, posts) + attachments[index] = GenerateRandomAttachment(r, post, index+1) + } + return attachments +} + +// randomUserAnswers returns randomly generated user answers +func randomUserAnswers(r *rand.Rand, attachments []types.Attachment, accs []simtypes.Account) (answers []types.UserAnswer) { + if len(attachments) == 0 { + return nil + } + + // Get only the polls + var polls []types.Attachment + for _, attachment := range attachments { + if types.IsPoll(attachment) { + polls = append(polls, attachment) + } + } + + answersNumber := r.Intn(50) + for index := 0; index < answersNumber; index++ { + attachment := RandomAttachment(r, polls) + answersIndexes := RandomAnswersIndexes(r, attachment.Content.GetCachedValue().(*types.Poll)) + user, _ := simtypes.RandomAcc(r, accs) + answer := types.NewUserAnswer(attachment.SubspaceID, attachment.PostID, attachment.ID, answersIndexes, user.Address.String()) + + // Make sure there are no duplicated answers + if !containsAnswer(answers, answer) { + answers = append(answers, answer) + } + } + return answers +} + +// containsAnswer tells whether the given answers slice contains an answer from the same user of the given one +func containsAnswer(answers []types.UserAnswer, answer types.UserAnswer) bool { + for _, item := range answers { + if item.SubspaceID == answer.SubspaceID && item.PostID == answer.PostID && item.PollID == answer.PollID && item.User == answer.User { + return true + } + } + return false +} + +// getGenesisPosts uses the given posts and attachments to return a genesis posts slice +func getGenesisPosts(posts []types.Post, attachments []types.Attachment) (genesisPosts []types.GenesisPost) { + if len(posts) == 0 { + return nil + } + + type postReference struct { + SubspaceID uint64 + PostID uint64 + } + + // Get the max attachment id for each post that has an attachment + maxAttachmentIDs := map[postReference]uint32{} + for _, attachment := range attachments { + key := postReference{SubspaceID: attachment.SubspaceID, PostID: attachment.PostID} + maxAttachmentID, ok := maxAttachmentIDs[key] + if !ok || maxAttachmentID < attachment.ID { + maxAttachmentIDs[key] = attachment.ID + } + } + + genesisPosts = make([]types.GenesisPost, len(posts)) + for i, post := range posts { + key := postReference{SubspaceID: post.SubspaceID, PostID: post.ID} + maxAttachmentID, ok := maxAttachmentIDs[key] + if !ok { + maxAttachmentID = 0 + } + genesisPosts[i] = types.NewGenesisPost(maxAttachmentID+1, post) + } + return genesisPosts +} diff --git a/x/posts/simulation/operations.go b/x/posts/simulation/operations.go new file mode 100644 index 0000000000..ec7c453c4d --- /dev/null +++ b/x/posts/simulation/operations.go @@ -0,0 +1,108 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + + feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + "github.com/cosmos/cosmos-sdk/codec" + sim "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/desmos-labs/desmos/v3/app/params" + "github.com/desmos-labs/desmos/v3/x/posts/keeper" +) + +// Simulation operation weights constants +const ( + OpWeightMsgCreatePost = "op_weight_msg_create_post" + OpWeightMsgEditPost = "op_weight_msg_edit_post" + OpWeightMsgDeletePost = "op_weight_msg_delete_post" + OpWeightMsgAddPostAttachment = "op_weight_msg_add_post_attachment" + OpWeightMsgRemovePostAttachment = "op_weight_msg_remove_post_attachment" + OpWeightMsgAnswerPoll = "op_weight_msg_answer_poll" + + DefaultGasValue = 200000 +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONCodec, + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) sim.WeightedOperations { + var weightMsgCreatePost int + appParams.GetOrGenerate(cdc, OpWeightMsgCreatePost, &weightMsgCreatePost, nil, + func(_ *rand.Rand) { + weightMsgCreatePost = params.DefaultWeightMsgCreatePost + }, + ) + + var weightMsgEditPost int + appParams.GetOrGenerate(cdc, OpWeightMsgEditPost, &weightMsgEditPost, nil, + func(_ *rand.Rand) { + weightMsgEditPost = params.DefaultWeightMsgEditPost + }, + ) + + var weightMsgDeletePost int + appParams.GetOrGenerate(cdc, OpWeightMsgDeletePost, &weightMsgDeletePost, nil, + func(_ *rand.Rand) { + weightMsgDeletePost = params.DefaultWeightMsgDeletePost + }, + ) + + var weightMsgAddPostAttachment int + appParams.GetOrGenerate(cdc, OpWeightMsgAddPostAttachment, &weightMsgAddPostAttachment, nil, + func(_ *rand.Rand) { + weightMsgAddPostAttachment = params.DefaultWeightMsgAddPostAttachment + }, + ) + + var weightMsgRemovePostAttachment int + appParams.GetOrGenerate(cdc, OpWeightMsgRemovePostAttachment, &weightMsgRemovePostAttachment, nil, + func(r *rand.Rand) { + weightMsgRemovePostAttachment = params.DefaultWeightMsgRemovePostAttachment + }, + ) + + var weightMsgAnswerPoll int + appParams.GetOrGenerate(cdc, OpWeightMsgAnswerPoll, &weightMsgAnswerPoll, nil, + func(r *rand.Rand) { + weightMsgAnswerPoll = params.DefaultWeightMsgAnswerPoll + }, + ) + + return sim.WeightedOperations{ + sim.NewWeightedOperation( + weightMsgCreatePost, + SimulateMsgCreatePost(k, sk, ak, bk, fk), + ), + sim.NewWeightedOperation( + weightMsgEditPost, + SimulateMsgEditPost(k, sk, ak, bk, fk), + ), + sim.NewWeightedOperation( + weightMsgDeletePost, + SimulateMsgDeletePost(k, sk, ak, bk, fk), + ), + sim.NewWeightedOperation( + weightMsgAddPostAttachment, + SimulateMsgAddPostAttachment(k, ak, bk, fk), + ), + sim.NewWeightedOperation( + weightMsgRemovePostAttachment, + SimulateMsgRemovePostAttachment(k, sk, ak, bk, fk), + ), + sim.NewWeightedOperation( + weightMsgAnswerPoll, + SimulateMsgAnswerPoll(k, sk, ak, bk, fk), + ), + } +} diff --git a/x/posts/simulation/operations_attachments.go b/x/posts/simulation/operations_attachments.go new file mode 100644 index 0000000000..00239065b7 --- /dev/null +++ b/x/posts/simulation/operations_attachments.go @@ -0,0 +1,165 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + "github.com/desmos-labs/desmos/v3/testutil/simtesting" + feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + subspacessim "github.com/desmos-labs/desmos/v3/x/subspaces/simulation" +) + +// SimulateMsgAddPostAttachment tests and runs a single msg add post attachment +func SimulateMsgAddPostAttachment( + k keeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + subspaceID, postID, content, editor, skip := randomAddPostAttachmentFields(r, ctx, accs, k) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "add post attachment"), nil, nil + } + + msg := types.NewMsgAddPostAttachment(subspaceID, postID, content, editor.Address.String()) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{editor.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "add post attachment"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "add post attachment", nil), nil, nil + } +} + +// randomAddPostAttachmentFields returns the data needed to add an attachment to an existing post +func randomAddPostAttachmentFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, +) (subspaceID uint64, postID uint64, content types.AttachmentContent, editor simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a random post + posts := k.GetPosts(ctx) + if len(posts) == 0 { + // Skip because there are no posts + skip = true + return + } + post := RandomPost(r, posts) + subspaceID = post.SubspaceID + postID = post.ID + + // Get an editor + editorAdd, _ := sdk.AccAddressFromBech32(post.Author) + acc := subspacessim.GetAccount(editorAdd, accs) + if acc == nil { + // Skip because the author is not an account we have access to + skip = true + return + } + editor = *acc + + // Generate a random attachment content + content = GenerateRandomAttachmentContent(r) + + return subspaceID, postID, content, editor, false +} + +// -------------------------------------------------------------------------------------------------------------------- + +// SimulateMsgRemovePostAttachment tests and runs a single msg remove post attachment +func SimulateMsgRemovePostAttachment( + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + subspaceID, postID, attachmentID, editor, skip := randomRemovePostAttachmentFields(r, ctx, accs, k, sk) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "remove post attachment"), nil, nil + } + + msg := types.NewMsgRemovePostAttachment(subspaceID, postID, attachmentID, editor.Address.String()) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{editor.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "remove post attachment"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "remove post attachment", nil), nil, nil + } +} + +// randomRemovePostAttachmentFields returns the data needed to remove an attachment from an existing post +func randomRemovePostAttachmentFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, sk subspaceskeeper.Keeper, +) (subspaceID uint64, postID uint64, attachmentID uint32, editor simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a random post + posts := k.GetPosts(ctx) + if len(posts) == 0 { + // Skip because there are no posts + skip = true + return + } + post := RandomPost(r, posts) + subspaceID = post.SubspaceID + postID = post.ID + + // Get an editor + editorAddr, _ := sdk.AccAddressFromBech32(post.Author) + if r.Intn(101) < 50 { + // 50% of a moderator removing an attachment + moderators, _ := sk.GetUsersWithPermission(ctx, subspaceID, subspacestypes.PermissionModerateContent) + editorAddr = subspacessim.RandomAddress(r, moderators) + } else if !sk.HasPermission(ctx, subspaceID, editorAddr, subspacestypes.PermissionEditOwnContent) { + // Skip because the user has not the permission to edit their own content + skip = true + return + } + + acc := subspacessim.GetAccount(editorAddr, accs) + if acc == nil { + // Skip because the author is not an account we have access to + skip = true + return + } + editor = *acc + + // Get a random attachment + attachments := k.GetPostAttachments(ctx, subspaceID, postID) + if len(attachments) == 0 { + // Skip because the post has no attachment + skip = true + return + } + + attachment := RandomAttachment(r, attachments) + attachmentID = attachment.ID + + return subspaceID, postID, attachmentID, editor, false +} diff --git a/x/posts/simulation/operations_polls.go b/x/posts/simulation/operations_polls.go new file mode 100644 index 0000000000..a96d33d3c7 --- /dev/null +++ b/x/posts/simulation/operations_polls.go @@ -0,0 +1,88 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + "github.com/desmos-labs/desmos/v3/testutil/simtesting" + feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + subspacessim "github.com/desmos-labs/desmos/v3/x/subspaces/simulation" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// SimulateMsgAnswerPoll tests and runs a single msg answer poll post +func SimulateMsgAnswerPoll( + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + answer, user, skip := randomAnswerPollFields(r, ctx, accs, k, sk) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "answer poll"), nil, nil + } + + msg := types.NewMsgAnswerPoll(answer.SubspaceID, answer.PostID, answer.PollID, answer.AnswersIndexes, user.Address.String()) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{user.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "answer poll"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "answer poll", nil), nil, nil + } +} + +// randomAnswerPollFields returns the data needed to answer a user poll +func randomAnswerPollFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, sk subspaceskeeper.Keeper, +) (answer types.UserAnswer, user simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a poll + var polls []types.Attachment + k.IterateActivePollsQueue(ctx, time.Now(), func(index int64, poll types.Attachment) (stop bool) { + polls = append(polls, poll) + return false + }) + if len(polls) == 0 { + // Skip because there are no active polls + skip = true + return + } + + // Get a random poll + poll := RandomAttachment(r, polls) + + // Get a user + users, _ := sk.GetUsersWithPermission(ctx, poll.SubspaceID, subspacestypes.PermissionInteractWithContent) + acc := subspacessim.GetAccount(subspacessim.RandomAddress(r, users), accs) + if acc == nil { + // Skip the operation withofut error as the account is not valid + skip = true + return + } + user = *acc + + // Get some answers + answersIndexes := RandomAnswersIndexes(r, poll.Content.GetCachedValue().(*types.Poll)) + userAnswer := types.NewUserAnswer(poll.SubspaceID, poll.PostID, poll.ID, answersIndexes, user.Address.String()) + return userAnswer, user, false +} diff --git a/x/posts/simulation/operations_posts.go b/x/posts/simulation/operations_posts.go new file mode 100644 index 0000000000..223814967c --- /dev/null +++ b/x/posts/simulation/operations_posts.go @@ -0,0 +1,245 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + "github.com/desmos-labs/desmos/v3/testutil/simtesting" + feeskeeper "github.com/desmos-labs/desmos/v3/x/fees/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/keeper" + "github.com/desmos-labs/desmos/v3/x/posts/types" + subspaceskeeper "github.com/desmos-labs/desmos/v3/x/subspaces/keeper" + subspacessim "github.com/desmos-labs/desmos/v3/x/subspaces/simulation" + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// SimulateMsgCreatePost tests and runs a single msg create post +func SimulateMsgCreatePost( + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + data, author, skip := randomPostCreateFields(r, ctx, accs, k, sk) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "create post"), nil, nil + } + + msg := types.NewMsgCreatePost( + data.SubspaceID, + data.ExternalID, + data.Text, + data.ConversationID, + data.ReplySettings, + data.Entities, + nil, + data.ReferencedPosts, + author.Address.String(), + ) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{author.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "create post"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "create post", nil), nil, nil + } +} + +// randomPostCreateFields returns the data needed to create a post +func randomPostCreateFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, sk subspaceskeeper.Keeper, +) (post types.Post, author simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a subspace id + subspaces := sk.GetAllSubspaces(ctx) + if len(subspaces) == 0 { + // Skip because there are no subspaces + skip = true + return + } + subspace := subspacessim.RandomSubspace(r, subspaces) + + // Get an author + users, _ := sk.GetUsersWithPermission(ctx, subspace.ID, subspacestypes.PermissionWrite) + acc := subspacessim.GetAccount(subspacessim.RandomAddress(r, users), accs) + if acc == nil { + // Skip the operation without error as the account is not valid + skip = true + return + } + author = *acc + + postID, err := k.GetNextPostID(ctx, subspace.ID) + if err != nil { + panic(err) + } + + post = GenerateRandomPost(r, accs, subspace.ID, postID, k.GetParams(ctx)) + err = k.ValidatePost(ctx, post) + if err != nil { + // Skip the operation because the post is not valid (there are too many reasons why it might be) + skip = true + return + } + + return post, author, false +} + +// -------------------------------------------------------------------------------------------------------------------- + +// SimulateMsgEditPost tests and runs a single msg edit post +func SimulateMsgEditPost( + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + subspaceID, postID, data, editor, skip := randomPostEditFields(r, ctx, accs, k, sk) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "edit post"), nil, nil + } + + msg := types.NewMsgEditPost(subspaceID, postID, data.Text, data.Entities, editor.Address.String()) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{editor.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "edit post"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "edit post", nil), nil, nil + } +} + +// randomPostEditFields returns the data needed to edit a post +func randomPostEditFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, sk subspaceskeeper.Keeper, +) (subspaceID uint64, postID uint64, update types.PostUpdate, editor simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a random post + posts := k.GetPosts(ctx) + if len(posts) == 0 { + // Skip because there are no posts + skip = true + return + } + post := RandomPost(r, posts) + subspaceID = post.SubspaceID + postID = post.ID + + // Get the post author + authorAdr, _ := sdk.AccAddressFromBech32(post.Author) + authorAcc := subspacessim.GetAccount(authorAdr, accs) + if authorAcc == nil { + // Skip because the author is not an account we have access to + skip = true + return + } + editor = *authorAcc + + // Check the permissions + if !sk.HasPermission(ctx, subspaceID, authorAdr, subspacestypes.PermissionEditOwnContent) { + // Skip because the user has not the permissions + skip = true + return + } + + // Generate a random update + update = types.NewPostUpdate( + GenerateRandomText(r, k.GetParams(ctx).MaxTextLength), + nil, + time.Now(), + ) + return subspaceID, postID, update, editor, false +} + +// -------------------------------------------------------------------------------------------------------------------- + +// SimulateMsgDeletePost tests and runs a single msg delete post +func SimulateMsgDeletePost( + k keeper.Keeper, sk subspaceskeeper.Keeper, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper, fk feeskeeper.Keeper, +) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + + subspaceID, postID, editor, skip := randomPostDeleteFields(r, ctx, accs, k, sk) + if skip { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "delete post"), nil, nil + } + + msg := types.NewMsgDeletePost(subspaceID, postID, editor.Address.String()) + err = simtesting.SendMsg(r, app, ak, bk, fk, msg, ctx, chainID, DefaultGasValue, []cryptotypes.PrivKey{editor.PrivKey}) + if err != nil { + return simtypes.NoOpMsg(types.RouterKey, types.ModuleName, "delete post"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "delete post", nil), nil, nil + } +} + +// randomPostEditFields returns the data needed to delete a post +func randomPostDeleteFields( + r *rand.Rand, ctx sdk.Context, accs []simtypes.Account, k keeper.Keeper, sk subspaceskeeper.Keeper, +) (subspaceID uint64, postID uint64, user simtypes.Account, skip bool) { + if len(accs) == 0 { + // Skip because there are no accounts + skip = true + return + } + + // Get a random post + posts := k.GetPosts(ctx) + if len(posts) == 0 { + // Skip because there are no posts + skip = true + return + } + post := RandomPost(r, posts) + subspaceID = post.SubspaceID + postID = post.ID + + // Get the user + authorAdr, _ := sdk.AccAddressFromBech32(post.Author) + if r.Intn(101) < 50 { + // 50% of a moderator deleting the post + moderators, _ := sk.GetUsersWithPermission(ctx, subspaceID, subspacestypes.PermissionModerateContent) + authorAdr = subspacessim.RandomAddress(r, moderators) + } else if !sk.HasPermission(ctx, subspaceID, authorAdr, subspacestypes.PermissionEditOwnContent) { + // Skip because the user has not the permissions + skip = true + return + } + + userAcc := subspacessim.GetAccount(authorAdr, accs) + if userAcc == nil { + // Skip because the author is not an account we have access to + skip = true + return + } + user = *userAcc + + return subspaceID, postID, user, false +} diff --git a/x/posts/simulation/params.go b/x/posts/simulation/params.go new file mode 100644 index 0000000000..228d49d2ae --- /dev/null +++ b/x/posts/simulation/params.go @@ -0,0 +1,25 @@ +package simulation + +// DONTCOVER + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/x/simulation" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func ParamChanges(r *rand.Rand) []simtypes.ParamChange { + return []simtypes.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, string(types.MaxTextLengthKey), + func(r *rand.Rand) string { + maxTextLength := RandomMaxTextLength(r) + return fmt.Sprintf(`%d`, maxTextLength) + }, + ), + } +} diff --git a/x/posts/simulation/utils.go b/x/posts/simulation/utils.go new file mode 100644 index 0000000000..747c67da68 --- /dev/null +++ b/x/posts/simulation/utils.go @@ -0,0 +1,147 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + "time" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +var ( + mediaTypes = []string{ + "image/png", + "image/jpeg", + "image/gif", + "video/mp4", + } + allowedReplySettings = []types.ReplySetting{ + types.REPLY_SETTING_EVERYONE, + types.REPLY_SETTING_MENTIONS, + types.REPLY_SETTING_FOLLOWERS, + types.REPLY_SETTING_MUTUAL, + } +) + +// GenerateRandomPost generates a random post +func GenerateRandomPost(r *rand.Rand, accounts []simtypes.Account, subspaceID uint64, postID uint64, params types.Params) types.Post { + author, _ := simtypes.RandomAcc(r, accounts) + return types.NewPost( + subspaceID, + postID, + GenerateRandomText(r, 20), + GenerateRandomText(r, params.MaxTextLength), + author.Address.String(), + 0, + nil, + nil, + RandomReplySettings(r), + time.Now(), + nil, + ) +} + +// GenerateRandomText returns a random text that does not exceed the given max length +func GenerateRandomText(r *rand.Rand, maxLength uint32) string { + return simtypes.RandStringOfLength(r, int(maxLength)) +} + +// RandomReplySettings returns a random valid ReplySetting for the post +func RandomReplySettings(r *rand.Rand) types.ReplySetting { + return allowedReplySettings[r.Intn(len(allowedReplySettings))] +} + +// RandomPost returns a random post from the slice given +func RandomPost(r *rand.Rand, posts []types.Post) types.Post { + return posts[r.Intn(len(posts))] +} + +// GenerateRandomAttachment generates a random attachment +func GenerateRandomAttachment(r *rand.Rand, post types.Post, id uint32) types.Attachment { + return types.NewAttachment( + post.SubspaceID, + post.ID, + id, + GenerateRandomAttachmentContent(r), + ) +} + +// GenerateRandomAttachmentContent returns a randomly generated attachment content +func GenerateRandomAttachmentContent(r *rand.Rand) types.AttachmentContent { + // 50% of being a poll + if r.Intn(101) < 50 { + return GenerateRandomPoll(r) + } + + return GenerateRandomMedia(r) +} + +// GenerateRandomMedia returns a randomly generated media content +func GenerateRandomMedia(r *rand.Rand) *types.Media { + return types.NewMedia( + GenerateRandomText(r, 50), + mediaTypes[r.Intn(len(mediaTypes))], + ) +} + +// GenerateRandomPoll returns a randomly generated poll content +func GenerateRandomPoll(r *rand.Rand) *types.Poll { + answersNumber := r.Intn(5) + 2 // At least 2 answers are required to make sense + answers := make([]types.Poll_ProvidedAnswer, answersNumber) + for index := 0; index < answersNumber; index++ { + answers[index] = types.NewProvidedAnswer(GenerateRandomText(r, 10), nil) + } + + // 50% of accepting multiple answer edits + acceptsMultipleAnswers := r.Intn(101) < 50 + + // 50% of allowing answers edits + allowAnswerEdits := r.Intn(101) < 50 + + return types.NewPoll( + GenerateRandomText(r, 30), + answers, + time.Now().Add(30*24*time.Hour), + acceptsMultipleAnswers, + allowAnswerEdits, + nil, + ) +} + +// RandomAttachment returns a random attachment from the ones given +func RandomAttachment(r *rand.Rand, attachments []types.Attachment) types.Attachment { + return attachments[r.Intn(len(attachments))] +} + +// RandomAnswersIndexes returns a random answers indexes slice based on the given poll +func RandomAnswersIndexes(r *rand.Rand, poll *types.Poll) (answersIndexes []uint32) { + maxAnswersNumber := 1 + if poll.AllowsMultipleAnswers { + maxAnswersNumber = r.Intn(len(poll.ProvidedAnswers)) + 1 + } + + // Generate some answer indexes + indexes := make([]uint32, maxAnswersNumber) + for index := 0; index < maxAnswersNumber; index++ { + indexes[index] = uint32(r.Intn(len(poll.ProvidedAnswers))) + } + + // Eliminate duplicated generated indexes + uniqueIndexes := map[uint32]bool{} + for _, index := range indexes { + if _, inserted := uniqueIndexes[index]; !inserted { + answersIndexes = append(answersIndexes, index) + } + uniqueIndexes[index] = true + } + + return answersIndexes +} + +// RandomMaxTextLength returns a random max text length in the [30, 999] range +func RandomMaxTextLength(r *rand.Rand) uint32 { + return uint32(r.Intn(950)) + 30 +} diff --git a/x/posts/types/codec.go b/x/posts/types/codec.go new file mode 100644 index 0000000000..874f97d0c3 --- /dev/null +++ b/x/posts/types/codec.go @@ -0,0 +1,61 @@ +package types + +// DONTCOVER + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + cdc.RegisterInterface((*AttachmentContent)(nil), nil) + cdc.RegisterConcrete(&Poll{}, "desmos/Poll", nil) + cdc.RegisterConcrete(&Media{}, "desmos/Media", nil) + + cdc.RegisterConcrete(MsgCreatePost{}, "desmos/MsgCreatePost", nil) + cdc.RegisterConcrete(MsgEditPost{}, "desmos/MsgEditPost", nil) + cdc.RegisterConcrete(MsgAddPostAttachment{}, "desmos/MsgAddPostAttachment", nil) + cdc.RegisterConcrete(MsgRemovePostAttachment{}, "desmos/MsgRemovePostAttachment", nil) + cdc.RegisterConcrete(MsgDeletePost{}, "desmos/MsgDeletePost", nil) + cdc.RegisterConcrete(MsgAnswerPoll{}, "desmos/MsgAnswerPoll", nil) +} + +func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterInterface( + "desmos.posts.v1.AttachmentContent", + (*AttachmentContent)(nil), + &Poll{}, + &Media{}, + ) + + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgCreatePost{}, + &MsgEditPost{}, + &MsgAddPostAttachment{}, + &MsgRemovePostAttachment{}, + &MsgDeletePost{}, + &MsgAnswerPoll{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + + // AminoCodec references the global x/posts module codec. Note, the codec should + // ONLY be used in certain instances of tests and for JSON encoding as Amino is + // still used for that purpose. + // + // The actual codec used for serialization should be provided to x/posts and + // defined at the application level. + AminoCodec = codec.NewAminoCodec(amino) +) + +func init() { + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) +} diff --git a/x/posts/types/errors.go b/x/posts/types/errors.go new file mode 100644 index 0000000000..2fca7ea4ff --- /dev/null +++ b/x/posts/types/errors.go @@ -0,0 +1,10 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + ErrInvalidGenesis = sdkerrors.Register(ModuleName, 1, "invalid genesis state") + ErrInvalidPost = sdkerrors.Register(ModuleName, 2, "invalid post") +) diff --git a/x/posts/types/events.go b/x/posts/types/events.go new file mode 100644 index 0000000000..db794be213 --- /dev/null +++ b/x/posts/types/events.go @@ -0,0 +1,21 @@ +package types + +// Posts module event types +const ( + EventTypeCreatePost = "create_post" + EventTypeEditPost = "edit_post" + EventTypeDeletePost = "delete_post" + EventTypeAddPostAttachment = "add_post_attachment" + EventTypeRemovePostAttachment = "remove_post_attachment" + EventTypeAnswerPoll = "answer_poll" + EventTypeTallyPoll = "tally_poll" + + AttributeValueCategory = ModuleName + AttributeKeySubspaceID = "subspace_id" + AttributeKeyPostID = "post_id" + AttributeKeyAuthor = "author" + AttributeKeyCreationTime = "creation_date" + AttributeKeyLastEditTime = "last_edit_date" + AttributeKeyAttachmentID = "attachment_id" + AttributeKeyPollID = "poll_id" +) diff --git a/x/posts/types/expected_keepers.go b/x/posts/types/expected_keepers.go new file mode 100644 index 0000000000..a8eb1ea5a2 --- /dev/null +++ b/x/posts/types/expected_keepers.go @@ -0,0 +1,28 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + subspacestypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// SubspacesKeeper represents a keeper that deals with subspaces +type SubspacesKeeper interface { + // HasSubspace tells whether the subspace with the given id exists or not + HasSubspace(ctx sdk.Context, subspaceID uint64) bool + + // HasPermission tells whether the given user has the provided permission inside the subspace with the specified id + HasPermission(ctx sdk.Context, subspaceID uint64, user sdk.AccAddress, permission subspacestypes.Permission) bool + + // IterateSubspaces iterates through the subspaces set and performs the given function + IterateSubspaces(ctx sdk.Context, fn func(index int64, subspace subspacestypes.Subspace) (stop bool)) +} + +// RelationshipsKeeper represents a keeper that deals with relationships +type RelationshipsKeeper interface { + // HasUserBlocked tells whether the given blocker has blocked the user inside the provided subspace + HasUserBlocked(ctx sdk.Context, blocker, user string, subspaceID uint64) bool + + // HasRelationship tells whether the relationship between the user and counterparty exists for the given subspace + HasRelationship(ctx sdk.Context, user, counterparty string, subspaceID uint64) bool +} diff --git a/x/posts/types/genesis.go b/x/posts/types/genesis.go new file mode 100644 index 0000000000..4c78cd441a --- /dev/null +++ b/x/posts/types/genesis.go @@ -0,0 +1,199 @@ +package types + +import "fmt" + +// NewSubspaceDataEntry returns a new SubspaceDataEntry instance +func NewSubspaceDataEntry(subspaceID uint64, initialPostID uint64) SubspaceDataEntry { + return SubspaceDataEntry{ + SubspaceID: subspaceID, + InitialPostID: initialPostID, + } +} + +// Validate returns an error if something is wrong within the entry data +func (e SubspaceDataEntry) Validate() error { + if e.SubspaceID == 0 { + return fmt.Errorf("invalid subspace id: %d", e.SubspaceID) + } + + if e.InitialPostID == 0 { + return fmt.Errorf("invalid initial post id: %d", e.InitialPostID) + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewGenesisPost returns a new GenesisPost instance +func NewGenesisPost(initialAttachmentID uint32, post Post) GenesisPost { + return GenesisPost{ + Post: post, + InitialAttachmentID: initialAttachmentID, + } +} + +// Validate returns an error if something is wrong within the entry data +func (p GenesisPost) Validate() error { + if p.InitialAttachmentID == 0 { + return fmt.Errorf("invalid initial attachment id: %d", p.InitialAttachmentID) + } + + return p.Post.Validate() +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewGenesisState returns a new GenesisState instance +func NewGenesisState( + subspacesData []SubspaceDataEntry, + posts []GenesisPost, + attachments []Attachment, + userAnswers []UserAnswer, + params Params, +) *GenesisState { + return &GenesisState{ + SubspacesData: subspacesData, + GenesisPosts: posts, + Attachments: attachments, + UserAnswers: userAnswers, + Params: params, + } +} + +// DefaultGenesisState returns a default GenesisState +func DefaultGenesisState() *GenesisState { + return NewGenesisState(nil, nil, nil, nil, DefaultParams()) +} + +// getInitialPostID returns the initial post id for the given subspace, 0 if not found +func (e *GenesisState) getInitialPostID(subspaceID uint64) uint64 { + for _, entry := range e.SubspacesData { + if entry.SubspaceID == subspaceID { + return entry.InitialPostID + } + } + return 0 +} + +// getInitialAttachmentID returns the initial attachment id for the given post +func (e *GenesisState) getInitialAttachmentID(subspaceID uint64, postID uint64) uint32 { + for _, post := range e.GenesisPosts { + if post.SubspaceID == subspaceID && post.ID == postID { + return post.InitialAttachmentID + } + } + return 0 +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ValidateGenesis validates the given genesis state and returns an error if something is invalid +func ValidateGenesis(data *GenesisState) error { + for _, entry := range data.SubspacesData { + if containsDuplicatedSubspaceDataEntry(data.SubspacesData, entry) { + return fmt.Errorf("duplicated subspace data entry: %d", entry.SubspaceID) + } + + err := entry.Validate() + if err != nil { + return err + } + } + + for _, post := range data.GenesisPosts { + if containsDuplicatedPost(data.GenesisPosts, post) { + return fmt.Errorf("duplicated post: subspace id %d, post id %d", post.Post.SubspaceID, post.Post.ID) + } + + initialPostID := data.getInitialPostID(post.SubspaceID) + if post.ID >= initialPostID { + return fmt.Errorf("post id must be lower than initial post id: subspace id %d", post.SubspaceID) + } + + err := post.Validate() + if err != nil { + return err + } + } + + for _, attachment := range data.Attachments { + if containsDuplicatedAttachment(data.Attachments, attachment) { + return fmt.Errorf("duplicated attachment: subspace id %d, post id %d, attachment id %d", + attachment.SubspaceID, attachment.PostID, attachment.ID) + } + + initialAttachmentID := data.getInitialAttachmentID(attachment.SubspaceID, attachment.PostID) + if attachment.ID >= initialAttachmentID { + return fmt.Errorf("attachment id must be lower than initial attachment id: subspace id %d, post id %d", + attachment.SubspaceID, attachment.PostID) + } + + err := attachment.Validate() + if err != nil { + return err + } + } + + for _, answer := range data.UserAnswers { + if containsDuplicatedAnswer(data.UserAnswers, answer) { + return fmt.Errorf("duplicated user answer: subspace id %d, post id %d, poll id %d, user: %s", + answer.SubspaceID, answer.PostID, answer.PollID, answer.User) + } + + err := answer.Validate() + if err != nil { + return err + } + } + + return data.Params.Validate() +} + +// containsDuplicatedSubspaceDataEntry tells whether the given entries slice contains +// two or more entries for the same subspace +func containsDuplicatedSubspaceDataEntry(entries []SubspaceDataEntry, entry SubspaceDataEntry) bool { + var count = 0 + for _, s := range entries { + if s.SubspaceID == entry.SubspaceID { + count++ + } + } + return count > 1 +} + +// containsDuplicatedPost tells whether the given posts slice contains two or more posts +// having the same id of the given one +func containsDuplicatedPost(posts []GenesisPost, post GenesisPost) bool { + var count = 0 + for _, s := range posts { + if s.Post.SubspaceID == post.SubspaceID && s.ID == post.ID { + count++ + } + } + return count > 1 +} + +// containsDuplicatedAttachment tells whether the given attachments slice contains two or more attachments +// having the same id of the given one +func containsDuplicatedAttachment(attachments []Attachment, attachment Attachment) bool { + var count = 0 + for _, s := range attachments { + if s.SubspaceID == attachment.SubspaceID && s.PostID == attachment.PostID && s.ID == attachment.ID { + count++ + } + } + return count > 1 +} + +// containsDuplicatedAnswer tells whether the given user answers slice contains two or more answers +// by the same user as the given one +func containsDuplicatedAnswer(answers []UserAnswer, answer UserAnswer) bool { + var count = 0 + for _, s := range answers { + if s.SubspaceID == answer.SubspaceID && s.PostID == answer.PostID && s.PollID == answer.PollID && s.User == answer.User { + count++ + } + } + return count > 1 +} diff --git a/x/posts/types/genesis.pb.go b/x/posts/types/genesis.pb.go new file mode 100644 index 0000000000..686231679d --- /dev/null +++ b/x/posts/types/genesis.pb.go @@ -0,0 +1,1087 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: desmos/posts/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + 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 + +// GenesisState contains the data of the genesis state for the posts module +type GenesisState struct { + SubspacesData []SubspaceDataEntry `protobuf:"bytes,1,rep,name=subspaces_data,json=subspacesData,proto3" json:"subspaces_data"` + GenesisPosts []GenesisPost `protobuf:"bytes,2,rep,name=genesis_posts,json=genesisPosts,proto3" json:"genesis_posts"` + Attachments []Attachment `protobuf:"bytes,3,rep,name=attachments,proto3" json:"attachments"` + UserAnswers []UserAnswer `protobuf:"bytes,4,rep,name=user_answers,json=userAnswers,proto3" json:"user_answers"` + Params Params `protobuf:"bytes,5,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_26b7acf2775f2913, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.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 *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetSubspacesData() []SubspaceDataEntry { + if m != nil { + return m.SubspacesData + } + return nil +} + +func (m *GenesisState) GetGenesisPosts() []GenesisPost { + if m != nil { + return m.GenesisPosts + } + return nil +} + +func (m *GenesisState) GetAttachments() []Attachment { + if m != nil { + return m.Attachments + } + return nil +} + +func (m *GenesisState) GetUserAnswers() []UserAnswer { + if m != nil { + return m.UserAnswers + } + return nil +} + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// SubspaceDataEntry contains the data for a given subspace +type SubspaceDataEntry struct { + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + InitialPostID uint64 `protobuf:"varint,2,opt,name=initial_post_id,json=initialPostId,proto3" json:"initial_post_id,omitempty"` +} + +func (m *SubspaceDataEntry) Reset() { *m = SubspaceDataEntry{} } +func (m *SubspaceDataEntry) String() string { return proto.CompactTextString(m) } +func (*SubspaceDataEntry) ProtoMessage() {} +func (*SubspaceDataEntry) Descriptor() ([]byte, []int) { + return fileDescriptor_26b7acf2775f2913, []int{1} +} +func (m *SubspaceDataEntry) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SubspaceDataEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SubspaceDataEntry.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 *SubspaceDataEntry) XXX_Merge(src proto.Message) { + xxx_messageInfo_SubspaceDataEntry.Merge(m, src) +} +func (m *SubspaceDataEntry) XXX_Size() int { + return m.Size() +} +func (m *SubspaceDataEntry) XXX_DiscardUnknown() { + xxx_messageInfo_SubspaceDataEntry.DiscardUnknown(m) +} + +var xxx_messageInfo_SubspaceDataEntry proto.InternalMessageInfo + +func (m *SubspaceDataEntry) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *SubspaceDataEntry) GetInitialPostID() uint64 { + if m != nil { + return m.InitialPostID + } + return 0 +} + +// GenesisPost contains the genesis data for a single post +type GenesisPost struct { + Post `protobuf:"bytes,1,opt,name=post,proto3,embedded=post" json:"post"` + InitialAttachmentID uint32 `protobuf:"varint,2,opt,name=initial_attachment_id,json=initialAttachmentId,proto3" json:"initial_attachment_id,omitempty"` +} + +func (m *GenesisPost) Reset() { *m = GenesisPost{} } +func (m *GenesisPost) String() string { return proto.CompactTextString(m) } +func (*GenesisPost) ProtoMessage() {} +func (*GenesisPost) Descriptor() ([]byte, []int) { + return fileDescriptor_26b7acf2775f2913, []int{2} +} +func (m *GenesisPost) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisPost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisPost.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 *GenesisPost) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisPost.Merge(m, src) +} +func (m *GenesisPost) XXX_Size() int { + return m.Size() +} +func (m *GenesisPost) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisPost.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisPost proto.InternalMessageInfo + +func (m *GenesisPost) GetInitialAttachmentID() uint32 { + if m != nil { + return m.InitialAttachmentID + } + return 0 +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "desmos.posts.v1.GenesisState") + proto.RegisterType((*SubspaceDataEntry)(nil), "desmos.posts.v1.SubspaceDataEntry") + proto.RegisterType((*GenesisPost)(nil), "desmos.posts.v1.GenesisPost") +} + +func init() { proto.RegisterFile("desmos/posts/v1/genesis.proto", fileDescriptor_26b7acf2775f2913) } + +var fileDescriptor_26b7acf2775f2913 = []byte{ + // 479 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0x3f, 0x6f, 0xd3, 0x40, + 0x18, 0xc6, 0x73, 0x69, 0xa8, 0xaa, 0x73, 0xdc, 0xaa, 0x57, 0xaa, 0x5a, 0xa5, 0xd8, 0x51, 0xa6, + 0x2c, 0xd8, 0x6a, 0x22, 0x06, 0xd8, 0x1a, 0x8c, 0x8a, 0xc5, 0x40, 0xe5, 0x8a, 0x85, 0x25, 0xba, + 0xc4, 0x27, 0xd7, 0x52, 0xfc, 0x47, 0x7e, 0x2f, 0x81, 0x7e, 0x04, 0x36, 0x46, 0xd8, 0xfa, 0x71, + 0x3a, 0x66, 0x64, 0xb2, 0x90, 0xb3, 0xf0, 0x01, 0xf8, 0x00, 0xc8, 0xe7, 0x73, 0x62, 0xc5, 0xea, + 0x66, 0xdf, 0xfb, 0x3c, 0xbf, 0x7b, 0xee, 0xb9, 0xc3, 0x2f, 0x3d, 0x06, 0x61, 0x0c, 0x56, 0x12, + 0x03, 0x07, 0x6b, 0x79, 0x69, 0xf9, 0x2c, 0x62, 0x10, 0x80, 0x99, 0xa4, 0x31, 0x8f, 0xc9, 0x51, + 0x39, 0x36, 0xc5, 0xd8, 0x5c, 0x5e, 0x9e, 0x3f, 0xf7, 0x63, 0x3f, 0x16, 0x33, 0xab, 0xf8, 0x2a, + 0x65, 0xe7, 0x17, 0xbb, 0x94, 0x30, 0xf6, 0xd8, 0x5c, 0x42, 0xfa, 0xff, 0xda, 0xb8, 0x7b, 0x5d, + 0x62, 0x6f, 0x39, 0xe5, 0x8c, 0x7c, 0xc2, 0x87, 0xb0, 0x98, 0x42, 0x42, 0x67, 0x0c, 0x26, 0x1e, + 0xe5, 0x54, 0x43, 0xbd, 0xbd, 0x81, 0x32, 0xec, 0x9b, 0x3b, 0xdb, 0x99, 0xb7, 0x52, 0x66, 0x53, + 0x4e, 0xdf, 0x47, 0x3c, 0xbd, 0x1f, 0x77, 0x1e, 0x33, 0xa3, 0xe5, 0xaa, 0x1b, 0x7f, 0x31, 0x21, + 0xd7, 0x58, 0x95, 0xb9, 0x27, 0xc2, 0xaa, 0xb5, 0x05, 0xef, 0xa2, 0xc1, 0x93, 0x31, 0x6e, 0x62, + 0xe0, 0x92, 0xd4, 0xf5, 0xb7, 0x4b, 0x40, 0xde, 0x61, 0x85, 0x72, 0x4e, 0x67, 0x77, 0x21, 0x8b, + 0x38, 0x68, 0x7b, 0x02, 0xf3, 0xa2, 0x81, 0xb9, 0xda, 0x68, 0x24, 0xa5, 0xee, 0x22, 0x36, 0xee, + 0x2e, 0x80, 0xa5, 0x13, 0x1a, 0xc1, 0x57, 0x96, 0x82, 0xd6, 0x79, 0x82, 0xf2, 0x19, 0x58, 0x7a, + 0x25, 0x34, 0x15, 0x65, 0xb1, 0x59, 0x01, 0xf2, 0x1a, 0xef, 0x27, 0x34, 0xa5, 0x21, 0x68, 0xcf, + 0x7a, 0x68, 0xa0, 0x0c, 0xcf, 0x1a, 0xfe, 0x1b, 0x31, 0x96, 0x5e, 0x29, 0x7e, 0x7b, 0xf0, 0xf3, + 0xc1, 0x40, 0x7f, 0x1f, 0x0c, 0xd4, 0xff, 0x8e, 0xf0, 0x71, 0xa3, 0x3f, 0x62, 0x61, 0xa5, 0xea, + 0x6e, 0x12, 0x78, 0x1a, 0xea, 0xa1, 0x41, 0x67, 0x7c, 0x98, 0x67, 0x06, 0xae, 0xb4, 0x8e, 0xed, + 0xe2, 0x4a, 0xe2, 0x78, 0xe4, 0x0d, 0x3e, 0x0a, 0xa2, 0x80, 0x07, 0x74, 0x2e, 0xba, 0x2d, 0x4c, + 0x6d, 0x61, 0x3a, 0xce, 0x33, 0x43, 0x75, 0xca, 0x51, 0xd1, 0x9e, 0x63, 0xbb, 0x6a, 0x50, 0xfb, + 0xf5, 0x6a, 0x59, 0x7e, 0x21, 0xac, 0xd4, 0xba, 0x27, 0x23, 0xdc, 0x29, 0x60, 0x62, 0x7b, 0x65, + 0x78, 0xda, 0x3c, 0x5a, 0x71, 0x41, 0x07, 0xc5, 0xc1, 0x56, 0x99, 0x81, 0x5c, 0x21, 0x26, 0x1f, + 0xf1, 0x69, 0x95, 0x64, 0x5b, 0x77, 0x95, 0x47, 0x1d, 0x9f, 0xe5, 0x99, 0x71, 0x22, 0xf3, 0x6c, + 0x2f, 0xc8, 0xb1, 0xdd, 0x93, 0xa0, 0xb1, 0x58, 0xcb, 0x36, 0xfe, 0xf0, 0x98, 0xeb, 0x68, 0x95, + 0xeb, 0xe8, 0x4f, 0xae, 0xa3, 0x1f, 0x6b, 0xbd, 0xb5, 0x5a, 0xeb, 0xad, 0xdf, 0x6b, 0xbd, 0xf5, + 0xc5, 0xf4, 0x03, 0x7e, 0xb7, 0x98, 0x9a, 0xb3, 0x38, 0xb4, 0xca, 0x84, 0xaf, 0xe6, 0x74, 0x0a, + 0xf2, 0xdb, 0x5a, 0x8e, 0xac, 0x6f, 0xf2, 0xc9, 0xf3, 0xfb, 0x84, 0xc1, 0x74, 0x5f, 0xbc, 0xf7, + 0xd1, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x1e, 0x31, 0xf0, 0x55, 0x03, 0x00, 0x00, +} + +func (this *GenesisState) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*GenesisState) + if !ok { + that2, ok := that.(GenesisState) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.SubspacesData) != len(that1.SubspacesData) { + return false + } + for i := range this.SubspacesData { + if !this.SubspacesData[i].Equal(&that1.SubspacesData[i]) { + return false + } + } + if len(this.GenesisPosts) != len(that1.GenesisPosts) { + return false + } + for i := range this.GenesisPosts { + if !this.GenesisPosts[i].Equal(&that1.GenesisPosts[i]) { + return false + } + } + if len(this.Attachments) != len(that1.Attachments) { + return false + } + for i := range this.Attachments { + if !this.Attachments[i].Equal(&that1.Attachments[i]) { + return false + } + } + if len(this.UserAnswers) != len(that1.UserAnswers) { + return false + } + for i := range this.UserAnswers { + if !this.UserAnswers[i].Equal(&that1.UserAnswers[i]) { + return false + } + } + if !this.Params.Equal(&that1.Params) { + return false + } + return true +} +func (this *SubspaceDataEntry) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*SubspaceDataEntry) + if !ok { + that2, ok := that.(SubspaceDataEntry) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.SubspaceID != that1.SubspaceID { + return false + } + if this.InitialPostID != that1.InitialPostID { + return false + } + return true +} +func (this *GenesisPost) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*GenesisPost) + if !ok { + that2, ok := that.(GenesisPost) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.Post.Equal(&that1.Post) { + return false + } + if this.InitialAttachmentID != that1.InitialAttachmentID { + return false + } + return true +} +func (m *GenesisState) 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 *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.UserAnswers) > 0 { + for iNdEx := len(m.UserAnswers) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.UserAnswers[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Attachments) > 0 { + for iNdEx := len(m.Attachments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attachments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.GenesisPosts) > 0 { + for iNdEx := len(m.GenesisPosts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.GenesisPosts[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.SubspacesData) > 0 { + for iNdEx := len(m.SubspacesData) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SubspacesData[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *SubspaceDataEntry) 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 *SubspaceDataEntry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SubspaceDataEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.InitialPostID != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.InitialPostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *GenesisPost) 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 *GenesisPost) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisPost) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.InitialAttachmentID != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.InitialAttachmentID)) + i-- + dAtA[i] = 0x10 + } + { + size, err := m.Post.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.SubspacesData) > 0 { + for _, e := range m.SubspacesData { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.GenesisPosts) > 0 { + for _, e := range m.GenesisPosts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Attachments) > 0 { + for _, e := range m.Attachments { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.UserAnswers) > 0 { + for _, e := range m.UserAnswers { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *SubspaceDataEntry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovGenesis(uint64(m.SubspaceID)) + } + if m.InitialPostID != 0 { + n += 1 + sovGenesis(uint64(m.InitialPostID)) + } + return n +} + +func (m *GenesisPost) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Post.Size() + n += 1 + l + sovGenesis(uint64(l)) + if m.InitialAttachmentID != 0 { + n += 1 + sovGenesis(uint64(m.InitialAttachmentID)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) 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 ErrIntOverflowGenesis + } + 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: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspacesData", 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.SubspacesData = append(m.SubspacesData, SubspaceDataEntry{}) + if err := m.SubspacesData[len(m.SubspacesData)-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 GenesisPosts", 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.GenesisPosts = append(m.GenesisPosts, GenesisPost{}) + if err := m.GenesisPosts[len(m.GenesisPosts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attachments", 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.Attachments = append(m.Attachments, Attachment{}) + if err := m.Attachments[len(m.Attachments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserAnswers", 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.UserAnswers = append(m.UserAnswers, UserAnswer{}) + if err := m.UserAnswers[len(m.UserAnswers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", 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 + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubspaceDataEntry) 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 ErrIntOverflowGenesis + } + 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: SubspaceDataEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubspaceDataEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialPostID", wireType) + } + m.InitialPostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.InitialPostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisPost) 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 ErrIntOverflowGenesis + } + 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: GenesisPost: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisPost: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Post", 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 + } + if err := m.Post.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialAttachmentID", wireType) + } + m.InitialAttachmentID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.InitialAttachmentID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(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, ErrIntOverflowGenesis + } + 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, ErrIntOverflowGenesis + } + 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, ErrIntOverflowGenesis + } + 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, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/posts/types/genesis_test.go b/x/posts/types/genesis_test.go new file mode 100644 index 0000000000..bc42e572a7 --- /dev/null +++ b/x/posts/types/genesis_test.go @@ -0,0 +1,295 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func TestValidateGenesis(t *testing.T) { + testCases := []struct { + name string + data *types.GenesisState + shouldErr bool + }{ + { + name: "invalid subspace data entry returns error", + data: types.NewGenesisState([]types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(0, 0), + }, nil, nil, nil, types.Params{}), + shouldErr: true, + }, + { + name: "duplicated subspace data entries return error", + data: types.NewGenesisState([]types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + types.NewSubspaceDataEntry(1, 3), + }, nil, nil, nil, types.Params{}), + shouldErr: true, + }, + { + name: "invalid initial post id returns error", + data: types.NewGenesisState(nil, []types.GenesisPost{ + types.NewGenesisPost(0, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, nil, nil, types.Params{}), + shouldErr: true, + }, + { + name: "invalid genesis post returns error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(1, types.NewPost( + 1, + 0, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + nil, + nil, + types.Params{}, + ), + shouldErr: true, + }, + { + name: "duplicated genesis posts return error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(1, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + types.NewGenesisPost(3, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + nil, + nil, + types.Params{}, + ), + shouldErr: true, + }, + { + name: "duplicated attachments return error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(1, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + nil, + types.Params{}, + ), + shouldErr: true, + }, + { + name: "invalid initial attachment id returns error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(0, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + nil, + types.Params{}, + ), + shouldErr: true, + }, + { + name: "invalid attachment returns error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(1, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + []types.Attachment{ + types.NewAttachment(0, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + }, + nil, + types.Params{}, + ), + shouldErr: true, + }, + + { + name: "invalid user answer returns error", + data: types.NewGenesisState(nil, nil, nil, []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, types.Params{}), + shouldErr: true, + }, + { + name: "duplicated user answers return error", + data: types.NewGenesisState(nil, nil, nil, []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, types.Params{}), + shouldErr: true, + }, + { + name: "default genesis does not error", + data: types.DefaultGenesisState(), + shouldErr: false, + }, + { + name: "valid genesis state does not error", + data: types.NewGenesisState( + []types.SubspaceDataEntry{ + types.NewSubspaceDataEntry(1, 2), + }, + []types.GenesisPost{ + types.NewGenesisPost(2, types.NewPost( + 1, + 1, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 0, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )), + }, + []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + )), + }, + []types.UserAnswer{ + types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1vs8dps0ktst5ekynmszxuxphfq08rhmepsn8st"), + }, + types.NewParams(100), + ), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := types.ValidateGenesis(tc.data) + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} diff --git a/x/posts/types/hooks.go b/x/posts/types/hooks.go new file mode 100644 index 0000000000..40f747dcfb --- /dev/null +++ b/x/posts/types/hooks.go @@ -0,0 +1,27 @@ +package types + +// DONTCOVER + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Event Hooks +// These can be utilized to communicate between a posts keeper and another +// keeper which must take particular actions when posts/attachments/polls change +// state. The second keeper must implement this interface, which then the +// posts keeper can call. + +// PostsHooks event hooks for posts objects (noalias) +type PostsHooks interface { + AfterPostSaved(ctx sdk.Context, subspaceID uint64, postID uint64) // Must be called when a post is saved + AfterPostDeleted(ctx sdk.Context, subspaceID uint64, postID uint64) // Must be called when a post is deleted + + AfterAttachmentSaved(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) // Must be called when a post attachment is saved + AfterAttachmentDeleted(ctx sdk.Context, subspaceID uint64, postID uint64, attachmentID uint32) // Must be called when a post attachment is deleted + + AfterPollAnswerSaved(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) // Must be called when a poll user answer is saved + AfterPollAnswerDeleted(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32, user string) // Must be called when a poll user answer is deleted + + AfterPollVotingPeriodEnded(ctx sdk.Context, subspaceID uint64, postID uint64, pollID uint32) // Must be called when a poll tally results are saved +} diff --git a/x/posts/types/keys.go b/x/posts/types/keys.go new file mode 100644 index 0000000000..26c37ffc04 --- /dev/null +++ b/x/posts/types/keys.go @@ -0,0 +1,162 @@ +package types + +import ( + "encoding/binary" + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + subspacetypes "github.com/desmos-labs/desmos/v3/x/subspaces/types" +) + +// DONTCOVER + +const ( + ModuleName = "posts" + RouterKey = ModuleName + StoreKey = ModuleName + QuerierRoute = ModuleName + + ActionCreatePost = "create_post" + ActionEditPost = "edit_post" + ActionAddPostAttachment = "add_post_attachment" + ActionRemovePostAttachment = "remove_post_attachment" + ActionDeletePost = "delete_post" + ActionAnswerPoll = "answer_poll" + + DoNotModify = "[do-not-modify]" +) + +var ( + NextPostIDPrefix = []byte{0x00} + PostPrefix = []byte{0x01} + NextAttachmentIDPrefix = []byte{0x02} + AttachmentPrefix = []byte{0x03} + UserAnswerPrefix = []byte{0x04} + ActivePollQueuePrefix = []byte{0x05} +) + +// GetPostIDBytes returns the byte representation of the postID +func GetPostIDBytes(postID uint64) (postIDBz []byte) { + postIDBz = make([]byte, 8) + binary.BigEndian.PutUint64(postIDBz, postID) + return +} + +// GetPostIDFromBytes returns postID in uint64 format from a byte array +func GetPostIDFromBytes(bz []byte) (postID uint64) { + return binary.BigEndian.Uint64(bz) +} + +// GetSubspacePostIDBytes returns the byte representation of the subspaceID merged with the postID +func GetSubspacePostIDBytes(subspaceID uint64, postID uint64) []byte { + return append(subspacetypes.GetSubspaceIDBytes(subspaceID), GetPostIDBytes(postID)...) +} + +// NextPostIDStoreKey returns the key uses to store the next post id for the given subspace +func NextPostIDStoreKey(subspaceID uint64) []byte { + return append(NextPostIDPrefix, subspacetypes.GetSubspaceIDBytes(subspaceID)...) +} + +// SubspacePostsPrefix returns the store prefix used to store all the posts related to the given subspace +func SubspacePostsPrefix(subspaceID uint64) []byte { + return append(PostPrefix, subspacetypes.GetSubspaceIDBytes(subspaceID)...) +} + +// PostStoreKey returns the key for a specific post +func PostStoreKey(subspaceID uint64, postID uint64) []byte { + return append(SubspacePostsPrefix(subspaceID), GetPostIDBytes(postID)...) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// GetAttachmentIDBytes returns the byte representation of the attachmentID +func GetAttachmentIDBytes(attachmentID uint32) (attachmentIDBz []byte) { + attachmentIDBz = make([]byte, 4) + binary.BigEndian.PutUint32(attachmentIDBz, attachmentID) + return +} + +// GetAttachmentIDFromBytes returns the attachmentID in uint32 format from a byte array +func GetAttachmentIDFromBytes(bz []byte) (attachmentID uint32) { + return binary.BigEndian.Uint32(bz) +} + +// NextAttachmentIDStoreKey returns the store key that is used to store the attachment id to be used next for the given post +func NextAttachmentIDStoreKey(subspaceID uint64, postID uint64) []byte { + return append(NextAttachmentIDPrefix, GetSubspacePostIDBytes(subspaceID, postID)...) +} + +// PostAttachmentsPrefix returns the store prefix used to store all the given post attachments +func PostAttachmentsPrefix(subspaceID uint64, postID uint64) []byte { + return append(AttachmentPrefix, GetSubspacePostIDBytes(subspaceID, postID)...) +} + +// AttachmentStoreKey returns the store key that is used to store the attachment having the given id +func AttachmentStoreKey(subspaceID uint64, postID uint64, attachmentID uint32) []byte { + return append(PostAttachmentsPrefix(subspaceID, postID), GetAttachmentIDBytes(attachmentID)...) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var ( + lenTime = len(sdk.FormatTimeBytes(time.Now())) + lenPrefix = len(ActivePollQueuePrefix) + lenSubspaceID = 64 / 8 + lenPostID = 64 / 8 +) + +// ActivePollByTimeKey gets the active poll queue key by endTime +func ActivePollByTimeKey(endTime time.Time) []byte { + return append(ActivePollQueuePrefix, sdk.FormatTimeBytes(endTime)...) +} + +// ActivePollQueueKey returns the key for a pollID in the activePollQueue +func ActivePollQueueKey(subspaceID uint64, postID uint64, pollID uint32, endTime time.Time) []byte { + return append(ActivePollByTimeKey(endTime), GetPollIDBytes(subspaceID, postID, pollID)...) +} + +// SplitActivePollQueueKey split the active poll key and returns the poll id and endTime +func SplitActivePollQueueKey(key []byte) (subspaceID uint64, postID uint64, pollID uint32, endTime time.Time) { + if len(key[lenPrefix:]) != 20+lenTime { + panic(fmt.Errorf("unexpected key length (%d ≠ %d)", len(key[1:]), lenTime+8)) + } + + endTime, err := sdk.ParseTimeBytes(key[lenPrefix : lenPrefix+lenTime]) + if err != nil { + panic(err) + } + + subspaceID = subspacetypes.GetSubspaceIDFromBytes(key[lenPrefix+lenTime : lenPrefix+lenTime+lenSubspaceID]) + postID = GetPostIDFromBytes(key[lenPrefix+lenTime+lenSubspaceID : lenPrefix+lenTime+lenSubspaceID+lenPostID]) + pollID = GetAttachmentIDFromBytes(key[lenPrefix+lenTime+lenSubspaceID+lenPostID:]) + return subspaceID, postID, pollID, endTime +} + +// GetPollIDBytes returns the byte representation of the provided pollID +func GetPollIDBytes(subspaceID uint64, postID uint64, pollID uint32) []byte { + return append(GetSubspacePostIDBytes(subspaceID, postID), GetAttachmentIDBytes(pollID)...) +} + +// GetPollIDFromBytes returns the pollID in uint32 format from a byte array +func GetPollIDFromBytes(bz []byte) (subspaceID uint64, postID uint64, pollID uint32) { + if len(bz) != 20 { + panic(fmt.Errorf("unexpected key length (%d ≠ %d", len(bz), 20)) + } + + subspaceID = subspacetypes.GetSubspaceIDFromBytes(bz[:lenSubspaceID]) + postID = GetPostIDFromBytes(bz[lenSubspaceID : lenSubspaceID+lenPostID]) + pollID = GetAttachmentIDFromBytes(bz[lenSubspaceID+lenPostID:]) + return subspaceID, postID, pollID +} + +// PollAnswersPrefix returns the store prefix used to store the polls associated with the given post +func PollAnswersPrefix(subspaceID uint64, postID uint64, pollID uint32) []byte { + return append(UserAnswerPrefix, GetPollIDBytes(subspaceID, postID, pollID)...) +} + +// PollAnswerStoreKey returns the store key used to store the poll answer for the given user +func PollAnswerStoreKey(subspaceID uint64, postID uint64, pollID uint32, user string) []byte { + return append(PollAnswersPrefix(subspaceID, postID, pollID), []byte(user)...) +} diff --git a/x/posts/types/keys_test.go b/x/posts/types/keys_test.go new file mode 100644 index 0000000000..7fcdfbcb5c --- /dev/null +++ b/x/posts/types/keys_test.go @@ -0,0 +1,91 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func TestSplitActivePollQueueKey(t *testing.T) { + testCases := []struct { + name string + key []byte + shouldErr bool + expSubspaceID uint64 + expPostID uint64 + expoPollID uint32 + expDate time.Time + }{ + { + name: "invalid key returns error", + key: []byte{}, + shouldErr: true, + }, + { + name: "valid key returns proper values", + key: types.ActivePollQueueKey(1, 2, 3, time.Date(2020, 1, 1, 12, 00, 00, 00, time.UTC)), + expSubspaceID: 1, + expPostID: 2, + expoPollID: 3, + expDate: time.Date(2020, 1, 1, 12, 00, 00, 00, time.UTC), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.shouldErr { + require.Panics(t, func() { types.SplitActivePollQueueKey(tc.key) }) + } else { + subspaceID, postID, pollID, date := types.SplitActivePollQueueKey(tc.key) + require.Equal(t, tc.expSubspaceID, subspaceID) + require.Equal(t, tc.expPostID, postID) + require.Equal(t, tc.expoPollID, pollID) + require.Equal(t, tc.expDate, date) + } + }) + } +} + +func TestGetPollIDFromBytes(t *testing.T) { + testCases := []struct { + name string + key []byte + shouldErr bool + expSubspaceID uint64 + expPostID uint64 + expPollID uint32 + }{ + { + name: "invalid key length returns error", + key: []byte{}, + shouldErr: true, + }, + { + name: "valid key length returns proper values", + key: types.GetPollIDBytes(1, 1, 1), + shouldErr: false, + expSubspaceID: 1, + expPostID: 1, + expPollID: 1, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.shouldErr { + require.Panics(t, func() { types.GetPollIDFromBytes(tc.key) }) + } else { + subspaceID, postID, pollID := types.GetPollIDFromBytes(tc.key) + require.Equal(t, tc.expSubspaceID, subspaceID) + require.Equal(t, tc.expPostID, postID) + require.Equal(t, tc.expPollID, pollID) + } + }) + } + +} diff --git a/x/posts/types/models.go b/x/posts/types/models.go new file mode 100644 index 0000000000..5e6b3ca1e3 --- /dev/null +++ b/x/posts/types/models.go @@ -0,0 +1,675 @@ +package types + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/gogo/protobuf/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ParsePostID parses the given value as a post id, returning an error if it's invalid +func ParsePostID(value string) (uint64, error) { + if value == "" { + return 0, nil + } + + postID, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid post id: %s", err) + } + return postID, nil +} + +// NewPost allows to build a new Post instance +func NewPost( + subspaceID uint64, + id uint64, + externalID string, + text string, + author string, + conversationID uint64, + entities *Entities, + referencedPosts []PostReference, + replySetting ReplySetting, + creationDate time.Time, + lastEditedDate *time.Time, +) Post { + return Post{ + SubspaceID: subspaceID, + ID: id, + ExternalID: externalID, + Text: text, + Entities: entities, + Author: author, + ConversationID: conversationID, + ReferencedPosts: referencedPosts, + ReplySettings: replySetting, + CreationDate: creationDate, + LastEditedDate: lastEditedDate, + } +} + +// Validate implements fmt.Validator +func (p Post) Validate() error { + if p.SubspaceID == 0 { + return fmt.Errorf("invalid subspace id: %d", p.SubspaceID) + } + + if p.ID == 0 { + return fmt.Errorf("invalid post id: %d", p.ID) + } + + if p.Entities != nil { + err := p.Entities.Validate() + if err != nil { + return fmt.Errorf("invalid entities: %s", err) + } + + // Make sure that no entity has a start or end index that is greater to the text length + maxIndexAllowed := uint64(0) + if len(strings.TrimSpace(p.Text)) > 0 { + maxIndexAllowed = uint64(len(strings.TrimSpace(p.Text)) - 1) + } + + for _, segment := range p.Entities.getSegments() { + if segment.start > maxIndexAllowed || segment.end > maxIndexAllowed { + return fmt.Errorf("entity cannot have start/end index greater than text length") + } + } + } + + _, err := sdk.AccAddressFromBech32(p.Author) + if err != nil { + return fmt.Errorf("invalid author address: %s", err) + } + + if p.ConversationID >= p.ID { + return fmt.Errorf("invalid conversation id: %d", p.ConversationID) + } + + for _, reference := range p.ReferencedPosts { + err = reference.Validate() + if err != nil { + return fmt.Errorf("invalid post reference: %s", err) + } + + if reference.PostID >= p.ID { + return fmt.Errorf("invalid referenced post id: %d", reference.PostID) + } + } + + if p.ReplySettings == REPLY_SETTING_UNSPECIFIED { + return fmt.Errorf("invalid reply setting: %s", p.ReplySettings) + } + + if p.CreationDate.IsZero() { + return fmt.Errorf("invalid post creation date: %s", err) + } + + if p.LastEditedDate != nil { + // Instead of zero, we should use nil + if p.LastEditedDate.IsZero() { + return fmt.Errorf("invalid post last edited date: %s", err) + } + + // Make sure the creation date is always before the last edit date + if p.LastEditedDate.Before(p.CreationDate) { + return fmt.Errorf("last edited date cannot be before the creation date") + } + } + + return nil +} + +// IsUserMentioned tells whether the given user is mentioned inside the post or not +func (p Post) IsUserMentioned(user string) bool { + for _, mention := range p.GetMentionedUsers() { + if mention == user { + return true + } + } + return false +} + +// GetMentionedUsers returns all the mentioned users +func (p Post) GetMentionedUsers() []string { + if p.Entities == nil { + return nil + } + + mentions := make([]string, len(p.Entities.Mentions)) + for i, mention := range p.Entities.Mentions { + mentions[i] = mention.Tag + } + return mentions +} + +// NewPostReference returns a new PostReference instance +func NewPostReference(referenceType PostReference_Type, postID uint64) PostReference { + return PostReference{ + Type: referenceType, + PostID: postID, + } +} + +// Validate implements fmt.Validator +func (r PostReference) Validate() error { + if r.Type == TYPE_UNSPECIFIED { + return fmt.Errorf("invalid reference type: %s", r.Type) + } + + if r.PostID == 0 { + return fmt.Errorf("invalid post id: %d", r.PostID) + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +// PostUpdate contains all the data that can be updated about a post. +// When performing an update, if a text field should not be edited then it must be set to types.DoNotModify. +type PostUpdate struct { + // If it shouldn't replace the current text, it must be set to types.DoNotModify + Text string + + // Update's entities will always replace the existing ones + Entities *Entities + + UpdateTime time.Time +} + +// NewPostUpdate returns a new PostUpdate instance +func NewPostUpdate(text string, entities *Entities, updateTime time.Time) PostUpdate { + return PostUpdate{ + Text: text, + Entities: entities, + UpdateTime: updateTime, + } +} + +// Update updates the fields of a given post without validating it. +// Before storing the updated post, a validation with keeper.ValidatePost should +// be performed. +func (p Post) Update(update PostUpdate) Post { + if update.Text == DoNotModify { + update.Text = p.Text + } + + return NewPost( + p.SubspaceID, + p.ID, + p.ExternalID, + update.Text, + p.Author, + p.ConversationID, + update.Entities, + p.ReferencedPosts, + p.ReplySettings, + p.CreationDate, + &update.UpdateTime, + ) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewEntities returns a new Entities instance +func NewEntities(hashtags []Tag, mentions []Tag, urls []Url) *Entities { + return &Entities{ + Hashtags: hashtags, + Mentions: mentions, + Urls: urls, + } +} + +// Validate implements fmt.Validator +func (e *Entities) Validate() error { + for _, tag := range e.Hashtags { + err := tag.Validate() + if err != nil { + return fmt.Errorf("invalid hashtag: %s", err) + } + } + + for _, tag := range e.Mentions { + err := tag.Validate() + if err != nil { + return fmt.Errorf("invalid mention: %s", err) + } + } + + for _, url := range e.Urls { + err := url.Validate() + if err != nil { + return fmt.Errorf("invalid url: %s", err) + } + } + + // --- Make sure there are no overlapping entities based on (start, end) --- + + // Map all entities into segments + segments := e.getSegments() + + if len(segments) < 1 { + // We cannot have an empty entities here + return fmt.Errorf("entities must have at least one entity inside") + } + + if len(segments) < 2 { + // With less than 2 segments there cannot be any overlap + return nil + } + + // Sort the segments + sort.Slice(segments, func(i, j int) bool { + return segments[i].start < segments[j].start + }) + + // Verify there are no overlapping segments + for index := 0; index < len(segments)-1; index++ { + first, second := segments[index], segments[index+1] + if first.end >= second.start { + return fmt.Errorf("entities cannot overlap: start %d end %d", first.end, second.start) + } + } + + return nil +} + +// entitySegment represents an entity as a segment within a post text. +// This is used to easily determine whether there are overlapping entities specified within the post. +// Two entities are said to overlap if the end of the first is after the start of the second. +type entitySegment struct { + // start represents the index inside the post text at which this segment begins + start uint64 + + // end represents the index inside the post text at which this segment ends + end uint64 +} + +// getSegments maps all the provided Entities into segments +func (e *Entities) getSegments() []entitySegment { + segments := make([]entitySegment, len(e.Hashtags)+len(e.Mentions)+len(e.Urls)) + i := 0 + for _, hashtag := range e.Hashtags { + segments[i] = entitySegment{start: hashtag.Start, end: hashtag.End} + i++ + } + + for _, mention := range e.Mentions { + segments[i] = entitySegment{start: mention.Start, end: mention.End} + i++ + } + + for _, url := range e.Urls { + segments[i] = entitySegment{start: url.Start, end: url.End} + i++ + } + + return segments +} + +// NewTag returns a new Tag instance +func NewTag(start, end uint64, tag string) Tag { + return Tag{ + Start: start, + End: end, + Tag: tag, + } +} + +// Validate implements fmt.Validator +func (t Tag) Validate() error { + if t.Start > t.End { + return fmt.Errorf("invalid start and end indexes: %d %d", t.Start, t.End) + } + + if strings.TrimSpace(t.Tag) == "" { + return fmt.Errorf("invalid start and end indexes: %d %d", t.Start, t.End) + } + + return nil +} + +// NewURL returns a new Url instance +func NewURL(start, end uint64, url, displayURL string) Url { + return Url{ + Start: start, + End: end, + Url: url, + DisplayUrl: displayURL, + } +} + +// Validate implements fmt.Validator +func (u Url) Validate() error { + if u.Start > u.End { + return fmt.Errorf("invalid start and end indexes: %d %d", u.Start, u.End) + } + + if strings.TrimSpace(u.Url) == "" { + return fmt.Errorf("invalid url value: %s", u.Url) + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +// ParseAttachmentID parses the given value as an attachment id, returning an error if it's invalid +func ParseAttachmentID(value string) (uint32, error) { + if value == "" { + return 0, nil + } + + attachmentID, err := strconv.ParseUint(value, 10, 32) + if err != nil { + return 0, fmt.Errorf("invalid attachment id: %s", err) + } + return uint32(attachmentID), nil +} + +// Attachments represents a slice of Attachment objects +type Attachments []Attachment + +// NewAttachments returns a new Attachments instance +func NewAttachments(attachments ...Attachment) Attachments { + return Attachments(attachments) +} + +// Validate implements fmt.Validators fmt.Validator +func (a Attachments) Validate() error { + ids := map[uint32]bool{} + for _, attachment := range a { + if _, ok := ids[attachment.ID]; ok { + return fmt.Errorf("duplicated attachment id: %d", attachment.ID) + } + ids[attachment.ID] = true + + err := attachment.Validate() + if err != nil { + return err + } + } + + return nil +} + +// NewAttachment returns a new Attachment instance +func NewAttachment(subspaceID uint64, postID uint64, id uint32, content AttachmentContent) Attachment { + contentAny, err := codectypes.NewAnyWithValue(content) + if err != nil { + panic("failed to pack content to any type") + } + + return Attachment{ + SubspaceID: subspaceID, + PostID: postID, + ID: id, + Content: contentAny, + } +} + +// Validate implements fmt.Validator +func (a Attachment) Validate() error { + if a.SubspaceID == 0 { + return fmt.Errorf("invalid subspace id: %d", a.SubspaceID) + } + + if a.PostID == 0 { + return fmt.Errorf("invalid post id: %d", a.PostID) + } + + if a.ID == 0 { + return fmt.Errorf("invalid attachment id: %d", a.ID) + } + + if a.Content == nil { + return fmt.Errorf("invalid attachment content") + } + + return a.Content.GetCachedValue().(AttachmentContent).Validate() +} + +// UnpackInterfaces implements codectypes.UnpackInterfacesMessage +func (a *Attachment) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var content AttachmentContent + return unpacker.UnpackAny(a.Content, &content) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// AttachmentContent represents an attachment content +type AttachmentContent interface { + proto.Message + isAttachmentContent() + Validate() error +} + +// PackAttachments packs the given AttachmentContent instances as Any instances +func PackAttachments(attachments []AttachmentContent) ([]*codectypes.Any, error) { + attachmentAnys := make([]*codectypes.Any, len(attachments)) + for i := range attachments { + any, err := codectypes.NewAnyWithValue(attachments[i]) + if err != nil { + return nil, err + } + attachmentAnys[i] = any + } + return attachmentAnys, nil +} + +// UnpackAttachments unpacks the given Any instances as AttachmentContent +func UnpackAttachments(cdc codectypes.AnyUnpacker, attachmentAnys []*codectypes.Any) ([]AttachmentContent, error) { + attachments := make([]AttachmentContent, len(attachmentAnys)) + for i, any := range attachmentAnys { + var content AttachmentContent + err := cdc.UnpackAny(any, &content) + if err != nil { + return nil, err + } + attachments[i] = content + } + return attachments, nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +var _ AttachmentContent = &Media{} + +// NewMedia returns a new Media instance +func NewMedia(uri, mimeType string) *Media { + return &Media{ + Uri: uri, + MimeType: mimeType, + } +} + +func (*Media) isAttachmentContent() {} + +// Validate implements fmt.Validator +func (m *Media) Validate() error { + if strings.TrimSpace(m.Uri) == "" { + return fmt.Errorf("invalid uri: %s", m.Uri) + } + + if strings.TrimSpace(m.MimeType) == "" { + return fmt.Errorf("invalid mime type: %s", m.MimeType) + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +var _ AttachmentContent = &Poll{} + +// NewPoll returns a new Poll instance +func NewPoll( + question string, + providedAnswers []Poll_ProvidedAnswer, + endDate time.Time, + allowsMultipleAnswers bool, + allowsAnswerEdits bool, + tallyResults *PollTallyResults, +) *Poll { + return &Poll{ + Question: question, + ProvidedAnswers: providedAnswers, + EndDate: endDate, + AllowsMultipleAnswers: allowsMultipleAnswers, + AllowsAnswerEdits: allowsAnswerEdits, + FinalTallyResults: tallyResults, + } +} + +func (*Poll) isAttachmentContent() {} + +// Validate implements fmt.Validator +func (p *Poll) Validate() error { + if strings.TrimSpace(p.Question) == "" { + return fmt.Errorf("invalid question: %s", p.Question) + } + + if len(p.ProvidedAnswers) < 2 { + return fmt.Errorf("insufficient amount of provided answers: %d", len(p.ProvidedAnswers)) + } + + answers := map[string]bool{} + for _, answer := range p.ProvidedAnswers { + err := answer.Validate() + if err != nil { + return err + } + + if _, ok := answers[answer.Text]; ok { + return fmt.Errorf("duplicated provided answer: %s", answer.Text) + } + answers[answer.Text] = true + } + + if p.EndDate.IsZero() { + return fmt.Errorf("invalid end date: %s", p.EndDate) + } + + if p.FinalTallyResults != nil { + err := p.FinalTallyResults.Validate() + if err != nil { + return err + } + } + + return nil +} + +// IsPoll tells whether the given attachment represents a poll or not +func IsPoll(attachment Attachment) bool { + _, ok := attachment.Content.GetCachedValue().(*Poll) + return ok +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewProvidedAnswer returns a new Poll_ProvidedAnswer instance +func NewProvidedAnswer(text string, attachments []Attachment) Poll_ProvidedAnswer { + return Poll_ProvidedAnswer{ + Text: text, + Attachments: attachments, + } +} + +// Validate implements fmt.Validator +func (a Poll_ProvidedAnswer) Validate() error { + if strings.TrimSpace(a.Text) == "" { + return fmt.Errorf("invalid text: %s", a.Text) + } + + return Attachments(a.Attachments).Validate() +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewUserAnswer returns a new UserAnswer instance +func NewUserAnswer(subspaceID uint64, postID uint64, pollID uint32, answersIndexes []uint32, user string) UserAnswer { + return UserAnswer{ + SubspaceID: subspaceID, + PostID: postID, + PollID: pollID, + AnswersIndexes: answersIndexes, + User: user, + } +} + +// Validate implements fmt.Validator +func (a UserAnswer) Validate() error { + if a.SubspaceID == 0 { + return fmt.Errorf("invalid subspace id: %d", a.SubspaceID) + } + + if a.PostID == 0 { + return fmt.Errorf("invalid post id: %d", a.PostID) + } + + if a.PollID == 0 { + return fmt.Errorf("invalid poll id: %d", a.PollID) + } + + if len(a.AnswersIndexes) == 0 { + return fmt.Errorf("answer indexes cannot be empty") + } + + indexes := map[uint32]bool{} + for _, answer := range a.AnswersIndexes { + if _, ok := indexes[answer]; ok { + return fmt.Errorf("duplicated answer index: %d", answer) + } + indexes[answer] = true + } + + _, err := sdk.AccAddressFromBech32(a.User) + if err != nil { + return fmt.Errorf("invalid user address: %s", err) + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewPollTallyResults returns a new PollTallyResults instance +func NewPollTallyResults(results []PollTallyResults_AnswerResult) *PollTallyResults { + return &PollTallyResults{ + Results: results, + } +} + +// Validate implements fmt.Validator +func (r *PollTallyResults) Validate() error { + if len(r.Results) == 0 { + return fmt.Errorf("empty answer results") + } + + ids := map[uint32]bool{} + for _, answerResult := range r.Results { + if _, ok := ids[answerResult.AnswerIndex]; ok { + return fmt.Errorf("duplicated result for answer %d", answerResult.AnswerIndex) + } + ids[answerResult.AnswerIndex] = true + } + + return nil +} + +// NewAnswerResult returns a new PollTallyResults_AnswerResult instance +func NewAnswerResult(answerIndex uint32, votes uint64) PollTallyResults_AnswerResult { + return PollTallyResults_AnswerResult{ + AnswerIndex: answerIndex, + Votes: votes, + } +} diff --git a/x/posts/types/models.pb.go b/x/posts/types/models.pb.go new file mode 100644 index 0000000000..2f179dfd1c --- /dev/null +++ b/x/posts/types/models.pb.go @@ -0,0 +1,4544 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: desmos/posts/v1/models.proto + +package types + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// 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 + +// ReplySetting contains the possible reply settings that a post can have +type ReplySetting int32 + +const ( + // No reply setting specified + REPLY_SETTING_UNSPECIFIED ReplySetting = 0 + // Everyone will be able to reply to this post + REPLY_SETTING_EVERYONE ReplySetting = 1 + // Only followers of the author will be able to reply to this post + REPLY_SETTING_FOLLOWERS ReplySetting = 2 + // Only the author mutual followers will be able to reply to this post + REPLY_SETTING_MUTUAL ReplySetting = 3 + // Only people mentioned inside this post will be able to reply + REPLY_SETTING_MENTIONS ReplySetting = 4 +) + +var ReplySetting_name = map[int32]string{ + 0: "REPLY_SETTING_UNSPECIFIED", + 1: "REPLY_SETTING_EVERYONE", + 2: "REPLY_SETTING_FOLLOWERS", + 3: "REPLY_SETTING_MUTUAL", + 4: "REPLY_SETTING_MENTIONS", +} + +var ReplySetting_value = map[string]int32{ + "REPLY_SETTING_UNSPECIFIED": 0, + "REPLY_SETTING_EVERYONE": 1, + "REPLY_SETTING_FOLLOWERS": 2, + "REPLY_SETTING_MUTUAL": 3, + "REPLY_SETTING_MENTIONS": 4, +} + +func (x ReplySetting) String() string { + return proto.EnumName(ReplySetting_name, int32(x)) +} + +func (ReplySetting) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{0} +} + +// Type represents the different types of references +type PostReference_Type int32 + +const ( + // No reference specified + TYPE_UNSPECIFIED PostReference_Type = 0 + // This post is a reply to the referenced post + TYPE_REPLIED_TO PostReference_Type = 1 + // This post is a quote to the referenced post + TYPE_QUOTED PostReference_Type = 2 + // This post is a report of the referenced post + TYPE_REPOSTED PostReference_Type = 3 +) + +var PostReference_Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "TYPE_REPLIED_TO", + 2: "TYPE_QUOTED", + 3: "TYPE_REPOSTED", +} + +var PostReference_Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "TYPE_REPLIED_TO": 1, + "TYPE_QUOTED": 2, + "TYPE_REPOSTED": 3, +} + +func (x PostReference_Type) String() string { + return proto.EnumName(PostReference_Type_name, int32(x)) +} + +func (PostReference_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{1, 0} +} + +// Post contains all the information about a single post +type Post struct { + // Id of the subspace inside which the post has been created + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Unique id of the post + ID uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + // (optional) External id for this post + ExternalID string `protobuf:"bytes,3,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + // (optional) Text of the post + Text string `protobuf:"bytes,4,opt,name=text,proto3" json:"text,omitempty"` + // (optional) Entities connected to this post + Entities *Entities `protobuf:"bytes,5,opt,name=entities,proto3" json:"entities,omitempty"` + // Author of the post + Author string `protobuf:"bytes,6,opt,name=author,proto3" json:"author,omitempty"` + // (optional) Id of the original post of the conversation + ConversationID uint64 `protobuf:"varint,7,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + // A list this posts references (either as a reply, repost or quote) + ReferencedPosts []PostReference `protobuf:"bytes,8,rep,name=referenced_posts,json=referencedPosts,proto3" json:"referenced_posts"` + // Reply settings of this post + ReplySettings ReplySetting `protobuf:"varint,9,opt,name=reply_settings,json=replySettings,proto3,enum=desmos.posts.v1.ReplySetting" json:"reply_settings,omitempty"` + // Creation date of the post + CreationDate time.Time `protobuf:"bytes,10,opt,name=creation_date,json=creationDate,proto3,stdtime" json:"creation_date"` + // (optional) Last edited time of the post + LastEditedDate *time.Time `protobuf:"bytes,11,opt,name=last_edited_date,json=lastEditedDate,proto3,stdtime" json:"last_edited_date,omitempty"` +} + +func (m *Post) Reset() { *m = Post{} } +func (m *Post) String() string { return proto.CompactTextString(m) } +func (*Post) ProtoMessage() {} +func (*Post) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{0} +} +func (m *Post) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Post) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Post.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 *Post) XXX_Merge(src proto.Message) { + xxx_messageInfo_Post.Merge(m, src) +} +func (m *Post) XXX_Size() int { + return m.Size() +} +func (m *Post) XXX_DiscardUnknown() { + xxx_messageInfo_Post.DiscardUnknown(m) +} + +var xxx_messageInfo_Post proto.InternalMessageInfo + +func (m *Post) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *Post) GetID() uint64 { + if m != nil { + return m.ID + } + return 0 +} + +func (m *Post) GetExternalID() string { + if m != nil { + return m.ExternalID + } + return "" +} + +func (m *Post) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *Post) GetEntities() *Entities { + if m != nil { + return m.Entities + } + return nil +} + +func (m *Post) GetAuthor() string { + if m != nil { + return m.Author + } + return "" +} + +func (m *Post) GetConversationID() uint64 { + if m != nil { + return m.ConversationID + } + return 0 +} + +func (m *Post) GetReferencedPosts() []PostReference { + if m != nil { + return m.ReferencedPosts + } + return nil +} + +func (m *Post) GetReplySettings() ReplySetting { + if m != nil { + return m.ReplySettings + } + return REPLY_SETTING_UNSPECIFIED +} + +func (m *Post) GetCreationDate() time.Time { + if m != nil { + return m.CreationDate + } + return time.Time{} +} + +func (m *Post) GetLastEditedDate() *time.Time { + if m != nil { + return m.LastEditedDate + } + return nil +} + +// PostReference contains the details of a post reference +type PostReference struct { + // Type of reference + Type PostReference_Type `protobuf:"varint,1,opt,name=type,proto3,enum=desmos.posts.v1.PostReference_Type" json:"type,omitempty"` + // Id of the referenced post + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` +} + +func (m *PostReference) Reset() { *m = PostReference{} } +func (m *PostReference) String() string { return proto.CompactTextString(m) } +func (*PostReference) ProtoMessage() {} +func (*PostReference) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{1} +} +func (m *PostReference) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PostReference) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PostReference.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 *PostReference) XXX_Merge(src proto.Message) { + xxx_messageInfo_PostReference.Merge(m, src) +} +func (m *PostReference) XXX_Size() int { + return m.Size() +} +func (m *PostReference) XXX_DiscardUnknown() { + xxx_messageInfo_PostReference.DiscardUnknown(m) +} + +var xxx_messageInfo_PostReference proto.InternalMessageInfo + +func (m *PostReference) GetType() PostReference_Type { + if m != nil { + return m.Type + } + return TYPE_UNSPECIFIED +} + +func (m *PostReference) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +// Contains the details of entities parsed out of the post text +type Entities struct { + Hashtags []Tag `protobuf:"bytes,1,rep,name=hashtags,proto3" json:"hashtags"` + Mentions []Tag `protobuf:"bytes,2,rep,name=mentions,proto3" json:"mentions"` + Urls []Url `protobuf:"bytes,3,rep,name=urls,proto3" json:"urls"` +} + +func (m *Entities) Reset() { *m = Entities{} } +func (m *Entities) String() string { return proto.CompactTextString(m) } +func (*Entities) ProtoMessage() {} +func (*Entities) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{2} +} +func (m *Entities) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Entities) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Entities.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 *Entities) XXX_Merge(src proto.Message) { + xxx_messageInfo_Entities.Merge(m, src) +} +func (m *Entities) XXX_Size() int { + return m.Size() +} +func (m *Entities) XXX_DiscardUnknown() { + xxx_messageInfo_Entities.DiscardUnknown(m) +} + +var xxx_messageInfo_Entities proto.InternalMessageInfo + +func (m *Entities) GetHashtags() []Tag { + if m != nil { + return m.Hashtags + } + return nil +} + +func (m *Entities) GetMentions() []Tag { + if m != nil { + return m.Mentions + } + return nil +} + +func (m *Entities) GetUrls() []Url { + if m != nil { + return m.Urls + } + return nil +} + +// Tag represents a generic tag +type Tag struct { + // Index of the character inside the text at which the tag starts + Start uint64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + // Index of the character inside the text at which the tag ends + End uint64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + // Tag reference (user address, hashtag value, etc) + Tag string `protobuf:"bytes,3,opt,name=tag,proto3" json:"tag,omitempty"` +} + +func (m *Tag) Reset() { *m = Tag{} } +func (m *Tag) String() string { return proto.CompactTextString(m) } +func (*Tag) ProtoMessage() {} +func (*Tag) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{3} +} +func (m *Tag) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Tag) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Tag.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 *Tag) XXX_Merge(src proto.Message) { + xxx_messageInfo_Tag.Merge(m, src) +} +func (m *Tag) XXX_Size() int { + return m.Size() +} +func (m *Tag) XXX_DiscardUnknown() { + xxx_messageInfo_Tag.DiscardUnknown(m) +} + +var xxx_messageInfo_Tag proto.InternalMessageInfo + +func (m *Tag) GetStart() uint64 { + if m != nil { + return m.Start + } + return 0 +} + +func (m *Tag) GetEnd() uint64 { + if m != nil { + return m.End + } + return 0 +} + +func (m *Tag) GetTag() string { + if m != nil { + return m.Tag + } + return "" +} + +// Url contains the details of a generic URL +type Url struct { + // Index of the character inside the text at which the URL starts + Start uint64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + // Index of the character inside the text at which the URL ends + End uint64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + // Value of the URL where the user should be redirected to + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` + // (optional) Display value of the URL + DisplayUrl string `protobuf:"bytes,4,opt,name=display_url,json=displayUrl,proto3" json:"display_url,omitempty"` +} + +func (m *Url) Reset() { *m = Url{} } +func (m *Url) String() string { return proto.CompactTextString(m) } +func (*Url) ProtoMessage() {} +func (*Url) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{4} +} +func (m *Url) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Url) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Url.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 *Url) XXX_Merge(src proto.Message) { + xxx_messageInfo_Url.Merge(m, src) +} +func (m *Url) XXX_Size() int { + return m.Size() +} +func (m *Url) XXX_DiscardUnknown() { + xxx_messageInfo_Url.DiscardUnknown(m) +} + +var xxx_messageInfo_Url proto.InternalMessageInfo + +func (m *Url) GetStart() uint64 { + if m != nil { + return m.Start + } + return 0 +} + +func (m *Url) GetEnd() uint64 { + if m != nil { + return m.End + } + return 0 +} + +func (m *Url) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +func (m *Url) GetDisplayUrl() string { + if m != nil { + return m.DisplayUrl + } + return "" +} + +// Attachment contains the data of a single post attachment +type Attachment struct { + // Id of the subspace inside which the post to which this attachment should be + // connected is + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to which this attachment should be connected + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // If of this attachment + ID uint32 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + // Content of the attachment + Content *types.Any `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` +} + +func (m *Attachment) Reset() { *m = Attachment{} } +func (m *Attachment) String() string { return proto.CompactTextString(m) } +func (*Attachment) ProtoMessage() {} +func (*Attachment) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{5} +} +func (m *Attachment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Attachment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Attachment.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 *Attachment) XXX_Merge(src proto.Message) { + xxx_messageInfo_Attachment.Merge(m, src) +} +func (m *Attachment) XXX_Size() int { + return m.Size() +} +func (m *Attachment) XXX_DiscardUnknown() { + xxx_messageInfo_Attachment.DiscardUnknown(m) +} + +var xxx_messageInfo_Attachment proto.InternalMessageInfo + +func (m *Attachment) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *Attachment) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *Attachment) GetID() uint32 { + if m != nil { + return m.ID + } + return 0 +} + +func (m *Attachment) GetContent() *types.Any { + if m != nil { + return m.Content + } + return nil +} + +// Media represents a media attachment +type Media struct { + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` + MimeType string `protobuf:"bytes,3,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"` +} + +func (m *Media) Reset() { *m = Media{} } +func (m *Media) String() string { return proto.CompactTextString(m) } +func (*Media) ProtoMessage() {} +func (*Media) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{6} +} +func (m *Media) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Media) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Media.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 *Media) XXX_Merge(src proto.Message) { + xxx_messageInfo_Media.Merge(m, src) +} +func (m *Media) XXX_Size() int { + return m.Size() +} +func (m *Media) XXX_DiscardUnknown() { + xxx_messageInfo_Media.DiscardUnknown(m) +} + +var xxx_messageInfo_Media proto.InternalMessageInfo + +func (m *Media) GetUri() string { + if m != nil { + return m.Uri + } + return "" +} + +func (m *Media) GetMimeType() string { + if m != nil { + return m.MimeType + } + return "" +} + +// Poll represents a poll attachment +type Poll struct { + // Question of the poll + Question string `protobuf:"bytes,1,opt,name=question,proto3" json:"question,omitempty"` + // Answers the users can choose from + ProvidedAnswers []Poll_ProvidedAnswer `protobuf:"bytes,2,rep,name=provided_answers,json=providedAnswers,proto3" json:"provided_answers"` + // Date at which the poll will close + EndDate time.Time `protobuf:"bytes,3,opt,name=end_date,json=endDate,proto3,stdtime" json:"end_date"` + // Whether the poll allows multiple choices from the same user or not + AllowsMultipleAnswers bool `protobuf:"varint,4,opt,name=allows_multiple_answers,json=allowsMultipleAnswers,proto3" json:"allows_multiple_answers,omitempty"` + // Whether the poll allows to edit an answer or not + AllowsAnswerEdits bool `protobuf:"varint,5,opt,name=allows_answer_edits,json=allowsAnswerEdits,proto3" json:"allows_answer_edits,omitempty"` + // Final poll results + FinalTallyResults *PollTallyResults `protobuf:"bytes,6,opt,name=final_tally_results,json=finalTallyResults,proto3" json:"final_tally_results,omitempty"` +} + +func (m *Poll) Reset() { *m = Poll{} } +func (m *Poll) String() string { return proto.CompactTextString(m) } +func (*Poll) ProtoMessage() {} +func (*Poll) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{7} +} +func (m *Poll) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Poll) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Poll.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 *Poll) XXX_Merge(src proto.Message) { + xxx_messageInfo_Poll.Merge(m, src) +} +func (m *Poll) XXX_Size() int { + return m.Size() +} +func (m *Poll) XXX_DiscardUnknown() { + xxx_messageInfo_Poll.DiscardUnknown(m) +} + +var xxx_messageInfo_Poll proto.InternalMessageInfo + +func (m *Poll) GetQuestion() string { + if m != nil { + return m.Question + } + return "" +} + +func (m *Poll) GetProvidedAnswers() []Poll_ProvidedAnswer { + if m != nil { + return m.ProvidedAnswers + } + return nil +} + +func (m *Poll) GetEndDate() time.Time { + if m != nil { + return m.EndDate + } + return time.Time{} +} + +func (m *Poll) GetAllowsMultipleAnswers() bool { + if m != nil { + return m.AllowsMultipleAnswers + } + return false +} + +func (m *Poll) GetAllowsAnswerEdits() bool { + if m != nil { + return m.AllowsAnswerEdits + } + return false +} + +func (m *Poll) GetFinalTallyResults() *PollTallyResults { + if m != nil { + return m.FinalTallyResults + } + return nil +} + +// Provided answer contains the details of a possible poll answer +type Poll_ProvidedAnswer struct { + // (optional) Text of the answer + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + // Attachments of the answer + Attachments []Attachment `protobuf:"bytes,2,rep,name=attachments,proto3" json:"attachments"` +} + +func (m *Poll_ProvidedAnswer) Reset() { *m = Poll_ProvidedAnswer{} } +func (m *Poll_ProvidedAnswer) String() string { return proto.CompactTextString(m) } +func (*Poll_ProvidedAnswer) ProtoMessage() {} +func (*Poll_ProvidedAnswer) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{7, 0} +} +func (m *Poll_ProvidedAnswer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Poll_ProvidedAnswer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Poll_ProvidedAnswer.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 *Poll_ProvidedAnswer) XXX_Merge(src proto.Message) { + xxx_messageInfo_Poll_ProvidedAnswer.Merge(m, src) +} +func (m *Poll_ProvidedAnswer) XXX_Size() int { + return m.Size() +} +func (m *Poll_ProvidedAnswer) XXX_DiscardUnknown() { + xxx_messageInfo_Poll_ProvidedAnswer.DiscardUnknown(m) +} + +var xxx_messageInfo_Poll_ProvidedAnswer proto.InternalMessageInfo + +func (m *Poll_ProvidedAnswer) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *Poll_ProvidedAnswer) GetAttachments() []Attachment { + if m != nil { + return m.Attachments + } + return nil +} + +// UserAnswer represents a user answer to a poll +type UserAnswer struct { + // Subspace id inside which the post related to this attachment is located + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post associated to this attachment + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Id of the poll to which this answer is associated + PollID uint32 `protobuf:"varint,3,opt,name=poll_id,json=pollId,proto3" json:"poll_id,omitempty"` + // Indexes of the answers inside the ProvidedAnswers array + AnswersIndexes []uint32 `protobuf:"varint,4,rep,packed,name=answers_indexes,json=answersIndexes,proto3" json:"answers_indexes,omitempty"` + // Address of the user answering the poll + User string `protobuf:"bytes,5,opt,name=user,proto3" json:"user,omitempty"` +} + +func (m *UserAnswer) Reset() { *m = UserAnswer{} } +func (m *UserAnswer) String() string { return proto.CompactTextString(m) } +func (*UserAnswer) ProtoMessage() {} +func (*UserAnswer) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{8} +} +func (m *UserAnswer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *UserAnswer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_UserAnswer.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 *UserAnswer) XXX_Merge(src proto.Message) { + xxx_messageInfo_UserAnswer.Merge(m, src) +} +func (m *UserAnswer) XXX_Size() int { + return m.Size() +} +func (m *UserAnswer) XXX_DiscardUnknown() { + xxx_messageInfo_UserAnswer.DiscardUnknown(m) +} + +var xxx_messageInfo_UserAnswer proto.InternalMessageInfo + +func (m *UserAnswer) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *UserAnswer) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *UserAnswer) GetPollID() uint32 { + if m != nil { + return m.PollID + } + return 0 +} + +func (m *UserAnswer) GetAnswersIndexes() []uint32 { + if m != nil { + return m.AnswersIndexes + } + return nil +} + +func (m *UserAnswer) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +// PollTallyResults contains the tally results for a poll +type PollTallyResults struct { + Results []PollTallyResults_AnswerResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results"` +} + +func (m *PollTallyResults) Reset() { *m = PollTallyResults{} } +func (m *PollTallyResults) String() string { return proto.CompactTextString(m) } +func (*PollTallyResults) ProtoMessage() {} +func (*PollTallyResults) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{9} +} +func (m *PollTallyResults) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PollTallyResults) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PollTallyResults.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 *PollTallyResults) XXX_Merge(src proto.Message) { + xxx_messageInfo_PollTallyResults.Merge(m, src) +} +func (m *PollTallyResults) XXX_Size() int { + return m.Size() +} +func (m *PollTallyResults) XXX_DiscardUnknown() { + xxx_messageInfo_PollTallyResults.DiscardUnknown(m) +} + +var xxx_messageInfo_PollTallyResults proto.InternalMessageInfo + +func (m *PollTallyResults) GetResults() []PollTallyResults_AnswerResult { + if m != nil { + return m.Results + } + return nil +} + +// AnswerResult contains the result of a single poll provided answer +type PollTallyResults_AnswerResult struct { + // Index of the answer inside the poll's ProvidedAnswers slice + AnswerIndex uint32 `protobuf:"varint,1,opt,name=answer_index,json=answerIndex,proto3" json:"answer_index,omitempty"` + // Number of votes the answer has received + Votes uint64 `protobuf:"varint,2,opt,name=votes,proto3" json:"votes,omitempty"` +} + +func (m *PollTallyResults_AnswerResult) Reset() { *m = PollTallyResults_AnswerResult{} } +func (m *PollTallyResults_AnswerResult) String() string { return proto.CompactTextString(m) } +func (*PollTallyResults_AnswerResult) ProtoMessage() {} +func (*PollTallyResults_AnswerResult) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{9, 0} +} +func (m *PollTallyResults_AnswerResult) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PollTallyResults_AnswerResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PollTallyResults_AnswerResult.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 *PollTallyResults_AnswerResult) XXX_Merge(src proto.Message) { + xxx_messageInfo_PollTallyResults_AnswerResult.Merge(m, src) +} +func (m *PollTallyResults_AnswerResult) XXX_Size() int { + return m.Size() +} +func (m *PollTallyResults_AnswerResult) XXX_DiscardUnknown() { + xxx_messageInfo_PollTallyResults_AnswerResult.DiscardUnknown(m) +} + +var xxx_messageInfo_PollTallyResults_AnswerResult proto.InternalMessageInfo + +func (m *PollTallyResults_AnswerResult) GetAnswerIndex() uint32 { + if m != nil { + return m.AnswerIndex + } + return 0 +} + +func (m *PollTallyResults_AnswerResult) GetVotes() uint64 { + if m != nil { + return m.Votes + } + return 0 +} + +// Params contains the parameters for the posts module +type Params struct { + // Maximum length of the post text + MaxTextLength uint32 `protobuf:"varint,1,opt,name=max_text_length,json=maxTextLength,proto3" json:"max_text_length,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_9cb993d391569998, []int{10} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.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 *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetMaxTextLength() uint32 { + if m != nil { + return m.MaxTextLength + } + return 0 +} + +func init() { + proto.RegisterEnum("desmos.posts.v1.ReplySetting", ReplySetting_name, ReplySetting_value) + proto.RegisterEnum("desmos.posts.v1.PostReference_Type", PostReference_Type_name, PostReference_Type_value) + proto.RegisterType((*Post)(nil), "desmos.posts.v1.Post") + proto.RegisterType((*PostReference)(nil), "desmos.posts.v1.PostReference") + proto.RegisterType((*Entities)(nil), "desmos.posts.v1.Entities") + proto.RegisterType((*Tag)(nil), "desmos.posts.v1.Tag") + proto.RegisterType((*Url)(nil), "desmos.posts.v1.Url") + proto.RegisterType((*Attachment)(nil), "desmos.posts.v1.Attachment") + proto.RegisterType((*Media)(nil), "desmos.posts.v1.Media") + proto.RegisterType((*Poll)(nil), "desmos.posts.v1.Poll") + proto.RegisterType((*Poll_ProvidedAnswer)(nil), "desmos.posts.v1.Poll.ProvidedAnswer") + proto.RegisterType((*UserAnswer)(nil), "desmos.posts.v1.UserAnswer") + proto.RegisterType((*PollTallyResults)(nil), "desmos.posts.v1.PollTallyResults") + proto.RegisterType((*PollTallyResults_AnswerResult)(nil), "desmos.posts.v1.PollTallyResults.AnswerResult") + proto.RegisterType((*Params)(nil), "desmos.posts.v1.Params") +} + +func init() { proto.RegisterFile("desmos/posts/v1/models.proto", fileDescriptor_9cb993d391569998) } + +var fileDescriptor_9cb993d391569998 = []byte{ + // 1263 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4f, 0x73, 0xdb, 0x44, + 0x14, 0x8f, 0x6c, 0xd7, 0x71, 0x9e, 0xe3, 0x3f, 0xdd, 0x86, 0xd6, 0x75, 0xa9, 0x9d, 0xb6, 0x0c, + 0x64, 0x98, 0x41, 0x9e, 0xa6, 0x43, 0x99, 0x29, 0x07, 0x48, 0x6a, 0x15, 0xc4, 0x24, 0x71, 0xba, + 0x96, 0x61, 0x0a, 0x07, 0xcd, 0xc6, 0xda, 0x3a, 0x62, 0xd6, 0x92, 0xd1, 0xae, 0x53, 0xfb, 0x1b, + 0x70, 0xec, 0x91, 0x63, 0x07, 0xbe, 0x02, 0x77, 0xae, 0x3d, 0x31, 0x3d, 0x70, 0xe8, 0x29, 0x30, + 0xee, 0x85, 0x0f, 0xc0, 0x07, 0x60, 0x76, 0x57, 0x72, 0xe4, 0xa4, 0x50, 0x72, 0xe0, 0xb6, 0xfb, + 0xde, 0xef, 0x3d, 0xed, 0xfe, 0xde, 0xdb, 0xdf, 0x13, 0xbc, 0xed, 0x51, 0x3e, 0x0c, 0x79, 0x6b, + 0x14, 0x72, 0xc1, 0x5b, 0x47, 0xb7, 0x5b, 0xc3, 0xd0, 0xa3, 0x8c, 0x9b, 0xa3, 0x28, 0x14, 0x21, + 0xaa, 0x68, 0xaf, 0xa9, 0xbc, 0xe6, 0xd1, 0xed, 0xfa, 0xda, 0x20, 0x1c, 0x84, 0xca, 0xd7, 0x92, + 0x2b, 0x0d, 0xab, 0x5f, 0x1d, 0x84, 0xe1, 0x80, 0xd1, 0x96, 0xda, 0x1d, 0x8c, 0x1f, 0xb7, 0x48, + 0x30, 0x8d, 0x5d, 0xcd, 0xd3, 0x2e, 0xe1, 0x0f, 0x29, 0x17, 0x64, 0x38, 0xd2, 0x80, 0x9b, 0x2f, + 0x73, 0x90, 0xdb, 0x0f, 0xb9, 0x40, 0x2d, 0x28, 0xf2, 0xf1, 0x01, 0x1f, 0x91, 0x3e, 0x75, 0x7d, + 0xaf, 0x66, 0xac, 0x1b, 0x1b, 0xb9, 0xed, 0xf2, 0xec, 0xb8, 0x09, 0xdd, 0xd8, 0x6c, 0xb7, 0x31, + 0x24, 0x10, 0xdb, 0x43, 0x97, 0x21, 0xe3, 0x7b, 0xb5, 0x8c, 0xc2, 0xe5, 0x67, 0xc7, 0xcd, 0x8c, + 0xdd, 0xc6, 0x19, 0xdf, 0x93, 0x89, 0xe8, 0x44, 0xd0, 0x28, 0x20, 0x4c, 0x26, 0xca, 0xae, 0x1b, + 0x1b, 0x2b, 0x3a, 0x91, 0x15, 0x9b, 0x65, 0xa2, 0x04, 0x62, 0x7b, 0x08, 0x41, 0x4e, 0xd0, 0x89, + 0xa8, 0xe5, 0x24, 0x12, 0xab, 0x35, 0xfa, 0x10, 0x0a, 0x34, 0x10, 0xbe, 0xf0, 0x29, 0xaf, 0x5d, + 0x58, 0x37, 0x36, 0x8a, 0x9b, 0x57, 0xcd, 0x53, 0x64, 0x98, 0x56, 0x0c, 0xc0, 0x73, 0x28, 0xba, + 0x0c, 0x79, 0x32, 0x16, 0x87, 0x61, 0x54, 0xcb, 0xab, 0x64, 0xf1, 0x0e, 0x7d, 0x0c, 0x95, 0x7e, + 0x18, 0x1c, 0xd1, 0x88, 0x13, 0xe1, 0x87, 0x81, 0x3c, 0xd7, 0xb2, 0x3a, 0x38, 0x9a, 0x1d, 0x37, + 0xcb, 0xf7, 0x53, 0x2e, 0xbb, 0x8d, 0xcb, 0x69, 0xa8, 0xed, 0xa1, 0x0e, 0x54, 0x23, 0xfa, 0x98, + 0x46, 0x34, 0xe8, 0x53, 0xcf, 0x55, 0x9f, 0xaf, 0x15, 0xd6, 0xb3, 0x1b, 0xc5, 0xcd, 0xc6, 0x99, + 0x33, 0x49, 0x2a, 0x71, 0x02, 0xde, 0xce, 0x3d, 0x3f, 0x6e, 0x2e, 0xe1, 0xca, 0x49, 0xb4, 0x74, + 0x73, 0xd4, 0x86, 0x72, 0x44, 0x47, 0x6c, 0xea, 0x72, 0x2a, 0x84, 0x1f, 0x0c, 0x78, 0x6d, 0x65, + 0xdd, 0xd8, 0x28, 0x6f, 0x5e, 0x3f, 0x93, 0x0e, 0x4b, 0x58, 0x57, 0xa3, 0x70, 0x29, 0x4a, 0xed, + 0x38, 0xb2, 0xa1, 0xd4, 0x8f, 0xa8, 0xbe, 0x8f, 0x47, 0x04, 0xad, 0x81, 0xe2, 0xa9, 0x6e, 0xea, + 0x92, 0x9b, 0x49, 0xc9, 0x4d, 0x27, 0x29, 0xf9, 0x76, 0x41, 0x9e, 0xe7, 0xe9, 0xef, 0x4d, 0x03, + 0xaf, 0x26, 0xa1, 0x6d, 0x22, 0x28, 0xfa, 0x02, 0xaa, 0x8c, 0x70, 0xe1, 0x52, 0xcf, 0x17, 0xd4, + 0xd3, 0xd9, 0x8a, 0x6f, 0xcc, 0x96, 0x53, 0x99, 0xca, 0x32, 0xd2, 0x52, 0x81, 0x32, 0xd7, 0xbd, + 0xc2, 0x0f, 0xcf, 0x9a, 0xc6, 0x9f, 0xcf, 0x9a, 0xc6, 0xcd, 0xdf, 0x0c, 0x28, 0x2d, 0xf0, 0x81, + 0x3e, 0x82, 0x9c, 0x98, 0x8e, 0xa8, 0x6a, 0xae, 0xf2, 0xe6, 0xad, 0x7f, 0x67, 0xcf, 0x74, 0xa6, + 0x23, 0x8a, 0x55, 0x00, 0xba, 0x05, 0xcb, 0x12, 0xe4, 0xce, 0x1b, 0x0e, 0x66, 0xc7, 0xcd, 0xbc, + 0x84, 0xdb, 0x6d, 0x9c, 0x97, 0x2e, 0xdb, 0xbb, 0xf9, 0x0d, 0xe4, 0x64, 0x08, 0x5a, 0x83, 0xaa, + 0xf3, 0x68, 0xdf, 0x72, 0x7b, 0x7b, 0xdd, 0x7d, 0xeb, 0xbe, 0xfd, 0xc0, 0xb6, 0xda, 0xd5, 0x25, + 0x74, 0x09, 0x2a, 0xca, 0x8a, 0xad, 0xfd, 0x1d, 0xdb, 0x6a, 0xbb, 0x4e, 0xa7, 0x6a, 0xa0, 0x0a, + 0x14, 0x95, 0xf1, 0x61, 0xaf, 0xe3, 0x58, 0xed, 0x6a, 0x06, 0x5d, 0x84, 0x52, 0x82, 0xea, 0x74, + 0xa5, 0x29, 0x5b, 0xcf, 0x7d, 0xff, 0x53, 0x63, 0x29, 0x75, 0xad, 0x9f, 0x0d, 0x28, 0x24, 0xad, + 0x87, 0xee, 0x42, 0xe1, 0x90, 0xf0, 0x43, 0x41, 0x06, 0xbc, 0x66, 0xa8, 0x9e, 0x58, 0x3b, 0x73, + 0x2b, 0x87, 0x0c, 0xe2, 0x4e, 0x98, 0x63, 0x65, 0xdc, 0x50, 0x76, 0x6d, 0x18, 0xf0, 0x5a, 0xe6, + 0xcd, 0x71, 0x09, 0x16, 0x99, 0x90, 0x1b, 0x47, 0x8c, 0xd7, 0xb2, 0xff, 0x10, 0xd3, 0x8b, 0x58, + 0x1c, 0xa3, 0x70, 0xa9, 0x63, 0xdb, 0x90, 0x75, 0xc8, 0x00, 0xad, 0xc1, 0x05, 0x2e, 0x48, 0x24, + 0xf4, 0x03, 0xc7, 0x7a, 0x83, 0xaa, 0x90, 0xa5, 0x41, 0xcc, 0x2d, 0x96, 0x4b, 0x69, 0x11, 0x64, + 0xa0, 0x5f, 0x2f, 0x96, 0xcb, 0x54, 0xaa, 0x6f, 0x21, 0xdb, 0x8b, 0xd8, 0x79, 0x52, 0x8d, 0x23, + 0x96, 0xa4, 0x1a, 0x47, 0x0c, 0x35, 0xa1, 0xe8, 0xf9, 0x7c, 0xc4, 0xc8, 0xd4, 0x95, 0x1e, 0xfd, + 0xf0, 0x21, 0x36, 0xf5, 0x22, 0xb6, 0xc8, 0x36, 0x6c, 0x09, 0x41, 0xfa, 0x87, 0x92, 0x83, 0xf3, + 0xab, 0xd4, 0x7f, 0xe9, 0x9c, 0x58, 0xca, 0xe4, 0x01, 0x4b, 0x0b, 0x52, 0x66, 0xc2, 0x72, 0x3f, + 0x0c, 0x04, 0x0d, 0xb4, 0x38, 0x49, 0xc2, 0x4f, 0x3f, 0x87, 0xad, 0x60, 0x8a, 0x13, 0x50, 0xea, + 0xd8, 0x9f, 0xc2, 0x85, 0x5d, 0xea, 0xf9, 0x44, 0x5f, 0xde, 0x57, 0xdf, 0x56, 0x97, 0xf7, 0xd1, + 0x35, 0x58, 0x19, 0xfa, 0x43, 0xea, 0xaa, 0x97, 0xa0, 0x49, 0x29, 0x48, 0x83, 0xec, 0xdd, 0x54, + 0x86, 0xbf, 0xb2, 0x52, 0x98, 0x19, 0x43, 0x75, 0x28, 0x7c, 0x37, 0xa6, 0x5c, 0xd6, 0x5f, 0xdd, + 0x77, 0x05, 0xcf, 0xf7, 0xa8, 0x07, 0xd5, 0x51, 0x14, 0x1e, 0xf9, 0x1e, 0xf5, 0x5c, 0x12, 0xf0, + 0x27, 0x34, 0x4a, 0xda, 0xe9, 0x9d, 0xd7, 0x3c, 0x2e, 0xc6, 0xcc, 0xfd, 0x18, 0xbd, 0xa5, 0xc0, + 0x89, 0x40, 0x8d, 0x16, 0xac, 0x1c, 0x7d, 0x22, 0xd5, 0x37, 0xd6, 0x81, 0xec, 0x39, 0x54, 0x65, + 0x99, 0x06, 0x4a, 0x04, 0xd0, 0x5d, 0xb8, 0x42, 0x18, 0x0b, 0x9f, 0x70, 0x77, 0x38, 0x66, 0xc2, + 0x1f, 0x31, 0x3a, 0x3f, 0x9e, 0x24, 0xb2, 0x80, 0xdf, 0xd2, 0xee, 0xdd, 0xd8, 0x9b, 0x7c, 0xd8, + 0x84, 0x4b, 0x71, 0x9c, 0x86, 0x2b, 0x45, 0xd2, 0x13, 0xa0, 0x80, 0x2f, 0x6a, 0x97, 0xc6, 0x4a, + 0xc5, 0xe1, 0xe8, 0x21, 0x5c, 0x7a, 0xec, 0xcb, 0x41, 0x23, 0x08, 0x63, 0x53, 0x37, 0xa2, 0x7c, + 0xcc, 0x04, 0x57, 0xe2, 0x5f, 0xdc, 0xbc, 0xf1, 0x5a, 0x0a, 0x1c, 0x89, 0xc4, 0x1a, 0x88, 0x2f, + 0xaa, 0xe8, 0xb4, 0xa9, 0xce, 0xa1, 0xbc, 0x48, 0xd2, 0x7c, 0x3e, 0x19, 0xa9, 0xf9, 0x74, 0x1f, + 0x8a, 0x64, 0xde, 0x95, 0x09, 0xe7, 0xd7, 0xce, 0x7c, 0xf0, 0xa4, 0x73, 0x63, 0xaa, 0xd3, 0x51, + 0x27, 0xc5, 0x4e, 0x95, 0xfd, 0x57, 0x03, 0xa0, 0xc7, 0x69, 0x14, 0x7f, 0xfb, 0xff, 0xe9, 0x77, + 0x05, 0x62, 0xf3, 0xf1, 0x5c, 0x4a, 0x40, 0x8c, 0x69, 0x10, 0x93, 0x63, 0xf9, 0x3d, 0xa8, 0xc4, + 0x35, 0x73, 0xfd, 0xc0, 0xa3, 0x13, 0x2a, 0x6b, 0x97, 0xdd, 0x28, 0xe1, 0x72, 0x6c, 0xb6, 0xb5, + 0x55, 0xf2, 0x33, 0xe6, 0x34, 0x52, 0x55, 0x5a, 0xc1, 0x6a, 0x9d, 0xba, 0xd0, 0x2f, 0x06, 0x54, + 0x4f, 0xf3, 0x8e, 0xf6, 0x60, 0x39, 0xa9, 0x95, 0x56, 0x4d, 0xf3, 0x8d, 0xb5, 0x32, 0x35, 0x23, + 0x7a, 0x17, 0xb3, 0x99, 0x24, 0xa9, 0x3f, 0x84, 0xd5, 0xb4, 0x1b, 0xdd, 0x80, 0xd5, 0xb8, 0x81, + 0xd4, 0xd1, 0x15, 0x6f, 0x25, 0x5c, 0xd4, 0x36, 0x75, 0x6e, 0xa9, 0x5e, 0x47, 0xa1, 0xa0, 0x3c, + 0x56, 0x2a, 0xbd, 0x79, 0x6d, 0x49, 0xee, 0x41, 0x7e, 0x9f, 0x44, 0x64, 0xc8, 0xd1, 0xbb, 0x50, + 0x19, 0x92, 0x89, 0x2b, 0x3b, 0xc0, 0x65, 0x34, 0x18, 0x88, 0xc3, 0x38, 0x73, 0x69, 0x48, 0x26, + 0x0e, 0x9d, 0x88, 0x1d, 0x65, 0x3c, 0x89, 0x7d, 0xff, 0x47, 0x03, 0x56, 0xd3, 0x43, 0x1c, 0x5d, + 0x87, 0xab, 0x72, 0x02, 0x3d, 0x72, 0xbb, 0x96, 0xe3, 0xd8, 0x7b, 0x9f, 0x9d, 0x9a, 0x52, 0x75, + 0xb8, 0xbc, 0xe8, 0xb6, 0xbe, 0xb4, 0xf0, 0xa3, 0xce, 0x9e, 0x55, 0x35, 0xd0, 0x35, 0xb8, 0xb2, + 0xe8, 0x7b, 0xd0, 0xd9, 0xd9, 0xe9, 0x7c, 0x65, 0xe1, 0x6e, 0x35, 0x83, 0x6a, 0xb0, 0xb6, 0xe8, + 0xdc, 0xed, 0x39, 0xbd, 0xad, 0x9d, 0x6a, 0xf6, 0x6c, 0xca, 0x5d, 0x6b, 0xcf, 0xb1, 0x3b, 0x7b, + 0xdd, 0x6a, 0x4e, 0xcf, 0xb6, 0xed, 0xcf, 0x9f, 0xcf, 0x1a, 0xc6, 0x8b, 0x59, 0xc3, 0xf8, 0x63, + 0xd6, 0x30, 0x9e, 0xbe, 0x6a, 0x2c, 0xbd, 0x78, 0xd5, 0x58, 0x7a, 0xf9, 0xaa, 0xb1, 0xf4, 0xb5, + 0x39, 0xf0, 0xc5, 0xe1, 0xf8, 0xc0, 0xec, 0x87, 0xc3, 0x96, 0x2e, 0xd0, 0x07, 0x8c, 0x1c, 0xf0, + 0x78, 0xdd, 0x3a, 0xba, 0xd3, 0x9a, 0xc4, 0xbf, 0xae, 0x52, 0xcd, 0xf8, 0x41, 0x5e, 0xc9, 0xc3, + 0x9d, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x93, 0x8d, 0xe6, 0x1f, 0xd7, 0x0a, 0x00, 0x00, +} + +func (this *Post) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Post) + if !ok { + that2, ok := that.(Post) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.SubspaceID != that1.SubspaceID { + return false + } + if this.ID != that1.ID { + return false + } + if this.ExternalID != that1.ExternalID { + return false + } + if this.Text != that1.Text { + return false + } + if !this.Entities.Equal(that1.Entities) { + return false + } + if this.Author != that1.Author { + return false + } + if this.ConversationID != that1.ConversationID { + return false + } + if len(this.ReferencedPosts) != len(that1.ReferencedPosts) { + return false + } + for i := range this.ReferencedPosts { + if !this.ReferencedPosts[i].Equal(&that1.ReferencedPosts[i]) { + return false + } + } + if this.ReplySettings != that1.ReplySettings { + return false + } + if !this.CreationDate.Equal(that1.CreationDate) { + return false + } + if that1.LastEditedDate == nil { + if this.LastEditedDate != nil { + return false + } + } else if !this.LastEditedDate.Equal(*that1.LastEditedDate) { + return false + } + return true +} +func (this *PostReference) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PostReference) + if !ok { + that2, ok := that.(PostReference) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Type != that1.Type { + return false + } + if this.PostID != that1.PostID { + return false + } + return true +} +func (this *Entities) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Entities) + if !ok { + that2, ok := that.(Entities) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.Hashtags) != len(that1.Hashtags) { + return false + } + for i := range this.Hashtags { + if !this.Hashtags[i].Equal(&that1.Hashtags[i]) { + return false + } + } + if len(this.Mentions) != len(that1.Mentions) { + return false + } + for i := range this.Mentions { + if !this.Mentions[i].Equal(&that1.Mentions[i]) { + return false + } + } + if len(this.Urls) != len(that1.Urls) { + return false + } + for i := range this.Urls { + if !this.Urls[i].Equal(&that1.Urls[i]) { + return false + } + } + return true +} +func (this *Tag) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Tag) + if !ok { + that2, ok := that.(Tag) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Start != that1.Start { + return false + } + if this.End != that1.End { + return false + } + if this.Tag != that1.Tag { + return false + } + return true +} +func (this *Url) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Url) + if !ok { + that2, ok := that.(Url) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Start != that1.Start { + return false + } + if this.End != that1.End { + return false + } + if this.Url != that1.Url { + return false + } + if this.DisplayUrl != that1.DisplayUrl { + return false + } + return true +} +func (this *Attachment) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Attachment) + if !ok { + that2, ok := that.(Attachment) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.SubspaceID != that1.SubspaceID { + return false + } + if this.PostID != that1.PostID { + return false + } + if this.ID != that1.ID { + return false + } + if !this.Content.Equal(that1.Content) { + return false + } + return true +} +func (this *Media) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Media) + if !ok { + that2, ok := that.(Media) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Uri != that1.Uri { + return false + } + if this.MimeType != that1.MimeType { + return false + } + return true +} +func (this *Poll) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Poll) + if !ok { + that2, ok := that.(Poll) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Question != that1.Question { + return false + } + if len(this.ProvidedAnswers) != len(that1.ProvidedAnswers) { + return false + } + for i := range this.ProvidedAnswers { + if !this.ProvidedAnswers[i].Equal(&that1.ProvidedAnswers[i]) { + return false + } + } + if !this.EndDate.Equal(that1.EndDate) { + return false + } + if this.AllowsMultipleAnswers != that1.AllowsMultipleAnswers { + return false + } + if this.AllowsAnswerEdits != that1.AllowsAnswerEdits { + return false + } + if !this.FinalTallyResults.Equal(that1.FinalTallyResults) { + return false + } + return true +} +func (this *Poll_ProvidedAnswer) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Poll_ProvidedAnswer) + if !ok { + that2, ok := that.(Poll_ProvidedAnswer) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Text != that1.Text { + return false + } + if len(this.Attachments) != len(that1.Attachments) { + return false + } + for i := range this.Attachments { + if !this.Attachments[i].Equal(&that1.Attachments[i]) { + return false + } + } + return true +} +func (this *UserAnswer) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*UserAnswer) + if !ok { + that2, ok := that.(UserAnswer) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.SubspaceID != that1.SubspaceID { + return false + } + if this.PostID != that1.PostID { + return false + } + if this.PollID != that1.PollID { + return false + } + if len(this.AnswersIndexes) != len(that1.AnswersIndexes) { + return false + } + for i := range this.AnswersIndexes { + if this.AnswersIndexes[i] != that1.AnswersIndexes[i] { + return false + } + } + if this.User != that1.User { + return false + } + return true +} +func (this *PollTallyResults) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PollTallyResults) + if !ok { + that2, ok := that.(PollTallyResults) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.Results) != len(that1.Results) { + return false + } + for i := range this.Results { + if !this.Results[i].Equal(&that1.Results[i]) { + return false + } + } + return true +} +func (this *PollTallyResults_AnswerResult) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PollTallyResults_AnswerResult) + if !ok { + that2, ok := that.(PollTallyResults_AnswerResult) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.AnswerIndex != that1.AnswerIndex { + return false + } + if this.Votes != that1.Votes { + return false + } + return true +} +func (this *Params) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Params) + if !ok { + that2, ok := that.(Params) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.MaxTextLength != that1.MaxTextLength { + return false + } + return true +} +func (m *Post) 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 *Post) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Post) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.LastEditedDate != nil { + n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastEditedDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastEditedDate):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintModels(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x5a + } + n2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreationDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationDate):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintModels(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x52 + if m.ReplySettings != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.ReplySettings)) + i-- + dAtA[i] = 0x48 + } + if len(m.ReferencedPosts) > 0 { + for iNdEx := len(m.ReferencedPosts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ReferencedPosts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if m.ConversationID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.ConversationID)) + i-- + dAtA[i] = 0x38 + } + if len(m.Author) > 0 { + i -= len(m.Author) + copy(dAtA[i:], m.Author) + i = encodeVarintModels(dAtA, i, uint64(len(m.Author))) + i-- + dAtA[i] = 0x32 + } + if m.Entities != nil { + { + size, err := m.Entities.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintModels(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0x22 + } + if len(m.ExternalID) > 0 { + i -= len(m.ExternalID) + copy(dAtA[i:], m.ExternalID) + i = encodeVarintModels(dAtA, i, uint64(len(m.ExternalID))) + i-- + dAtA[i] = 0x1a + } + if m.ID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.ID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *PostReference) 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 *PostReference) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PostReference) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PostID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.Type != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Entities) 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 *Entities) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Entities) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Urls) > 0 { + for iNdEx := len(m.Urls) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Urls[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Mentions) > 0 { + for iNdEx := len(m.Mentions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Mentions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Hashtags) > 0 { + for iNdEx := len(m.Hashtags) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Hashtags[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Tag) 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 *Tag) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Tag) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Tag) > 0 { + i -= len(m.Tag) + copy(dAtA[i:], m.Tag) + i = encodeVarintModels(dAtA, i, uint64(len(m.Tag))) + i-- + dAtA[i] = 0x1a + } + if m.End != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.End)) + i-- + dAtA[i] = 0x10 + } + if m.Start != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.Start)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Url) 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 *Url) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Url) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.DisplayUrl) > 0 { + i -= len(m.DisplayUrl) + copy(dAtA[i:], m.DisplayUrl) + i = encodeVarintModels(dAtA, i, uint64(len(m.DisplayUrl))) + i-- + dAtA[i] = 0x22 + } + if len(m.Url) > 0 { + i -= len(m.Url) + copy(dAtA[i:], m.Url) + i = encodeVarintModels(dAtA, i, uint64(len(m.Url))) + i-- + dAtA[i] = 0x1a + } + if m.End != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.End)) + i-- + dAtA[i] = 0x10 + } + if m.Start != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.Start)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Attachment) 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 *Attachment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Attachment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Content != nil { + { + size, err := m.Content.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.ID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.ID)) + i-- + dAtA[i] = 0x18 + } + if m.PostID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Media) 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 *Media) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Media) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.MimeType) > 0 { + i -= len(m.MimeType) + copy(dAtA[i:], m.MimeType) + i = encodeVarintModels(dAtA, i, uint64(len(m.MimeType))) + i-- + dAtA[i] = 0x1a + } + if len(m.Uri) > 0 { + i -= len(m.Uri) + copy(dAtA[i:], m.Uri) + i = encodeVarintModels(dAtA, i, uint64(len(m.Uri))) + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} + +func (m *Poll) 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 *Poll) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Poll) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.FinalTallyResults != nil { + { + size, err := m.FinalTallyResults.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + if m.AllowsAnswerEdits { + i-- + if m.AllowsAnswerEdits { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.AllowsMultipleAnswers { + i-- + if m.AllowsMultipleAnswers { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EndDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate):]) + if err6 != nil { + return 0, err6 + } + i -= n6 + i = encodeVarintModels(dAtA, i, uint64(n6)) + i-- + dAtA[i] = 0x1a + if len(m.ProvidedAnswers) > 0 { + for iNdEx := len(m.ProvidedAnswers) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ProvidedAnswers[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Question) > 0 { + i -= len(m.Question) + copy(dAtA[i:], m.Question) + i = encodeVarintModels(dAtA, i, uint64(len(m.Question))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Poll_ProvidedAnswer) 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 *Poll_ProvidedAnswer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Poll_ProvidedAnswer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Attachments) > 0 { + for iNdEx := len(m.Attachments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attachments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintModels(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *UserAnswer) 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 *UserAnswer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UserAnswer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.User) > 0 { + i -= len(m.User) + copy(dAtA[i:], m.User) + i = encodeVarintModels(dAtA, i, uint64(len(m.User))) + i-- + dAtA[i] = 0x2a + } + if len(m.AnswersIndexes) > 0 { + dAtA8 := make([]byte, len(m.AnswersIndexes)*10) + var j7 int + for _, num := range m.AnswersIndexes { + for num >= 1<<7 { + dAtA8[j7] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j7++ + } + dAtA8[j7] = uint8(num) + j7++ + } + i -= j7 + copy(dAtA[i:], dAtA8[:j7]) + i = encodeVarintModels(dAtA, i, uint64(j7)) + i-- + dAtA[i] = 0x22 + } + if m.PollID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.PollID)) + i-- + dAtA[i] = 0x18 + } + if m.PostID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *PollTallyResults) 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 *PollTallyResults) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PollTallyResults) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Results) > 0 { + for iNdEx := len(m.Results) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Results[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintModels(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PollTallyResults_AnswerResult) 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 *PollTallyResults_AnswerResult) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PollTallyResults_AnswerResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Votes != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.Votes)) + i-- + dAtA[i] = 0x10 + } + if m.AnswerIndex != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.AnswerIndex)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Params) 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 *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MaxTextLength != 0 { + i = encodeVarintModels(dAtA, i, uint64(m.MaxTextLength)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintModels(dAtA []byte, offset int, v uint64) int { + offset -= sovModels(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Post) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovModels(uint64(m.SubspaceID)) + } + if m.ID != 0 { + n += 1 + sovModels(uint64(m.ID)) + } + l = len(m.ExternalID) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + l = len(m.Text) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + if m.Entities != nil { + l = m.Entities.Size() + n += 1 + l + sovModels(uint64(l)) + } + l = len(m.Author) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + if m.ConversationID != 0 { + n += 1 + sovModels(uint64(m.ConversationID)) + } + if len(m.ReferencedPosts) > 0 { + for _, e := range m.ReferencedPosts { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + if m.ReplySettings != 0 { + n += 1 + sovModels(uint64(m.ReplySettings)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationDate) + n += 1 + l + sovModels(uint64(l)) + if m.LastEditedDate != nil { + l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastEditedDate) + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *PostReference) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovModels(uint64(m.Type)) + } + if m.PostID != 0 { + n += 1 + sovModels(uint64(m.PostID)) + } + return n +} + +func (m *Entities) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Hashtags) > 0 { + for _, e := range m.Hashtags { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + if len(m.Mentions) > 0 { + for _, e := range m.Mentions { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + if len(m.Urls) > 0 { + for _, e := range m.Urls { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + return n +} + +func (m *Tag) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Start != 0 { + n += 1 + sovModels(uint64(m.Start)) + } + if m.End != 0 { + n += 1 + sovModels(uint64(m.End)) + } + l = len(m.Tag) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *Url) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Start != 0 { + n += 1 + sovModels(uint64(m.Start)) + } + if m.End != 0 { + n += 1 + sovModels(uint64(m.End)) + } + l = len(m.Url) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + l = len(m.DisplayUrl) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *Attachment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovModels(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovModels(uint64(m.PostID)) + } + if m.ID != 0 { + n += 1 + sovModels(uint64(m.ID)) + } + if m.Content != nil { + l = m.Content.Size() + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *Media) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Uri) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + l = len(m.MimeType) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *Poll) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Question) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + if len(m.ProvidedAnswers) > 0 { + for _, e := range m.ProvidedAnswers { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EndDate) + n += 1 + l + sovModels(uint64(l)) + if m.AllowsMultipleAnswers { + n += 2 + } + if m.AllowsAnswerEdits { + n += 2 + } + if m.FinalTallyResults != nil { + l = m.FinalTallyResults.Size() + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *Poll_ProvidedAnswer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Text) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + if len(m.Attachments) > 0 { + for _, e := range m.Attachments { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + return n +} + +func (m *UserAnswer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovModels(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovModels(uint64(m.PostID)) + } + if m.PollID != 0 { + n += 1 + sovModels(uint64(m.PollID)) + } + if len(m.AnswersIndexes) > 0 { + l = 0 + for _, e := range m.AnswersIndexes { + l += sovModels(uint64(e)) + } + n += 1 + sovModels(uint64(l)) + l + } + l = len(m.User) + if l > 0 { + n += 1 + l + sovModels(uint64(l)) + } + return n +} + +func (m *PollTallyResults) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Results) > 0 { + for _, e := range m.Results { + l = e.Size() + n += 1 + l + sovModels(uint64(l)) + } + } + return n +} + +func (m *PollTallyResults_AnswerResult) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.AnswerIndex != 0 { + n += 1 + sovModels(uint64(m.AnswerIndex)) + } + if m.Votes != 0 { + n += 1 + sovModels(uint64(m.Votes)) + } + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MaxTextLength != 0 { + n += 1 + sovModels(uint64(m.MaxTextLength)) + } + return n +} + +func sovModels(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozModels(x uint64) (n int) { + return sovModels(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Post) 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 ErrIntOverflowModels + } + 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: Post: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Post: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + m.ID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExternalID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExternalID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Entities == nil { + m.Entities = &Entities{} + } + if err := m.Entities.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Author", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Author = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConversationID", wireType) + } + m.ConversationID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConversationID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReferencedPosts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReferencedPosts = append(m.ReferencedPosts, PostReference{}) + if err := m.ReferencedPosts[len(m.ReferencedPosts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReplySettings", wireType) + } + m.ReplySettings = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReplySettings |= ReplySetting(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreationDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.CreationDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastEditedDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LastEditedDate == nil { + m.LastEditedDate = new(time.Time) + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.LastEditedDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PostReference) 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 ErrIntOverflowModels + } + 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: PostReference: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PostReference: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= PostReference_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Entities) 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 ErrIntOverflowModels + } + 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: Entities: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Entities: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hashtags", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hashtags = append(m.Hashtags, Tag{}) + if err := m.Hashtags[len(m.Hashtags)-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 Mentions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Mentions = append(m.Mentions, Tag{}) + if err := m.Mentions[len(m.Mentions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Urls", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Urls = append(m.Urls, Url{}) + if err := m.Urls[len(m.Urls)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Tag) 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 ErrIntOverflowModels + } + 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: Tag: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Tag: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Start", wireType) + } + m.Start = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Start |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field End", wireType) + } + m.End = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.End |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Tag", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Tag = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Url) 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 ErrIntOverflowModels + } + 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: Url: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Url: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Start", wireType) + } + m.Start = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Start |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field End", wireType) + } + m.End = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.End |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Url = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DisplayUrl", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DisplayUrl = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Attachment) 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 ErrIntOverflowModels + } + 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: Attachment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Attachment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + m.ID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Content", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Content == nil { + m.Content = &types.Any{} + } + if err := m.Content.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Media) 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 ErrIntOverflowModels + } + 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: Media: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Media: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uri", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Uri = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MimeType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MimeType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Poll) 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 ErrIntOverflowModels + } + 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: Poll: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Poll: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Question", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Question = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProvidedAnswers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProvidedAnswers = append(m.ProvidedAnswers, Poll_ProvidedAnswer{}) + if err := m.ProvidedAnswers[len(m.ProvidedAnswers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EndDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EndDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowsMultipleAnswers", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowsMultipleAnswers = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowsAnswerEdits", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowsAnswerEdits = bool(v != 0) + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FinalTallyResults", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FinalTallyResults == nil { + m.FinalTallyResults = &PollTallyResults{} + } + if err := m.FinalTallyResults.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Poll_ProvidedAnswer) 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 ErrIntOverflowModels + } + 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: ProvidedAnswer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProvidedAnswer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attachments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attachments = append(m.Attachments, Attachment{}) + if err := m.Attachments[len(m.Attachments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UserAnswer) 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 ErrIntOverflowModels + } + 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: UserAnswer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UserAnswer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PollID", wireType) + } + m.PollID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PollID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType == 0 { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AnswersIndexes = append(m.AnswersIndexes, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.AnswersIndexes) == 0 { + m.AnswersIndexes = make([]uint32, 0, elementCount) + } + for iNdEx < postIndex { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AnswersIndexes = append(m.AnswersIndexes, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field AnswersIndexes", wireType) + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + 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 ErrInvalidLengthModels + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.User = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PollTallyResults) 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 ErrIntOverflowModels + } + 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: PollTallyResults: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PollTallyResults: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthModels + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthModels + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Results = append(m.Results, PollTallyResults_AnswerResult{}) + if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PollTallyResults_AnswerResult) 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 ErrIntOverflowModels + } + 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: AnswerResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnswerResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AnswerIndex", wireType) + } + m.AnswerIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AnswerIndex |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Votes", wireType) + } + m.Votes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Votes |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) 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 ErrIntOverflowModels + } + 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: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxTextLength", wireType) + } + m.MaxTextLength = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModels + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxTextLength |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipModels(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModels + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipModels(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, ErrIntOverflowModels + } + 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, ErrIntOverflowModels + } + 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, ErrIntOverflowModels + } + 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, ErrInvalidLengthModels + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupModels + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthModels + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthModels = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowModels = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupModels = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/posts/types/models_test.go b/x/posts/types/models_test.go new file mode 100644 index 0000000000..575d755260 --- /dev/null +++ b/x/posts/types/models_test.go @@ -0,0 +1,1144 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func TestPost_Validate(t *testing.T) { + invalidEditDate := time.Date(2019, 1, 1, 12, 00, 00, 000, time.UTC) + testCases := []struct { + name string + post types.Post + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + post: types.NewPost( + 0, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Url{ + types.NewURL(1, 1, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + post: types.NewPost( + 1, + 0, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Url{ + types.NewURL(1, 1, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid entities returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, ""), + }, + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Url{ + types.NewURL(1, 1, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid hashtag index returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities([]types.Tag{ + types.NewTag(1, 10, "tag"), + }, nil, nil), + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid mention index returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities(nil, []types.Tag{ + types.NewTag(10, 1, "tag"), + }, nil), + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid url index returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities(nil, nil, []types.Url{ + types.NewURL(10, 1, "URL", "Display URL"), + }), + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid author address returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid conversation id returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + &invalidEditDate, + ), + shouldErr: true, + }, + { + name: "invalid post reference returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 0), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid post reference id returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 2), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid reply setting returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_UNSPECIFIED, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: true, + }, + { + name: "invalid creation date returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Time{}, + nil, + ), + shouldErr: true, + }, + { + name: "zero-value last edited date returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + &time.Time{}, + ), + shouldErr: true, + }, + { + name: "last edited date before creation date returns error", + post: types.NewPost( + 1, + 2, + "External id", + "Text", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 1, "tag"), + }, + []types.Tag{ + types.NewTag(2, 3, "tag"), + }, + []types.Url{ + types.NewURL(3, 4, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + &invalidEditDate, + ), + shouldErr: true, + }, + { + name: "valid post returns no error", + post: types.NewPost( + 1, + 2, + "External id", + "This is a post text that does not contain any useful information", + "cosmos1eqpa6mv2jgevukaqtjmx5535vhc3mm3cf458zg", + 1, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.post.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestPostReference_Validate(t *testing.T) { + testCases := []struct { + name string + reference types.PostReference + shouldErr bool + }{ + { + name: "invalid reference type returns error", + reference: types.NewPostReference(types.TYPE_UNSPECIFIED, 1), + shouldErr: true, + }, + { + name: "invalid post id returns error", + reference: types.NewPostReference(types.TYPE_QUOTED, 0), + shouldErr: true, + }, + { + name: "valid reference returns no error", + reference: types.NewPostReference(types.TYPE_QUOTED, 1), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.reference.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestEntities_Validate(t *testing.T) { + testCases := []struct { + name string + entities *types.Entities + shouldErr bool + }{ + { + name: "empty entities returns error", + entities: types.NewEntities(nil, nil, nil), + shouldErr: true, + }, + { + name: "invalid hashtag returns error", + entities: types.NewEntities( + []types.Tag{ + types.NewTag(0, 0, ""), + }, + nil, + nil, + ), + shouldErr: true, + }, + { + name: "mention returns error", + entities: types.NewEntities( + nil, + []types.Tag{ + types.NewTag(0, 0, ""), + }, + nil, + ), + shouldErr: true, + }, + { + name: "invalid url returns error", + entities: types.NewEntities( + nil, + nil, + []types.Url{ + types.NewURL(0, 0, "", ""), + }, + ), + shouldErr: true, + }, + { + name: "overlapping hashtags return error", + entities: types.NewEntities( + []types.Tag{ + types.NewTag(1, 5, "First tag"), + types.NewTag(4, 10, "Second tag"), + }, + nil, + nil, + ), + shouldErr: true, + }, + { + name: "overlapping mentions return error", + entities: types.NewEntities( + nil, + []types.Tag{ + types.NewTag(1, 5, "First mention"), + types.NewTag(5, 10, "Second mention"), + }, + nil, + ), + shouldErr: true, + }, + { + name: "overlapping URLs return error", + entities: types.NewEntities( + nil, + nil, + []types.Url{ + types.NewURL(3, 4, "second url", "Second URL"), + types.NewURL(1, 5, "first url", "First URL"), + }, + ), + shouldErr: true, + }, + { + name: "overlapping hashtag and mention return error", + entities: types.NewEntities( + []types.Tag{ + types.NewTag(1, 10, "First tag"), + }, + []types.Tag{ + types.NewTag(9, 15, "First mention"), + }, + nil, + ), + shouldErr: true, + }, + { + name: "overlapping hashtag and url return error", + entities: types.NewEntities( + []types.Tag{ + types.NewTag(1, 10, "First tag"), + }, + nil, + []types.Url{ + types.NewURL(1, 5, "first url", "First URL"), + }, + ), + shouldErr: true, + }, + { + name: "overlapping mention and url return error", + entities: types.NewEntities( + nil, + []types.Tag{ + types.NewTag(8, 30, "First mention"), + }, + []types.Url{ + types.NewURL(1, 15, "first url", "First URL"), + }, + ), + shouldErr: true, + }, + { + name: "valid entities returns no error", + entities: types.NewEntities( + []types.Tag{ + types.NewTag(1, 2, "first_tag"), + }, + []types.Tag{ + types.NewTag(3, 4, "first_mention"), + }, + []types.Url{ + types.NewURL(5, 6, "url", "Display URL"), + }, + ), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.entities.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestTag_Validate(t *testing.T) { + testCases := []struct { + name string + tag types.Tag + shouldErr bool + }{ + { + name: "invalid start and end values return error", + tag: types.NewTag(1, 0, "My tag"), + shouldErr: true, + }, + { + name: "invalid tag value returns error", + tag: types.NewTag(1, 10, " "), + shouldErr: true, + }, + { + name: "valid tag returns no error", + tag: types.NewTag(1, 10, "My tag"), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.tag.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestUrl_Validate(t *testing.T) { + testCases := []struct { + name string + url types.Url + shouldErr bool + }{ + { + name: "invalid start and end values returns error", + url: types.NewURL(10, 1, "url", ""), + shouldErr: true, + }, + { + name: "invalid url value returns error", + url: types.NewURL(1, 10, "", "Display URL"), + shouldErr: true, + }, + { + name: "valid url returns no error", + url: types.NewURL(1, 10, "ftp://user:password@example.com", "Display URL"), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.url.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestAttachment_Validate(t *testing.T) { + testCases := []struct { + name string + attachment types.Attachment + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + attachment: types.NewAttachment(0, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + shouldErr: true, + }, + { + name: "invalid post id returns error", + attachment: types.NewAttachment(1, 0, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + shouldErr: true, + }, + { + name: "invalid id returns error", + attachment: types.NewAttachment(1, 1, 0, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + shouldErr: true, + }, + { + name: "invalid attachment type returns error", + attachment: types.Attachment{SubspaceID: 1, PostID: 1, ID: 1, Content: nil}, + shouldErr: true, + }, + { + name: "valid poll attachment returns no error", + attachment: types.NewAttachment(1, 1, 1, types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + )), + shouldErr: false, + }, + { + name: "valid media attachment returns no error", + attachment: types.NewAttachment(1, 1, 1, types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + )), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.attachment.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestAttachments_Validate(t *testing.T) { + testCases := []struct { + name string + attachments types.Attachments + shouldErr bool + }{ + { + name: "duplicated attachment id returns error", + attachments: types.Attachments{ + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + }, + shouldErr: true, + }, + { + name: "empty attachments return no error", + attachments: types.Attachments{}, + shouldErr: false, + }, + { + name: "valid attachments return no error", + attachments: types.Attachments{ + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + types.NewAttachment(1, 1, 2, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + }, + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.attachments.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestMedia_Validate(t *testing.T) { + testCases := []struct { + name string + media *types.Media + shouldErr bool + }{ + { + name: "invalid uri returns error", + media: types.NewMedia("", "image/png"), + shouldErr: true, + }, + { + name: "invalid mime type returns error", + media: types.NewMedia("ftp://user:password@example.com/image.png", ""), + shouldErr: true, + }, + { + name: "valid media returns no error", + media: types.NewMedia("ftp://user:password@example.com/image.png", "image/png"), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.media.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestPoll_Validate(t *testing.T) { + testCases := []struct { + name string + poll *types.Poll + shouldErr bool + }{ + { + name: "invalid question returns error", + poll: types.NewPoll( + "", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "empty provided answers return error", + poll: types.NewPoll( + "What animal is best?", + nil, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "single provided answer returns error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "invalid provided answer returns error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "duplicated provided answers return error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Cat", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "invalid end date returns error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Time{}, + false, + false, + nil, + ), + shouldErr: true, + }, + { + name: "invalid poll results return error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 1), + types.NewAnswerResult(0, 1), + }), + ), + shouldErr: true, + }, + { + name: "valid poll returns no error", + poll: types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(0, 1), + types.NewAnswerResult(1, 1), + }), + ), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.poll.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestPoll_ProvidedAnswer_Validate(t *testing.T) { + testCases := []struct { + name string + answer types.Poll_ProvidedAnswer + shouldErr bool + }{ + { + name: "invalid text returns error", + answer: types.NewProvidedAnswer("", nil), + shouldErr: true, + }, + { + name: "invalid attachment returns error", + answer: types.NewProvidedAnswer("Cat", []types.Attachment{ + types.NewAttachment(1, 1, 0, types.NewMedia("", "")), + }), + shouldErr: true, + }, + { + name: "duplicated attachment returns error", + answer: types.NewProvidedAnswer("Cat", []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + }), + shouldErr: true, + }, + { + name: "valid answer returns no error", + answer: types.NewProvidedAnswer("Cat", []types.Attachment{ + types.NewAttachment(1, 1, 1, types.NewMedia("ftp://user:password@example.com/image.png", "image/png")), + }), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.answer.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestUserAnswer_Validate(t *testing.T) { + testCases := []struct { + name string + answer types.UserAnswer + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + answer: types.NewUserAnswer(0, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: true, + }, + { + name: "invalid post id returns error", + answer: types.NewUserAnswer(1, 0, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: true, + }, + { + name: "invalid poll id returns error", + answer: types.NewUserAnswer(1, 1, 0, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: true, + }, + { + name: "empty answer indexes returns error", + answer: types.NewUserAnswer(1, 1, 1, nil, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: true, + }, + { + name: "duplicated answer indexes returns error", + answer: types.NewUserAnswer(1, 1, 1, []uint32{1, 1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: true, + }, + { + name: "invalid user address returns error", + answer: types.NewUserAnswer(1, 1, 1, []uint32{1}, ""), + shouldErr: true, + }, + { + name: "valid answer returns no error", + answer: types.NewUserAnswer(1, 1, 1, []uint32{1}, "cosmos1jseuux3pktht0kkhlcsv4kqff3mql65udqs4jw"), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.answer.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} + +func TestPollTallyResults_Validate(t *testing.T) { + testCases := []struct { + name string + results *types.PollTallyResults + shouldErr bool + }{ + { + name: "empty answer results return error", + results: types.NewPollTallyResults(nil), + shouldErr: true, + }, + { + name: "duplicated answer results return error", + results: types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(1, 10), + types.NewAnswerResult(1, 10), + }), + shouldErr: true, + }, + { + name: "valid tally results return no error", + results: types.NewPollTallyResults([]types.PollTallyResults_AnswerResult{ + types.NewAnswerResult(1, 10), + types.NewAnswerResult(2, 10), + }), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.results.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/posts/types/msgs.go b/x/posts/types/msgs.go new file mode 100644 index 0000000000..04110d33e5 --- /dev/null +++ b/x/posts/types/msgs.go @@ -0,0 +1,413 @@ +package types + +import ( + "fmt" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + _ sdk.Msg = &MsgCreatePost{} + _ sdk.Msg = &MsgEditPost{} + _ sdk.Msg = &MsgAddPostAttachment{} + _ sdk.Msg = &MsgRemovePostAttachment{} + _ sdk.Msg = &MsgDeletePost{} + _ sdk.Msg = &MsgAnswerPoll{} +) + +// NewMsgCreatePost returns a new MsgCreatePost instance +func NewMsgCreatePost( + subspaceID uint64, + externalID string, + text string, + conversationID uint64, + replySettings ReplySetting, + entities *Entities, + attachments []AttachmentContent, + referencedPosts []PostReference, + author string, +) *MsgCreatePost { + attachmentsAnis := make([]*codectypes.Any, len(attachments)) + for i, attachment := range attachments { + attachmentAny, err := codectypes.NewAnyWithValue(attachment) + if err != nil { + panic("failed to pack attachment content to any type") + } + attachmentsAnis[i] = attachmentAny + } + + return &MsgCreatePost{ + SubspaceID: subspaceID, + ExternalID: externalID, + Text: text, + Entities: entities, + Attachments: attachmentsAnis, + Author: author, + ConversationID: conversationID, + ReplySettings: replySettings, + ReferencedPosts: referencedPosts, + } +} + +// Route implements sdk.Msg +func (msg MsgCreatePost) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgCreatePost) Type() string { return ActionCreatePost } + +// ValidateBasic implements sdk.Msg +func (msg MsgCreatePost) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + _, err := sdk.AccAddressFromBech32(msg.Author) + if err != nil { + return fmt.Errorf("invalid author address: %s", err) + } + + if msg.ReplySettings == REPLY_SETTING_UNSPECIFIED { + return fmt.Errorf("invalid reply setting: %s", msg.ReplySettings) + } + + if msg.Entities != nil { + err := msg.Entities.Validate() + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid entities: %s", err) + } + } + + for _, attachment := range msg.Attachments { + err = attachment.GetCachedValue().(AttachmentContent).Validate() + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid attachment: %s", err) + } + } + + for _, reference := range msg.ReferencedPosts { + err = reference.Validate() + if err != nil { + return fmt.Errorf("invalid post reference: %s", err) + } + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgCreatePost) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgCreatePost) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Author) + return []sdk.AccAddress{addr} +} + +// UnpackInterfaces implements codectypes.UnpackInterfacesMessage +func (msg *MsgCreatePost) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, attachment := range msg.Attachments { + var content AttachmentContent + err := unpacker.UnpackAny(attachment, &content) + if err != nil { + return err + } + } + + return nil +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewMsgEditPost returns a new MsgEditPost instance +func NewMsgEditPost( + subspaceID uint64, + postID uint64, + text string, + entities *Entities, + editor string, +) *MsgEditPost { + return &MsgEditPost{ + SubspaceID: subspaceID, + PostID: postID, + Text: text, + Entities: entities, + Editor: editor, + } +} + +// Route implements sdk.Msg +func (msg MsgEditPost) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgEditPost) Type() string { return ActionEditPost } + +// ValidateBasic implements sdk.Msg +func (msg MsgEditPost) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + if msg.PostID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %d", msg.PostID) + } + + if msg.Entities != nil { + err := msg.Entities.Validate() + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid entities: %s", err) + } + } + + _, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return fmt.Errorf("invalid editor address: %s", err) + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgEditPost) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgEditPost) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Editor) + return []sdk.AccAddress{addr} +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewMsgAddPostAttachment returns a new MsgAddPostAttachment instance +func NewMsgAddPostAttachment(subspaceID uint64, postID uint64, content AttachmentContent, editor string) *MsgAddPostAttachment { + contentAny, err := codectypes.NewAnyWithValue(content) + if err != nil { + panic(fmt.Errorf("failed to pack attachment content to any")) + } + + return &MsgAddPostAttachment{ + SubspaceID: subspaceID, + PostID: postID, + Content: contentAny, + Editor: editor, + } +} + +// Route implements sdk.Msg +func (msg MsgAddPostAttachment) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgAddPostAttachment) Type() string { return ActionAddPostAttachment } + +// ValidateBasic implements sdk.Msg +func (msg MsgAddPostAttachment) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + if msg.PostID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %d", msg.PostID) + } + + if msg.Content == nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid attachment content") + } + + _, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return fmt.Errorf("invalid editor address: %s", err) + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgAddPostAttachment) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgAddPostAttachment) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Editor) + return []sdk.AccAddress{addr} +} + +// UnpackInterfaces implements codectypes.UnpackInterfacesMessage +func (msg *MsgAddPostAttachment) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var content AttachmentContent + return unpacker.UnpackAny(msg.Content, &content) +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewMsgRemovePostAttachment returns a new MsgRemovePostAttachment instance +func NewMsgRemovePostAttachment(subspaceID uint64, postID uint64, attachmentID uint32, editor string) *MsgRemovePostAttachment { + return &MsgRemovePostAttachment{ + SubspaceID: subspaceID, + PostID: postID, + AttachmentID: attachmentID, + Editor: editor, + } +} + +// Route implements sdk.Msg +func (msg MsgRemovePostAttachment) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgRemovePostAttachment) Type() string { return ActionRemovePostAttachment } + +// ValidateBasic implements sdk.Msg +func (msg MsgRemovePostAttachment) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + if msg.PostID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %d", msg.PostID) + } + + if msg.AttachmentID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid attachment id: %d", msg.AttachmentID) + } + + _, err := sdk.AccAddressFromBech32(msg.Editor) + if err != nil { + return fmt.Errorf("invalid editor address: %s", err) + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgRemovePostAttachment) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgRemovePostAttachment) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Editor) + return []sdk.AccAddress{addr} +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewMsgDeletePost returns a new MsgDeletePost instance +func NewMsgDeletePost(subspaceID uint64, postID uint64, signer string) *MsgDeletePost { + return &MsgDeletePost{ + SubspaceID: subspaceID, + PostID: postID, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgDeletePost) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgDeletePost) Type() string { return ActionDeletePost } + +// ValidateBasic implements sdk.Msg +func (msg MsgDeletePost) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + if msg.PostID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %d", msg.PostID) + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return fmt.Errorf("invalid signer address: %s", err) + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgDeletePost) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgDeletePost) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Signer) + return []sdk.AccAddress{addr} +} + +// -------------------------------------------------------------------------------------------------------------------- + +// NewMsgAnswerPoll returns a new MsgAnswerPoll instance +func NewMsgAnswerPoll( + subspaceID uint64, + postID uint64, + pollID uint32, + answersIndexes []uint32, + signer string, +) *MsgAnswerPoll { + return &MsgAnswerPoll{ + SubspaceID: subspaceID, + PostID: postID, + PollID: pollID, + AnswersIndexes: answersIndexes, + Signer: signer, + } +} + +// Route implements sdk.Msg +func (msg MsgAnswerPoll) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgAnswerPoll) Type() string { return ActionAnswerPoll } + +// ValidateBasic implements sdk.Msg +func (msg MsgAnswerPoll) ValidateBasic() error { + if msg.SubspaceID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid subspace id: %d", msg.SubspaceID) + } + + if msg.PostID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %d", msg.PostID) + } + + if msg.PollID == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid poll id: %d", msg.PollID) + } + + if len(msg.AnswersIndexes) == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "at least one answer is required") + } + + // Check duplicated answers + answers := map[uint32]bool{} + for _, answer := range msg.AnswersIndexes { + if _, ok := answers[answer]; ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "duplicated answer index: %d", answer) + } + answers[answer] = true + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return fmt.Errorf("invalid signer address: %s", err) + } + + return nil +} + +// GetSignBytes implements sdk.Msg +func (msg MsgAnswerPoll) GetSignBytes() []byte { + return sdk.MustSortJSON(AminoCodec.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgAnswerPoll) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(msg.Signer) + return []sdk.AccAddress{addr} +} diff --git a/x/posts/types/msgs.pb.go b/x/posts/types/msgs.pb.go new file mode 100644 index 0000000000..0967d48245 --- /dev/null +++ b/x/posts/types/msgs.pb.go @@ -0,0 +1,3625 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: desmos/posts/v1/msgs.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// 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 + +// MsgCreatePost represents the message to be used to create a post. +type MsgCreatePost struct { + // Id of the subspace inside which the post must be created + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // (optional) External id for this post + ExternalID string `protobuf:"bytes,2,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty"` + // (optional) Text of the post + Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` + // (optional) Entities connected to this post + Entities *Entities `protobuf:"bytes,4,opt,name=entities,proto3" json:"entities,omitempty"` + // Attachments of the post + Attachments []*types.Any `protobuf:"bytes,5,rep,name=attachments,proto3" json:"attachments,omitempty"` + // Author of the post + Author string `protobuf:"bytes,6,opt,name=author,proto3" json:"author,omitempty"` + // (optional) Id of the original post of the conversation + ConversationID uint64 `protobuf:"varint,7,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` + // Reply settings of this post + ReplySettings ReplySetting `protobuf:"varint,8,opt,name=reply_settings,json=replySettings,proto3,enum=desmos.posts.v1.ReplySetting" json:"reply_settings,omitempty"` + // A list this posts references (either as a reply, repost or quote) + ReferencedPosts []PostReference `protobuf:"bytes,9,rep,name=referenced_posts,json=referencedPosts,proto3" json:"referenced_posts"` +} + +func (m *MsgCreatePost) Reset() { *m = MsgCreatePost{} } +func (m *MsgCreatePost) String() string { return proto.CompactTextString(m) } +func (*MsgCreatePost) ProtoMessage() {} +func (*MsgCreatePost) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{0} +} +func (m *MsgCreatePost) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreatePost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreatePost.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 *MsgCreatePost) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreatePost.Merge(m, src) +} +func (m *MsgCreatePost) XXX_Size() int { + return m.Size() +} +func (m *MsgCreatePost) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreatePost.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreatePost proto.InternalMessageInfo + +func (m *MsgCreatePost) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgCreatePost) GetExternalID() string { + if m != nil { + return m.ExternalID + } + return "" +} + +func (m *MsgCreatePost) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *MsgCreatePost) GetEntities() *Entities { + if m != nil { + return m.Entities + } + return nil +} + +func (m *MsgCreatePost) GetAttachments() []*types.Any { + if m != nil { + return m.Attachments + } + return nil +} + +func (m *MsgCreatePost) GetAuthor() string { + if m != nil { + return m.Author + } + return "" +} + +func (m *MsgCreatePost) GetConversationID() uint64 { + if m != nil { + return m.ConversationID + } + return 0 +} + +func (m *MsgCreatePost) GetReplySettings() ReplySetting { + if m != nil { + return m.ReplySettings + } + return REPLY_SETTING_UNSPECIFIED +} + +func (m *MsgCreatePost) GetReferencedPosts() []PostReference { + if m != nil { + return m.ReferencedPosts + } + return nil +} + +// MsgCreatePostResponse defines the Msg/CreatePost response type. +type MsgCreatePostResponse struct { + // Id of the newly created post + PostID uint64 `protobuf:"varint,1,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Creation date of the post + CreationDate time.Time `protobuf:"bytes,2,opt,name=creation_date,json=creationDate,proto3,stdtime" json:"creation_date"` +} + +func (m *MsgCreatePostResponse) Reset() { *m = MsgCreatePostResponse{} } +func (m *MsgCreatePostResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreatePostResponse) ProtoMessage() {} +func (*MsgCreatePostResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{1} +} +func (m *MsgCreatePostResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreatePostResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreatePostResponse.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 *MsgCreatePostResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreatePostResponse.Merge(m, src) +} +func (m *MsgCreatePostResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreatePostResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreatePostResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreatePostResponse proto.InternalMessageInfo + +func (m *MsgCreatePostResponse) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgCreatePostResponse) GetCreationDate() time.Time { + if m != nil { + return m.CreationDate + } + return time.Time{} +} + +// MsgEditPost represents the message to be used to edit a post. +type MsgEditPost struct { + // Id of the subspace inside which the post is + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to edit + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // New text of the post. If set to [do-not-modify] it will change the current + // post's text. + Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"` + // New entities connected to this post. These will always replace the current + // post's entities + Entities *Entities `protobuf:"bytes,4,opt,name=entities,proto3" json:"entities,omitempty"` + // Editor of the post + Editor string `protobuf:"bytes,5,opt,name=editor,proto3" json:"editor,omitempty"` +} + +func (m *MsgEditPost) Reset() { *m = MsgEditPost{} } +func (m *MsgEditPost) String() string { return proto.CompactTextString(m) } +func (*MsgEditPost) ProtoMessage() {} +func (*MsgEditPost) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{2} +} +func (m *MsgEditPost) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgEditPost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgEditPost.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 *MsgEditPost) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgEditPost.Merge(m, src) +} +func (m *MsgEditPost) XXX_Size() int { + return m.Size() +} +func (m *MsgEditPost) XXX_DiscardUnknown() { + xxx_messageInfo_MsgEditPost.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgEditPost proto.InternalMessageInfo + +func (m *MsgEditPost) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgEditPost) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgEditPost) GetText() string { + if m != nil { + return m.Text + } + return "" +} + +func (m *MsgEditPost) GetEntities() *Entities { + if m != nil { + return m.Entities + } + return nil +} + +func (m *MsgEditPost) GetEditor() string { + if m != nil { + return m.Editor + } + return "" +} + +// MsgCreatePostResponse defines the Msg/EditPost response type. +type MsgEditPostResponse struct { + // Edit date of the post + EditDate time.Time `protobuf:"bytes,1,opt,name=edit_date,json=editDate,proto3,stdtime" json:"edit_date"` +} + +func (m *MsgEditPostResponse) Reset() { *m = MsgEditPostResponse{} } +func (m *MsgEditPostResponse) String() string { return proto.CompactTextString(m) } +func (*MsgEditPostResponse) ProtoMessage() {} +func (*MsgEditPostResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{3} +} +func (m *MsgEditPostResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgEditPostResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgEditPostResponse.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 *MsgEditPostResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgEditPostResponse.Merge(m, src) +} +func (m *MsgEditPostResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgEditPostResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgEditPostResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgEditPostResponse proto.InternalMessageInfo + +func (m *MsgEditPostResponse) GetEditDate() time.Time { + if m != nil { + return m.EditDate + } + return time.Time{} +} + +// MsgDeletePost represents the message used when deleting a post. +type MsgDeletePost struct { + // Id of the subspace containing the post + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to be deleted + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // User that is deleting the post + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgDeletePost) Reset() { *m = MsgDeletePost{} } +func (m *MsgDeletePost) String() string { return proto.CompactTextString(m) } +func (*MsgDeletePost) ProtoMessage() {} +func (*MsgDeletePost) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{4} +} +func (m *MsgDeletePost) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDeletePost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDeletePost.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 *MsgDeletePost) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDeletePost.Merge(m, src) +} +func (m *MsgDeletePost) XXX_Size() int { + return m.Size() +} +func (m *MsgDeletePost) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDeletePost.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDeletePost proto.InternalMessageInfo + +func (m *MsgDeletePost) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgDeletePost) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgDeletePost) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +// MsgDeletePostResponse represents the Msg/DeletePost response type +type MsgDeletePostResponse struct { +} + +func (m *MsgDeletePostResponse) Reset() { *m = MsgDeletePostResponse{} } +func (m *MsgDeletePostResponse) String() string { return proto.CompactTextString(m) } +func (*MsgDeletePostResponse) ProtoMessage() {} +func (*MsgDeletePostResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{5} +} +func (m *MsgDeletePostResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDeletePostResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDeletePostResponse.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 *MsgDeletePostResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDeletePostResponse.Merge(m, src) +} +func (m *MsgDeletePostResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgDeletePostResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDeletePostResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDeletePostResponse proto.InternalMessageInfo + +// MsgAddPostAttachment represents the message that should be +// used when adding an attachment to post +type MsgAddPostAttachment struct { + // Id of the subspace containing the post + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to which to add the attachment + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Content of the attachment + Content *types.Any `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` + // Editor of the post + Editor string `protobuf:"bytes,4,opt,name=editor,proto3" json:"editor,omitempty"` +} + +func (m *MsgAddPostAttachment) Reset() { *m = MsgAddPostAttachment{} } +func (m *MsgAddPostAttachment) String() string { return proto.CompactTextString(m) } +func (*MsgAddPostAttachment) ProtoMessage() {} +func (*MsgAddPostAttachment) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{6} +} +func (m *MsgAddPostAttachment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddPostAttachment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddPostAttachment.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 *MsgAddPostAttachment) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddPostAttachment.Merge(m, src) +} +func (m *MsgAddPostAttachment) XXX_Size() int { + return m.Size() +} +func (m *MsgAddPostAttachment) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddPostAttachment.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddPostAttachment proto.InternalMessageInfo + +func (m *MsgAddPostAttachment) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgAddPostAttachment) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgAddPostAttachment) GetContent() *types.Any { + if m != nil { + return m.Content + } + return nil +} + +func (m *MsgAddPostAttachment) GetEditor() string { + if m != nil { + return m.Editor + } + return "" +} + +// MsgAddPostAttachmentResponse defines the Msg/AddPostAttachment response type. +type MsgAddPostAttachmentResponse struct { + // New id of the uploaded attachment + AttachmentID uint32 `protobuf:"varint,1,opt,name=attachment_id,json=attachmentId,proto3" json:"attachment_id,omitempty"` + // Edit date of the post + EditDate time.Time `protobuf:"bytes,2,opt,name=edit_date,json=editDate,proto3,stdtime" json:"edit_date"` +} + +func (m *MsgAddPostAttachmentResponse) Reset() { *m = MsgAddPostAttachmentResponse{} } +func (m *MsgAddPostAttachmentResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAddPostAttachmentResponse) ProtoMessage() {} +func (*MsgAddPostAttachmentResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{7} +} +func (m *MsgAddPostAttachmentResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAddPostAttachmentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAddPostAttachmentResponse.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 *MsgAddPostAttachmentResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAddPostAttachmentResponse.Merge(m, src) +} +func (m *MsgAddPostAttachmentResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAddPostAttachmentResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAddPostAttachmentResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAddPostAttachmentResponse proto.InternalMessageInfo + +func (m *MsgAddPostAttachmentResponse) GetAttachmentID() uint32 { + if m != nil { + return m.AttachmentID + } + return 0 +} + +func (m *MsgAddPostAttachmentResponse) GetEditDate() time.Time { + if m != nil { + return m.EditDate + } + return time.Time{} +} + +// MsgRemovePostAttachment represents the message to be used when +// removing an attachment from a post +type MsgRemovePostAttachment struct { + // Id of the subspace containing the post + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post from which to remove the attachment + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Id of the attachment to be removed + AttachmentID uint32 `protobuf:"varint,3,opt,name=attachment_id,json=attachmentId,proto3" json:"attachment_id,omitempty"` + // User that is removing the attachment + Editor string `protobuf:"bytes,4,opt,name=editor,proto3" json:"editor,omitempty"` +} + +func (m *MsgRemovePostAttachment) Reset() { *m = MsgRemovePostAttachment{} } +func (m *MsgRemovePostAttachment) String() string { return proto.CompactTextString(m) } +func (*MsgRemovePostAttachment) ProtoMessage() {} +func (*MsgRemovePostAttachment) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{8} +} +func (m *MsgRemovePostAttachment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemovePostAttachment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemovePostAttachment.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 *MsgRemovePostAttachment) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemovePostAttachment.Merge(m, src) +} +func (m *MsgRemovePostAttachment) XXX_Size() int { + return m.Size() +} +func (m *MsgRemovePostAttachment) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemovePostAttachment.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemovePostAttachment proto.InternalMessageInfo + +func (m *MsgRemovePostAttachment) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgRemovePostAttachment) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgRemovePostAttachment) GetAttachmentID() uint32 { + if m != nil { + return m.AttachmentID + } + return 0 +} + +func (m *MsgRemovePostAttachment) GetEditor() string { + if m != nil { + return m.Editor + } + return "" +} + +// MsgRemovePostAttachmentResponse defines the +// Msg/RemovePostAttachment response type. +type MsgRemovePostAttachmentResponse struct { + // Edit date of the post + EditDate time.Time `protobuf:"bytes,1,opt,name=edit_date,json=editDate,proto3,stdtime" json:"edit_date"` +} + +func (m *MsgRemovePostAttachmentResponse) Reset() { *m = MsgRemovePostAttachmentResponse{} } +func (m *MsgRemovePostAttachmentResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRemovePostAttachmentResponse) ProtoMessage() {} +func (*MsgRemovePostAttachmentResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{9} +} +func (m *MsgRemovePostAttachmentResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemovePostAttachmentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemovePostAttachmentResponse.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 *MsgRemovePostAttachmentResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemovePostAttachmentResponse.Merge(m, src) +} +func (m *MsgRemovePostAttachmentResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRemovePostAttachmentResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemovePostAttachmentResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemovePostAttachmentResponse proto.InternalMessageInfo + +func (m *MsgRemovePostAttachmentResponse) GetEditDate() time.Time { + if m != nil { + return m.EditDate + } + return time.Time{} +} + +// MsgAnswerPoll represents the message used to answer a poll +type MsgAnswerPoll struct { + // Id of the subspace containing the post + SubspaceID uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post that contains the poll to be answered + PostID uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Id of the poll to be answered + PollID uint32 `protobuf:"varint,3,opt,name=poll_id,json=pollId,proto3" json:"poll_id,omitempty"` + // Indexes of the answer inside the ProvidedAnswers array + AnswersIndexes []uint32 `protobuf:"varint,4,rep,packed,name=answers_indexes,json=answersIndexes,proto3" json:"answers_indexes,omitempty"` + // Address of the user answering the poll + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgAnswerPoll) Reset() { *m = MsgAnswerPoll{} } +func (m *MsgAnswerPoll) String() string { return proto.CompactTextString(m) } +func (*MsgAnswerPoll) ProtoMessage() {} +func (*MsgAnswerPoll) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{10} +} +func (m *MsgAnswerPoll) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAnswerPoll) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAnswerPoll.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 *MsgAnswerPoll) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAnswerPoll.Merge(m, src) +} +func (m *MsgAnswerPoll) XXX_Size() int { + return m.Size() +} +func (m *MsgAnswerPoll) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAnswerPoll.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAnswerPoll proto.InternalMessageInfo + +func (m *MsgAnswerPoll) GetSubspaceID() uint64 { + if m != nil { + return m.SubspaceID + } + return 0 +} + +func (m *MsgAnswerPoll) GetPostID() uint64 { + if m != nil { + return m.PostID + } + return 0 +} + +func (m *MsgAnswerPoll) GetPollID() uint32 { + if m != nil { + return m.PollID + } + return 0 +} + +func (m *MsgAnswerPoll) GetAnswersIndexes() []uint32 { + if m != nil { + return m.AnswersIndexes + } + return nil +} + +func (m *MsgAnswerPoll) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +// MsgAnswerPollResponse represents the MSg/AnswerPoll response type +type MsgAnswerPollResponse struct { +} + +func (m *MsgAnswerPollResponse) Reset() { *m = MsgAnswerPollResponse{} } +func (m *MsgAnswerPollResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAnswerPollResponse) ProtoMessage() {} +func (*MsgAnswerPollResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d9b1165b51f80014, []int{11} +} +func (m *MsgAnswerPollResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAnswerPollResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAnswerPollResponse.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 *MsgAnswerPollResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAnswerPollResponse.Merge(m, src) +} +func (m *MsgAnswerPollResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAnswerPollResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAnswerPollResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAnswerPollResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgCreatePost)(nil), "desmos.posts.v1.MsgCreatePost") + proto.RegisterType((*MsgCreatePostResponse)(nil), "desmos.posts.v1.MsgCreatePostResponse") + proto.RegisterType((*MsgEditPost)(nil), "desmos.posts.v1.MsgEditPost") + proto.RegisterType((*MsgEditPostResponse)(nil), "desmos.posts.v1.MsgEditPostResponse") + proto.RegisterType((*MsgDeletePost)(nil), "desmos.posts.v1.MsgDeletePost") + proto.RegisterType((*MsgDeletePostResponse)(nil), "desmos.posts.v1.MsgDeletePostResponse") + proto.RegisterType((*MsgAddPostAttachment)(nil), "desmos.posts.v1.MsgAddPostAttachment") + proto.RegisterType((*MsgAddPostAttachmentResponse)(nil), "desmos.posts.v1.MsgAddPostAttachmentResponse") + proto.RegisterType((*MsgRemovePostAttachment)(nil), "desmos.posts.v1.MsgRemovePostAttachment") + proto.RegisterType((*MsgRemovePostAttachmentResponse)(nil), "desmos.posts.v1.MsgRemovePostAttachmentResponse") + proto.RegisterType((*MsgAnswerPoll)(nil), "desmos.posts.v1.MsgAnswerPoll") + proto.RegisterType((*MsgAnswerPollResponse)(nil), "desmos.posts.v1.MsgAnswerPollResponse") +} + +func init() { proto.RegisterFile("desmos/posts/v1/msgs.proto", fileDescriptor_d9b1165b51f80014) } + +var fileDescriptor_d9b1165b51f80014 = []byte{ + // 888 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xbd, 0x6f, 0xdb, 0x46, + 0x14, 0xf7, 0x59, 0xb2, 0x2c, 0x3f, 0x59, 0x72, 0xca, 0xba, 0x8e, 0x42, 0xb8, 0xa2, 0xa0, 0x7e, + 0x69, 0x09, 0xd9, 0x28, 0x48, 0x97, 0x4e, 0x52, 0x64, 0xa0, 0x1c, 0xd4, 0x06, 0x8c, 0x87, 0xa2, + 0x8b, 0x41, 0x89, 0x2f, 0x34, 0x01, 0x8a, 0x27, 0xf0, 0xce, 0xaa, 0x3d, 0x74, 0xee, 0x9a, 0xb1, + 0x7f, 0x48, 0xd1, 0xb1, 0x63, 0x11, 0xa0, 0x4b, 0xc6, 0x4e, 0x6a, 0x20, 0xff, 0x23, 0xc5, 0x1d, + 0x3f, 0x1d, 0xd2, 0x81, 0x6b, 0xc4, 0xd9, 0xee, 0xee, 0xf7, 0x7b, 0x77, 0xef, 0xfd, 0xde, 0x07, + 0x09, 0xaa, 0x83, 0x6c, 0x4e, 0x99, 0xb1, 0xa0, 0x8c, 0x33, 0x63, 0xf9, 0xc8, 0x98, 0x33, 0x97, + 0xe9, 0x8b, 0x90, 0x72, 0xaa, 0xec, 0x45, 0x98, 0x2e, 0x31, 0x7d, 0xf9, 0x48, 0xdd, 0x77, 0xa9, + 0x4b, 0x25, 0x66, 0x88, 0x55, 0x44, 0x53, 0x1f, 0xb8, 0x94, 0xba, 0x3e, 0x1a, 0x72, 0x37, 0x3d, + 0x7b, 0x61, 0xd8, 0xc1, 0x45, 0x0c, 0x69, 0x6f, 0x43, 0xdc, 0x9b, 0x23, 0xe3, 0xf6, 0x7c, 0x11, + 0x13, 0x0e, 0x0b, 0xcf, 0x53, 0x07, 0xfd, 0xd8, 0x81, 0xde, 0x9b, 0x0a, 0x34, 0x27, 0xcc, 0x7d, + 0x1a, 0xa2, 0xcd, 0xf1, 0x19, 0x65, 0x5c, 0x31, 0xa0, 0xc1, 0xce, 0xa6, 0x6c, 0x61, 0xcf, 0xf0, + 0xc4, 0x73, 0xda, 0xa4, 0x4b, 0xfa, 0xd5, 0x51, 0x6b, 0xbd, 0xd2, 0xe0, 0x79, 0x7c, 0x6c, 0x8e, + 0x2d, 0x48, 0x28, 0xa6, 0x23, 0x0c, 0xf0, 0x9c, 0x63, 0x18, 0xd8, 0xbe, 0x30, 0xd8, 0xec, 0x92, + 0xfe, 0x4e, 0x64, 0x70, 0x14, 0x1f, 0x0b, 0x83, 0x84, 0x62, 0x3a, 0x8a, 0x02, 0x55, 0x8e, 0xe7, + 0xbc, 0x5d, 0x11, 0x4c, 0x4b, 0xae, 0x95, 0x27, 0x50, 0xc7, 0x80, 0x7b, 0xdc, 0x43, 0xd6, 0xae, + 0x76, 0x49, 0xbf, 0x31, 0x78, 0xa0, 0xbf, 0xa5, 0x8d, 0x7e, 0x14, 0x13, 0xac, 0x94, 0xaa, 0x7c, + 0x03, 0x0d, 0x9b, 0x73, 0x7b, 0x76, 0x3a, 0xc7, 0x80, 0xb3, 0xf6, 0x56, 0xb7, 0xd2, 0x6f, 0x0c, + 0xf6, 0xf5, 0x48, 0x13, 0x3d, 0xd1, 0x44, 0x1f, 0x06, 0x17, 0x56, 0x9e, 0xa8, 0x1c, 0x40, 0xcd, + 0x3e, 0xe3, 0xa7, 0x34, 0x6c, 0xd7, 0xa4, 0x13, 0xf1, 0x4e, 0xf9, 0x16, 0xf6, 0x66, 0x34, 0x58, + 0x62, 0xc8, 0x6c, 0xee, 0xd1, 0x40, 0xc4, 0xb3, 0x2d, 0x05, 0x50, 0xd6, 0x2b, 0xad, 0xf5, 0x34, + 0x07, 0x99, 0x63, 0xab, 0x95, 0xa7, 0x9a, 0x8e, 0x32, 0x86, 0x56, 0x88, 0x0b, 0xff, 0xe2, 0x84, + 0x21, 0xe7, 0x5e, 0xe0, 0xb2, 0x76, 0xbd, 0x4b, 0xfa, 0xad, 0xc1, 0xa7, 0x85, 0x48, 0x2c, 0x41, + 0x7b, 0x1e, 0xb1, 0xac, 0x66, 0x98, 0xdb, 0x31, 0xe5, 0x07, 0xb8, 0x17, 0xe2, 0x0b, 0x0c, 0x31, + 0x98, 0xa1, 0x73, 0x22, 0x4d, 0xda, 0x3b, 0x32, 0xae, 0x4e, 0xe1, 0x1e, 0x91, 0x30, 0x2b, 0x21, + 0x8f, 0xaa, 0xaf, 0x56, 0xda, 0x86, 0xb5, 0x97, 0x59, 0x0b, 0x98, 0xf5, 0x7e, 0x25, 0xf0, 0xc9, + 0x95, 0x14, 0x5b, 0xc8, 0x16, 0x34, 0x60, 0xa8, 0x7c, 0x06, 0xdb, 0xe2, 0xaa, 0x2c, 0xcd, 0xb0, + 0x5e, 0x69, 0x35, 0x41, 0x31, 0xc7, 0x56, 0x4d, 0x40, 0xa6, 0xa3, 0x98, 0xd0, 0x9c, 0x09, 0x53, + 0x21, 0x87, 0x63, 0x73, 0x94, 0x09, 0x6e, 0x0c, 0xd4, 0x82, 0xc8, 0xc7, 0x49, 0xe1, 0x8d, 0xea, + 0xc2, 0x91, 0x97, 0xff, 0x6a, 0xc4, 0xda, 0x4d, 0x4c, 0xc7, 0x36, 0xc7, 0xde, 0x5f, 0x04, 0x1a, + 0x13, 0xe6, 0x1e, 0x39, 0x1e, 0xbf, 0x5d, 0xa9, 0xe5, 0x1c, 0xde, 0xbc, 0xd6, 0xe1, 0xf7, 0x58, + 0x5e, 0x07, 0x50, 0x43, 0xc7, 0xe3, 0x34, 0x6c, 0x6f, 0x45, 0x65, 0x12, 0xed, 0x7a, 0x3f, 0xc2, + 0xc7, 0xb9, 0x38, 0x52, 0x3d, 0x87, 0xb0, 0x23, 0x08, 0x91, 0x4c, 0xe4, 0x7f, 0xc8, 0x54, 0x17, + 0x66, 0x52, 0xa2, 0x5f, 0x64, 0x3b, 0x8e, 0xd1, 0xc7, 0xdb, 0xb6, 0xe3, 0x8d, 0x34, 0x3a, 0x80, + 0x1a, 0xf3, 0xdc, 0x00, 0xc3, 0x58, 0xa5, 0x78, 0xd7, 0xbb, 0x2f, 0x4b, 0x25, 0x7b, 0x3e, 0x09, + 0xad, 0xf7, 0x3b, 0x81, 0xfd, 0x09, 0x73, 0x87, 0x8e, 0x2c, 0xaa, 0x61, 0xda, 0x4a, 0x77, 0xe4, + 0x9f, 0x0e, 0xdb, 0x33, 0x1a, 0x70, 0x0c, 0xa2, 0x34, 0x5e, 0xd7, 0xd3, 0x09, 0x29, 0x97, 0xa8, + 0xea, 0x95, 0x44, 0xfd, 0x46, 0xe0, 0xb0, 0xcc, 0xed, 0x34, 0x65, 0x4f, 0xa0, 0x99, 0xcd, 0x85, + 0x24, 0x80, 0xe6, 0xe8, 0xde, 0x7a, 0xa5, 0xed, 0x66, 0x74, 0x73, 0x6c, 0xed, 0x66, 0x34, 0xd3, + 0xb9, 0x9a, 0xe9, 0xcd, 0x5b, 0x65, 0xfa, 0x4f, 0x02, 0xf7, 0x27, 0xcc, 0xb5, 0x70, 0x4e, 0x97, + 0xf8, 0x41, 0x44, 0x2d, 0xc4, 0x5a, 0xb9, 0x51, 0xac, 0xd7, 0x69, 0xeb, 0x80, 0x76, 0x8d, 0xff, + 0xef, 0xb3, 0x21, 0xfe, 0x26, 0xb2, 0x23, 0x86, 0x01, 0xfb, 0x19, 0xc3, 0x67, 0xd4, 0xf7, 0xef, + 0x48, 0x1c, 0x49, 0xf2, 0xfd, 0x4c, 0x96, 0x98, 0xe4, 0xfb, 0x11, 0xc9, 0x17, 0x5f, 0xae, 0xaf, + 0x60, 0xcf, 0x96, 0x8e, 0xb0, 0x13, 0x2f, 0x70, 0xf0, 0x5c, 0x4e, 0x93, 0x4a, 0xbf, 0x69, 0xb5, + 0xe2, 0x63, 0x33, 0x3a, 0xcd, 0xf5, 0xd7, 0x56, 0x49, 0x7f, 0x65, 0xc1, 0x24, 0x4a, 0x0d, 0xfe, + 0xa8, 0x42, 0x65, 0xc2, 0x5c, 0xe5, 0x18, 0x20, 0xf7, 0x2d, 0x2e, 0x4e, 0xfc, 0x2b, 0x83, 0x5c, + 0xfd, 0xf2, 0xdd, 0x78, 0x9a, 0x87, 0xef, 0xa1, 0x9e, 0x0e, 0xdd, 0xc3, 0x32, 0x9b, 0x04, 0x55, + 0x3f, 0x7f, 0x17, 0x9a, 0xde, 0x77, 0x0c, 0x90, 0x1b, 0x51, 0xa5, 0x5e, 0x66, 0x78, 0xb9, 0x97, + 0xc5, 0x19, 0xa3, 0x78, 0xf0, 0x51, 0x71, 0xbe, 0x7c, 0x51, 0x66, 0x5c, 0xa0, 0xa9, 0x0f, 0x6f, + 0x44, 0x4b, 0x9f, 0x0a, 0x61, 0xbf, 0xb4, 0xf1, 0xfa, 0x65, 0xd7, 0x94, 0x31, 0xd5, 0xaf, 0x6f, + 0xca, 0xcc, 0x8b, 0x96, 0xab, 0xe2, 0x52, 0xd1, 0x32, 0xbc, 0x5c, 0xb4, 0x62, 0xe1, 0x8c, 0xbe, + 0x7b, 0xb5, 0xee, 0x90, 0xd7, 0xeb, 0x0e, 0x79, 0xb3, 0xee, 0x90, 0x97, 0x97, 0x9d, 0x8d, 0xd7, + 0x97, 0x9d, 0x8d, 0x7f, 0x2e, 0x3b, 0x1b, 0x3f, 0xe9, 0xae, 0xc7, 0x4f, 0xcf, 0xa6, 0xfa, 0x8c, + 0xce, 0x8d, 0xe8, 0xae, 0x87, 0xbe, 0x3d, 0x65, 0xf1, 0xda, 0x58, 0x3e, 0x36, 0xce, 0xe3, 0x9f, + 0x42, 0x7e, 0xb1, 0x40, 0x36, 0xad, 0xc9, 0x8e, 0x7c, 0xfc, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x07, 0x0c, 0x91, 0x4e, 0xb0, 0x0a, 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 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // CreatePost allows to create a new post + CreatePost(ctx context.Context, in *MsgCreatePost, opts ...grpc.CallOption) (*MsgCreatePostResponse, error) + // EditPost allows to edit an existing post + EditPost(ctx context.Context, in *MsgEditPost, opts ...grpc.CallOption) (*MsgEditPostResponse, error) + // DeletePost allows to delete an existing post + DeletePost(ctx context.Context, in *MsgDeletePost, opts ...grpc.CallOption) (*MsgDeletePostResponse, error) + // AddPostAttachment allows to add a new attachment to a post + AddPostAttachment(ctx context.Context, in *MsgAddPostAttachment, opts ...grpc.CallOption) (*MsgAddPostAttachmentResponse, error) + // RemovePostAttachment allows to remove an attachment from a post + RemovePostAttachment(ctx context.Context, in *MsgRemovePostAttachment, opts ...grpc.CallOption) (*MsgRemovePostAttachmentResponse, error) + // AnswerPoll allows to answer a post poll + AnswerPoll(ctx context.Context, in *MsgAnswerPoll, opts ...grpc.CallOption) (*MsgAnswerPollResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) CreatePost(ctx context.Context, in *MsgCreatePost, opts ...grpc.CallOption) (*MsgCreatePostResponse, error) { + out := new(MsgCreatePostResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/CreatePost", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) EditPost(ctx context.Context, in *MsgEditPost, opts ...grpc.CallOption) (*MsgEditPostResponse, error) { + out := new(MsgEditPostResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/EditPost", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) DeletePost(ctx context.Context, in *MsgDeletePost, opts ...grpc.CallOption) (*MsgDeletePostResponse, error) { + out := new(MsgDeletePostResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/DeletePost", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) AddPostAttachment(ctx context.Context, in *MsgAddPostAttachment, opts ...grpc.CallOption) (*MsgAddPostAttachmentResponse, error) { + out := new(MsgAddPostAttachmentResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/AddPostAttachment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RemovePostAttachment(ctx context.Context, in *MsgRemovePostAttachment, opts ...grpc.CallOption) (*MsgRemovePostAttachmentResponse, error) { + out := new(MsgRemovePostAttachmentResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/RemovePostAttachment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) AnswerPoll(ctx context.Context, in *MsgAnswerPoll, opts ...grpc.CallOption) (*MsgAnswerPollResponse, error) { + out := new(MsgAnswerPollResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Msg/AnswerPoll", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // CreatePost allows to create a new post + CreatePost(context.Context, *MsgCreatePost) (*MsgCreatePostResponse, error) + // EditPost allows to edit an existing post + EditPost(context.Context, *MsgEditPost) (*MsgEditPostResponse, error) + // DeletePost allows to delete an existing post + DeletePost(context.Context, *MsgDeletePost) (*MsgDeletePostResponse, error) + // AddPostAttachment allows to add a new attachment to a post + AddPostAttachment(context.Context, *MsgAddPostAttachment) (*MsgAddPostAttachmentResponse, error) + // RemovePostAttachment allows to remove an attachment from a post + RemovePostAttachment(context.Context, *MsgRemovePostAttachment) (*MsgRemovePostAttachmentResponse, error) + // AnswerPoll allows to answer a post poll + AnswerPoll(context.Context, *MsgAnswerPoll) (*MsgAnswerPollResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) CreatePost(ctx context.Context, req *MsgCreatePost) (*MsgCreatePostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreatePost not implemented") +} +func (*UnimplementedMsgServer) EditPost(ctx context.Context, req *MsgEditPost) (*MsgEditPostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EditPost not implemented") +} +func (*UnimplementedMsgServer) DeletePost(ctx context.Context, req *MsgDeletePost) (*MsgDeletePostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeletePost not implemented") +} +func (*UnimplementedMsgServer) AddPostAttachment(ctx context.Context, req *MsgAddPostAttachment) (*MsgAddPostAttachmentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddPostAttachment not implemented") +} +func (*UnimplementedMsgServer) RemovePostAttachment(ctx context.Context, req *MsgRemovePostAttachment) (*MsgRemovePostAttachmentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemovePostAttachment not implemented") +} +func (*UnimplementedMsgServer) AnswerPoll(ctx context.Context, req *MsgAnswerPoll) (*MsgAnswerPollResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AnswerPoll not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_CreatePost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreatePost) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreatePost(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/CreatePost", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreatePost(ctx, req.(*MsgCreatePost)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_EditPost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgEditPost) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).EditPost(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/EditPost", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).EditPost(ctx, req.(*MsgEditPost)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_DeletePost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgDeletePost) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).DeletePost(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/DeletePost", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).DeletePost(ctx, req.(*MsgDeletePost)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_AddPostAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAddPostAttachment) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AddPostAttachment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/AddPostAttachment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AddPostAttachment(ctx, req.(*MsgAddPostAttachment)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RemovePostAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRemovePostAttachment) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RemovePostAttachment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/RemovePostAttachment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RemovePostAttachment(ctx, req.(*MsgRemovePostAttachment)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_AnswerPoll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAnswerPoll) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AnswerPoll(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Msg/AnswerPoll", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AnswerPoll(ctx, req.(*MsgAnswerPoll)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "desmos.posts.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreatePost", + Handler: _Msg_CreatePost_Handler, + }, + { + MethodName: "EditPost", + Handler: _Msg_EditPost_Handler, + }, + { + MethodName: "DeletePost", + Handler: _Msg_DeletePost_Handler, + }, + { + MethodName: "AddPostAttachment", + Handler: _Msg_AddPostAttachment_Handler, + }, + { + MethodName: "RemovePostAttachment", + Handler: _Msg_RemovePostAttachment_Handler, + }, + { + MethodName: "AnswerPoll", + Handler: _Msg_AnswerPoll_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "desmos/posts/v1/msgs.proto", +} + +func (m *MsgCreatePost) 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 *MsgCreatePost) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreatePost) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ReferencedPosts) > 0 { + for iNdEx := len(m.ReferencedPosts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ReferencedPosts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } + } + if m.ReplySettings != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.ReplySettings)) + i-- + dAtA[i] = 0x40 + } + if m.ConversationID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.ConversationID)) + i-- + dAtA[i] = 0x38 + } + if len(m.Author) > 0 { + i -= len(m.Author) + copy(dAtA[i:], m.Author) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Author))) + i-- + dAtA[i] = 0x32 + } + if len(m.Attachments) > 0 { + for iNdEx := len(m.Attachments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attachments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.Entities != nil { + { + size, err := m.Entities.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0x1a + } + if len(m.ExternalID) > 0 { + i -= len(m.ExternalID) + copy(dAtA[i:], m.ExternalID) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.ExternalID))) + i-- + dAtA[i] = 0x12 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgCreatePostResponse) 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 *MsgCreatePostResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreatePostResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + n2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreationDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationDate):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintMsgs(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x12 + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgEditPost) 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 *MsgEditPost) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgEditPost) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Editor) > 0 { + i -= len(m.Editor) + copy(dAtA[i:], m.Editor) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Editor))) + i-- + dAtA[i] = 0x2a + } + if m.Entities != nil { + { + size, err := m.Entities.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.Text) > 0 { + i -= len(m.Text) + copy(dAtA[i:], m.Text) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Text))) + i-- + dAtA[i] = 0x1a + } + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgEditPostResponse) 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 *MsgEditPostResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgEditPostResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EditDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate):]) + if err4 != nil { + return 0, err4 + } + i -= n4 + i = encodeVarintMsgs(dAtA, i, uint64(n4)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgDeletePost) 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 *MsgDeletePost) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDeletePost) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgDeletePostResponse) 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 *MsgDeletePostResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDeletePostResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgAddPostAttachment) 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 *MsgAddPostAttachment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddPostAttachment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Editor) > 0 { + i -= len(m.Editor) + copy(dAtA[i:], m.Editor) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Editor))) + i-- + dAtA[i] = 0x22 + } + if m.Content != nil { + { + size, err := m.Content.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintMsgs(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgAddPostAttachmentResponse) 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 *MsgAddPostAttachmentResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAddPostAttachmentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EditDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate):]) + if err6 != nil { + return 0, err6 + } + i -= n6 + i = encodeVarintMsgs(dAtA, i, uint64(n6)) + i-- + dAtA[i] = 0x12 + if m.AttachmentID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.AttachmentID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgRemovePostAttachment) 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 *MsgRemovePostAttachment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemovePostAttachment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Editor) > 0 { + i -= len(m.Editor) + copy(dAtA[i:], m.Editor) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Editor))) + i-- + dAtA[i] = 0x22 + } + if m.AttachmentID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.AttachmentID)) + i-- + dAtA[i] = 0x18 + } + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgRemovePostAttachmentResponse) 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 *MsgRemovePostAttachmentResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemovePostAttachmentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.EditDate, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate):]) + if err7 != nil { + return 0, err7 + } + i -= n7 + i = encodeVarintMsgs(dAtA, i, uint64(n7)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgAnswerPoll) 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 *MsgAnswerPoll) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAnswerPoll) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintMsgs(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + if len(m.AnswersIndexes) > 0 { + dAtA9 := make([]byte, len(m.AnswersIndexes)*10) + var j8 int + for _, num := range m.AnswersIndexes { + for num >= 1<<7 { + dAtA9[j8] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j8++ + } + dAtA9[j8] = uint8(num) + j8++ + } + i -= j8 + copy(dAtA[i:], dAtA9[:j8]) + i = encodeVarintMsgs(dAtA, i, uint64(j8)) + i-- + dAtA[i] = 0x22 + } + if m.PollID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PollID)) + i-- + dAtA[i] = 0x18 + } + if m.PostID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.PostID)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceID != 0 { + i = encodeVarintMsgs(dAtA, i, uint64(m.SubspaceID)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgAnswerPollResponse) 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 *MsgAnswerPollResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAnswerPollResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintMsgs(dAtA []byte, offset int, v uint64) int { + offset -= sovMsgs(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgCreatePost) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + l = len(m.ExternalID) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + l = len(m.Text) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + if m.Entities != nil { + l = m.Entities.Size() + n += 1 + l + sovMsgs(uint64(l)) + } + if len(m.Attachments) > 0 { + for _, e := range m.Attachments { + l = e.Size() + n += 1 + l + sovMsgs(uint64(l)) + } + } + l = len(m.Author) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + if m.ConversationID != 0 { + n += 1 + sovMsgs(uint64(m.ConversationID)) + } + if m.ReplySettings != 0 { + n += 1 + sovMsgs(uint64(m.ReplySettings)) + } + if len(m.ReferencedPosts) > 0 { + for _, e := range m.ReferencedPosts { + l = e.Size() + n += 1 + l + sovMsgs(uint64(l)) + } + } + return n +} + +func (m *MsgCreatePostResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationDate) + n += 1 + l + sovMsgs(uint64(l)) + return n +} + +func (m *MsgEditPost) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + l = len(m.Text) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + if m.Entities != nil { + l = m.Entities.Size() + n += 1 + l + sovMsgs(uint64(l)) + } + l = len(m.Editor) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + return n +} + +func (m *MsgEditPostResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate) + n += 1 + l + sovMsgs(uint64(l)) + return n +} + +func (m *MsgDeletePost) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + return n +} + +func (m *MsgDeletePostResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgAddPostAttachment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + if m.Content != nil { + l = m.Content.Size() + n += 1 + l + sovMsgs(uint64(l)) + } + l = len(m.Editor) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + return n +} + +func (m *MsgAddPostAttachmentResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.AttachmentID != 0 { + n += 1 + sovMsgs(uint64(m.AttachmentID)) + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate) + n += 1 + l + sovMsgs(uint64(l)) + return n +} + +func (m *MsgRemovePostAttachment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + if m.AttachmentID != 0 { + n += 1 + sovMsgs(uint64(m.AttachmentID)) + } + l = len(m.Editor) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + return n +} + +func (m *MsgRemovePostAttachmentResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.EditDate) + n += 1 + l + sovMsgs(uint64(l)) + return n +} + +func (m *MsgAnswerPoll) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceID != 0 { + n += 1 + sovMsgs(uint64(m.SubspaceID)) + } + if m.PostID != 0 { + n += 1 + sovMsgs(uint64(m.PostID)) + } + if m.PollID != 0 { + n += 1 + sovMsgs(uint64(m.PollID)) + } + if len(m.AnswersIndexes) > 0 { + l = 0 + for _, e := range m.AnswersIndexes { + l += sovMsgs(uint64(e)) + } + n += 1 + sovMsgs(uint64(l)) + l + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovMsgs(uint64(l)) + } + return n +} + +func (m *MsgAnswerPollResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovMsgs(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMsgs(x uint64) (n int) { + return sovMsgs(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgCreatePost) 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 ErrIntOverflowMsgs + } + 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: MsgCreatePost: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreatePost: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExternalID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExternalID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Entities == nil { + m.Entities = &Entities{} + } + if err := m.Entities.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attachments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Attachments = append(m.Attachments, &types.Any{}) + if err := m.Attachments[len(m.Attachments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Author", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Author = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConversationID", wireType) + } + m.ConversationID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ConversationID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReplySettings", wireType) + } + m.ReplySettings = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReplySettings |= ReplySetting(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReferencedPosts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReferencedPosts = append(m.ReferencedPosts, PostReference{}) + if err := m.ReferencedPosts[len(m.ReferencedPosts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreatePostResponse) 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 ErrIntOverflowMsgs + } + 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: MsgCreatePostResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreatePostResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CreationDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.CreationDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgEditPost) 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 ErrIntOverflowMsgs + } + 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: MsgEditPost: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgEditPost: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Text", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Text = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Entities == nil { + m.Entities = &Entities{} + } + if err := m.Entities.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Editor", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Editor = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgEditPostResponse) 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 ErrIntOverflowMsgs + } + 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: MsgEditPostResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgEditPostResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EditDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EditDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDeletePost) 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 ErrIntOverflowMsgs + } + 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: MsgDeletePost: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDeletePost: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDeletePostResponse) 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 ErrIntOverflowMsgs + } + 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: MsgDeletePostResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDeletePostResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAddPostAttachment) 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 ErrIntOverflowMsgs + } + 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: MsgAddPostAttachment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddPostAttachment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Content", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Content == nil { + m.Content = &types.Any{} + } + if err := m.Content.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Editor", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Editor = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAddPostAttachmentResponse) 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 ErrIntOverflowMsgs + } + 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: MsgAddPostAttachmentResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAddPostAttachmentResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AttachmentID", wireType) + } + m.AttachmentID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AttachmentID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EditDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EditDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemovePostAttachment) 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 ErrIntOverflowMsgs + } + 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: MsgRemovePostAttachment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemovePostAttachment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AttachmentID", wireType) + } + m.AttachmentID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AttachmentID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Editor", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Editor = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemovePostAttachmentResponse) 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 ErrIntOverflowMsgs + } + 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: MsgRemovePostAttachmentResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemovePostAttachmentResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EditDate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.EditDate, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAnswerPoll) 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 ErrIntOverflowMsgs + } + 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: MsgAnswerPoll: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAnswerPoll: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceID", wireType) + } + m.SubspaceID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostID", wireType) + } + m.PostID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostID |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PollID", wireType) + } + m.PollID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PollID |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType == 0 { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AnswersIndexes = append(m.AnswersIndexes, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthMsgs + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.AnswersIndexes) == 0 { + m.AnswersIndexes = make([]uint32, 0, elementCount) + } + for iNdEx < postIndex { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AnswersIndexes = append(m.AnswersIndexes, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field AnswersIndexes", wireType) + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMsgs + } + 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 ErrInvalidLengthMsgs + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMsgs + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAnswerPollResponse) 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 ErrIntOverflowMsgs + } + 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: MsgAnswerPollResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAnswerPollResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipMsgs(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMsgs + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMsgs(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, ErrIntOverflowMsgs + } + 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, ErrIntOverflowMsgs + } + 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, ErrIntOverflowMsgs + } + 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, ErrInvalidLengthMsgs + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMsgs + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMsgs + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMsgs = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMsgs = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMsgs = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/posts/types/msgs_test.go b/x/posts/types/msgs_test.go new file mode 100644 index 0000000000..07418b5137 --- /dev/null +++ b/x/posts/types/msgs_test.go @@ -0,0 +1,689 @@ +package types_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +var attachments = []types.AttachmentContent{ + types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", + ), + types.NewPoll( + "What animal is best?", + []types.Poll_ProvidedAnswer{ + types.NewProvidedAnswer("Cat", nil), + types.NewProvidedAnswer("Dog", nil), + }, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + false, + false, + nil, + ), +} + +var msgCreatePost = types.NewMsgCreatePost( + 1, + "External ID", + "This is a text", + 1, + types.REPLY_SETTING_EVERYONE, + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + attachments, + []types.PostReference{ + types.NewPostReference(types.TYPE_QUOTED, 1), + }, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgCreatePost_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgCreatePost.Route()) +} + +func TestMsgCreatePost_Type(t *testing.T) { + require.Equal(t, types.ActionCreatePost, msgCreatePost.Type()) +} + +func TestMsgCreatePost_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgCreatePost + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgCreatePost( + 0, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + msgCreatePost.ReplySettings, + msgCreatePost.Entities, + attachments, + msgCreatePost.ReferencedPosts, + msgCreatePost.Author, + ), + shouldErr: true, + }, + { + name: "invalid reply settings returns error", + msg: types.NewMsgCreatePost( + msgCreatePost.SubspaceID, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + types.REPLY_SETTING_UNSPECIFIED, + msgCreatePost.Entities, + attachments, + msgCreatePost.ReferencedPosts, + msgCreatePost.Author, + ), + shouldErr: true, + }, + { + name: "invalid entities returns error", + msg: types.NewMsgCreatePost( + msgCreatePost.SubspaceID, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + msgCreatePost.ReplySettings, + types.NewEntities([]types.Tag{ + types.NewTag(1, 1, "My tag"), + types.NewTag(1, 1, "My tag"), + }, nil, nil), + attachments, + msgCreatePost.ReferencedPosts, + msgCreatePost.Author, + ), + shouldErr: true, + }, + { + name: "invalid attachments returns error", + msg: types.NewMsgCreatePost( + msgCreatePost.SubspaceID, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + msgCreatePost.ReplySettings, + msgCreatePost.Entities, + []types.AttachmentContent{ + types.NewMedia("", ""), + }, + msgCreatePost.ReferencedPosts, + msgCreatePost.Author, + ), + shouldErr: true, + }, + { + name: "invalid post reference returns error", + msg: types.NewMsgCreatePost( + msgCreatePost.SubspaceID, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + msgCreatePost.ReplySettings, + msgCreatePost.Entities, + attachments, + []types.PostReference{ + types.NewPostReference(types.TYPE_UNSPECIFIED, 0), + }, + msgCreatePost.Author, + ), + shouldErr: true, + }, + { + name: "invalid author returns error", + msg: types.NewMsgCreatePost( + msgCreatePost.SubspaceID, + msgCreatePost.ExternalID, + msgCreatePost.Text, + msgCreatePost.ConversationID, + msgCreatePost.ReplySettings, + msgCreatePost.Entities, + attachments, + msgCreatePost.ReferencedPosts, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgCreatePost, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgCreatePost_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgCreatePost","value":{"attachments":[{"type":"desmos/Media","value":{"mime_type":"image/png","uri":"ftp://user:password@example.com/image.png"}},{"type":"desmos/Poll","value":{"end_date":"2020-01-01T12:00:00Z","provided_answers":[{"attachments":null,"text":"Cat"},{"attachments":null,"text":"Dog"}],"question":"What animal is best?"}}],"author":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","conversation_id":"1","entities":{"hashtags":[{"end":"3","start":"1","tag":"tag"}],"mentions":[{"end":"6","start":"4","tag":"tag"}],"urls":[{"display_url":"Display URL","end":"9","start":"7","url":"URL"}]},"external_id":"External ID","referenced_posts":[{"post_id":"1","type":2}],"reply_settings":1,"subspace_id":"1","text":"This is a text"}}` + require.Equal(t, expected, string(msgCreatePost.GetSignBytes())) +} + +func TestMsgCreatePost_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgCreatePost.Author) + require.Equal(t, []sdk.AccAddress{addr}, msgCreatePost.GetSigners()) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var msgEditPost = types.NewMsgEditPost( + 1, + 1, + "Edited text", + types.NewEntities( + []types.Tag{ + types.NewTag(1, 3, "tag"), + }, + []types.Tag{ + types.NewTag(4, 6, "tag"), + }, + []types.Url{ + types.NewURL(7, 9, "URL", "Display URL"), + }, + ), + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgEditPost_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgEditPost.Route()) +} + +func TestMsgEditPost_Type(t *testing.T) { + require.Equal(t, types.ActionEditPost, msgEditPost.Type()) +} + +func TestMsgEditPost_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgEditPost + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgEditPost( + 0, + msgEditPost.PostID, + msgEditPost.Text, + msgEditPost.Entities, + msgEditPost.Editor, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + msg: types.NewMsgEditPost( + msgEditPost.SubspaceID, + 0, + msgEditPost.Text, + msgEditPost.Entities, + msgEditPost.Editor, + ), + shouldErr: true, + }, + { + name: "invalid entities returns error", + msg: types.NewMsgEditPost( + msgEditPost.SubspaceID, + msgEditPost.PostID, + msgEditPost.Text, + types.NewEntities([]types.Tag{ + types.NewTag(1, 1, "My tag"), + types.NewTag(1, 1, "My tag"), + }, nil, nil), + msgEditPost.Editor, + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + msg: types.NewMsgEditPost( + msgEditPost.SubspaceID, + msgEditPost.PostID, + msgEditPost.Text, + msgEditPost.Entities, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgEditPost, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgEditPost_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgEditPost","value":{"editor":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","entities":{"hashtags":[{"end":"3","start":"1","tag":"tag"}],"mentions":[{"end":"6","start":"4","tag":"tag"}],"urls":[{"display_url":"Display URL","end":"9","start":"7","url":"URL"}]},"post_id":"1","subspace_id":"1","text":"Edited text"}}` + require.Equal(t, expected, string(msgEditPost.GetSignBytes())) +} + +func TestMsgEditPost_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgEditPost.Editor) + require.Equal(t, []sdk.AccAddress{addr}, msgEditPost.GetSigners()) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var attachmentContent = types.NewMedia( + "ftp://user:password@example.com/image.png", + "image/png", +) + +var msgAddPostAttachment = types.NewMsgAddPostAttachment( + 1, + 1, + attachmentContent, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgAddPostAttachment_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgAddPostAttachment.Route()) +} + +func TestMsgAddPostAttachment_Type(t *testing.T) { + require.Equal(t, types.ActionAddPostAttachment, msgAddPostAttachment.Type()) +} + +func TestMsgAddPostAttachment_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgAddPostAttachment + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgAddPostAttachment( + 0, + msgAddPostAttachment.PostID, + attachmentContent, + msgAddPostAttachment.Editor, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + msg: types.NewMsgAddPostAttachment( + msgAddPostAttachment.SubspaceID, + 0, + attachmentContent, + msgAddPostAttachment.Editor, + ), + shouldErr: true, + }, + { + name: "invalid attachment content returns error", + msg: &types.MsgAddPostAttachment{ + SubspaceID: msgAddPostAttachment.SubspaceID, + PostID: msgAddPostAttachment.PostID, + Content: nil, + Editor: msgAddPostAttachment.Editor, + }, + shouldErr: true, + }, + { + name: "invalid editor returns error", + msg: types.NewMsgAddPostAttachment( + msgAddPostAttachment.SubspaceID, + msgAddPostAttachment.PostID, + attachmentContent, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgAddPostAttachment, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgAddPostAttachment_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgAddPostAttachment","value":{"content":{"type":"desmos/Media","value":{"mime_type":"image/png","uri":"ftp://user:password@example.com/image.png"}},"editor":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","post_id":"1","subspace_id":"1"}}` + require.Equal(t, expected, string(msgAddPostAttachment.GetSignBytes())) +} + +func TestMsgAddPostAttachment_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgAddPostAttachment.Editor) + require.Equal(t, []sdk.AccAddress{addr}, msgAddPostAttachment.GetSigners()) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var msgRemovePostAttachment = types.NewMsgRemovePostAttachment( + 1, + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgRemovePostAttachment_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgRemovePostAttachment.Route()) +} + +func TestMsgRemovePostAttachment_Type(t *testing.T) { + require.Equal(t, types.ActionRemovePostAttachment, msgRemovePostAttachment.Type()) +} + +func TestMsgRemovePostAttachment_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgRemovePostAttachment + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgRemovePostAttachment( + 0, + msgRemovePostAttachment.PostID, + msgRemovePostAttachment.AttachmentID, + msgRemovePostAttachment.Editor, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + msg: types.NewMsgRemovePostAttachment( + msgRemovePostAttachment.SubspaceID, + 0, + msgRemovePostAttachment.AttachmentID, + msgRemovePostAttachment.Editor, + ), + shouldErr: true, + }, + { + name: "invalid attachment id returns error", + msg: types.NewMsgRemovePostAttachment( + msgRemovePostAttachment.SubspaceID, + msgRemovePostAttachment.PostID, + 0, + msgRemovePostAttachment.Editor, + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + msg: types.NewMsgRemovePostAttachment( + msgRemovePostAttachment.SubspaceID, + msgRemovePostAttachment.PostID, + msgRemovePostAttachment.AttachmentID, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgRemovePostAttachment, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgRemovePostAttachment_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgRemovePostAttachment","value":{"attachment_id":1,"editor":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","post_id":"1","subspace_id":"1"}}` + require.Equal(t, expected, string(msgRemovePostAttachment.GetSignBytes())) +} + +func TestMsgRemovePostAttachment_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgRemovePostAttachment.Editor) + require.Equal(t, []sdk.AccAddress{addr}, msgRemovePostAttachment.GetSigners()) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var msgDeletePost = types.NewMsgDeletePost( + 1, + 1, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgDeletePost_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgDeletePost.Route()) +} + +func TestMsgDeletePost_Type(t *testing.T) { + require.Equal(t, types.ActionDeletePost, msgDeletePost.Type()) +} + +func TestMsgDeletePost_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgDeletePost + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgDeletePost( + 0, + msgDeletePost.PostID, + msgDeletePost.Signer, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + msg: types.NewMsgDeletePost( + msgDeletePost.SubspaceID, + 0, + msgDeletePost.Signer, + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + msg: types.NewMsgDeletePost( + msgDeletePost.SubspaceID, + msgDeletePost.PostID, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgDeletePost, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgDeletePost_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgDeletePost","value":{"post_id":"1","signer":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","subspace_id":"1"}}` + require.Equal(t, expected, string(msgDeletePost.GetSignBytes())) +} + +func TestMsgDeletePost_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgDeletePost.Signer) + require.Equal(t, []sdk.AccAddress{addr}, msgDeletePost.GetSigners()) +} + +// -------------------------------------------------------------------------------------------------------------------- + +var msgAnswerPoll = types.NewMsgAnswerPoll( + 1, + 1, + 1, + []uint32{1, 2, 3}, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", +) + +func TestMsgAnswerPoll_Route(t *testing.T) { + require.Equal(t, types.RouterKey, msgAnswerPoll.Route()) +} + +func TestMsgAnswerPoll_Type(t *testing.T) { + require.Equal(t, types.ActionAnswerPoll, msgAnswerPoll.Type()) +} + +func TestMsgAnswerPoll_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgAnswerPoll + shouldErr bool + }{ + { + name: "invalid subspace id returns error", + msg: types.NewMsgAnswerPoll( + 0, + msgAnswerPoll.PostID, + msgAnswerPoll.PollID, + msgAnswerPoll.AnswersIndexes, + msgAnswerPoll.Signer, + ), + shouldErr: true, + }, + { + name: "invalid post id returns error", + msg: types.NewMsgAnswerPoll( + msgAnswerPoll.SubspaceID, + 0, + msgAnswerPoll.PollID, + msgAnswerPoll.AnswersIndexes, + msgAnswerPoll.Signer, + ), + shouldErr: true, + }, + { + name: "invalid poll id returns error", + msg: types.NewMsgAnswerPoll( + msgAnswerPoll.SubspaceID, + msgAnswerPoll.PostID, + 0, + msgAnswerPoll.AnswersIndexes, + msgAnswerPoll.Signer, + ), + shouldErr: true, + }, + { + name: "empty answers returns error", + msg: types.NewMsgAnswerPoll( + msgAnswerPoll.SubspaceID, + msgAnswerPoll.PostID, + msgAnswerPoll.PollID, + nil, + msgAnswerPoll.Signer, + ), + shouldErr: true, + }, + { + name: "duplicated answers returns error", + msg: types.NewMsgAnswerPoll( + msgAnswerPoll.SubspaceID, + msgAnswerPoll.PostID, + msgAnswerPoll.PollID, + []uint32{1, 2, 3, 4, 1}, + msgAnswerPoll.Signer, + ), + shouldErr: true, + }, + { + name: "invalid editor returns error", + msg: types.NewMsgAnswerPoll( + msgAnswerPoll.SubspaceID, + msgAnswerPoll.PostID, + msgAnswerPoll.PollID, + msgAnswerPoll.AnswersIndexes, + "", + ), + shouldErr: true, + }, + { + name: "valid message returns no error", + msg: msgAnswerPoll, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgAnswerPoll_GetSignBytes(t *testing.T) { + expected := `{"type":"desmos/MsgAnswerPoll","value":{"answers_indexes":[1,2,3],"poll_id":1,"post_id":"1","signer":"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd","subspace_id":"1"}}` + require.Equal(t, expected, string(msgAnswerPoll.GetSignBytes())) +} + +func TestMsgAnswerPoll_GetSigners(t *testing.T) { + addr, _ := sdk.AccAddressFromBech32(msgAnswerPoll.Signer) + require.Equal(t, []sdk.AccAddress{addr}, msgAnswerPoll.GetSigners()) +} diff --git a/x/posts/types/params.go b/x/posts/types/params.go new file mode 100644 index 0000000000..89ac4358e5 --- /dev/null +++ b/x/posts/types/params.go @@ -0,0 +1,68 @@ +package types + +import ( + "fmt" + + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +const ( + // DefaultParamsSpace represents the default paramspace for the Params keeper + DefaultParamsSpace = ModuleName +) + +var ( + // DefaultMaxTextLength represents the default max length for post texts + DefaultMaxTextLength uint32 = 500 +) + +var ( + // MaxTextLengthKey represents the key used to store the max length for posts texts + MaxTextLengthKey = []byte("MaxTextLength") +) + +// ------------------------------------------------------------------------------------------------------------------- + +// ParamKeyTable Key declaration for parameters +func ParamKeyTable() paramstypes.KeyTable { + return paramstypes.NewKeyTable(). + RegisterParamSet(&Params{}) +} + +// NewParams returns a new Params instance +func NewParams(maxTextLength uint32) Params { + return Params{ + MaxTextLength: maxTextLength, + } +} + +// DefaultParams return default paramsModule +func DefaultParams() Params { + return Params{ + MaxTextLength: DefaultMaxTextLength, + } +} + +// ParamSetPairs implements the ParamSet interface and returns the key/value pairs +// of posts module's parameters. +func (params *Params) ParamSetPairs() paramstypes.ParamSetPairs { + return paramstypes.ParamSetPairs{ + paramstypes.NewParamSetPair(MaxTextLengthKey, ¶ms.MaxTextLength, ValidateMaxTextLength), + } +} + +// Validate perform basic checks on all parameters to ensure they are correct +func (params Params) Validate() error { + return ValidateMaxTextLength(params.MaxTextLength) +} + +// ------------------------------------------------------------------------------------------------------------------- + +func ValidateMaxTextLength(i interface{}) error { + _, isUint32 := i.(uint32) + if !isUint32 { + return fmt.Errorf("invalid parameters type: %s", i) + } + + return nil +} diff --git a/x/posts/types/params_test.go b/x/posts/types/params_test.go new file mode 100644 index 0000000000..f1f6ecc4de --- /dev/null +++ b/x/posts/types/params_test.go @@ -0,0 +1,41 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/desmos-labs/desmos/v3/x/posts/types" +) + +func TestParams_Validate(t *testing.T) { + testCases := []struct { + name string + params types.Params + shouldErr bool + }{ + { + name: "default params returns no error", + params: types.DefaultParams(), + shouldErr: false, + }, + { + name: "valid params returns no error", + params: types.NewParams(100), + shouldErr: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := tc.params.Validate() + if tc.shouldErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } + +} diff --git a/x/posts/types/query.go b/x/posts/types/query.go new file mode 100644 index 0000000000..aaec0e736e --- /dev/null +++ b/x/posts/types/query.go @@ -0,0 +1,66 @@ +package types + +// DONTCOVER + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + query "github.com/cosmos/cosmos-sdk/types/query" +) + +// NewQueryPostsRequest returns a new QueryPostsRequest instance +func NewQueryPostsRequest(subspaceID uint64, pagination *query.PageRequest) *QueryPostsRequest { + return &QueryPostsRequest{ + SubspaceId: subspaceID, + Pagination: pagination, + } +} + +// NewQueryPostRequest returns a new QueryPostRequest instance +func NewQueryPostRequest(subspaceID uint64, postID uint64) *QueryPostRequest { + return &QueryPostRequest{ + SubspaceId: subspaceID, + PostId: postID, + } +} + +// NewQueryPostAttachmentsRequest returns a new QueryPostAttachmentsRequest instance +func NewQueryPostAttachmentsRequest( + subspaceID uint64, postID uint64, pagination *query.PageRequest, +) *QueryPostAttachmentsRequest { + return &QueryPostAttachmentsRequest{ + SubspaceId: subspaceID, + PostId: postID, + Pagination: pagination, + } +} + +// UnpackInterfaces implements codectypes.UnpackInterfacesMessage +func (r *QueryPostAttachmentsResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, a := range r.Attachments { + var content AttachmentContent + err := unpacker.UnpackAny(a.Content, &content) + if err != nil { + return err + } + } + + return nil +} + +// NewQueryPollAnswersRequest returns a new QueryPollAnswersRequest instance +func NewQueryPollAnswersRequest( + subspaceID uint64, postID uint64, pollID uint32, user string, pagination *query.PageRequest, +) *QueryPollAnswersRequest { + return &QueryPollAnswersRequest{ + SubspaceId: subspaceID, + PostId: postID, + PollId: pollID, + User: user, + Pagination: pagination, + } +} + +// NewQueryParamsRequest returns a new QueryParamsRequest instance +func NewQueryParamsRequest() *QueryParamsRequest { + return &QueryParamsRequest{} +} diff --git a/x/posts/types/query.pb.go b/x/posts/types/query.pb.go new file mode 100644 index 0000000000..77c56384bf --- /dev/null +++ b/x/posts/types/query.pb.go @@ -0,0 +1,2620 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: desmos/posts/v1/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 + +// QueryPostsRequest is the request type for the Query/Posts RPC method +type QueryPostsRequest struct { + // Id of the subspace to query the posts for + SubspaceId uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPostsRequest) Reset() { *m = QueryPostsRequest{} } +func (m *QueryPostsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPostsRequest) ProtoMessage() {} +func (*QueryPostsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{0} +} +func (m *QueryPostsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostsRequest.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 *QueryPostsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostsRequest.Merge(m, src) +} +func (m *QueryPostsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPostsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostsRequest proto.InternalMessageInfo + +func (m *QueryPostsRequest) GetSubspaceId() uint64 { + if m != nil { + return m.SubspaceId + } + return 0 +} + +func (m *QueryPostsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPostsResponse is the response type for the Query/Posts RPC method +type QueryPostsResponse struct { + Posts []Post `protobuf:"bytes,1,rep,name=posts,proto3" json:"posts"` + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPostsResponse) Reset() { *m = QueryPostsResponse{} } +func (m *QueryPostsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPostsResponse) ProtoMessage() {} +func (*QueryPostsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{1} +} +func (m *QueryPostsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostsResponse.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 *QueryPostsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostsResponse.Merge(m, src) +} +func (m *QueryPostsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPostsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostsResponse proto.InternalMessageInfo + +func (m *QueryPostsResponse) GetPosts() []Post { + if m != nil { + return m.Posts + } + return nil +} + +func (m *QueryPostsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPostRequest is the request type for the Query/Post RPC method +type QueryPostRequest struct { + // Id of the subspace inside which the post lies + SubspaceId uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to query for + PostId uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` +} + +func (m *QueryPostRequest) Reset() { *m = QueryPostRequest{} } +func (m *QueryPostRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPostRequest) ProtoMessage() {} +func (*QueryPostRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{2} +} +func (m *QueryPostRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostRequest.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 *QueryPostRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostRequest.Merge(m, src) +} +func (m *QueryPostRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPostRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostRequest proto.InternalMessageInfo + +func (m *QueryPostRequest) GetSubspaceId() uint64 { + if m != nil { + return m.SubspaceId + } + return 0 +} + +func (m *QueryPostRequest) GetPostId() uint64 { + if m != nil { + return m.PostId + } + return 0 +} + +// QueryPostResponse is the response type for the Query/Post RPC method +type QueryPostResponse struct { + Post Post `protobuf:"bytes,1,opt,name=post,proto3" json:"post"` +} + +func (m *QueryPostResponse) Reset() { *m = QueryPostResponse{} } +func (m *QueryPostResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPostResponse) ProtoMessage() {} +func (*QueryPostResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{3} +} +func (m *QueryPostResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostResponse.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 *QueryPostResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostResponse.Merge(m, src) +} +func (m *QueryPostResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPostResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostResponse proto.InternalMessageInfo + +func (m *QueryPostResponse) GetPost() Post { + if m != nil { + return m.Post + } + return Post{} +} + +// QueryPostsRequest is the request type for the Query/PostAttachments RPC +// method +type QueryPostAttachmentsRequest struct { + // Id of the subspace where the post is stored + SubspaceId uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post to query the attachments for + PostId uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPostAttachmentsRequest) Reset() { *m = QueryPostAttachmentsRequest{} } +func (m *QueryPostAttachmentsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPostAttachmentsRequest) ProtoMessage() {} +func (*QueryPostAttachmentsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{4} +} +func (m *QueryPostAttachmentsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostAttachmentsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostAttachmentsRequest.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 *QueryPostAttachmentsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostAttachmentsRequest.Merge(m, src) +} +func (m *QueryPostAttachmentsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPostAttachmentsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostAttachmentsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostAttachmentsRequest proto.InternalMessageInfo + +func (m *QueryPostAttachmentsRequest) GetSubspaceId() uint64 { + if m != nil { + return m.SubspaceId + } + return 0 +} + +func (m *QueryPostAttachmentsRequest) GetPostId() uint64 { + if m != nil { + return m.PostId + } + return 0 +} + +func (m *QueryPostAttachmentsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPostAttachmentsResponse is the response type for the +// Query/PostAttachments RPC method +type QueryPostAttachmentsResponse struct { + Attachments []Attachment `protobuf:"bytes,1,rep,name=attachments,proto3" json:"attachments"` + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPostAttachmentsResponse) Reset() { *m = QueryPostAttachmentsResponse{} } +func (m *QueryPostAttachmentsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPostAttachmentsResponse) ProtoMessage() {} +func (*QueryPostAttachmentsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{5} +} +func (m *QueryPostAttachmentsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPostAttachmentsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPostAttachmentsResponse.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 *QueryPostAttachmentsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPostAttachmentsResponse.Merge(m, src) +} +func (m *QueryPostAttachmentsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPostAttachmentsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPostAttachmentsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPostAttachmentsResponse proto.InternalMessageInfo + +func (m *QueryPostAttachmentsResponse) GetAttachments() []Attachment { + if m != nil { + return m.Attachments + } + return nil +} + +func (m *QueryPostAttachmentsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPollAnswersRequest is the request type for the Query/PollAnswers RPC +// method +type QueryPollAnswersRequest struct { + // Id of the subspace where the post is stored + SubspaceId uint64 `protobuf:"varint,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"` + // Id of the post that holds the poll + PostId uint64 `protobuf:"varint,2,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"` + // Id of the poll to query the answers for + PollId uint32 `protobuf:"varint,3,opt,name=poll_id,json=pollId,proto3" json:"poll_id,omitempty"` + // (Optional) Address of the user to query the responses for + User string `protobuf:"bytes,4,opt,name=user,proto3" json:"user,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,5,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPollAnswersRequest) Reset() { *m = QueryPollAnswersRequest{} } +func (m *QueryPollAnswersRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPollAnswersRequest) ProtoMessage() {} +func (*QueryPollAnswersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{6} +} +func (m *QueryPollAnswersRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPollAnswersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPollAnswersRequest.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 *QueryPollAnswersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPollAnswersRequest.Merge(m, src) +} +func (m *QueryPollAnswersRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPollAnswersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPollAnswersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPollAnswersRequest proto.InternalMessageInfo + +func (m *QueryPollAnswersRequest) GetSubspaceId() uint64 { + if m != nil { + return m.SubspaceId + } + return 0 +} + +func (m *QueryPollAnswersRequest) GetPostId() uint64 { + if m != nil { + return m.PostId + } + return 0 +} + +func (m *QueryPollAnswersRequest) GetPollId() uint32 { + if m != nil { + return m.PollId + } + return 0 +} + +func (m *QueryPollAnswersRequest) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *QueryPollAnswersRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPollAnswersResponse is the response type for the Query/PollAnswers RPC +// method +type QueryPollAnswersResponse struct { + Answers []UserAnswer `protobuf:"bytes,1,rep,name=answers,proto3" json:"answers"` + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPollAnswersResponse) Reset() { *m = QueryPollAnswersResponse{} } +func (m *QueryPollAnswersResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPollAnswersResponse) ProtoMessage() {} +func (*QueryPollAnswersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{7} +} +func (m *QueryPollAnswersResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPollAnswersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPollAnswersResponse.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 *QueryPollAnswersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPollAnswersResponse.Merge(m, src) +} +func (m *QueryPollAnswersResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPollAnswersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPollAnswersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPollAnswersResponse proto.InternalMessageInfo + +func (m *QueryPollAnswersResponse) GetAnswers() []UserAnswer { + if m != nil { + return m.Answers + } + return nil +} + +func (m *QueryPollAnswersResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryParamsRequest is the request type for the Query/Params RPC method +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{8} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.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 *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method +type QueryParamsResponse struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8109ee6901cf86f4, []int{9} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.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 *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryPostsRequest)(nil), "desmos.posts.v1.QueryPostsRequest") + proto.RegisterType((*QueryPostsResponse)(nil), "desmos.posts.v1.QueryPostsResponse") + proto.RegisterType((*QueryPostRequest)(nil), "desmos.posts.v1.QueryPostRequest") + proto.RegisterType((*QueryPostResponse)(nil), "desmos.posts.v1.QueryPostResponse") + proto.RegisterType((*QueryPostAttachmentsRequest)(nil), "desmos.posts.v1.QueryPostAttachmentsRequest") + proto.RegisterType((*QueryPostAttachmentsResponse)(nil), "desmos.posts.v1.QueryPostAttachmentsResponse") + proto.RegisterType((*QueryPollAnswersRequest)(nil), "desmos.posts.v1.QueryPollAnswersRequest") + proto.RegisterType((*QueryPollAnswersResponse)(nil), "desmos.posts.v1.QueryPollAnswersResponse") + proto.RegisterType((*QueryParamsRequest)(nil), "desmos.posts.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "desmos.posts.v1.QueryParamsResponse") +} + +func init() { proto.RegisterFile("desmos/posts/v1/query.proto", fileDescriptor_8109ee6901cf86f4) } + +var fileDescriptor_8109ee6901cf86f4 = []byte{ + // 715 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x4f, 0x4f, 0x13, 0x4f, + 0x18, 0xc7, 0x3b, 0xd0, 0x96, 0xfc, 0x9e, 0xe6, 0x17, 0x74, 0xc4, 0xb4, 0x16, 0x52, 0xea, 0x4a, + 0xb0, 0x1a, 0xd8, 0xb1, 0x10, 0x3d, 0xa8, 0x17, 0xd0, 0x20, 0x24, 0xc4, 0x60, 0x13, 0x2f, 0x5e, + 0xcc, 0xb4, 0x9d, 0x2c, 0x4d, 0xb6, 0x3b, 0x4b, 0x67, 0x5b, 0x25, 0x88, 0x07, 0x13, 0xef, 0x24, + 0xbe, 0x00, 0x7d, 0x01, 0x9e, 0x78, 0x15, 0x1c, 0x49, 0xbc, 0x78, 0x32, 0x06, 0xbc, 0xfb, 0x16, + 0xcc, 0xfc, 0x59, 0x58, 0x76, 0x29, 0x14, 0xc2, 0xad, 0x3b, 0xcf, 0xbf, 0xcf, 0xf3, 0x9d, 0xe7, + 0x99, 0x14, 0xc6, 0x9b, 0x4c, 0xb4, 0xb9, 0x20, 0x3e, 0x17, 0x81, 0x20, 0xbd, 0x2a, 0xd9, 0xe8, + 0xb2, 0xce, 0xa6, 0xed, 0x77, 0x78, 0xc0, 0xf1, 0xa8, 0x36, 0xda, 0xca, 0x68, 0xf7, 0xaa, 0xc5, + 0x31, 0x87, 0x3b, 0x5c, 0xd9, 0x88, 0xfc, 0xa5, 0xdd, 0x8a, 0x13, 0x0e, 0xe7, 0x8e, 0xcb, 0x08, + 0xf5, 0x5b, 0x84, 0x7a, 0x1e, 0x0f, 0x68, 0xd0, 0xe2, 0x9e, 0x30, 0xd6, 0xfb, 0x0d, 0xae, 0x2a, + 0xd4, 0xa9, 0x60, 0x3a, 0x3b, 0xe9, 0x55, 0xeb, 0x2c, 0xa0, 0x55, 0xe2, 0x53, 0xa7, 0xe5, 0x29, + 0xe7, 0x30, 0x53, 0x9c, 0xa6, 0xcd, 0x9b, 0xcc, 0x35, 0x99, 0xac, 0x0f, 0x70, 0xfd, 0x95, 0x8c, + 0x5f, 0x93, 0xd6, 0x1a, 0xdb, 0xe8, 0x32, 0x11, 0xe0, 0x49, 0xc8, 0x89, 0x6e, 0x5d, 0xf8, 0xb4, + 0xc1, 0xde, 0xb6, 0x9a, 0x05, 0x54, 0x46, 0x95, 0x74, 0x0d, 0xc2, 0xa3, 0x95, 0x26, 0x5e, 0x02, + 0x38, 0xae, 0x53, 0x18, 0x2a, 0xa3, 0x4a, 0x6e, 0x6e, 0xda, 0xd6, 0x50, 0xb6, 0x84, 0xb2, 0x75, + 0xcb, 0x06, 0xca, 0x5e, 0xa3, 0x0e, 0x33, 0xc9, 0x6b, 0x91, 0x48, 0x6b, 0x07, 0x01, 0x8e, 0x96, + 0x17, 0x3e, 0xf7, 0x04, 0xc3, 0x55, 0xc8, 0x28, 0xda, 0x02, 0x2a, 0x0f, 0x57, 0x72, 0x73, 0x37, + 0xed, 0x98, 0x66, 0xb6, 0x74, 0x5f, 0x4c, 0xef, 0xfd, 0x9a, 0x4c, 0xd5, 0xb4, 0x27, 0x7e, 0x71, + 0x0a, 0xd1, 0xdd, 0x73, 0x89, 0x74, 0xbd, 0x13, 0x48, 0xab, 0x70, 0xed, 0x88, 0x68, 0x60, 0x3d, + 0xf2, 0x30, 0x22, 0x31, 0xa4, 0x71, 0x48, 0x19, 0xb3, 0xf2, 0x73, 0xa5, 0x69, 0x3d, 0x8f, 0xc8, + 0x7b, 0xd4, 0x1e, 0x81, 0xb4, 0x34, 0xab, 0x3c, 0xe7, 0x74, 0xa7, 0x1c, 0xad, 0xaf, 0x08, 0xc6, + 0x8f, 0xd2, 0x2c, 0x04, 0x01, 0x6d, 0xac, 0xb7, 0x99, 0x77, 0x81, 0xfb, 0xea, 0xc7, 0x17, 0xbb, + 0xc8, 0xe1, 0x4b, 0x5f, 0xe4, 0x77, 0x04, 0x13, 0xa7, 0x13, 0x9a, 0x9e, 0x9f, 0x41, 0x8e, 0x1e, + 0x1f, 0x9b, 0x8b, 0x1d, 0x4f, 0xb4, 0x7e, 0x1c, 0x6a, 0x04, 0x88, 0x46, 0x5d, 0xdd, 0x25, 0xef, + 0x21, 0xc8, 0x1b, 0x5c, 0xd7, 0x5d, 0xf0, 0xc4, 0x3b, 0xd6, 0xb9, 0x02, 0x31, 0x95, 0xc1, 0x75, + 0xa5, 0x41, 0x2a, 0xf9, 0xbf, 0x34, 0xb8, 0xee, 0x4a, 0x13, 0x63, 0x48, 0x77, 0x05, 0xeb, 0x14, + 0xd2, 0x65, 0x54, 0xf9, 0xaf, 0xa6, 0x7e, 0xc7, 0x94, 0xcf, 0x5c, 0x5a, 0xf9, 0x6f, 0x08, 0x0a, + 0xc9, 0x56, 0x8c, 0xea, 0x4f, 0x60, 0x84, 0xea, 0xa3, 0xbe, 0x8a, 0xbf, 0x16, 0xac, 0xa3, 0xc3, + 0x8c, 0xe2, 0x61, 0xc4, 0xd5, 0xa9, 0x3d, 0x16, 0x2e, 0x39, 0xed, 0xd0, 0x76, 0xa8, 0xb3, 0xb5, + 0x0a, 0x37, 0x4e, 0x9c, 0x1a, 0xe4, 0x87, 0x90, 0xf5, 0xd5, 0x89, 0x59, 0x8f, 0x7c, 0x72, 0x3d, + 0x94, 0xd9, 0xd0, 0x1a, 0xe7, 0xb9, 0xbf, 0x19, 0xc8, 0xa8, 0x74, 0xf8, 0x23, 0x64, 0xd4, 0x6b, + 0x82, 0xad, 0x44, 0x64, 0xe2, 0xa5, 0x2b, 0xde, 0x39, 0xd3, 0x47, 0x23, 0x59, 0x33, 0x9f, 0x7e, + 0xfc, 0xf9, 0x32, 0x34, 0x8d, 0xa7, 0x48, 0xfc, 0x29, 0xdd, 0x8a, 0x4c, 0xca, 0xb6, 0x3e, 0xc6, + 0x9f, 0x11, 0xa4, 0x65, 0x3c, 0xbe, 0xdd, 0x3f, 0x77, 0x58, 0xde, 0x3a, 0xcb, 0xc5, 0x54, 0x7f, + 0xa4, 0xaa, 0x3f, 0xc0, 0xf6, 0x20, 0xd5, 0xc9, 0x96, 0x19, 0xcd, 0x6d, 0xbc, 0x8b, 0x60, 0x34, + 0xb6, 0x8d, 0x78, 0xa6, 0x7f, 0xbd, 0xe4, 0xb3, 0x52, 0x9c, 0x1d, 0xd0, 0xdb, 0x80, 0x2e, 0x2a, + 0xd0, 0xa7, 0xf8, 0xf1, 0xc5, 0x40, 0x49, 0x74, 0xc3, 0x77, 0x11, 0xe4, 0x22, 0x83, 0x8c, 0x2b, + 0xfd, 0x10, 0xe2, 0x6b, 0x5b, 0xbc, 0x37, 0x80, 0xa7, 0x01, 0x7d, 0xa9, 0x40, 0x97, 0xf1, 0xd2, + 0x05, 0x41, 0xe5, 0x36, 0xab, 0x6f, 0xb5, 0xe3, 0xdb, 0x24, 0x5c, 0x94, 0x00, 0xb2, 0x7a, 0x26, + 0x71, 0xbf, 0x71, 0x8a, 0x0e, 0x7e, 0x71, 0xea, 0x6c, 0x27, 0x03, 0x39, 0xa9, 0x20, 0x6f, 0xe1, + 0x7c, 0x02, 0x52, 0x4f, 0xfc, 0xe2, 0xf2, 0xde, 0x41, 0x09, 0xed, 0x1f, 0x94, 0xd0, 0xef, 0x83, + 0x12, 0xda, 0x39, 0x2c, 0xa5, 0xf6, 0x0f, 0x4b, 0xa9, 0x9f, 0x87, 0xa5, 0xd4, 0x1b, 0xdb, 0x69, + 0x05, 0xeb, 0xdd, 0xba, 0xdd, 0xe0, 0x6d, 0x13, 0x3c, 0xeb, 0xd2, 0xba, 0x08, 0x13, 0xf5, 0xe6, + 0xc9, 0x7b, 0x93, 0x2d, 0xd8, 0xf4, 0x99, 0xa8, 0x67, 0xd5, 0x5f, 0x81, 0xf9, 0x7f, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xfe, 0x82, 0xb0, 0xa2, 0xb8, 0x08, 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 { + // Posts queries all the posts inside a given subspace + Posts(ctx context.Context, in *QueryPostsRequest, opts ...grpc.CallOption) (*QueryPostsResponse, error) + // Post queries for a single post inside a given subspace + Post(ctx context.Context, in *QueryPostRequest, opts ...grpc.CallOption) (*QueryPostResponse, error) + // PostAttachments queries the attachments of the post having the given id + PostAttachments(ctx context.Context, in *QueryPostAttachmentsRequest, opts ...grpc.CallOption) (*QueryPostAttachmentsResponse, error) + // PollAnswers queries the answers for the poll having the given id + PollAnswers(ctx context.Context, in *QueryPollAnswersRequest, opts ...grpc.CallOption) (*QueryPollAnswersResponse, error) + // Params queries the module parameters + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Posts(ctx context.Context, in *QueryPostsRequest, opts ...grpc.CallOption) (*QueryPostsResponse, error) { + out := new(QueryPostsResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Query/Posts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Post(ctx context.Context, in *QueryPostRequest, opts ...grpc.CallOption) (*QueryPostResponse, error) { + out := new(QueryPostResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Query/Post", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PostAttachments(ctx context.Context, in *QueryPostAttachmentsRequest, opts ...grpc.CallOption) (*QueryPostAttachmentsResponse, error) { + out := new(QueryPostAttachmentsResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Query/PostAttachments", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PollAnswers(ctx context.Context, in *QueryPollAnswersRequest, opts ...grpc.CallOption) (*QueryPollAnswersResponse, error) { + out := new(QueryPollAnswersResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Query/PollAnswers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/desmos.posts.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Posts queries all the posts inside a given subspace + Posts(context.Context, *QueryPostsRequest) (*QueryPostsResponse, error) + // Post queries for a single post inside a given subspace + Post(context.Context, *QueryPostRequest) (*QueryPostResponse, error) + // PostAttachments queries the attachments of the post having the given id + PostAttachments(context.Context, *QueryPostAttachmentsRequest) (*QueryPostAttachmentsResponse, error) + // PollAnswers queries the answers for the poll having the given id + PollAnswers(context.Context, *QueryPollAnswersRequest) (*QueryPollAnswersResponse, error) + // Params queries the module parameters + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Posts(ctx context.Context, req *QueryPostsRequest) (*QueryPostsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Posts not implemented") +} +func (*UnimplementedQueryServer) Post(ctx context.Context, req *QueryPostRequest) (*QueryPostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Post not implemented") +} +func (*UnimplementedQueryServer) PostAttachments(ctx context.Context, req *QueryPostAttachmentsRequest) (*QueryPostAttachmentsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PostAttachments not implemented") +} +func (*UnimplementedQueryServer) PollAnswers(ctx context.Context, req *QueryPollAnswersRequest) (*QueryPollAnswersResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PollAnswers not implemented") +} +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Posts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPostsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Posts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Query/Posts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Posts(ctx, req.(*QueryPostsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Post_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPostRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Post(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Query/Post", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Post(ctx, req.(*QueryPostRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PostAttachments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPostAttachmentsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PostAttachments(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Query/PostAttachments", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PostAttachments(ctx, req.(*QueryPostAttachmentsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PollAnswers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPollAnswersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PollAnswers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Query/PollAnswers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PollAnswers(ctx, req.(*QueryPollAnswersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/desmos.posts.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "desmos.posts.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Posts", + Handler: _Query_Posts_Handler, + }, + { + MethodName: "Post", + Handler: _Query_Post_Handler, + }, + { + MethodName: "PostAttachments", + Handler: _Query_PostAttachments_Handler, + }, + { + MethodName: "PollAnswers", + Handler: _Query_PollAnswers_Handler, + }, + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "desmos/posts/v1/query.proto", +} + +func (m *QueryPostsRequest) 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 *QueryPostsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostsRequest) 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 m.SubspaceId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.SubspaceId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPostsResponse) 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 *QueryPostsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostsResponse) 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.Posts) > 0 { + for iNdEx := len(m.Posts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Posts[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 (m *QueryPostRequest) 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 *QueryPostRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PostId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PostId)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.SubspaceId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPostResponse) 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 *QueryPostResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Post.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 *QueryPostAttachmentsRequest) 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 *QueryPostAttachmentsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostAttachmentsRequest) 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] = 0x1a + } + if m.PostId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PostId)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.SubspaceId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPostAttachmentsResponse) 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 *QueryPostAttachmentsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPostAttachmentsResponse) 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.Attachments) > 0 { + for iNdEx := len(m.Attachments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Attachments[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 (m *QueryPollAnswersRequest) 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 *QueryPollAnswersRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPollAnswersRequest) 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] = 0x2a + } + if len(m.User) > 0 { + i -= len(m.User) + copy(dAtA[i:], m.User) + i = encodeVarintQuery(dAtA, i, uint64(len(m.User))) + i-- + dAtA[i] = 0x22 + } + if m.PollId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PollId)) + i-- + dAtA[i] = 0x18 + } + if m.PostId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PostId)) + i-- + dAtA[i] = 0x10 + } + if m.SubspaceId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.SubspaceId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPollAnswersResponse) 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 *QueryPollAnswersResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPollAnswersResponse) 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.Answers) > 0 { + for iNdEx := len(m.Answers) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Answers[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 (m *QueryParamsRequest) 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 *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) 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 *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.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 *QueryPostsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceId != 0 { + n += 1 + sovQuery(uint64(m.SubspaceId)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPostsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Posts) > 0 { + for _, e := range m.Posts { + 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 (m *QueryPostRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceId != 0 { + n += 1 + sovQuery(uint64(m.SubspaceId)) + } + if m.PostId != 0 { + n += 1 + sovQuery(uint64(m.PostId)) + } + return n +} + +func (m *QueryPostResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Post.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPostAttachmentsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceId != 0 { + n += 1 + sovQuery(uint64(m.SubspaceId)) + } + if m.PostId != 0 { + n += 1 + sovQuery(uint64(m.PostId)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPostAttachmentsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Attachments) > 0 { + for _, e := range m.Attachments { + 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 (m *QueryPollAnswersRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SubspaceId != 0 { + n += 1 + sovQuery(uint64(m.SubspaceId)) + } + if m.PostId != 0 { + n += 1 + sovQuery(uint64(m.PostId)) + } + if m.PollId != 0 { + n += 1 + sovQuery(uint64(m.PollId)) + } + l = len(m.User) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPollAnswersResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Answers) > 0 { + for _, e := range m.Answers { + 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 (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.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 *QueryPostsRequest) 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: QueryPostsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceId", wireType) + } + m.SubspaceId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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.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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPostsResponse) 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: QueryPostsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Posts", 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.Posts = append(m.Posts, Post{}) + if err := m.Posts[len(m.Posts)-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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPostRequest) 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: QueryPostRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceId", wireType) + } + m.SubspaceId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostId", wireType) + } + m.PostId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPostResponse) 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: QueryPostResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Post", 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 err := m.Post.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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPostAttachmentsRequest) 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: QueryPostAttachmentsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostAttachmentsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceId", wireType) + } + m.SubspaceId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostId", wireType) + } + m.PostId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + 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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPostAttachmentsResponse) 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: QueryPostAttachmentsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPostAttachmentsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Attachments", 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.Attachments = append(m.Attachments, Attachment{}) + if err := m.Attachments[len(m.Attachments)-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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPollAnswersRequest) 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: QueryPollAnswersRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPollAnswersRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubspaceId", wireType) + } + m.SubspaceId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubspaceId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PostId", wireType) + } + m.PostId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PostId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PollId", wireType) + } + m.PollId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PollId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field User", 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.User = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + 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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPollAnswersResponse) 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: QueryPollAnswersResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPollAnswersResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Answers", 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.Answers = append(m.Answers, UserAnswer{}) + if err := m.Answers[len(m.Answers)-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) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsRequest) 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: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) 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: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", 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 err := m.Params.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) || (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/posts/types/query.pb.gw.go b/x/posts/types/query.pb.gw.go new file mode 100644 index 0000000000..f6e5d99505 --- /dev/null +++ b/x/posts/types/query.pb.gw.go @@ -0,0 +1,682 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: desmos/posts/v1/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 + +var ( + filter_Query_Posts_0 = &utilities.DoubleArray{Encoding: map[string]int{"subspace_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_Posts_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Posts_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Posts(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Posts_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Posts_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Posts(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Post_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + msg, err := client.Post(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Post_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + msg, err := server.Post(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PostAttachments_0 = &utilities.DoubleArray{Encoding: map[string]int{"subspace_id": 0, "post_id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} +) + +func request_Query_PostAttachments_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostAttachmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PostAttachments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PostAttachments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PostAttachments_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPostAttachmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PostAttachments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PostAttachments(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PollAnswers_0 = &utilities.DoubleArray{Encoding: map[string]int{"subspace_id": 0, "post_id": 1, "poll_id": 2}, Base: []int{1, 1, 2, 3, 0, 0, 0}, Check: []int{0, 1, 1, 1, 2, 3, 4}} +) + +func request_Query_PollAnswers_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPollAnswersRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + val, ok = pathParams["poll_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "poll_id") + } + + protoReq.PollId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "poll_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PollAnswers_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PollAnswers(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PollAnswers_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPollAnswersRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["subspace_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subspace_id") + } + + protoReq.SubspaceId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subspace_id", err) + } + + val, ok = pathParams["post_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id") + } + + protoReq.PostId, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err) + } + + val, ok = pathParams["poll_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "poll_id") + } + + protoReq.PollId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "poll_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PollAnswers_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PollAnswers(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(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_Posts_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_Posts_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_Posts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Post_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_Post_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_Post_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PostAttachments_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_PostAttachments_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_PostAttachments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PollAnswers_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_PollAnswers_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_PollAnswers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_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_Params_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_Params_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_Posts_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_Posts_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_Posts_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Post_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_Post_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_Post_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PostAttachments_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_PostAttachments_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_PostAttachments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PollAnswers_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_PollAnswers_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_PollAnswers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_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_Params_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_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Posts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 1}, []string{"desmos", "posts", "v1", "subspace_id"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_Post_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 1, 1, 0, 4, 1, 5, 4}, []string{"desmos", "posts", "v1", "subspace_id", "post_id"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_PostAttachments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 1, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"desmos", "posts", "v1", "subspace_id", "post_id", "attachments"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_PollAnswers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 1, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6, 2, 7}, []string{"desmos", "posts", "v1", "subspace_id", "post_id", "polls", "poll_id", "answers"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"desmos", "posts", "v1", "params"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_Query_Posts_0 = runtime.ForwardResponseMessage + + forward_Query_Post_0 = runtime.ForwardResponseMessage + + forward_Query_PostAttachments_0 = runtime.ForwardResponseMessage + + forward_Query_PollAnswers_0 = runtime.ForwardResponseMessage + + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/profiles/types/models_chain_links_test.go b/x/profiles/types/models_chain_links_test.go index db9e7c37d5..3ae8af6049 100644 --- a/x/profiles/types/models_chain_links_test.go +++ b/x/profiles/types/models_chain_links_test.go @@ -636,24 +636,24 @@ func TestHexAddress_GetValue(t *testing.T) { func TestUnpackAddressData(t *testing.T) { testCases := []struct { - name string - address *codectypes.Any - shouldError bool + name string + address *codectypes.Any + shouldErr bool }{ { - name: "invalid address returns error", - address: testutil.NewAny(secp256k1.GenPrivKey()), - shouldError: true, + name: "invalid address returns error", + address: testutil.NewAny(secp256k1.GenPrivKey()), + shouldErr: true, }, { - name: "valid Bech32 data returns no error", - address: testutil.NewAny(types.NewBech32Address("cosmos1tdgrkvx2qgjk0uqsmdhm6dcz6wvwh9f8t37x0k", "cosmos")), - shouldError: false, + name: "valid Bech32 data returns no error", + address: testutil.NewAny(types.NewBech32Address("cosmos1tdgrkvx2qgjk0uqsmdhm6dcz6wvwh9f8t37x0k", "cosmos")), + shouldErr: false, }, { - name: "valid Base58 data returns no error", - address: testutil.NewAny(types.NewBase58Address("5AfetAwZzftP8i5JBNatzWeccfXd4KvKq6TRfAvacFaN")), - shouldError: false, + name: "valid Base58 data returns no error", + address: testutil.NewAny(types.NewBase58Address("5AfetAwZzftP8i5JBNatzWeccfXd4KvKq6TRfAvacFaN")), + shouldErr: false, }, } @@ -663,7 +663,7 @@ func TestUnpackAddressData(t *testing.T) { cdc, _ := app.MakeCodecs() _, err := types.UnpackAddressData(cdc, tc.address) - if tc.shouldError { + if tc.shouldErr { require.Error(t, err) } else { require.NoError(t, err) diff --git a/x/subspaces/legacy/v2/store_test.go b/x/subspaces/legacy/v2/store_test.go index 2e29129f60..1b7e348931 100644 --- a/x/subspaces/legacy/v2/store_test.go +++ b/x/subspaces/legacy/v2/store_test.go @@ -33,7 +33,7 @@ func TestMigrateStore(t *testing.T) { store: func(ctx sdk.Context) { kvStore := ctx.KVStore(keys[types.StoreKey]) - group := types.NewUserGroup(11, 11, "Test group", "", 0b11111111111111111111111111000001) + group := types.NewUserGroup(11, 11, "Test group", "", 0b11111111111111111111111100000001) kvStore.Set(types.GroupStoreKey(group.SubspaceID, group.ID), cdc.MustMarshal(&group)) }, check: func(ctx sdk.Context) { @@ -53,7 +53,7 @@ func TestMigrateStore(t *testing.T) { addr, err := sdk.AccAddressFromBech32("cosmos12e7ejq92sma437d3svemgfvl8sul8lxfs69mjv") require.NoError(t, err) - kvStore.Set(types.UserPermissionStoreKey(11, addr), types.MarshalPermission(0b11111111111111111111111111000001)) + kvStore.Set(types.UserPermissionStoreKey(11, addr), types.MarshalPermission(0b11111111111111111111111100000001)) }, check: func(ctx sdk.Context) { kvStore := ctx.KVStore(keys[types.StoreKey]) diff --git a/x/subspaces/simulation/genesis.go b/x/subspaces/simulation/genesis.go index d35304a3f6..080ccadff7 100644 --- a/x/subspaces/simulation/genesis.go +++ b/x/subspaces/simulation/genesis.go @@ -61,12 +61,7 @@ func randomUserGroups( groupID := uint32(i + 1) // Get a random permission - permission := RandomPermission(r, []types.Permission{ - types.PermissionNothing, - types.PermissionWrite, - types.PermissionManageGroups, - types.PermissionEverything, - }) + permission := RandomPermission(r, validPermissions) // Build the group details groups[i] = types.NewUserGroup(subspace.ID, groupID, RandomName(r), RandomDescription(r), permission) @@ -127,12 +122,7 @@ func randomACL(r *rand.Rand, accounts []simtypes.Account, subspaces []types.Subs target := account.Address.String() // Get a random permission - permission := RandomPermission(r, []types.Permission{ - types.PermissionNothing, - types.PermissionWrite, - types.PermissionManageGroups, - types.PermissionEverything, - }) + permission := RandomPermission(r, validPermissions) // Crete the entry entries[index] = types.NewACLEntry(subspace.ID, target, permission) @@ -178,9 +168,9 @@ func sanitizeSubspaces(slice []types.GenesisSubspace) []types.GenesisSubspace { // sanitizeUserGroups sanitizes the given slice by removing all the double groups func sanitizeUserGroups(slice []types.UserGroup) []types.UserGroup { - groups := map[string]int{} + groups := map[string]bool{} for _, value := range slice { - groups[fmt.Sprintf("%d%s", value.SubspaceID, value.Name)] = 1 + groups[fmt.Sprintf("%d%s", value.SubspaceID, value.Name)] = true } var unique []types.UserGroup @@ -199,9 +189,9 @@ func sanitizeUserGroups(slice []types.UserGroup) []types.UserGroup { // sanitizeSubspaces sanitizes the given slice by removing all the double entries func sanitizeACLEntry(slice []types.ACLEntry) []types.ACLEntry { - entries := map[string]int{} + entries := map[string]bool{} for _, value := range slice { - entries[fmt.Sprintf("%d%s", value.SubspaceID, value.User)] = 1 + entries[fmt.Sprintf("%d%s", value.SubspaceID, value.User)] = true } var unique []types.ACLEntry @@ -220,9 +210,9 @@ func sanitizeACLEntry(slice []types.ACLEntry) []types.ACLEntry { // sanitizeStrings sanitizes the given slice by removing all duplicated values func sanitizeStrings(slice []string) []string { - values := map[string]int{} + values := map[string]bool{} for _, value := range slice { - values[value] = 1 + values[value] = true } count := 0 diff --git a/x/subspaces/simulation/operations_groups.go b/x/subspaces/simulation/operations_groups.go index dbdc58161e..f09426dc25 100644 --- a/x/subspaces/simulation/operations_groups.go +++ b/x/subspaces/simulation/operations_groups.go @@ -215,12 +215,7 @@ func randomSetUserGroupPermissionsFields( groupID = RandomGroup(r, groups).ID // Get a permission - permissions = RandomPermission(r, []types.Permission{ - types.PermissionWrite, - types.PermissionModerateContent, - types.PermissionChangeInfo, - types.PermissionManageGroups, - }) + permissions = RandomPermission(r, validPermissions) // Get a signer signers, _ := k.GetUsersWithPermission(ctx, subspace.ID, types.PermissionSetPermissions) @@ -232,9 +227,9 @@ func randomSetUserGroupPermissionsFields( } account = *acc - // Make sure the user can change this group's permissions + // Make sure the user can change this group's validPermissions if subspace.Owner != account.Address.String() && k.IsMemberOfGroup(ctx, subspaceID, groupID, account.Address) { - // If the user is not the subspace owner and it's part of the user group they cannot edit the group permissions + // If the user is not the subspace owner and it's part of the user group they cannot edit the group validPermissions skip = true return } diff --git a/x/subspaces/simulation/operations_permissions.go b/x/subspaces/simulation/operations_permissions.go index f52035ebcd..89f358054d 100644 --- a/x/subspaces/simulation/operations_permissions.go +++ b/x/subspaces/simulation/operations_permissions.go @@ -67,12 +67,7 @@ func randomSetUserPermissionsFields( target = targetAcc.Address.String() // Get a permission - permissions = RandomPermission(r, []types.Permission{ - types.PermissionWrite, - types.PermissionModerateContent, - types.PermissionChangeInfo, - types.PermissionManageGroups, - }) + permissions = RandomPermission(r, validPermissions) // Get a signer signers, _ := k.GetUsersWithPermission(ctx, subspace.ID, types.PermissionSetPermissions) diff --git a/x/subspaces/simulation/utils.go b/x/subspaces/simulation/utils.go index ebed6821af..265b521d92 100644 --- a/x/subspaces/simulation/utils.go +++ b/x/subspaces/simulation/utils.go @@ -14,6 +14,16 @@ import ( "github.com/desmos-labs/desmos/v3/x/subspaces/types" ) +var ( + validPermissions = []types.Permission{ + types.PermissionWrite | types.PermissionEditOwnContent | types.PermissionInteractWithContent, + types.PermissionWrite | types.PermissionInteractWithContent | types.PermissionModerateContent, + types.PermissionWrite | types.PermissionEditOwnContent | types.PermissionChangeInfo, + types.PermissionWrite | types.PermissionEditOwnContent | types.PermissionInteractWithContent | types.PermissionDeleteSubspace, + types.PermissionEverything, + } +) + // RandomGenesisSubspace picks a random genesis subspace from the given slice func RandomGenesisSubspace(r *rand.Rand, subspaces []types.GenesisSubspace) types.GenesisSubspace { return subspaces[r.Intn(len(subspaces))] diff --git a/x/subspaces/types/permissions.go b/x/subspaces/types/permissions.go index dc70f88123..4cb5ba2d3b 100644 --- a/x/subspaces/types/permissions.go +++ b/x/subspaces/types/permissions.go @@ -32,9 +32,15 @@ const ( // PermissionDeleteSubspace allows users to delete the subspace. PermissionDeleteSubspace = Permission(0b100000) + // PermissionInteractWithContent allows users to interact with content inside the subspace (eg. polls) + PermissionInteractWithContent = Permission(0b1000000) + + // PermissionEditOwnContent allows users to edit their own content inside the subspace + PermissionEditOwnContent = Permission(0b10000000) + // PermissionEverything allows to do everything. // This should usually be reserved only to the owner (which has it by default) - PermissionEverything = Permission(0b111111) + PermissionEverything = Permission(0b11111111) ) var ( diff --git a/x/subspaces/types/permissions_test.go b/x/subspaces/types/permissions_test.go index e93570dff2..cf96a7d54d 100644 --- a/x/subspaces/types/permissions_test.go +++ b/x/subspaces/types/permissions_test.go @@ -179,8 +179,8 @@ func TestSanitizePermission(t *testing.T) { }, { name: "extra bits are set to 0", - permission: 0b11111111111111111111111111000001, - expResult: types.PermissionWrite, + permission: 0b11111111111111111111111111111111, + expResult: types.PermissionEverything, }, }