feat: protoreflect based amino json encoder (#14877)

This commit is contained in:
Matt Kocubinski 2023-02-23 15:41:06 -06:00 committed by GitHub
parent 7d28f63291
commit 976ecd4c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 10895 additions and 268 deletions

View File

@ -13,9 +13,11 @@ require (
cosmossdk.io/x/evidence v0.1.0
cosmossdk.io/x/feegrant v0.0.0-20230117113717-50e7c4a4ceff
cosmossdk.io/x/nft v0.0.0-20230113085233-fae3332d62fc
cosmossdk.io/x/tx v0.2.0
cosmossdk.io/x/upgrade v0.0.0-20230127052425-54c8e1568335
github.com/cometbft/cometbft v0.37.0-alpha.3
github.com/cosmos/cosmos-db v1.0.0-rc.1
github.com/cosmos/cosmos-proto v1.0.0-beta.2
// this version is not used as it is always replaced by the latest Cosmos SDK version
github.com/cosmos/cosmos-sdk v0.48.0
github.com/cosmos/gogoproto v1.4.6
@ -37,7 +39,6 @@ require (
cosmossdk.io/client/v2 v2.0.0-20230220152935-67f04e629623 // indirect
cosmossdk.io/collections v0.0.0-20230214153846-b6c6e4e99177 // indirect
cosmossdk.io/core v0.5.1 // indirect
cosmossdk.io/x/tx v0.2.0 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
@ -61,7 +62,6 @@ require (
github.com/cometbft/cometbft-db v0.7.0 // indirect
github.com/confio/ics23/go v0.9.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.2 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.21.0-alpha.1 // indirect
@ -113,6 +113,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/iancoleman/strcase v0.2.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@ -191,9 +192,11 @@ require (
// It must be in sync with SimApp temporary replaces
replace (
// TODO tag all extracted modules after SDK refactor
cosmossdk.io/client/v2 => ../client/v2
cosmossdk.io/x/evidence => ../x/evidence
cosmossdk.io/x/feegrant => ../x/feegrant
cosmossdk.io/x/nft => ../x/nft
cosmossdk.io/x/tx => ../x/tx
cosmossdk.io/x/upgrade => ../x/upgrade
// This can be deleted after the CometBFT PR is merged
github.com/cometbft/cometbft => github.com/cometbft/cometbft v0.0.0-20230203130311-387422ac220d

View File

@ -190,8 +190,6 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V
cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
cosmossdk.io/api v0.3.1 h1:NNiOclKRR0AOlO4KIqeaG6PS6kswOMhHD0ir0SscNXE=
cosmossdk.io/api v0.3.1/go.mod h1:DfHfMkiNA2Uhy8fj0JJlOCYOBp4eWUUJ1te5zBGNyIw=
cosmossdk.io/client/v2 v2.0.0-20230220152935-67f04e629623 h1:QzZA1P+twvj10ylCVXzHodLge+RvrwoNIlWpU+MrRa0=
cosmossdk.io/client/v2 v2.0.0-20230220152935-67f04e629623/go.mod h1:Yo6R3XSXKxi+G642kK7+ZOh2czNTwTO1b8ywOvwaFQo=
cosmossdk.io/collections v0.0.0-20230214153846-b6c6e4e99177 h1:At2M0aYQKEAWqr6JtZrJPOlfhdvENiGwJg2NCNHwIdc=
cosmossdk.io/collections v0.0.0-20230214153846-b6c6e4e99177/go.mod h1:uqCi8FG+Bh2vv/qf5xZ8iab0E0c/DMA/cwafbp8dkcU=
cosmossdk.io/core v0.5.1 h1:vQVtFrIYOQJDV3f7rw4pjjVqc1id4+mE0L9hHP66pyI=
@ -206,8 +204,6 @@ cosmossdk.io/math v1.0.0-beta.6.0.20230216172121-959ce49135e4 h1:/jnzJ9zFsL7qkV8
cosmossdk.io/math v1.0.0-beta.6.0.20230216172121-959ce49135e4/go.mod h1:gUVtWwIzfSXqcOT+lBVz2jyjfua8DoBdzRsIyaUAT/8=
cosmossdk.io/store v0.0.0-20230206092147-e03195e4b8a7 h1:IwyDN/YaQmF+Pmuv8d7vRWMM/k2RjSmPBycMcmd3ICE=
cosmossdk.io/store v0.0.0-20230206092147-e03195e4b8a7/go.mod h1:1XOtuYs7jsfQkn7G3VQXB6I+2tHXKHZw2U/AafNbnlk=
cosmossdk.io/x/tx v0.2.0 h1:53f5TIXhpPYJGMm47SUslcV2i8JNBEN3eE08BmxE/Zg=
cosmossdk.io/x/tx v0.2.0/go.mod h1:CTko7wgt7aBdbxOesZ+Wo1uO/03ueKzIQ0iI323Rqgk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
@ -685,6 +681,8 @@ github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw
github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=

View File

@ -0,0 +1 @@
testdata/rapid

View File

@ -0,0 +1,684 @@
package aminojson
import (
"fmt"
"reflect"
"testing"
"time"
cosmos_proto "github.com/cosmos/cosmos-proto"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"pgregory.net/rapid"
"github.com/cosmos/cosmos-proto/rapidproto"
gogoproto "github.com/cosmos/gogoproto/proto"
"cosmossdk.io/api/amino"
authapi "cosmossdk.io/api/cosmos/auth/v1beta1"
authzapi "cosmossdk.io/api/cosmos/authz/v1beta1"
bankapi "cosmossdk.io/api/cosmos/bank/v1beta1"
v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
consensusapi "cosmossdk.io/api/cosmos/consensus/v1"
"cosmossdk.io/api/cosmos/crypto/ed25519"
multisigapi "cosmossdk.io/api/cosmos/crypto/multisig"
"cosmossdk.io/api/cosmos/crypto/secp256k1"
distapi "cosmossdk.io/api/cosmos/distribution/v1beta1"
evidenceapi "cosmossdk.io/api/cosmos/evidence/v1beta1"
feegrantapi "cosmossdk.io/api/cosmos/feegrant/v1beta1"
gov_v1_api "cosmossdk.io/api/cosmos/gov/v1"
gov_v1beta1_api "cosmossdk.io/api/cosmos/gov/v1beta1"
groupapi "cosmossdk.io/api/cosmos/group/v1"
mintapi "cosmossdk.io/api/cosmos/mint/v1beta1"
paramsapi "cosmossdk.io/api/cosmos/params/v1beta1"
slashingapi "cosmossdk.io/api/cosmos/slashing/v1beta1"
stakingapi "cosmossdk.io/api/cosmos/staking/v1beta1"
upgradeapi "cosmossdk.io/api/cosmos/upgrade/v1beta1"
vestingapi "cosmossdk.io/api/cosmos/vesting/v1beta1"
"cosmossdk.io/x/evidence"
evidencetypes "cosmossdk.io/x/evidence/types"
feegranttypes "cosmossdk.io/x/feegrant"
feegrantmodule "cosmossdk.io/x/feegrant/module"
"cosmossdk.io/x/tx/aminojson"
"cosmossdk.io/x/upgrade"
upgradetypes "cosmossdk.io/x/upgrade/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
ed25519types "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
secp256k1types "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
gogo_testpb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/gogo/testpb"
pulsar_testpb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/pulsar/testpb"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/auth"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
authztypes "github.com/cosmos/cosmos-sdk/x/authz"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
"github.com/cosmos/cosmos-sdk/x/bank"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/consensus"
consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
"github.com/cosmos/cosmos-sdk/x/distribution"
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/gov"
gov_v1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
gov_v1beta1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
grouptypes "github.com/cosmos/cosmos-sdk/x/group"
groupmodule "github.com/cosmos/cosmos-sdk/x/group/module"
"github.com/cosmos/cosmos-sdk/x/mint"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/types/proposal"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
type generatedType struct {
pulsar proto.Message
gogo gogoproto.Message
opts rapidproto.GeneratorOptions
}
func genType(gogo gogoproto.Message, pulsar proto.Message, opts rapidproto.GeneratorOptions) generatedType {
return generatedType{
pulsar: pulsar,
gogo: gogo,
opts: opts,
}
}
func withDecisionPolicy(opts rapidproto.GeneratorOptions) rapidproto.GeneratorOptions {
return opts.
WithAnyTypes(
&groupapi.ThresholdDecisionPolicy{},
&groupapi.PercentageDecisionPolicy{}).
WithDisallowNil().
WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.ThresholdDecisionPolicy{}).
WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.PercentageDecisionPolicy{})
}
func generatorFieldMapper(t *rapid.T, field protoreflect.FieldDescriptor, name string) (protoreflect.Value, bool) {
opts := field.Options()
switch {
case proto.HasExtension(opts, cosmos_proto.E_Scalar):
scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string)
switch scalar {
case "cosmos.Int":
i32 := rapid.Int32().Draw(t, name)
return protoreflect.ValueOfString(fmt.Sprintf("%d", i32)), true
case "cosmos.Dec":
return protoreflect.ValueOfString(""), true
}
case field.Kind() == protoreflect.BytesKind:
if proto.HasExtension(opts, amino.E_Encoding) {
encoding := proto.GetExtension(opts, amino.E_Encoding).(string)
if encoding == "cosmos_dec_bytes" {
return protoreflect.ValueOfBytes([]byte{}), true
}
}
}
return protoreflect.Value{}, false
}
var (
genOpts = rapidproto.GeneratorOptions{
Resolver: protoregistry.GlobalTypes,
FieldMaps: []rapidproto.FieldMapper{generatorFieldMapper},
}
genTypes = []generatedType{
// auth
genType(&authtypes.Params{}, &authapi.Params{}, genOpts),
genType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, genOpts.WithAnyTypes(&ed25519.PubKey{})),
genType(&authtypes.ModuleAccount{}, &authapi.ModuleAccount{}, genOpts.WithAnyTypes(&ed25519.PubKey{})),
genType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, genOpts),
genType(&authtypes.MsgUpdateParams{}, &authapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
// authz
genType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, genOpts),
genType(&authztypes.Grant{}, &authzapi.Grant{},
genOpts.WithAnyTypes(&authzapi.GenericAuthorization{}).
WithDisallowNil().
WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}),
),
genType(&authztypes.MsgGrant{}, &authzapi.MsgGrant{},
genOpts.WithAnyTypes(&authzapi.GenericAuthorization{}).
WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}).
WithDisallowNil(),
),
genType(&authztypes.MsgExec{}, &authzapi.MsgExec{},
genOpts.WithAnyTypes(&authzapi.MsgGrant{}, &authzapi.GenericAuthorization{}).
WithDisallowNil().
WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}).
WithInterfaceHint("cosmos.base.v1beta1.Msg", &authzapi.MsgGrant{}),
),
// bank
genType(&banktypes.MsgSend{}, &bankapi.MsgSend{}, genOpts.WithDisallowNil()),
genType(&banktypes.MsgMultiSend{}, &bankapi.MsgMultiSend{}, genOpts.WithDisallowNil()),
genType(&banktypes.MsgUpdateParams{}, &bankapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
genType(&banktypes.MsgSetSendEnabled{}, &bankapi.MsgSetSendEnabled{}, genOpts),
genType(&banktypes.SendAuthorization{}, &bankapi.SendAuthorization{}, genOpts),
genType(&banktypes.Params{}, &bankapi.Params{}, genOpts),
// consensus
genType(&consensustypes.MsgUpdateParams{}, &consensusapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
// crypto
genType(&multisig.LegacyAminoPubKey{}, &multisigapi.LegacyAminoPubKey{},
genOpts.WithAnyTypes(&ed25519.PubKey{}, &secp256k1.PubKey{})),
// distribution
genType(&disttypes.MsgWithdrawDelegatorReward{}, &distapi.MsgWithdrawDelegatorReward{}, genOpts),
genType(&disttypes.MsgWithdrawValidatorCommission{}, &distapi.MsgWithdrawValidatorCommission{}, genOpts),
genType(&disttypes.MsgSetWithdrawAddress{}, &distapi.MsgSetWithdrawAddress{}, genOpts),
genType(&disttypes.MsgFundCommunityPool{}, &distapi.MsgFundCommunityPool{}, genOpts),
genType(&disttypes.MsgUpdateParams{}, &distapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
genType(&disttypes.MsgCommunityPoolSpend{}, &distapi.MsgCommunityPoolSpend{}, genOpts),
genType(&disttypes.MsgDepositValidatorRewardsPool{}, &distapi.MsgDepositValidatorRewardsPool{}, genOpts),
genType(&disttypes.Params{}, &distapi.Params{}, genOpts),
// evidence
genType(&evidencetypes.Equivocation{}, &evidenceapi.Equivocation{}, genOpts.WithDisallowNil()),
genType(&evidencetypes.MsgSubmitEvidence{}, &evidenceapi.MsgSubmitEvidence{},
genOpts.WithAnyTypes(&evidenceapi.Equivocation{}).
WithDisallowNil().
WithInterfaceHint("cosmos.evidence.v1beta1.Evidence", &evidenceapi.Equivocation{})),
// feegrant
genType(&feegranttypes.MsgGrantAllowance{}, &feegrantapi.MsgGrantAllowance{},
genOpts.WithDisallowNil().
WithAnyTypes(
&feegrantapi.BasicAllowance{},
&feegrantapi.PeriodicAllowance{}).
WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}).
WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}),
),
genType(&feegranttypes.MsgRevokeAllowance{}, &feegrantapi.MsgRevokeAllowance{}, genOpts),
genType(&feegranttypes.BasicAllowance{}, &feegrantapi.BasicAllowance{}, genOpts.WithDisallowNil()),
genType(&feegranttypes.PeriodicAllowance{}, &feegrantapi.PeriodicAllowance{}, genOpts.WithDisallowNil()),
genType(&feegranttypes.AllowedMsgAllowance{}, &feegrantapi.AllowedMsgAllowance{},
genOpts.WithDisallowNil().
WithAnyTypes(
&feegrantapi.BasicAllowance{},
&feegrantapi.PeriodicAllowance{}).
WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}).
WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}),
),
// gov v1beta1
genType(&gov_v1beta1_types.MsgSubmitProposal{}, &gov_v1beta1_api.MsgSubmitProposal{},
genOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}).
WithDisallowNil().
WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}),
),
genType(&gov_v1beta1_types.MsgDeposit{}, &gov_v1beta1_api.MsgDeposit{}, genOpts),
genType(&gov_v1beta1_types.MsgVote{}, &gov_v1beta1_api.MsgVote{}, genOpts),
genType(&gov_v1beta1_types.MsgVoteWeighted{}, &gov_v1beta1_api.MsgVoteWeighted{}, genOpts),
genType(&gov_v1beta1_types.TextProposal{}, &gov_v1beta1_api.TextProposal{}, genOpts),
// gov v1
genType(&gov_v1_types.MsgSubmitProposal{}, &gov_v1_api.MsgSubmitProposal{},
genOpts.WithAnyTypes(&gov_v1_api.MsgVote{}, &gov_v1_api.MsgVoteWeighted{}, &gov_v1_api.MsgDeposit{},
&gov_v1_api.MsgExecLegacyContent{}, &gov_v1_api.MsgUpdateParams{}).
WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}).
WithDisallowNil(),
),
genType(&gov_v1_types.MsgDeposit{}, &gov_v1_api.MsgDeposit{}, genOpts),
genType(&gov_v1_types.MsgVote{}, &gov_v1_api.MsgVote{}, genOpts),
genType(&gov_v1_types.MsgVoteWeighted{}, &gov_v1_api.MsgVoteWeighted{}, genOpts),
genType(&gov_v1_types.MsgExecLegacyContent{}, &gov_v1_api.MsgExecLegacyContent{},
genOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}).
WithDisallowNil().
WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{})),
genType(&gov_v1_types.MsgUpdateParams{}, &gov_v1_api.MsgUpdateParams{}, genOpts.WithDisallowNil()),
// group
genType(&grouptypes.MsgCreateGroup{}, &groupapi.MsgCreateGroup{}, genOpts),
genType(&grouptypes.MsgUpdateGroupMembers{}, &groupapi.MsgUpdateGroupMembers{}, genOpts),
genType(&grouptypes.MsgUpdateGroupAdmin{}, &groupapi.MsgUpdateGroupAdmin{}, genOpts),
genType(&grouptypes.MsgUpdateGroupMetadata{}, &groupapi.MsgUpdateGroupMetadata{}, genOpts),
genType(&grouptypes.MsgCreateGroupWithPolicy{}, &groupapi.MsgCreateGroupWithPolicy{},
withDecisionPolicy(genOpts)),
genType(&grouptypes.MsgCreateGroupPolicy{}, &groupapi.MsgCreateGroupPolicy{},
withDecisionPolicy(genOpts)),
genType(&grouptypes.MsgUpdateGroupPolicyAdmin{}, &groupapi.MsgUpdateGroupPolicyAdmin{}, genOpts),
genType(&grouptypes.MsgUpdateGroupPolicyDecisionPolicy{}, &groupapi.MsgUpdateGroupPolicyDecisionPolicy{},
withDecisionPolicy(genOpts)),
genType(&grouptypes.MsgUpdateGroupPolicyMetadata{}, &groupapi.MsgUpdateGroupPolicyMetadata{}, genOpts),
genType(&grouptypes.MsgSubmitProposal{}, &groupapi.MsgSubmitProposal{},
genOpts.WithDisallowNil().
WithAnyTypes(&groupapi.MsgCreateGroup{}, &groupapi.MsgUpdateGroupMembers{}).
WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgCreateGroup{}).
WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgUpdateGroupMembers{}),
),
genType(&grouptypes.MsgVote{}, &groupapi.MsgVote{}, genOpts),
genType(&grouptypes.MsgExec{}, &groupapi.MsgExec{}, genOpts),
genType(&grouptypes.MsgLeaveGroup{}, &groupapi.MsgLeaveGroup{}, genOpts),
// mint
genType(&minttypes.Params{}, &mintapi.Params{}, genOpts),
genType(&minttypes.MsgUpdateParams{}, &mintapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
// params
genType(&proposal.ParameterChangeProposal{}, &paramsapi.ParameterChangeProposal{}, genOpts),
// slashing
genType(&slashingtypes.Params{}, &slashingapi.Params{}, genOpts.WithDisallowNil()),
genType(&slashingtypes.MsgUnjail{}, &slashingapi.MsgUnjail{}, genOpts),
genType(&slashingtypes.MsgUpdateParams{}, &slashingapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
// staking
genType(&stakingtypes.MsgCreateValidator{}, &stakingapi.MsgCreateValidator{},
genOpts.WithDisallowNil().
WithAnyTypes(&ed25519.PubKey{}).
WithInterfaceHint("cosmos.crypto.PubKey", &ed25519.PubKey{}),
),
genType(&stakingtypes.MsgEditValidator{}, &stakingapi.MsgEditValidator{}, genOpts.WithDisallowNil()),
genType(&stakingtypes.MsgDelegate{}, &stakingapi.MsgDelegate{}, genOpts.WithDisallowNil()),
genType(&stakingtypes.MsgUndelegate{}, &stakingapi.MsgUndelegate{}, genOpts.WithDisallowNil()),
genType(&stakingtypes.MsgBeginRedelegate{}, &stakingapi.MsgBeginRedelegate{}, genOpts.WithDisallowNil()),
genType(&stakingtypes.MsgUpdateParams{}, &stakingapi.MsgUpdateParams{}, genOpts.WithDisallowNil()),
genType(&stakingtypes.StakeAuthorization{}, &stakingapi.StakeAuthorization{}, genOpts),
// upgrade
genType(&upgradetypes.Plan{}, &upgradeapi.Plan{}, genOpts.WithDisallowNil()),
genType(&upgradetypes.SoftwareUpgradeProposal{}, &upgradeapi.SoftwareUpgradeProposal{}, genOpts.WithDisallowNil()),
genType(&upgradetypes.CancelSoftwareUpgradeProposal{}, &upgradeapi.CancelSoftwareUpgradeProposal{}, genOpts),
genType(&upgradetypes.MsgSoftwareUpgrade{}, &upgradeapi.MsgSoftwareUpgrade{}, genOpts.WithDisallowNil()),
genType(&upgradetypes.MsgCancelUpgrade{}, &upgradeapi.MsgCancelUpgrade{}, genOpts),
// vesting
genType(&vestingtypes.BaseVestingAccount{}, &vestingapi.BaseVestingAccount{}, genOpts.WithDisallowNil()),
genType(&vestingtypes.ContinuousVestingAccount{}, &vestingapi.ContinuousVestingAccount{}, genOpts.WithDisallowNil()),
genType(&vestingtypes.DelayedVestingAccount{}, &vestingapi.DelayedVestingAccount{}, genOpts.WithDisallowNil()),
genType(&vestingtypes.PeriodicVestingAccount{}, &vestingapi.PeriodicVestingAccount{}, genOpts.WithDisallowNil()),
genType(&vestingtypes.PermanentLockedAccount{}, &vestingapi.PermanentLockedAccount{}, genOpts.WithDisallowNil()),
genType(&vestingtypes.MsgCreateVestingAccount{}, &vestingapi.MsgCreateVestingAccount{}, genOpts),
genType(&vestingtypes.MsgCreatePermanentLockedAccount{}, &vestingapi.MsgCreatePermanentLockedAccount{}, genOpts),
genType(&vestingtypes.MsgCreatePeriodicVestingAccount{}, &vestingapi.MsgCreatePeriodicVestingAccount{}, genOpts),
}
)
// TestAminoJSON_Equivalence tests that x/tx/Encoder encoding is equivalent to the legacy Encoder encoding.
// A custom generator is used to generate random messages that are then encoded using both encoders. The custom
// generator only supports proto.Message (which implement the protoreflect API) so in order to test legacy gogo types
// we end up with a workflow as follows:
//
// 1. Generate a random protobuf proto.Message using the custom generator
// 2. Marshal the proto.Message to protobuf binary bytes
// 3. Unmarshal the protobuf bytes to a gogoproto.Message
// 4. Marshal the gogoproto.Message to amino JSON bytes
// 5. Marshal the proto.Message to amino JSON bytes
// 6. Compare the amino JSON bytes from steps 4 and 5
//
// In order for step 3 to work certain restrictions on the data generated in step 1 must be enforced and are described
// by the mutation of genOpts passed to the generator.
func TestAminoJSON_Equivalence(t *testing.T) {
encCfg := testutil.MakeTestEncodingConfig(
auth.AppModuleBasic{}, authzmodule.AppModuleBasic{}, bank.AppModuleBasic{}, consensus.AppModuleBasic{},
distribution.AppModuleBasic{}, evidence.AppModuleBasic{}, feegrantmodule.AppModuleBasic{},
gov.AppModuleBasic{}, groupmodule.AppModuleBasic{}, mint.AppModuleBasic{}, params.AppModuleBasic{},
slashing.AppModuleBasic{}, staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, vesting.AppModuleBasic{})
aj := aminojson.NewAminoJSON()
for _, tt := range genTypes {
name := string(tt.pulsar.ProtoReflect().Descriptor().FullName())
t.Run(name, func(t *testing.T) {
gen := rapidproto.MessageGenerator(tt.pulsar, tt.opts)
fmt.Printf("testing %s\n", tt.pulsar.ProtoReflect().Descriptor().FullName())
rapid.Check(t, func(t *rapid.T) {
// uncomment to debug; catch a panic and inspect application state
//defer func() {
// if r := recover(); r != nil {
// //fmt.Printf("Panic: %+v\n", r)
// t.FailNow()
// }
//}()
msg := gen.Draw(t, "msg")
postFixPulsarMessage(msg)
gogo := tt.gogo
sanity := tt.pulsar
protoBz, err := proto.Marshal(msg)
require.NoError(t, err)
err = proto.Unmarshal(protoBz, sanity)
require.NoError(t, err)
err = encCfg.Codec.Unmarshal(protoBz, gogo)
require.NoError(t, err)
legacyAminoJson, err := encCfg.Amino.MarshalJSON(gogo)
require.NoError(t, err)
aminoJson, err := aj.Marshal(msg)
require.NoError(t, err)
require.Equal(t, string(legacyAminoJson), string(aminoJson))
})
})
}
}
func newAny(t *testing.T, msg proto.Message) *anypb.Any {
bz, err := proto.Marshal(msg)
require.NoError(t, err)
typeName := fmt.Sprintf("/%s", msg.ProtoReflect().Descriptor().FullName())
return &anypb.Any{
TypeUrl: typeName,
Value: bz,
}
}
// TestAminoJSON_LegacyParity tests that the Encoder encoder produces the same output as the Encoder encoder.
func TestAminoJSON_LegacyParity(t *testing.T) {
encCfg := testutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, authzmodule.AppModuleBasic{},
bank.AppModuleBasic{}, distribution.AppModuleBasic{}, slashing.AppModuleBasic{}, staking.AppModuleBasic{},
vesting.AppModuleBasic{})
aj := aminojson.NewAminoJSON()
addr1 := types.AccAddress("addr1")
now := time.Now()
genericAuth, _ := codectypes.NewAnyWithValue(&authztypes.GenericAuthorization{Msg: "foo"})
genericAuthPulsar := newAny(t, &authzapi.GenericAuthorization{Msg: "foo"})
pubkeyAny, _ := codectypes.NewAnyWithValue(&secp256k1types.PubKey{Key: []byte("foo")})
pubkeyAnyPulsar := newAny(t, &secp256k1.PubKey{Key: []byte("foo")})
dec10bz, _ := types.NewDec(10).Marshal()
int123bz, _ := types.NewInt(123).Marshal()
cases := map[string]struct {
gogo gogoproto.Message
pulsar proto.Message
pulsarMarshalFails bool
// this will fail in cases where a lossy encoding of an empty array to protobuf occurs. the unmarshalled bytes
// represent the array as nil, and a subsequent marshal to JSON represent the array as null instead of empty.
roundTripUnequal bool
// pulsar does not support marshalling a math.Dec as anything except a string. Therefore, we cannot unmarshal
// a pulsar encoded Math.dec (the string representation of a Decimal) into a gogo Math.dec (expecting an int64).
protoUnmarshalFails bool
}{
"auth/params": {gogo: &authtypes.Params{TxSigLimit: 10}, pulsar: &authapi.Params{TxSigLimit: 10}},
"auth/module_account": {
gogo: &authtypes.ModuleAccount{
BaseAccount: authtypes.NewBaseAccountWithAddress(addr1), Permissions: []string{}},
pulsar: &authapi.ModuleAccount{
BaseAccount: &authapi.BaseAccount{Address: addr1.String()}, Permissions: []string{}},
roundTripUnequal: true,
},
"auth/base_account": {
gogo: &authtypes.BaseAccount{Address: addr1.String(), PubKey: pubkeyAny},
pulsar: &authapi.BaseAccount{Address: addr1.String(), PubKey: pubkeyAnyPulsar},
},
"authz/msg_grant": {
gogo: &authztypes.MsgGrant{
Grant: authztypes.Grant{Expiration: &now, Authorization: genericAuth}},
pulsar: &authzapi.MsgGrant{
Grant: &authzapi.Grant{Expiration: timestamppb.New(now), Authorization: genericAuthPulsar}},
},
"authz/msg_update_params": {
gogo: &authtypes.MsgUpdateParams{Params: authtypes.Params{TxSigLimit: 10}},
pulsar: &authapi.MsgUpdateParams{Params: &authapi.Params{TxSigLimit: 10}},
},
"authz/msg_exec/empty_msgs": {
gogo: &authztypes.MsgExec{Msgs: []*codectypes.Any{}},
pulsar: &authzapi.MsgExec{Msgs: []*anypb.Any{}},
},
"distribution/delegator_starting_info": {
gogo: &disttypes.DelegatorStartingInfo{},
pulsar: &distapi.DelegatorStartingInfo{},
},
"distribution/delegator_starting_info/non_zero_dec": {
gogo: &disttypes.DelegatorStartingInfo{Stake: types.NewDec(10)},
pulsar: &distapi.DelegatorStartingInfo{Stake: "10.000000000000000000"},
protoUnmarshalFails: true,
},
"distribution/delegation_delegator_reward": {
gogo: &disttypes.DelegationDelegatorReward{},
pulsar: &distapi.DelegationDelegatorReward{},
},
"distribution/community_pool_spend_proposal_with_deposit": {
gogo: &disttypes.CommunityPoolSpendProposalWithDeposit{},
pulsar: &distapi.CommunityPoolSpendProposalWithDeposit{},
},
"distribution/msg_withdraw_delegator_reward": {
gogo: &disttypes.MsgWithdrawDelegatorReward{DelegatorAddress: "foo"},
pulsar: &distapi.MsgWithdrawDelegatorReward{DelegatorAddress: "foo"},
},
"crypto/ed25519": {
gogo: &ed25519types.PubKey{Key: []byte("key")},
pulsar: &ed25519.PubKey{Key: []byte("key")},
},
"crypto/secp256k1": {
gogo: &secp256k1types.PubKey{Key: []byte("key")},
pulsar: &secp256k1.PubKey{Key: []byte("key")},
},
"crypto/legacy_amino_pubkey": {
gogo: &multisig.LegacyAminoPubKey{PubKeys: []*codectypes.Any{pubkeyAny}},
pulsar: &multisigapi.LegacyAminoPubKey{PublicKeys: []*anypb.Any{pubkeyAnyPulsar}},
},
"crypto/legacy_amino_pubkey/empty": {
gogo: &multisig.LegacyAminoPubKey{},
pulsar: &multisigapi.LegacyAminoPubKey{},
},
"consensus/evidence_params/duration": {
gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: 1e9 + 7},
pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{Seconds: 1, Nanos: 7}},
},
"consensus/evidence_params/big_duration": {
gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999},
pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{
Seconds: rapidproto.MaxDurationSeconds, Nanos: 999999999}},
},
"consensus/evidence_params/too_big_duration": {
gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: time.Duration(rapidproto.MaxDurationSeconds*1e9) + 999999999},
pulsar: &gov_v1beta1_api.VotingParams{VotingPeriod: &durationpb.Duration{
Seconds: rapidproto.MaxDurationSeconds + 1, Nanos: 999999999}},
pulsarMarshalFails: true,
},
// amino.dont_omitempty + empty/nil lists produce some surprising results
"bank/send_authorization/empty_coins": {
gogo: &banktypes.SendAuthorization{SpendLimit: []types.Coin{}},
pulsar: &bankapi.SendAuthorization{SpendLimit: []*v1beta1.Coin{}},
},
"bank/send_authorization/nil_coins": {
gogo: &banktypes.SendAuthorization{SpendLimit: nil},
pulsar: &bankapi.SendAuthorization{SpendLimit: nil},
},
"bank/send_authorization/empty_list": {
gogo: &banktypes.SendAuthorization{AllowList: []string{}},
pulsar: &bankapi.SendAuthorization{AllowList: []string{}},
},
"bank/send_authorization/nil_list": {
gogo: &banktypes.SendAuthorization{AllowList: nil},
pulsar: &bankapi.SendAuthorization{AllowList: nil},
},
"bank/msg_multi_send/nil_everything": {
gogo: &banktypes.MsgMultiSend{},
pulsar: &bankapi.MsgMultiSend{},
},
"slashing/params/empty_dec": {
gogo: &slashingtypes.Params{DowntimeJailDuration: 1e9 + 7},
pulsar: &slashingapi.Params{DowntimeJailDuration: &durationpb.Duration{Seconds: 1, Nanos: 7}},
},
// This test cases demonstrates the expected contract and proper way to set a cosmos.Dec field represented
// as bytes in protobuf message, namely:
// dec10bz, _ := types.NewDec(10).Marshal()
"slashing/params/dec": {
gogo: &slashingtypes.Params{
DowntimeJailDuration: 1e9 + 7,
MinSignedPerWindow: types.NewDec(10)},
pulsar: &slashingapi.Params{
DowntimeJailDuration: &durationpb.Duration{Seconds: 1, Nanos: 7},
MinSignedPerWindow: dec10bz,
},
},
"staking/create_validator": {
gogo: &stakingtypes.MsgCreateValidator{Pubkey: pubkeyAny},
pulsar: &stakingapi.MsgCreateValidator{
Pubkey: pubkeyAnyPulsar,
Description: &stakingapi.Description{},
Commission: &stakingapi.CommissionRates{},
Value: &v1beta1.Coin{},
},
},
"staking/msg_cancel_unbonding_delegation_response": {
gogo: &stakingtypes.MsgCancelUnbondingDelegationResponse{},
pulsar: &stakingapi.MsgCancelUnbondingDelegationResponse{},
},
"staking/stake_authorization_empty": {
gogo: &stakingtypes.StakeAuthorization{},
pulsar: &stakingapi.StakeAuthorization{},
},
"staking/stake_authorization_allow": {
gogo: &stakingtypes.StakeAuthorization{
Validators: &stakingtypes.StakeAuthorization_AllowList{
AllowList: &stakingtypes.StakeAuthorization_Validators{Address: []string{"foo"}},
}},
pulsar: &stakingapi.StakeAuthorization{
Validators: &stakingapi.StakeAuthorization_AllowList{
AllowList: &stakingapi.StakeAuthorization_Validators{Address: []string{"foo"}},
}},
},
"vesting/base_account_empty": {
gogo: &vestingtypes.BaseVestingAccount{BaseAccount: &authtypes.BaseAccount{}},
pulsar: &vestingapi.BaseVestingAccount{BaseAccount: &authapi.BaseAccount{}},
},
"vesting/base_account_pubkey": {
gogo: &vestingtypes.BaseVestingAccount{BaseAccount: &authtypes.BaseAccount{PubKey: pubkeyAny}},
pulsar: &vestingapi.BaseVestingAccount{BaseAccount: &authapi.BaseAccount{PubKey: pubkeyAnyPulsar}},
},
"math/int_as_string": {
gogo: &gogo_testpb.IntAsString{IntAsString: types.NewInt(123)},
pulsar: &pulsar_testpb.IntAsString{IntAsString: "123"},
},
"math/int_as_string/empty": {
gogo: &gogo_testpb.IntAsString{},
pulsar: &pulsar_testpb.IntAsString{},
},
"math/int_as_bytes": {
gogo: &gogo_testpb.IntAsBytes{IntAsBytes: types.NewInt(123)},
pulsar: &pulsar_testpb.IntAsBytes{IntAsBytes: int123bz},
},
"math/int_as_bytes/empty": {
gogo: &gogo_testpb.IntAsBytes{},
pulsar: &pulsar_testpb.IntAsBytes{},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
gogoBytes, err := encCfg.Amino.MarshalJSON(tc.gogo)
require.NoError(t, err)
pulsarBytes, err := aj.Marshal(tc.pulsar)
if tc.pulsarMarshalFails {
require.Error(t, err)
return
}
require.NoError(t, err)
fmt.Printf("pulsar: %s\n", string(pulsarBytes))
fmt.Printf(" gogo: %s\n", string(gogoBytes))
require.Equal(t, string(gogoBytes), string(pulsarBytes))
pulsarProtoBytes, err := proto.Marshal(tc.pulsar)
require.NoError(t, err)
gogoType := reflect.TypeOf(tc.gogo).Elem()
newGogo := reflect.New(gogoType).Interface().(gogoproto.Message)
err = encCfg.Codec.Unmarshal(pulsarProtoBytes, newGogo)
if tc.protoUnmarshalFails {
require.Error(t, err)
return
}
require.NoError(t, err)
newGogoBytes, err := encCfg.Amino.MarshalJSON(newGogo)
if tc.roundTripUnequal {
require.NotEqual(t, string(gogoBytes), string(newGogoBytes))
return
}
require.Equal(t, string(gogoBytes), string(newGogoBytes))
})
}
}
func TestSendAuthorization(t *testing.T) {
encCfg := testutil.MakeTestEncodingConfig(auth.AppModuleBasic{}, authzmodule.AppModuleBasic{},
distribution.AppModuleBasic{}, bank.AppModuleBasic{})
aj := aminojson.NewAminoJSON()
// beware, Coins has as custom MarshalJSON method which changes how nil is handled
// nil -> [] (empty list)
// [] -> [] (empty list)
// https://github.com/cosmos/cosmos-sdk/blob/be9bd7a8c1b41b115d58f4e76ee358e18a52c0af/types/coin.go#L199
// explicitly show the default for clarity
pulsar := &bankapi.SendAuthorization{SpendLimit: []*v1beta1.Coin{}}
sanityPulsar := &bankapi.SendAuthorization{}
gogo := &banktypes.SendAuthorization{SpendLimit: types.Coins{}}
protoBz, err := proto.Marshal(pulsar)
require.NoError(t, err)
err = encCfg.Codec.Unmarshal(protoBz, gogo)
require.NoError(t, err)
err = proto.Unmarshal(protoBz, sanityPulsar)
// !!!
// empty []string is not the same as nil []string. this is a bug in gogo.
// `[]string` -> proto.Marshal -> legacyAmino.UnmarshalProto (unmarshals empty slice as nil)
// -> legacyAmino.MarshalJson -> `null`
// `[]string` -> [proto.Marshal -> pulsar.Unmarshal] -> amino.MarshalJson -> `[]`
require.Nil(t, gogo.SpendLimit)
require.Nil(t, sanityPulsar.SpendLimit)
require.NotNil(t, pulsar.SpendLimit)
require.Zero(t, len(pulsar.SpendLimit))
legacyAminoJson, err := encCfg.Amino.MarshalJSON(gogo)
aminoJson, err := aj.Marshal(sanityPulsar)
require.Equal(t, string(legacyAminoJson), string(aminoJson))
aminoJson, err = aj.Marshal(pulsar)
require.NoError(t, err)
// at this point, pulsar.SpendLimit = [], and gogo.SpendLimit = nil, but they will both marshal to `[]`
// this is *only* possible because of Cosmos SDK's custom MarshalJSON method for Coins
require.Equal(t, string(legacyAminoJson), string(aminoJson))
}
func postFixPulsarMessage(msg proto.Message) {
switch m := msg.(type) {
case *authapi.ModuleAccount:
if m.BaseAccount == nil {
m.BaseAccount = &authapi.BaseAccount{}
}
_, _, bz := testdata.KeyTestPubAddr()
// always set address to a valid bech32 address
text, _ := bech32.ConvertAndEncode("cosmos", bz)
m.BaseAccount.Address = text
// see negative test
if len(m.Permissions) == 0 {
m.Permissions = nil
}
}
}

View File

@ -0,0 +1,4 @@
codegen:
@echo "Generating proto files"
buf --template buf.gen.gogo.yaml generate
buf --template buf.gen.pulsar.yaml generate

View File

@ -0,0 +1,8 @@
version: v1
plugins:
- name: gocosmos
out: ./gogo
opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types
- name: grpc-gateway
out: ./gogo
opt: logtostderr=true,allow_colon_final_segments=true

View File

@ -0,0 +1,15 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/testpb/pulsar
except:
- buf.build/googleapis/googleapis
- buf.build/cosmos/gogo-proto
- buf.build/cosmos/cosmos-proto
override:
buf.build/cosmos/cosmos-sdk: cosmossdk.io/api
plugins:
- name: go-pulsar
out: ./pulsar
opt: paths=source_relative

View File

@ -0,0 +1,11 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: cosmos
repository: cosmos-proto
commit: 1935555c206d4afb9e94615dfd0fad31
- remote: buf.build
owner: cosmos
repository: gogo-proto
commit: 34d970b699f84aa382f3c29773a60836

View File

@ -0,0 +1,12 @@
version: v1
deps:
- buf.build/cosmos/cosmos-proto
- buf.build/cosmos/gogo-proto
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX
breaking:
ignore:
- testpb

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
syntax = "proto3";
package testpb;
import "gogoproto/gogo.proto";
import "amino/amino.proto";
import "cosmos_proto/cosmos.proto";
message streng {
string value = 1;
}
message TestRepeatedFields {
repeated streng nullable_omitempty = 1;
// not supported for empty sets
// go-amino emits nothing but the protoreflect library emits a null
repeated streng nullable_dont_omitempty = 2 [(amino.dont_omitempty) = true];
// not supported for empty sets
// go-amino emits a null but the protoreflect library emits nothing
repeated streng non_nullable_omitempty = 3 [(gogoproto.nullable) = false];
repeated streng non_nullable_dont_omitempty = 4 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
message TestNullableFields {
streng nullable_omitempty = 1;
// not supported
// go-amino emits nothing and the protoreflect returns an error
// alternatively protoreflect could emit `{}`
streng nullable_dont_omitempty = 2 [(amino.dont_omitempty) = true];
// not supported
// go-amino emits `{}` but the protoreflect library emits nothing
streng non_nullable_omitempty = 3 [(gogoproto.nullable) = false];
streng non_nullable_dont_omitempty = 4 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
message IntAsString {
string int_as_string = 1 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(amino.dont_omitempty) = true,
(gogoproto.nullable) = false
];
}
message IntAsBytes {
bytes int_as_bytes = 1 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(amino.dont_omitempty) = true,
(gogoproto.nullable) = false
];
}

View File

@ -0,0 +1,81 @@
package aminojson
import (
"fmt"
"testing"
gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/protobuf/proto"
"github.com/stretchr/testify/require"
"cosmossdk.io/x/tx/aminojson"
"github.com/cosmos/cosmos-sdk/codec"
gogopb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/gogo/testpb"
pulsarpb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/pulsar/testpb"
)
func TestRepeatedFields(t *testing.T) {
cdc := codec.NewLegacyAmino()
aj := aminojson.NewAminoJSON()
cases := map[string]struct {
gogo gogoproto.Message
pulsar proto.Message
unequal bool
errs bool
}{
"unsupported_empty_sets": {
gogo: &gogopb.TestRepeatedFields{},
pulsar: &pulsarpb.TestRepeatedFields{},
unequal: true,
},
"unsupported_empty_sets_are_set": {
gogo: &gogopb.TestRepeatedFields{
NullableDontOmitempty: []*gogopb.Streng{{Value: "foo"}},
NonNullableOmitempty: []gogopb.Streng{{Value: "foo"}},
},
pulsar: &pulsarpb.TestRepeatedFields{
NullableDontOmitempty: []*pulsarpb.Streng{{Value: "foo"}},
NonNullableOmitempty: []*pulsarpb.Streng{{Value: "foo"}},
},
},
"unsupported_nullable": {
gogo: &gogopb.TestNullableFields{},
pulsar: &pulsarpb.TestNullableFields{},
errs: true,
},
"unsupported_nullable_set": {
gogo: &gogopb.TestNullableFields{
NullableDontOmitempty: &gogopb.Streng{Value: "foo"},
NonNullableDontOmitempty: gogopb.Streng{Value: "foo"},
},
pulsar: &pulsarpb.TestNullableFields{
NullableDontOmitempty: &pulsarpb.Streng{Value: "foo"},
NonNullableDontOmitempty: &pulsarpb.Streng{Value: "foo"},
},
unequal: true,
},
}
for n, tc := range cases {
t.Run(n, func(t *testing.T) {
gogoBz, err := cdc.MarshalJSON(tc.gogo)
require.NoError(t, err)
pulsarBz, err := aj.Marshal(tc.pulsar)
if tc.errs {
require.Error(t, err)
return
}
require.NoError(t, err)
fmt.Printf(" gogo: %s\npulsar: %s\n", string(gogoBz), string(pulsarBz))
if tc.unequal {
require.NotEqual(t, string(gogoBz), string(pulsarBz))
} else {
require.Equal(t, string(gogoBz), string(pulsarBz))
}
})
}
}

View File

@ -1,221 +0,0 @@
package rapidproto
import (
"fmt"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
)
func MessageGenerator[T proto.Message](x T, options GeneratorOptions) *rapid.Generator[T] {
msgType := x.ProtoReflect().Type()
return rapid.Custom(func(t *rapid.T) T {
msg := msgType.New()
options.setFields(t, msg, 0)
return msg.Interface().(T)
})
}
type GeneratorOptions struct {
AnyTypeURLs []string
Resolver protoregistry.MessageTypeResolver
}
const depthLimit = 10
func (opts GeneratorOptions) setFields(t *rapid.T, msg protoreflect.Message, depth int) bool {
// to avoid stack overflow we limit the depth of nested messages
if depth > depthLimit {
return false
}
descriptor := msg.Descriptor()
fullName := descriptor.FullName()
switch fullName {
case timestampFullName:
opts.genTimestamp(t, msg)
return true
case durationFullName:
opts.genDuration(t, msg)
return true
case anyFullName:
return opts.genAny(t, msg, depth)
case fieldMaskFullName:
opts.genFieldMask(t, msg)
return true
default:
fields := descriptor.Fields()
n := fields.Len()
for i := 0; i < n; i++ {
field := fields.Get(i)
if !rapid.Bool().Draw(t, fmt.Sprintf("gen-%s", field.Name())) {
continue
}
opts.setFieldValue(t, msg, field, depth)
}
return true
}
}
const (
timestampFullName = "google.protobuf.Timestamp"
durationFullName = "google.protobuf.Duration"
anyFullName = "google.protobuf.Any"
fieldMaskFullName = "google.protobuf.FieldMask"
)
func (opts GeneratorOptions) setFieldValue(t *rapid.T, msg protoreflect.Message, field protoreflect.FieldDescriptor, depth int) {
name := string(field.Name())
kind := field.Kind()
switch {
case field.IsList():
list := msg.Mutable(field).List()
n := rapid.IntRange(0, 10).Draw(t, fmt.Sprintf("%sN", name))
for i := 0; i < n; i++ {
if kind == protoreflect.MessageKind || kind == protoreflect.GroupKind {
if !opts.setFields(t, list.AppendMutable().Message(), depth+1) {
list.Truncate(i)
}
} else {
list.Append(opts.genScalarFieldValue(t, field, fmt.Sprintf("%s%d", name, i)))
}
}
case field.IsMap():
m := msg.Mutable(field).Map()
n := rapid.IntRange(0, 10).Draw(t, fmt.Sprintf("%sN", name))
for i := 0; i < n; i++ {
keyField := field.MapKey()
valueField := field.MapValue()
valueKind := valueField.Kind()
key := opts.genScalarFieldValue(t, keyField, fmt.Sprintf("%s%d-key", name, i))
if valueKind == protoreflect.MessageKind || valueKind == protoreflect.GroupKind {
if !opts.setFields(t, m.Mutable(key.MapKey()).Message(), depth+1) {
m.Clear(key.MapKey())
}
} else {
value := opts.genScalarFieldValue(t, valueField, fmt.Sprintf("%s%d-key", name, i))
m.Set(key.MapKey(), value)
}
}
default:
if kind == protoreflect.MessageKind || kind == protoreflect.GroupKind {
if !opts.setFields(t, msg.Mutable(field).Message(), depth+1) {
msg.Clear(field)
}
} else {
msg.Set(field, opts.genScalarFieldValue(t, field, name))
}
}
}
func (opts GeneratorOptions) genScalarFieldValue(t *rapid.T, field protoreflect.FieldDescriptor, name string) protoreflect.Value {
switch field.Kind() {
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
return protoreflect.ValueOfInt32(rapid.Int32().Draw(t, name))
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
return protoreflect.ValueOfUint32(rapid.Uint32().Draw(t, name))
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
return protoreflect.ValueOfInt64(rapid.Int64().Draw(t, name))
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
return protoreflect.ValueOfUint64(rapid.Uint64().Draw(t, name))
case protoreflect.BoolKind:
return protoreflect.ValueOfBool(rapid.Bool().Draw(t, name))
case protoreflect.BytesKind:
return protoreflect.ValueOfBytes(rapid.SliceOf(rapid.Byte()).Draw(t, name))
case protoreflect.FloatKind:
return protoreflect.ValueOfFloat32(rapid.Float32().Draw(t, name))
case protoreflect.DoubleKind:
return protoreflect.ValueOfFloat64(rapid.Float64().Draw(t, name))
case protoreflect.EnumKind:
enumValues := field.Enum().Values()
val := rapid.Int32Range(0, int32(enumValues.Len()-1)).Draw(t, name)
return protoreflect.ValueOfEnum(protoreflect.EnumNumber(val))
case protoreflect.StringKind:
return protoreflect.ValueOfString(rapid.String().Draw(t, name))
default:
t.Fatalf("unexpected %v", field)
return protoreflect.Value{}
}
}
const (
secondsName = "seconds"
nanosName = "nanos"
)
func (opts GeneratorOptions) genTimestamp(t *rapid.T, msg protoreflect.Message) {
seconds := rapid.Int64Range(-9999999999, 9999999999).Draw(t, "seconds")
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos")
setSecondsNanosFields(t, msg, seconds, nanos)
}
func (opts GeneratorOptions) genDuration(t *rapid.T, msg protoreflect.Message) {
seconds := rapid.Int64Range(0, 315576000000).Draw(t, "seconds")
nanos := rapid.Int32Range(0, 999999999).Draw(t, "nanos")
setSecondsNanosFields(t, msg, seconds, nanos)
}
func setSecondsNanosFields(t *rapid.T, message protoreflect.Message, seconds int64, nanos int32) {
fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
assert.Assert(t, secondsField != nil)
message.Set(secondsField, protoreflect.ValueOfInt64(seconds))
nanosField := fields.ByName(nanosName)
assert.Assert(t, nanosField != nil)
message.Set(nanosField, protoreflect.ValueOfInt32(nanos))
}
const (
typeURLName = "type_url"
valueName = "value"
)
func (opts GeneratorOptions) genAny(t *rapid.T, msg protoreflect.Message, depth int) bool {
if len(opts.AnyTypeURLs) == 0 {
return false
}
fields := msg.Descriptor().Fields()
typeURL := rapid.SampledFrom(opts.AnyTypeURLs).Draw(t, "type_url")
typ, err := opts.Resolver.FindMessageByURL(typeURL)
assert.NilError(t, err)
typeURLField := fields.ByName(typeURLName)
assert.Assert(t, typeURLField != nil)
msg.Set(typeURLField, protoreflect.ValueOfString(typeURL))
valueMsg := typ.New()
opts.setFields(t, valueMsg, depth+1)
valueBz, err := proto.Marshal(valueMsg.Interface())
assert.NilError(t, err)
valueField := fields.ByName(valueName)
assert.Assert(t, valueField != nil)
msg.Set(valueField, protoreflect.ValueOfBytes(valueBz))
return true
}
const (
pathsName = "paths"
)
func (opts GeneratorOptions) genFieldMask(t *rapid.T, msg protoreflect.Message) {
paths := rapid.SliceOfN(rapid.StringMatching("[a-z]+([.][a-z]+){0,2}"), 1, 5).Draw(t, "paths")
pathsField := msg.Descriptor().Fields().ByName(pathsName)
assert.Assert(t, pathsField != nil)
pathsList := msg.NewField(pathsField).List()
for _, path := range paths {
pathsList.Append(protoreflect.ValueOfString(path))
}
}

View File

@ -1,33 +0,0 @@
package rapidproto_test
import (
"fmt"
"testing"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
"pgregory.net/rapid"
"github.com/cosmos/cosmos-proto/testpb"
"github.com/cosmos/cosmos-sdk/testutil/rapidproto"
)
// TestRegression checks that the generator still produces the same output
// for the same random seeds, assuming that this data has been hand expected
// to generally look good.
func TestRegression(t *testing.T) {
gen := rapidproto.MessageGenerator(&testpb.A{}, rapidproto.GeneratorOptions{})
for i := 0; i < 5; i++ {
testRegressionSeed(t, i, gen)
}
}
func testRegressionSeed[X proto.Message](t *testing.T, seed int, generator *rapid.Generator[X]) {
x := generator.Example(seed)
bz, err := protojson.Marshal(x)
assert.NilError(t, err)
golden.Assert(t, string(bz), fmt.Sprintf("seed%d.json", seed))
}

View File

@ -1 +0,0 @@
{"enum":"Two", "someBoolean":true, "INT32":6, "SINT32":-53, "INT64":"-261", "SFIXED32":3, "FIXED32":65302, "FIXED64":"45044", "STRING":"󳲠~Âaႃ#", "MESSAGE":{"x":"ʰ="}, "MAP":{"":{"x":"௹"}, "%󠇯º$&.":{"x":"-"}, "=A":{}, "AA|𞀠":{"x":"a\u0000ๆ"}}, "LIST":[{}], "ONEOFSTRING":"", "imported":{}}

View File

@ -1 +0,0 @@
{"UINT32":177, "INT64":"-139958413", "SFIXED32":41418, "FIXED32":25381940, "FLOAT":-8.336453e+31, "SFIXED64":"-2503553836720", "DOUBLE":-0.03171187036377887, "STRING":"?˄~ע", "MESSAGE":{"x":"dDž#"}, "MAP":{"Ⱥa<":{"x":"+["}, "֑Ⱥ|@!`":{}}, "ONEOFSTRING":"\u0012\t?A", "imported":{}, "type":"A<>=*ى~~‮Ⱥ*ᾈാȺAᶊ?"}

View File

@ -1 +0,0 @@
{"INT32":-48, "UINT32":246, "INT64":"-21558176502", "SING64":"5030347", "UINT64":"28", "FIXED32":92, "DOUBLE":2.3547259926790202e-142, "STRING":"ಾ", "LIST":[{}, {}, {}, {}, {"x":" ᾚDzA{˭҄\nA^$?ᾦ,:<\"?_\u0014;|"}], "ONEOFSTRING":"𝟠Ÿ", "LISTENUM":["Two", "One", "One"]}

View File

@ -1 +0,0 @@
{"INT32":22525032, "SINT32":897, "INT64":"-301128487533312", "SFIXED64":"-71", "FIXED64":"14", "DOUBLE":-2.983041182946181, "STRING":"-A^'", "MESSAGE":{"x":"#ऻ;́\r"}, "LIST":[{}, {}, {}, {}, {}], "ONEOFSTRING":"", "imported":{}, "type":"₩\u0000^৴~౽ NjAৈ􁇸⃠𝖜ೄ"}

View File

@ -1 +0,0 @@
{"SINT32":1, "INT64":"-9223372036854775808", "SING64":"1", "FLOAT":-0.00013906474, "SFIXED64":"71414010", "STRING":"ף̂", "MESSAGE":{"x":""}, "LIST":[{}], "ONEOFSTRING":"#¯∑Ⱥ<E28891>", "LISTENUM":["One", "One", "Two", "Two", "One", "One", "One", "Two"], "imported":{}, "type":"\u001b<ʰ+`𑱐@\u001b*Dž‮\u0000#₻\u0000"}

1
x/tx/aminojson/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
testdata/rapid

37
x/tx/aminojson/any.go Normal file
View File

@ -0,0 +1,37 @@
package aminojson
import (
"fmt"
"io"
"google.golang.org/protobuf/types/known/anypb"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)
func (enc Encoder) marshalAny(message protoreflect.Message, writer io.Writer) error {
anyMsg := message.Interface().(*anypb.Any)
resolver := protoregistry.GlobalTypes
typ, err := resolver.FindMessageByURL(anyMsg.TypeUrl)
if err != nil {
return errors.Wrapf(err, "can't resolve type URL %s", anyMsg.TypeUrl)
}
valueMsg := typ.New()
err = proto.Unmarshal(anyMsg.Value, valueMsg.Interface())
if err != nil {
return err
}
_, named := getMessageAminoName(valueMsg)
if !named {
return fmt.Errorf("message %s is packed into an any field, so requires an amino.name annotation",
anyMsg.TypeUrl)
}
return enc.beginMarshal(valueMsg, writer)
}

170
x/tx/aminojson/encoder.go Normal file
View File

@ -0,0 +1,170 @@
package aminojson
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"github.com/pkg/errors"
"google.golang.org/protobuf/reflect/protoreflect"
authapi "cosmossdk.io/api/cosmos/auth/v1beta1"
"cosmossdk.io/api/cosmos/crypto/multisig"
"cosmossdk.io/math"
)
// cosmosIntEncoder provides legacy compatible encoding for cosmos.Int types. In gogo messages these are sometimes
// represented by a `cosmos-sdk/types.Int` through the usage of the option:
//
// (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"
//
// In pulsar message they represented as strings, which is the only format this encoder supports.
func cosmosIntEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error {
switch val := v.Interface().(type) {
case string:
if val == "" {
return jsonMarshal(w, "0")
}
return jsonMarshal(w, val)
case []byte:
if len(val) == 0 {
return jsonMarshal(w, "0")
}
var i math.Int
err := i.Unmarshal(val)
if err != nil {
return err
}
return jsonMarshal(w, i.String())
default:
return fmt.Errorf("unsupported type %T", val)
}
}
// cosmosDecEncoder provides legacy compatible encoding for cosmos.Dec and cosmos.Int types. These are sometimes
// represented as strings in pulsar messages and sometimes as bytes. This encoder handles both cases.
func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error {
switch val := v.Interface().(type) {
case string:
if val == "" {
return jsonMarshal(w, "0")
}
return jsonMarshal(w, val)
case []byte:
if len(val) == 0 {
return jsonMarshal(w, "0")
}
var dec math.LegacyDec
err := dec.Unmarshal(val)
if err != nil {
return err
}
return jsonMarshal(w, dec.String())
default:
return fmt.Errorf("unsupported type %T", val)
}
}
// nullSliceAsEmptyEncoder replicates the behavior at:
// https://github.com/cosmos/cosmos-sdk/blob/be9bd7a8c1b41b115d58f4e76ee358e18a52c0af/types/coin.go#L199-L205
func nullSliceAsEmptyEncoder(enc *Encoder, v protoreflect.Value, w io.Writer) error {
switch list := v.Interface().(type) {
case protoreflect.List:
if list.Len() == 0 {
_, err := w.Write([]byte("[]"))
return err
}
return enc.marshalList(list, w)
default:
return fmt.Errorf("unsupported type %T", list)
}
}
// keyFieldEncoder replicates the behavior at described at:
// https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/secp256k1/keys.proto#L16
// The message is treated if it were bytes directly without the key field specified.
func keyFieldEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error {
keyField := msg.Descriptor().Fields().ByName("key")
if keyField == nil {
return errors.New(`message encoder for key_field: no field named "key" found`)
}
bz := msg.Get(keyField).Bytes()
if len(bz) == 0 {
_, err := fmt.Fprint(w, "null")
return err
}
_, err := fmt.Fprintf(w, `"%s"`, base64.StdEncoding.EncodeToString(bz))
return err
}
type moduleAccountPretty struct {
Address string `json:"address"`
PubKey string `json:"public_key"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
Name string `json:"name"`
Permissions []string `json:"permissions"`
}
// moduleAccountEncoder replicates the behavior in
// https://github.com/cosmos/cosmos-sdk/blob/41a3dfeced2953beba3a7d11ec798d17ee19f506/x/auth/types/account.go#L230-L254
func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error {
ma := msg.Interface().(*authapi.ModuleAccount)
pretty := moduleAccountPretty{
PubKey: "",
Name: ma.Name,
Permissions: ma.Permissions,
}
if ma.BaseAccount != nil {
pretty.Address = ma.BaseAccount.Address
pretty.AccountNumber = ma.BaseAccount.AccountNumber
pretty.Sequence = ma.BaseAccount.Sequence
} else {
pretty.Address = ""
pretty.AccountNumber = 0
pretty.Sequence = 0
}
bz, err := json.Marshal(pretty)
if err != nil {
return err
}
_, err = w.Write(bz)
return err
}
// thresholdStringEncoder replicates the behavior at:
// https://github.com/cosmos/cosmos-sdk/blob/4a6a1e3cb8de459891cb0495052589673d14ef51/crypto/keys/multisig/amino.go#L35
// also see:
// https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/multisig/keys.proto#L15/
func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer) error {
pk, ok := msg.Interface().(*multisig.LegacyAminoPubKey)
if !ok {
return errors.New("thresholdStringEncoder: msg not a multisig.LegacyAminoPubKey")
}
_, err := w.Write([]byte(fmt.Sprintf(`{"threshold":"%d","pubkeys":`, pk.Threshold)))
if err != nil {
return err
}
if len(pk.PublicKeys) == 0 {
_, err = w.Write([]byte(`[]}`))
return err
}
fields := msg.Descriptor().Fields()
pubkeysField := fields.ByName("public_keys")
pubkeys := msg.Get(pubkeysField).List()
err = enc.marshalList(pubkeys, w)
if err != nil {
return err
}
_, err = w.Write([]byte(`}`))
return err
}

View File

@ -0,0 +1,3 @@
codegen:
@echo "Generating proto files"
buf generate

View File

@ -0,0 +1,15 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: cosmossdk.io/x/tx/aminojson/internal/testpb
except:
- buf.build/googleapis/googleapis
- buf.build/cosmos/gogo-proto
- buf.build/cosmos/cosmos-proto
override:
buf.build/cosmos/cosmos-sdk: cosmossdk.io/api
plugins:
- name: go-pulsar
out: .
opt: paths=source_relative

View File

@ -0,0 +1,7 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: cosmos
repository: cosmos-proto
commit: 1935555c206d4afb9e94615dfd0fad31

View File

@ -0,0 +1,11 @@
version: v1
deps:
- buf.build/cosmos/cosmos-proto
lint:
use:
- DEFAULT
except:
- PACKAGE_VERSION_SUFFIX
breaking:
ignore:
- testpb

View File

@ -0,0 +1,76 @@
syntax = "proto3";
package testpb;
import "amino/amino.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/empty.proto";
message WithAMap{
map<string, string> str_map = 1;
}
message WithAList{
repeated string dont_omitempty_list = 1 [ (amino.dont_omitempty) = true ];
repeated string list = 2;
}
message ABitOfEverything {
option (amino.name) = "ABitOfEverything";
NestedMessage message = 1;
AnEnum enum = 2;
repeated int32 repeated = 6;
string str = 7;
bool bool = 8;
bytes bytes = 9;
int32 i32 = 10;
fixed32 f32 = 11;
uint32 u32 = 12;
sint32 si32 = 13;
sfixed32 sf32 = 14;
int64 i64 = 15;
fixed64 f64 = 16;
uint64 u64 = 17;
sint64 si64 = 18;
sfixed64 sf64 = 19;
// The following types are not tested here because they are treated fundamentally differently in
// gogoproto. They are tested fully in /tests/integration/aminojson/aminojson_test.go
// Any types are not tested here because they are treated fundamentally differently in gogoproto.
// In the go-admin/gogoproto paradigm a custom SDK type is used to wrap the Any type, which has no
// equivalent in code in pulsar generated types.
//
// google.protobuf.Any any = 22
// Also not tested due to gogoproto differences. In gogoproto, time.Time is used instead of
// google.protobuf.Timestamp. There is no equivalent in pulsar generated types.
//
// google.protobuf.Timestamp timestamp = 23;
// Also not tested due to gogoproto differences. In gogoproto, time.Duration is used instead of
// google.protobuf.Duration. There is no equivalent in pulsar generated types.
//
// google.protobuf.Duration duration = 24;
}
message NestedMessage {
option (amino.name) = "NestedMessage";
string foo = 1;
int32 bar = 2;
}
enum AnEnum {
UNDEFINED = 0;
ONE = 1;
TWO = 2;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,300 @@
package aminojson
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
// MessageEncoder is a function that can encode a protobuf protoreflect.Message to JSON.
type MessageEncoder func(*Encoder, protoreflect.Message, io.Writer) error
// FieldEncoder is a function that can encode a protobuf protoreflect.Value to JSON.
type FieldEncoder func(*Encoder, protoreflect.Value, io.Writer) error
// Encoder is a JSON encoder that uses the Amino JSON encoding rules for protobuf messages.
type Encoder struct {
// maps cosmos_proto.scalar -> field encoder
scalarEncoders map[string]FieldEncoder
messageEncoders map[string]MessageEncoder
fieldEncoders map[string]FieldEncoder
}
// NewAminoJSON returns a new Encoder capable of serializing protobuf messages to JSON using the Amino JSON encoding
// rules.
func NewAminoJSON() Encoder {
enc := Encoder{
scalarEncoders: map[string]FieldEncoder{
"cosmos.Dec": cosmosDecEncoder,
"cosmos.Int": cosmosIntEncoder,
},
messageEncoders: map[string]MessageEncoder{
"key_field": keyFieldEncoder,
"module_account": moduleAccountEncoder,
"threshold_string": thresholdStringEncoder,
},
fieldEncoders: map[string]FieldEncoder{
"legacy_coins": nullSliceAsEmptyEncoder,
"cosmos_dec_bytes": cosmosDecEncoder,
},
}
return enc
}
// DefineMessageEncoding defines a custom encoding for a protobuf message. The `name` field must match a usage of
// an (amino.message_encoding) option in the protobuf message as in the following example. This encoding will be
// used instead of the default encoding for all usages of the tagged message.
//
// message ModuleAccount {
// option (amino.name) = "cosmos-sdk/ModuleAccount";
// option (amino.message_encoding) = "module_account";
// ...
// }
func (enc Encoder) DefineMessageEncoding(name string, encoder MessageEncoder) Encoder {
if enc.messageEncoders == nil {
enc.messageEncoders = map[string]MessageEncoder{}
}
enc.messageEncoders[name] = encoder
return enc
}
// DefineFieldEncoding defines a custom encoding for a protobuf field. The `name` field must match a usage of
// an (amino.encoding) option in the protobuf message as in the following example. This encoding will be used
// instead of the default encoding for all usages of the tagged field.
//
// message Balance {
// repeated cosmos.base.v1beta1.Coin coins = 2 [
// (amino.encoding) = "legacy_coins",
// (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
// (gogoproto.nullable) = false,
// (amino.dont_omitempty) = true
// ];
// ...
// }
func (enc Encoder) DefineFieldEncoding(name string, encoder FieldEncoder) Encoder {
if enc.fieldEncoders == nil {
enc.fieldEncoders = map[string]FieldEncoder{}
}
enc.fieldEncoders[name] = encoder
return enc
}
// Marshal serializes a protobuf message to JSON.
func (enc Encoder) Marshal(message proto.Message) ([]byte, error) {
buf := &bytes.Buffer{}
err := enc.beginMarshal(message.ProtoReflect(), buf)
return buf.Bytes(), err
}
func (enc Encoder) beginMarshal(msg protoreflect.Message, writer io.Writer) error {
name, named := getMessageAminoName(msg)
if named {
_, err := writer.Write([]byte(fmt.Sprintf(`{"type":"%s","value":`, name)))
if err != nil {
return err
}
}
err := enc.marshal(protoreflect.ValueOfMessage(msg), writer)
if err != nil {
return err
}
if named {
_, err = writer.Write([]byte("}"))
if err != nil {
return err
}
}
return nil
}
func (enc Encoder) marshal(value protoreflect.Value, writer io.Writer) error {
switch val := value.Interface().(type) {
case protoreflect.Message:
err := enc.marshalMessage(val, writer)
return err
case protoreflect.Map:
return errors.New("maps are not supported")
case protoreflect.List:
if !val.IsValid() {
_, err := writer.Write([]byte("null"))
return err
}
return enc.marshalList(val, writer)
case string, bool, int32, uint32, []byte, protoreflect.EnumNumber:
return jsonMarshal(writer, val)
case uint64, int64:
_, err := fmt.Fprintf(writer, `"%d"`, val) // quoted
return err
default:
return errors.Errorf("unknown type %T", val)
}
}
func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) error {
if msg == nil {
return errors.New("nil message")
}
switch msg.Descriptor().FullName() {
case timestampFullName:
// replicate https://github.com/tendermint/go-amino/blob/8e779b71f40d175cd1302d3cd41a75b005225a7a/json-encode.go#L45-L51
return marshalTimestamp(msg, writer)
case durationFullName:
return marshalDuration(msg, writer)
case anyFullName:
return enc.marshalAny(msg, writer)
}
if encoder := enc.getMessageEncoder(msg); encoder != nil {
err := encoder(&enc, msg, writer)
return err
}
_, err := writer.Write([]byte("{"))
if err != nil {
return err
}
fields := msg.Descriptor().Fields()
first := true
emptyOneOfWritten := map[string]bool{}
for i := 0; i < fields.Len(); i++ {
f := fields.Get(i)
v := msg.Get(f)
name := getAminoFieldName(f)
oneof := f.ContainingOneof()
isOneOf := oneof != nil
oneofFieldName, oneofTypeName, err := getOneOfNames(f)
if err != nil && isOneOf {
return err
}
writeNil := false
if !msg.Has(f) {
// msg.WhichOneof(oneof) == nil: no field of the oneof has been set
// !emptyOneOfWritten: we haven't written a null for this oneof yet (only write one null per empty oneof)
if isOneOf && msg.WhichOneof(oneof) == nil && !emptyOneOfWritten[oneofFieldName] {
name = oneofFieldName
writeNil = true
emptyOneOfWritten[oneofFieldName] = true
} else if omitEmpty(f) {
continue
} else if f.Kind() == protoreflect.MessageKind &&
f.Cardinality() != protoreflect.Repeated &&
!v.Message().IsValid() {
return errors.Errorf("not supported: dont_omit_empty=true on invalid (nil?) message field: %s", name)
}
}
if !first {
_, err = writer.Write([]byte(","))
if err != nil {
return err
}
}
if isOneOf && !writeNil {
_, err = writer.Write([]byte(fmt.Sprintf(`"%s":{"type":"%s","value":{`,
oneofFieldName, oneofTypeName)))
if err != nil {
return err
}
}
err = jsonMarshal(writer, name)
if err != nil {
return err
}
_, err = writer.Write([]byte(":"))
if err != nil {
return err
}
// encode value
if encoder := enc.getFieldEncoding(f); encoder != nil {
err = encoder(&enc, v, writer)
if err != nil {
return err
}
} else if writeNil {
_, err = writer.Write([]byte("null"))
if err != nil {
return err
}
} else {
err = enc.marshal(v, writer)
if err != nil {
return err
}
}
if isOneOf && !writeNil {
_, err = writer.Write([]byte("}}"))
if err != nil {
return err
}
}
first = false
}
_, err = writer.Write([]byte("}"))
return err
}
func jsonMarshal(w io.Writer, v interface{}) error {
blob, err := json.Marshal(v)
if err != nil {
return err
}
_, err = w.Write(blob)
return err
}
func (enc Encoder) marshalList(list protoreflect.List, writer io.Writer) error {
n := list.Len()
_, err := writer.Write([]byte("["))
if err != nil {
return err
}
first := true
for i := 0; i < n; i++ {
if !first {
_, err := writer.Write([]byte(","))
if err != nil {
return err
}
}
first = false
err = enc.marshal(list.Get(i), writer)
if err != nil {
return err
}
}
_, err = writer.Write([]byte("]"))
return err
}
const (
timestampFullName protoreflect.FullName = "google.protobuf.Timestamp"
durationFullName protoreflect.FullName = "google.protobuf.Duration"
anyFullName protoreflect.FullName = "google.protobuf.Any"
)

View File

@ -0,0 +1,133 @@
package aminojson_test
import (
"reflect"
"testing"
"github.com/cosmos/cosmos-proto/rapidproto"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-amino"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"pgregory.net/rapid"
"gotest.tools/v3/assert"
"cosmossdk.io/x/tx/aminojson"
"cosmossdk.io/x/tx/aminojson/internal/testpb"
)
func marshalLegacy(msg proto.Message) ([]byte, error) {
cdc := amino.NewCodec()
cdc.RegisterConcrete(&testpb.ABitOfEverything{}, "ABitOfEverything", nil)
cdc.RegisterConcrete(&testpb.NestedMessage{}, "NestedMessage", nil)
return cdc.MarshalJSON(msg)
}
func TestAminoJSON_EdgeCases(t *testing.T) {
cdc := amino.NewCodec()
cdc.RegisterConcrete(&testpb.ABitOfEverything{}, "ABitOfEverything", nil)
cdc.RegisterConcrete(&testpb.NestedMessage{}, "NestedMessage", nil)
aj := aminojson.NewAminoJSON()
cases := map[string]struct {
msg proto.Message
shouldErr bool
}{
"empty": {msg: &testpb.ABitOfEverything{}},
"single map": {msg: &testpb.WithAMap{StrMap: map[string]string{"foo": "bar"}}, shouldErr: true},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
bz, err := aj.Marshal(tc.msg)
if tc.shouldErr {
require.Error(t, err)
return
}
require.NoError(t, err)
rv := reflect.New(reflect.TypeOf(tc.msg).Elem()).Elem()
msg2 := rv.Addr().Interface().(proto.Message)
legacyBz, err := cdc.MarshalJSON(tc.msg)
assert.NilError(t, err)
require.Equal(t, string(legacyBz), string(bz))
goProtoJSON, err := protojson.Marshal(tc.msg)
err = cdc.UnmarshalJSON(bz, msg2)
assert.NilError(t, err, "unmarshal failed: %s vs %s", legacyBz, goProtoJSON)
})
}
}
func TestAminoJSON(t *testing.T) {
cdc := amino.NewCodec()
cdc.RegisterConcrete(&testpb.ABitOfEverything{}, "ABitOfEverything", nil)
cdc.RegisterConcrete(&testpb.NestedMessage{}, "NestedMessage", nil)
msg := &testpb.ABitOfEverything{
Message: &testpb.NestedMessage{
Foo: "test",
Bar: 0, // this is the default value and should be omitted from output
},
Enum: testpb.AnEnum_ONE,
Repeated: []int32{3, -7, 2, 6, 4},
Str: `abcxyz"foo"def`,
Bool: true,
Bytes: []byte{0, 1, 2, 3},
I32: -15,
F32: 1001,
U32: 1200,
Si32: -376,
Sf32: -1000,
I64: 14578294827584932,
F64: 9572348124213523654,
U64: 4759492485,
Si64: -59268425823934,
Sf64: -659101379604211154,
}
bz, err := aminojson.NewAminoJSON().Marshal(msg)
assert.NilError(t, err)
legacyBz, err := cdc.MarshalJSON(msg)
assert.NilError(t, err)
require.Equal(t, string(legacyBz), string(bz))
}
func TestRapid(t *testing.T) {
gen := rapidproto.MessageGenerator(&testpb.ABitOfEverything{}, rapidproto.GeneratorOptions{})
rapid.Check(t, func(t *rapid.T) {
msg := gen.Draw(t, "msg")
bz, err := aminojson.NewAminoJSON().Marshal(msg)
assert.NilError(t, err)
checkInvariants(t, msg, bz)
})
}
func checkInvariants(t *rapid.T, message proto.Message, marshaledBytes []byte) {
checkLegacyParity(t, message, marshaledBytes)
checkRoundTrip(t, message, marshaledBytes)
}
func checkLegacyParity(t *rapid.T, message proto.Message, marshaledBytes []byte) {
legacyBz, err := marshalLegacy(message)
assert.NilError(t, err)
require.Equal(t, string(legacyBz), string(marshaledBytes), "%s vs legacy: %s", string(marshaledBytes),
string(legacyBz))
}
func checkRoundTrip(t *rapid.T, message proto.Message, marshaledBytes []byte) {
cdc := amino.NewCodec()
cdc.RegisterConcrete(&testpb.ABitOfEverything{}, "ABitOfEverything", nil)
cdc.RegisterConcrete(&testpb.NestedMessage{}, "NestedMessage", nil)
message2 := message.ProtoReflect().New().Interface()
goProtoJSON, err := cdc.MarshalJSON(message)
assert.NilError(t, err)
err = cdc.UnmarshalJSON(marshaledBytes, message2)
assert.NilError(t, err, "%s vs %s", string(marshaledBytes), string(goProtoJSON))
}

90
x/tx/aminojson/options.go Normal file
View File

@ -0,0 +1,90 @@
package aminojson
import (
cosmos_proto "github.com/cosmos/cosmos-proto"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/api/amino"
)
// getMessageAminoName returns the amino name of a message if it has been set by the `amino.name` option.
// If the message does not have an amino name, then the function returns false.
func getMessageAminoName(msg protoreflect.Message) (string, bool) {
opts := msg.Descriptor().Options()
if proto.HasExtension(opts, amino.E_Name) {
name := proto.GetExtension(opts, amino.E_Name)
return name.(string), true
}
return "", false
}
// omitEmpty returns true if the field should be omitted if empty. Empty field omission is the default behavior.
func omitEmpty(field protoreflect.FieldDescriptor) bool {
opts := field.Options()
if proto.HasExtension(opts, amino.E_DontOmitempty) {
dontOmitEmpty := proto.GetExtension(opts, amino.E_DontOmitempty).(bool)
return !dontOmitEmpty
}
return true
}
// getAminoFieldName returns the amino field name of a field if it has been set by the `amino.field_name` option.
// If the field does not have an amino field name, then the function returns the protobuf field name.
func getAminoFieldName(field protoreflect.FieldDescriptor) string {
opts := field.Options()
if proto.HasExtension(opts, amino.E_FieldName) {
return proto.GetExtension(opts, amino.E_FieldName).(string)
}
return string(field.Name())
}
func getOneOfNames(field protoreflect.FieldDescriptor) (string, string, error) {
opts := field.Options()
oneOf := field.ContainingOneof()
if oneOf == nil {
return "", "", errors.Errorf("field %s must be within a oneof", field.Name())
}
fieldName := strcase.ToCamel(string(oneOf.Name()))
var typeName string
if proto.HasExtension(opts, amino.E_OneofName) {
typeName = proto.GetExtension(opts, amino.E_OneofName).(string)
} else {
return "", "", errors.Errorf("field %s within a oneof must have the amino.oneof_type_name option set",
field.Name())
}
return fieldName, typeName, nil
}
func (enc Encoder) getMessageEncoder(message protoreflect.Message) MessageEncoder {
opts := message.Descriptor().Options()
if proto.HasExtension(opts, amino.E_MessageEncoding) {
encoding := proto.GetExtension(opts, amino.E_MessageEncoding).(string)
if fn, ok := enc.messageEncoders[encoding]; ok {
return fn
}
}
return nil
}
func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEncoder {
opts := field.Options()
if proto.HasExtension(opts, amino.E_Encoding) {
encoding := proto.GetExtension(opts, amino.E_Encoding).(string)
if fn, ok := enc.fieldEncoders[encoding]; ok {
return fn
}
}
if proto.HasExtension(opts, cosmos_proto.E_Scalar) {
scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string)
if fn, ok := enc.scalarEncoders[scalar]; ok {
return fn
}
}
return nil
}

73
x/tx/aminojson/time.go Normal file
View File

@ -0,0 +1,73 @@
package aminojson
import (
"fmt"
"google.golang.org/protobuf/reflect/protoreflect"
"io"
"math"
"time"
)
const (
secondsName protoreflect.Name = "seconds"
nanosName protoreflect.Name = "nanos"
)
func marshalTimestamp(message protoreflect.Message, writer io.Writer) error {
fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return fmt.Errorf("expected seconds field")
}
nanosField := fields.ByName(nanosName)
if nanosField == nil {
return fmt.Errorf("expected nanos field")
}
seconds := message.Get(secondsField).Int()
nanos := message.Get(nanosField).Int()
if nanos < 0 {
return fmt.Errorf("nanos must be non-negative on timestamp %v", message)
}
t := time.Unix(seconds, nanos).UTC()
var str string
if nanos == 0 {
str = t.Format(time.RFC3339)
} else {
str = t.Format(time.RFC3339Nano)
}
_, err := fmt.Fprintf(writer, `"%s"`, str)
return err
}
// MaxDurationSeconds the maximum number of seconds (when expressed as nanoseconds) which can fit in an int64.
// gogoproto encodes google.protobuf.Duration as a time.Duration, which is 64-bit signed integer.
const MaxDurationSeconds = int64(math.MaxInt64/int(1e9)) - 1
func marshalDuration(message protoreflect.Message, writer io.Writer) error {
fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return fmt.Errorf("expected seconds field")
}
// todo
// check signs are consistent
seconds := message.Get(secondsField).Int()
if seconds > MaxDurationSeconds {
return fmt.Errorf("%d seconds would overflow an int64 when represented as nanoseconds", seconds)
}
nanosField := fields.ByName(nanosName)
if nanosField == nil {
return fmt.Errorf("expected nanos field")
}
nanos := message.Get(nanosField).Int()
totalNanos := nanos + (seconds * 1e9)
_, err := writer.Write([]byte(fmt.Sprintf(`"%d"`, totalNanos)))
return err
}

View File

@ -8,8 +8,13 @@ require (
cosmossdk.io/math v1.0.0-beta.6
github.com/cosmos/cosmos-proto v1.0.0-beta.2
github.com/google/go-cmp v0.5.9
github.com/iancoleman/strcase v0.2.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.1
google.golang.org/protobuf v1.28.1
github.com/tendermint/go-amino v0.16.0
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
gotest.tools/v3 v3.4.0
pgregory.net/rapid v0.5.5
)
require (

View File

@ -11,45 +11,85 @@ github.com/cosmos/gogoproto v1.4.6/go.mod h1:VS/ASYmPgv6zkPKLjR9EB91lwbLHOzaGCir
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 h1:frX3nT9RkKybPnjyI+yvZh6ZucTZatCCEm9D47sZ2zo=
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 h1:vArvWooPH749rNHpBGgVl+U9B9dATjiEhJzcWGlovNs=
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA=
pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=