diff --git a/.github/workflows/proto-buf-publisher.yml b/.github/workflows/proto-buf-publisher.yml index 281eb4fc6e..88b2f465e9 100644 --- a/.github/workflows/proto-buf-publisher.yml +++ b/.github/workflows/proto-buf-publisher.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3.1.0 - - uses: bufbuild/buf-setup-action@v1.8.0 + - uses: bufbuild/buf-setup-action@v1.9.0 # lint checks - uses: bufbuild/buf-lint-action@v1 diff --git a/app/params/weights.go b/app/params/weights.go index 9cb2dd39a4..d8eed7679f 100644 --- a/app/params/weights.go +++ b/app/params/weights.go @@ -20,7 +20,17 @@ const ( DefaultWeightCommunitySpendProposal int = 5 DefaultWeightTextProposal int = 5 DefaultWeightParamChangeProposal int = 5 + DefaultWeightMsgStoreCode int = 50 DefaultWeightMsgInstantiateContract int = 100 DefaultWeightMsgExecuteContract int = 100 + DefaultWeightMsgUpdateAdmin int = 25 + DefaultWeightMsgClearAdmin int = 10 + DefaultWeightMsgMigrateContract int = 50 + + DefaultWeightStoreCodeProposal int = 5 + DefaultWeightInstantiateContractProposal int = 5 + DefaultWeightUpdateAdminProposal int = 5 + DefaultWeightExecuteContractProposal int = 5 + DefaultWeightClearAdminProposal int = 5 ) diff --git a/go.mod b/go.mod index 2310330e03..57737e3147 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( cosmossdk.io/math v1.0.0-beta.3 github.com/CosmWasm/wasmvm v1.1.1 github.com/cosmos/cosmos-proto v1.0.0-alpha8 - github.com/cosmos/cosmos-sdk v0.46.3 + github.com/cosmos/cosmos-sdk v0.46.4 github.com/cosmos/gogoproto v1.4.2 - github.com/cosmos/iavl v0.19.3 + github.com/cosmos/iavl v0.19.4 github.com/cosmos/ibc-go/v5 v5.0.1 github.com/cosmos/interchain-accounts v0.3.2 github.com/dvsekhvalnov/jose2go v1.5.0 diff --git a/go.sum b/go.sum index 73c35f8002..f89b4180fa 100644 --- a/go.sum +++ b/go.sum @@ -235,8 +235,8 @@ github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= github.com/cosmos/cosmos-proto v1.0.0-alpha8 h1:d3pCRuMYYvGA5bM0ZbbjKn+AoQD4A7dyNG2wzwWalUw= github.com/cosmos/cosmos-proto v1.0.0-alpha8/go.mod h1:6/p+Bc4O8JKeZqe0VqUGTX31eoYqemTT4C1hLCWsO7I= -github.com/cosmos/cosmos-sdk v0.46.3 h1:2jdJYcSwh4AtFJKGoNGvmEy2mKDWtGaVZphGpvedljQ= -github.com/cosmos/cosmos-sdk v0.46.3/go.mod h1:AynIAjXwtS3T/ApdhNCz/7/KGMbZSUBbKRTcbukb2ic= +github.com/cosmos/cosmos-sdk v0.46.4 h1:I4CPfnz7lAPM7cEvvyTxL1h3M/ugbhTNr5ZRcnW8WsU= +github.com/cosmos/cosmos-sdk v0.46.4/go.mod h1:b5usG7aBEEvhaatYCdV6orFbDUsj4BG1V6UtKwNcJeQ= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 h1:iKclrn3YEOwk4jQHT2ulgzuXyxmzmPczUalMwW4XH9k= github.com/cosmos/cosmos-sdk/ics23/go v0.8.0/go.mod h1:2a4dBq88TUoqoWAU5eu0lGvpFP3wWDPgdHPargtyw30= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= @@ -246,8 +246,8 @@ github.com/cosmos/gogoproto v1.4.2 h1:UeGRcmFW41l0G0MiefWhkPEVEwvu78SZsHBvI78dAY github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.3 h1:cESO0OwTTxQm5rmyESKW+zESheDUYI7CcZDWWDwnuxg= -github.com/cosmos/iavl v0.19.3/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= +github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= +github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= github.com/cosmos/ibc-go/v5 v5.0.1 h1:ZI5xCi6RDOL+hyu6Wx/w6JoAYFlOoK5hijsRTVWo+RA= github.com/cosmos/ibc-go/v5 v5.0.1/go.mod h1:LX0DHLW3dfi/1e4BJzi8MGLWmQ4DSraPEgVjyo3VzAo= github.com/cosmos/interchain-accounts v0.3.2 h1:7S5rSndahpjkzUvG00kKZrTZsEuVHuVC9a7p0qDVcF8= diff --git a/x/wasm/client/cli/genesis_msg.go b/x/wasm/client/cli/genesis_msg.go index affeb34fa4..4ca5919808 100644 --- a/x/wasm/client/cli/genesis_msg.go +++ b/x/wasm/client/cli/genesis_msg.go @@ -68,6 +68,7 @@ func GenesisStoreCodeCmd(defaultNodeHome string, genesisMutator GenesisMutator) return nil }) }, + SilenceUsage: true, } cmd.Flags().String(flagRunAs, "", "The address that is stored as code creator") cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") @@ -136,6 +137,7 @@ func GenesisInstantiateContractCmd(defaultNodeHome string, genesisMutator Genesi return nil }) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists") @@ -189,6 +191,7 @@ func GenesisExecuteContractCmd(defaultNodeHome string, genesisMutator GenesisMut return nil }) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command") cmd.Flags().String(flagRunAs, "", "The address that pays the funds.") @@ -217,6 +220,7 @@ func GenesisListCodesCmd(defaultNodeHome string, genReader GenesisReader) *cobra } return printJSONOutput(cmd, all) }, + SilenceUsage: true, } cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") flags.AddQueryFlagsToCmd(cmd) @@ -239,6 +243,7 @@ func GenesisListContractsCmd(defaultNodeHome string, genReader GenesisReader) *c all := GetAllContracts(state) return printJSONOutput(cmd, all) }, + SilenceUsage: true, } cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") flags.AddQueryFlagsToCmd(cmd) diff --git a/x/wasm/client/cli/gov_tx.go b/x/wasm/client/cli/gov_tx.go index 3e1deb31cf..f341f1209c 100644 --- a/x/wasm/client/cli/gov_tx.go +++ b/x/wasm/client/cli/gov_tx.go @@ -80,6 +80,7 @@ func ProposalStoreCodeCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } cmd.Flags().String(flagRunAs, "", "The address that is stored as code creator") @@ -159,6 +160,7 @@ func ProposalInstantiateContractCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists") @@ -227,6 +229,7 @@ func ProposalMigrateContractCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags @@ -304,6 +307,7 @@ func ProposalExecuteContractCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } cmd.Flags().String(flagRunAs, "", "The address that is passed as sender to the contract on proposal execution") cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") @@ -366,6 +370,7 @@ func ProposalSudoContractCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flagsExecute @@ -428,6 +433,7 @@ func ProposalUpdateContractAdminCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") //nolint:staticcheck @@ -483,6 +489,7 @@ func ProposalClearContractAdminCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") //nolint:staticcheck @@ -542,6 +549,7 @@ func ProposalPinCodesCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") //nolint:staticcheck @@ -613,6 +621,7 @@ func ProposalUnpinCodesCmd() *cobra.Command { return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") //nolint:staticcheck @@ -731,6 +740,7 @@ $ %s tx gov submit-proposal update-instantiate-config 1:nobody 2:everybody 3:%s1 return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } // proposal flags cmd.Flags().String(cli.FlagTitle, "", "Title of proposal") //nolint:staticcheck diff --git a/x/wasm/client/cli/new_tx.go b/x/wasm/client/cli/new_tx.go index e3b47792e6..2be19350ac 100644 --- a/x/wasm/client/cli/new_tx.go +++ b/x/wasm/client/cli/new_tx.go @@ -34,6 +34,7 @@ func MigrateContractCmd() *cobra.Command { } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, + SilenceUsage: true, } flags.AddTxFlagsToCmd(cmd) return cmd @@ -79,6 +80,7 @@ func UpdateContractAdminCmd() *cobra.Command { } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, + SilenceUsage: true, } flags.AddTxFlagsToCmd(cmd) return cmd @@ -115,6 +117,7 @@ func ClearContractAdminCmd() *cobra.Command { } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, + SilenceUsage: true, } flags.AddTxFlagsToCmd(cmd) return cmd diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go index 8d13a1192a..a621ba0825 100644 --- a/x/wasm/client/cli/query.go +++ b/x/wasm/client/cli/query.go @@ -28,6 +28,7 @@ func GetQueryCmd() *cobra.Command { DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, + SilenceUsage: true, } queryCmd.AddCommand( GetCmdListCode(), @@ -137,6 +138,7 @@ func GetCmdListCode() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "list codes") @@ -182,6 +184,7 @@ func GetCmdListContractByCode() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "list contracts by code") @@ -224,6 +227,7 @@ func GetCmdQueryCode() *cobra.Command { fmt.Printf("Downloading wasm code to %s\n", args[1]) return os.WriteFile(args[1], res.Data, 0o600) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) return cmd @@ -263,6 +267,7 @@ func GetCmdQueryCodeInfo() *cobra.Command { return clientCtx.PrintProto(res.CodeInfoResponse) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) return cmd @@ -298,6 +303,7 @@ func GetCmdGetContractInfo() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) return cmd @@ -312,6 +318,7 @@ func GetCmdGetContractState() *cobra.Command { DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, + SilenceUsage: true, } cmd.AddCommand( GetCmdGetContractStateAll(), @@ -355,6 +362,7 @@ func GetCmdGetContractStateAll() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "contract state") @@ -396,6 +404,7 @@ func GetCmdGetContractStateRaw() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } decoder.RegisterFlags(cmd.PersistentFlags(), "key argument") flags.AddQueryFlagsToCmd(cmd) @@ -444,6 +453,7 @@ func GetCmdGetContractStateSmart() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } decoder.RegisterFlags(cmd.PersistentFlags(), "query argument") flags.AddQueryFlagsToCmd(cmd) @@ -487,6 +497,7 @@ func GetCmdGetContractHistory() *cobra.Command { return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) @@ -499,7 +510,7 @@ func GetCmdListPinnedCode() *cobra.Command { cmd := &cobra.Command{ Use: "pinned", Short: "List all pinned code ids", - Long: "\t\tLong: List all pinned code ids,\n", + Long: "List all pinned code ids", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) @@ -523,6 +534,7 @@ func GetCmdListPinnedCode() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) flags.AddPaginationFlagsToCmd(cmd, "list codes") @@ -534,7 +546,7 @@ func GetCmdListContractsByCreator() *cobra.Command { cmd := &cobra.Command{ Use: "list-contracts-by-creator [creator]", Short: "List all contracts by creator", - Long: "\t\tLong: List all contracts by creator,\n", + Long: "List all contracts by creator", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) @@ -563,6 +575,7 @@ func GetCmdListContractsByCreator() *cobra.Command { } return clientCtx.PrintProto(res) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) return cmd @@ -650,6 +663,7 @@ func GetCmdQueryParams() *cobra.Command { return clientCtx.PrintProto(&res.Params) }, + SilenceUsage: true, } flags.AddQueryFlagsToCmd(cmd) diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go index d65e45c2d1..7e4c6e36f0 100644 --- a/x/wasm/client/cli/tx.go +++ b/x/wasm/client/cli/tx.go @@ -42,6 +42,7 @@ func GetTxCmd() *cobra.Command { DisableFlagParsing: true, SuggestionsMinimumDistance: 2, RunE: client.ValidateCmd, + SilenceUsage: true, } txCmd.AddCommand( StoreCodeCmd(), @@ -76,6 +77,7 @@ func StoreCodeCmd() *cobra.Command { } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, + SilenceUsage: true, } cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") @@ -183,7 +185,7 @@ func InstantiateContractCmd() *cobra.Command { Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message. Each contract instance has a unique address assigned. Example: -$ %s wasmd tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey -a)" \ +$ %s tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey -a)" \ --from mykey --amount="100ustake" --label "local0.1.0" `, version.AppName, version.AppName), Aliases: []string{"start", "init", "inst", "i"}, @@ -202,6 +204,7 @@ $ %s wasmd tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey - } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") @@ -224,7 +227,7 @@ Each contract instance has a unique address assigned. They are assigned automati for special use cases, the given 'salt' argument and '--fix-msg' parameters can be used to generate a custom address. Predictable address example (also see '%s query wasm build-address -h'): -$ %s wasmd tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) --admin="$(%s keys show mykey -a)" \ +$ %s tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) --admin="$(%s keys show mykey -a)" \ --from mykey --amount="100ustake" --label "local0.1.0" \ --fix-msg `, version.AppName, version.AppName, version.AppName), @@ -262,6 +265,7 @@ $ %s wasmd tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") @@ -347,6 +351,7 @@ func ExecuteContractCmd() *cobra.Command { } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) }, + SilenceUsage: true, } cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command") diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index aee09bf7f9..ecb3b70fd0 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -120,7 +120,7 @@ func NewKeeper( capabilityKeeper types.CapabilityKeeper, portSource types.ICS20TransferPortSource, router MessageRouter, - queryRouter GRPCQueryRouter, + _ GRPCQueryRouter, homeDir string, wasmConfig types.WasmConfig, availableCapabilities string, @@ -151,7 +151,7 @@ func NewKeeper( maxQueryStackSize: types.DefaultMaxQueryStackSize, acceptedAccountTypes: defaultAcceptedAccountTypes, } - keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, keeper) + keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, keeper) for _, o := range opts { o.apply(keeper) } diff --git a/x/wasm/keeper/options.go b/x/wasm/keeper/options.go index cc5547f22c..223a771f63 100644 --- a/x/wasm/keeper/options.go +++ b/x/wasm/keeper/options.go @@ -57,7 +57,7 @@ func WithQueryHandlerDecorator(d func(old WasmVMQueryHandler) WasmVMQueryHandler } // WithQueryPlugins is an optional constructor parameter to pass custom query plugins for wasmVM requests. -// This option expects the default `QueryHandler` set an should not be combined with Option `WithQueryHandler` or `WithQueryHandlerDecorator`. +// This option expects the default `QueryHandler` set and should not be combined with Option `WithQueryHandler` or `WithQueryHandlerDecorator`. func WithQueryPlugins(x *QueryPlugins) Option { return optsFn(func(k *Keeper) { q, ok := k.wasmVMQueryHandler.(QueryPlugins) diff --git a/x/wasm/keeper/query_plugins.go b/x/wasm/keeper/query_plugins.go index 7a0a7528bb..e245596ecb 100644 --- a/x/wasm/keeper/query_plugins.go +++ b/x/wasm/keeper/query_plugins.go @@ -3,8 +3,11 @@ package keeper import ( "encoding/json" "errors" + "fmt" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + abci "github.com/tendermint/tendermint/abci/types" channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" @@ -99,7 +102,6 @@ func DefaultQueryPlugins( staking types.StakingKeeper, distKeeper types.DistributionKeeper, channelKeeper types.ChannelKeeper, - queryRouter GRPCQueryRouter, wasm wasmQueryKeeper, ) QueryPlugins { return QueryPlugins{ @@ -107,7 +109,7 @@ func DefaultQueryPlugins( Custom: NoCustomQuerier, IBC: IBCQuerier(wasm, channelKeeper), Staking: StakingQuerier(staking, distKeeper), - Stargate: StargateQuerier(queryRouter), + Stargate: RejectStargateQuerier(), Wasm: WasmQuerier(wasm), } } @@ -278,9 +280,47 @@ func IBCQuerier(wasm contractMetaDataSource, channelKeeper types.ChannelKeeper) } } -func StargateQuerier(queryRouter GRPCQueryRouter) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { - return func(ctx sdk.Context, msg *wasmvmtypes.StargateQuery) ([]byte, error) { - return nil, wasmvmtypes.UnsupportedRequest{Kind: "Stargate queries are disabled."} +// RejectStargateQuerier rejects all stargate queries +func RejectStargateQuerier() func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + return nil, wasmvmtypes.UnsupportedRequest{Kind: "Stargate queries are disabled"} + } +} + +// AcceptedStargateQueries define accepted Stargate queries as a map with path as key and response type as value. +// For example: +// acceptList["/cosmos.auth.v1beta1.Query/Account"]= &authtypes.QueryAccountResponse{} +type AcceptedStargateQueries map[string]codec.ProtoMarshaler + +// AcceptListStargateQuerier supports a preconfigured set of stargate queries only. +// All arguments must be non nil. +// +// Warning: Chains need to test and maintain their accept list carefully. +// There were critical consensus breaking issues in the past with non-deterministic behaviour in the SDK. +// +// This queries can be set via WithQueryPlugins option in the wasm keeper constructor: +// WithQueryPlugins(&QueryPlugins{Stargate: AcceptListStargateQuerier(acceptList, queryRouter, codec)}) +func AcceptListStargateQuerier(acceptList AcceptedStargateQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + protoResponse, accepted := acceptList[request.Path] + if !accepted { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)} + } + + route := queryRouter.Route(request.Path) + if route == nil { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)} + } + + res, err := route(ctx, abci.RequestQuery{ + Data: request.Data, + Path: request.Path, + }) + if err != nil { + return nil, err + } + + return ConvertProtoToJSONMarshal(codec, protoResponse, res.Value) } } @@ -527,6 +567,24 @@ func ConvertSdkCoinToWasmCoin(coin sdk.Coin) wasmvmtypes.Coin { } } +// ConvertProtoToJSONMarshal unmarshals the given bytes into a proto message and then marshals it to json. +// This is done so that clients calling stargate queries do not need to define their own proto unmarshalers, +// being able to use response directly by json marshalling, which is supported in cosmwasm. +func ConvertProtoToJSONMarshal(cdc codec.Codec, protoResponse codec.ProtoMarshaler, bz []byte) ([]byte, error) { + // unmarshal binary into stargate response data structure + err := cdc.Unmarshal(bz, protoResponse) + if err != nil { + return nil, sdkerrors.Wrap(err, "to proto") + } + + bz, err = cdc.MarshalJSON(protoResponse) + if err != nil { + return nil, sdkerrors.Wrap(err, "to json") + } + + return bz, nil +} + var _ WasmVMQueryHandler = WasmVMQueryHandlerFn(nil) // WasmVMQueryHandlerFn is a helper to construct a function based query handler. diff --git a/x/wasm/keeper/query_plugins_test.go b/x/wasm/keeper/query_plugins_test.go index 07de2f345b..149638afee 100644 --- a/x/wasm/keeper/query_plugins_test.go +++ b/x/wasm/keeper/query_plugins_test.go @@ -1,19 +1,32 @@ -package keeper +package keeper_test import ( + "encoding/hex" "encoding/json" + "fmt" "testing" - - "github.com/cosmos/cosmos-sdk/store" - dbm "github.com/tendermint/tm-db" + "time" wasmvmtypes "github.com/CosmWasm/wasmvm/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" channeltypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types" + "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + "github.com/CosmWasm/wasmd/app" + "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting" "github.com/CosmWasm/wasmd/x/wasm/types" ) @@ -307,8 +320,8 @@ func TestIBCQuerier(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { - h := IBCQuerier(spec.wasmKeeper, spec.channelKeeper) - gotResult, gotErr := h(sdk.Context{}, RandomAccountAddress(t), spec.srcQuery) + h := keeper.IBCQuerier(spec.wasmKeeper, spec.channelKeeper) + gotResult, gotErr := h(sdk.Context{}, keeper.RandomAccountAddress(t), spec.srcQuery) require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) if spec.expErr != nil { return @@ -324,10 +337,10 @@ func TestBankQuerierBalance(t *testing.T) { }} ctx := sdk.Context{} - q := BankQuerier(mock) + q := keeper.BankQuerier(mock) gotBz, gotErr := q(ctx, &wasmvmtypes.BankQuery{ Balance: &wasmvmtypes.BalanceQuery{ - Address: RandomBech32AccountAddress(t), + Address: keeper.RandomBech32AccountAddress(t), Denom: "ALX", }, }) @@ -344,9 +357,9 @@ func TestBankQuerierBalance(t *testing.T) { } func TestContractInfoWasmQuerier(t *testing.T) { - myValidContractAddr := RandomBech32AccountAddress(t) - myCreatorAddr := RandomBech32AccountAddress(t) - myAdminAddr := RandomBech32AccountAddress(t) + myValidContractAddr := keeper.RandomBech32AccountAddress(t) + myCreatorAddr := keeper.RandomBech32AccountAddress(t) + myAdminAddr := keeper.RandomBech32AccountAddress(t) var ctx sdk.Context specs := map[string]struct { @@ -433,7 +446,7 @@ func TestContractInfoWasmQuerier(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { - q := WasmQuerier(spec.mock) + q := keeper.WasmQuerier(spec.mock) gotBz, gotErr := q(ctx, spec.req) if spec.expErr { require.Error(t, gotErr) @@ -464,17 +477,82 @@ func TestQueryErrors(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { - mock := WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { + mock := keeper.WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { return nil, spec.src }) ctx := sdk.Context{}.WithGasMeter(sdk.NewInfiniteGasMeter()).WithMultiStore(store.NewCommitMultiStore(dbm.NewMemDB())) - q := NewQueryHandler(ctx, mock, sdk.AccAddress{}, NewDefaultWasmGasRegister()) + q := keeper.NewQueryHandler(ctx, mock, sdk.AccAddress{}, keeper.NewDefaultWasmGasRegister()) _, gotErr := q.Query(wasmvmtypes.QueryRequest{}, 1) assert.Equal(t, spec.expErr, gotErr) }) } } +func TestAcceptListStargateQuerier(t *testing.T) { + wasmApp := app.SetupWithEmptyStore(t) + ctx := wasmApp.NewUncachedContext(false, tmproto.Header{ChainID: "foo", Height: 1, Time: time.Now()}) + wasmApp.StakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()) + + addrs := app.AddTestAddrs(wasmApp, ctx, 2, sdk.NewInt(1_000_000)) + accepted := keeper.AcceptedStargateQueries{ + "/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{}, + "/no/route/to/this": &authtypes.QueryAccountResponse{}, + } + + marshal := func(pb proto.Message) []byte { + b, err := proto.Marshal(pb) + require.NoError(t, err) + return b + } + + specs := map[string]struct { + req *wasmvmtypes.StargateQuery + expErr bool + expResp string + }{ + "in accept list - success result": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.auth.v1beta1.Query/Account", + Data: marshal(&authtypes.QueryAccountRequest{Address: addrs[0].String()}), + }, + expResp: fmt.Sprintf(`{"account":{"@type":"/cosmos.auth.v1beta1.BaseAccount","address":%q,"pub_key":null,"account_number":"1","sequence":"0"}}`, addrs[0].String()), + }, + "in accept list - error result": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.auth.v1beta1.Query/Account", + Data: marshal(&authtypes.QueryAccountRequest{Address: sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).String()}), + }, + expErr: true, + }, + "not in accept list": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.bank.v1beta1.Query/AllBalances", + Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), + }, + expErr: true, + }, + "unknown route": { + req: &wasmvmtypes.StargateQuery{ + Path: "/no/route/to/this", + Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), + }, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + q := keeper.AcceptListStargateQuerier(accepted, wasmApp.GRPCQueryRouter(), wasmApp.AppCodec()) + gotBz, gotErr := q(ctx, spec.req) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.JSONEq(t, spec.expResp, string(gotBz), string(gotBz)) + }) + } +} + type mockWasmQueryKeeper struct { GetContractInfoFn func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo QueryRawFn func(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte @@ -536,3 +614,129 @@ func (m bankKeeperMock) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk } return m.GetAllBalancesFn(ctx, addr) } + +func TestConvertProtoToJSONMarshal(t *testing.T) { + testCases := []struct { + name string + queryPath string + protoResponseStruct codec.ProtoMarshaler + originalResponse string + expectedProtoResponse codec.ProtoMarshaler + expectedError bool + }{ + { + name: "successful conversion from proto response to json marshalled response", + queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", + originalResponse: "0a090a036261721202333012050a03666f6f", + protoResponseStruct: &banktypes.QueryAllBalancesResponse{}, + expectedProtoResponse: &banktypes.QueryAllBalancesResponse{ + Balances: sdk.NewCoins(sdk.NewCoin("bar", sdk.NewInt(30))), + Pagination: &query.PageResponse{ + NextKey: []byte("foo"), + }, + }, + }, + { + name: "invalid proto response struct", + queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", + originalResponse: "0a090a036261721202333012050a03666f6f", + protoResponseStruct: &authtypes.QueryAccountResponse{}, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { + originalVersionBz, err := hex.DecodeString(tc.originalResponse) + require.NoError(t, err) + appCodec := app.MakeEncodingConfig().Marshaler + + jsonMarshalledResponse, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.protoResponseStruct, originalVersionBz) + if tc.expectedError { + require.Error(t, err) + return + } + require.NoError(t, err) + + // check response by json marshalling proto response into json response manually + jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProtoResponse) + require.NoError(t, err) + require.JSONEq(t, string(jsonMarshalledResponse), string(jsonMarshalExpectedResponse)) + }) + } +} + +// TestDeterministicJsonMarshal tests that we get deterministic JSON marshalled response upon +// proto struct update in the state machine. +func TestDeterministicJsonMarshal(t *testing.T) { + testCases := []struct { + name string + originalResponse string + updatedResponse string + queryPath string + responseProtoStruct codec.ProtoMarshaler + expectedProto func() codec.ProtoMarshaler + }{ + /** + * + * Origin Response + * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331346c3268686a6e676c3939367772703935673867646a6871653038326375367a7732706c686b + * + * Updated Response + * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271122d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271 + // Origin proto + message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + } + // Updated proto + message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + // address is the address to query for. + string address = 2; + } + */ + { + "Query Account", + "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", + "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679122d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", + "/cosmos.auth.v1beta1.Query/Account", + &authtypes.QueryAccountResponse{}, + func() codec.ProtoMarshaler { + account := authtypes.BaseAccount{ + Address: "cosmos1f8uxultn8sqzhznrsz3q77xwaquhgrsg6jyvfy", + } + accountResponse, err := codectypes.NewAnyWithValue(&account) + require.NoError(t, err) + return &authtypes.QueryAccountResponse{ + Account: accountResponse, + } + }, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { + appCodec := app.MakeEncodingConfig().Marshaler + + originVersionBz, err := hex.DecodeString(tc.originalResponse) + require.NoError(t, err) + jsonMarshalledOriginalBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, originVersionBz) + require.NoError(t, err) + + newVersionBz, err := hex.DecodeString(tc.updatedResponse) + require.NoError(t, err) + jsonMarshalledUpdatedBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, newVersionBz) + require.NoError(t, err) + + // json marshalled bytes should be the same since we use the same proto struct for unmarshalling + require.Equal(t, jsonMarshalledOriginalBz, jsonMarshalledUpdatedBz) + + // raw build also make same result + jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProto()) + require.NoError(t, err) + require.Equal(t, jsonMarshalledUpdatedBz, jsonMarshalExpectedResponse) + }) + } +} diff --git a/x/wasm/keeper/reflect_test.go b/x/wasm/keeper/reflect_test.go index d6e4f6531c..0845c9f551 100644 --- a/x/wasm/keeper/reflect_test.go +++ b/x/wasm/keeper/reflect_test.go @@ -384,10 +384,10 @@ func TestReflectInvalidStargateQuery(t *testing.T) { }) require.NoError(t, err) - // make a query on the chain, should be blacklisted + // make a query on the chain, should not be whitelisted _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) require.Error(t, err) - require.Contains(t, err.Error(), "Stargate queries are disabled") + require.Contains(t, err.Error(), "Unsupported query") // now, try to build a protobuf query protoRequest = wasmvmtypes.QueryRequest{ @@ -404,7 +404,7 @@ func TestReflectInvalidStargateQuery(t *testing.T) { // make a query on the chain, should be blacklisted _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) require.Error(t, err) - require.Contains(t, err.Error(), "Stargate queries are disabled") + require.Contains(t, err.Error(), "Unsupported query") // and another one protoRequest = wasmvmtypes.QueryRequest{ @@ -421,7 +421,7 @@ func TestReflectInvalidStargateQuery(t *testing.T) { // make a query on the chain, should be blacklisted _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) require.Error(t, err) - require.Contains(t, err.Error(), "Stargate queries are disabled") + require.Contains(t, err.Error(), "Unsupported query") } type reflectState struct { diff --git a/x/wasm/keeper/testdata/reflect.go b/x/wasm/keeper/testdata/reflect.go index 51f5f14d53..64fed6150a 100644 --- a/x/wasm/keeper/testdata/reflect.go +++ b/x/wasm/keeper/testdata/reflect.go @@ -10,10 +10,17 @@ import ( //go:embed reflect.wasm var reflectContract []byte +//go:embed reflect_1_1.wasm +var migrateReflectContract []byte + func ReflectContractWasm() []byte { return reflectContract } +func MigrateReflectContractWasm() []byte { + return migrateReflectContract +} + // ReflectHandleMsg is used to encode handle messages type ReflectHandleMsg struct { Reflect *ReflectPayload `json:"reflect_msg,omitempty"` diff --git a/x/wasm/keeper/testdata/reflect_1_1.wasm b/x/wasm/keeper/testdata/reflect_1_1.wasm new file mode 100644 index 0000000000..7383a6d635 Binary files /dev/null and b/x/wasm/keeper/testdata/reflect_1_1.wasm differ diff --git a/x/wasm/module.go b/x/wasm/module.go index 7c4f7f1c6b..6de9a04318 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -194,8 +194,8 @@ func (AppModule) GenerateGenesisState(simState *module.SimulationState) { } // ProposalContents doesn't return any content functions for governance proposals. -func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { - return nil +func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + return simulation.ProposalContents(am.bankKeeper, am.keeper) } // RandomizedParams creates randomized bank param changes for the simulator. diff --git a/x/wasm/simulation/operations.go b/x/wasm/simulation/operations.go index 13c2f01670..34e7ef35d5 100644 --- a/x/wasm/simulation/operations.go +++ b/x/wasm/simulation/operations.go @@ -27,6 +27,9 @@ const ( OpWeightMsgStoreCode = "op_weight_msg_store_code" OpWeightMsgInstantiateContract = "op_weight_msg_instantiate_contract" OpWeightMsgExecuteContract = "op_weight_msg_execute_contract" + OpWeightMsgUpdateAdmin = "op_weight_msg_update_admin" + OpWeightMsgClearAdmin = "op_weight_msg_clear_admin" + OpWeightMsgMigrateContract = "op_weight_msg_migrate_contract" OpReflectContractPath = "op_reflect_contract_path" ) @@ -54,6 +57,9 @@ func WeightedOperations( weightMsgStoreCode int weightMsgInstantiateContract int weightMsgExecuteContract int + weightMsgUpdateAdmin int + weightMsgClearAdmin int + weightMsgMigrateContract int wasmContractPath string ) @@ -62,7 +68,6 @@ func WeightedOperations( weightMsgStoreCode = params.DefaultWeightMsgStoreCode }, ) - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgInstantiateContract, &weightMsgInstantiateContract, nil, func(_ *rand.Rand) { weightMsgInstantiateContract = params.DefaultWeightMsgInstantiateContract @@ -73,6 +78,21 @@ func WeightedOperations( weightMsgExecuteContract = params.DefaultWeightMsgExecuteContract }, ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgUpdateAdmin, &weightMsgUpdateAdmin, nil, + func(_ *rand.Rand) { + weightMsgUpdateAdmin = params.DefaultWeightMsgUpdateAdmin + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgClearAdmin, &weightMsgClearAdmin, nil, + func(_ *rand.Rand) { + weightMsgClearAdmin = params.DefaultWeightMsgClearAdmin + }, + ) + simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgMigrateContract, &weightMsgMigrateContract, nil, + func(_ *rand.Rand) { + weightMsgMigrateContract = params.DefaultWeightMsgMigrateContract + }, + ) simstate.AppParams.GetOrGenerate(simstate.Cdc, OpReflectContractPath, &wasmContractPath, nil, func(_ *rand.Rand) { wasmContractPath = "" @@ -81,7 +101,7 @@ func WeightedOperations( var wasmBz []byte if wasmContractPath == "" { - wasmBz = testdata.ReflectContractWasm() + wasmBz = testdata.MigrateReflectContractWasm() } else { var err error wasmBz, err = os.ReadFile(wasmContractPath) @@ -110,6 +130,194 @@ func WeightedOperations( DefaultSimulationExecutePayloader, ), ), + simulation.NewWeightedOperation( + weightMsgUpdateAdmin, + SimulateMsgUpdateAmin( + ak, + bk, + wasmKeeper, + DefaultSimulationUpdateAdminContractSelector, + ), + ), + simulation.NewWeightedOperation( + weightMsgClearAdmin, + SimulateMsgClearAdmin( + ak, + bk, + wasmKeeper, + DefaultSimulationClearAdminContractSelector, + ), + ), + simulation.NewWeightedOperation( + weightMsgMigrateContract, + SimulateMsgMigrateContract( + ak, + bk, + wasmKeeper, + DefaultSimulationMigrateContractSelector, + DefaultSimulationMigrateCodeIDSelector, + ), + ), + } +} + +type ( + MsgMigrateContractSelector func(sdk.Context, WasmKeeper, string) (sdk.AccAddress, types.ContractInfo) + MsgMigrateCodeIDSelector func(sdk.Context, WasmKeeper, uint64) uint64 +) + +func DefaultSimulationMigrateContractSelector(ctx sdk.Context, wasmKeeper WasmKeeper, adminAddress string) (sdk.AccAddress, types.ContractInfo) { + var contractAddress sdk.AccAddress + var contractInfo types.ContractInfo + wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info types.ContractInfo) bool { + if info.Admin != adminAddress { + return false + } + contractAddress = address + contractInfo = info + return true + }) + return contractAddress, contractInfo +} + +func DefaultSimulationMigrateCodeIDSelector(ctx sdk.Context, wasmKeeper WasmKeeper, currentCodeID uint64) uint64 { + var codeID uint64 + wasmKeeper.IterateCodeInfos(ctx, func(u uint64, info types.CodeInfo) bool { + if (info.InstantiateConfig.Permission != types.AccessTypeEverybody) || (u == currentCodeID) { + return false + } + codeID = u + return true + }) + return codeID +} + +func SimulateMsgMigrateContract( + ak types.AccountKeeper, + bk BankKeeper, + wasmKeeper WasmKeeper, + contractSelector MsgMigrateContractSelector, + codeIDSelector MsgMigrateCodeIDSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + ctAddress, info := contractSelector(ctx, wasmKeeper, simAccount.Address.String()) + if ctAddress == nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgMigrateContract{}.Type(), "no contract instance available"), nil, nil + } + + codeID := codeIDSelector(ctx, wasmKeeper, info.CodeID) + if codeID == 0 { + return simtypes.NoOpMsg(types.ModuleName, types.MsgMigrateContract{}.Type(), "no target contract available"), nil, nil + } + migrateMsg := types.MsgMigrateContract{ + Sender: simAccount.Address.String(), + Contract: ctAddress.String(), + CodeID: codeID, + Msg: []byte(`{}`), + } + + txCtx := BuildOperationInput(r, app, ctx, &migrateMsg, simAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +type MsgClearAdminContractSelector func(sdk.Context, WasmKeeper, string) sdk.AccAddress + +func DefaultSimulationClearAdminContractSelector(ctx sdk.Context, wasmKeeper WasmKeeper, adminAddress string) sdk.AccAddress { + var ctAddress sdk.AccAddress + wasmKeeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, info types.ContractInfo) bool { + if info.Admin != adminAddress { + return false + } + ctAddress = addr + return true + }) + return ctAddress +} + +func SimulateMsgClearAdmin( + ak types.AccountKeeper, + bk BankKeeper, + wasmKeeper WasmKeeper, + contractSelector MsgClearAdminContractSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simtypes.Account, + chainID string, + ) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) { + simAccount, _ := simtypes.RandomAcc(r, accounts) + ctAddress := contractSelector(ctx, wasmKeeper, simAccount.Address.String()) + if ctAddress == nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgClearAdmin{}.Type(), "no contract instance available"), nil, nil + } + + msg := types.MsgClearAdmin{ + Sender: simAccount.Address.String(), + Contract: ctAddress.String(), + } + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +type MsgUpdateAdminContractSelector func(sdk.Context, WasmKeeper, string) (sdk.AccAddress, types.ContractInfo) + +// DefaultSimulationUpdateAdminContractSelector picks the first contract which Admin != "" +func DefaultSimulationUpdateAdminContractSelector(ctx sdk.Context, wasmKeeper WasmKeeper, adminAddress string) (sdk.AccAddress, types.ContractInfo) { + var contractAddress sdk.AccAddress + var contractInfo types.ContractInfo + wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info types.ContractInfo) bool { + if info.Admin != adminAddress { + return false + } + contractAddress = address + contractInfo = info + return true + }) + return contractAddress, contractInfo +} + +func SimulateMsgUpdateAmin( + ak types.AccountKeeper, + bk BankKeeper, + wasmKeeper WasmKeeper, + contractSelector MsgUpdateAdminContractSelector, +) simtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accs []simtypes.Account, + chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + ctAddress, _ := contractSelector(ctx, wasmKeeper, simAccount.Address.String()) + if ctAddress == nil { + return simtypes.NoOpMsg(types.ModuleName, types.MsgUpdateAdmin{}.Type(), "no contract instance available"), nil, nil + } + + newAdmin, _ := simtypes.RandomAcc(r, accs) + if newAdmin.Address.String() == simAccount.Address.String() { + return simtypes.NoOpMsg(types.ModuleName, types.MsgUpdateAdmin{}.Type(), "new admin cannot be the same as current admin"), nil, nil + } + + msg := types.MsgUpdateAdmin{ + Sender: simAccount.Address.String(), + NewAdmin: newAdmin.Address.String(), + Contract: ctAddress.String(), + } + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, nil) + return simulation.GenAndDeliverTxWithRandFees(txCtx) } } @@ -180,9 +388,11 @@ func SimulateMsgInstantiateContract(ak types.AccountKeeper, bk BankKeeper, wasmK } } + adminAccount, _ := simtypes.RandomAcc(r, accs) + msg := types.MsgInstantiateContract{ Sender: simAccount.Address.String(), - Admin: simtypes.RandomAccounts(r, 1)[0].Address.String(), + Admin: adminAccount.Address.String(), CodeID: codeID, Label: simtypes.RandStringOfLength(r, 10), Msg: []byte(`{}`), diff --git a/x/wasm/simulation/proposals.go b/x/wasm/simulation/proposals.go new file mode 100644 index 0000000000..1ee9584811 --- /dev/null +++ b/x/wasm/simulation/proposals.go @@ -0,0 +1,222 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/CosmWasm/wasmd/app/params" + "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +const ( + WeightStoreCodeProposal = "weight_store_code_proposal" + WeightInstantiateContractProposal = "weight_instantiate_contract_proposal" + WeightUpdateAdminProposal = "weight_update_admin_proposal" + WeightExeContractProposal = "weight_execute_contract_proposal" + WeightClearAdminProposal = "weight_clear_admin_proposal" +) + +func ProposalContents(bk BankKeeper, wasmKeeper WasmKeeper) []simtypes.WeightedProposalContent { + return []simtypes.WeightedProposalContent{ + // simulation.NewWeightedProposalContent( + // WeightStoreCodeProposal, + // params.DefaultWeightStoreCodeProposal, + // SimulateStoreCodeProposal(wasmKeeper), + // ), + simulation.NewWeightedProposalContent( + WeightInstantiateContractProposal, + params.DefaultWeightInstantiateContractProposal, + SimulateInstantiateContractProposal( + bk, + wasmKeeper, + DefaultSimulationCodeIDSelector, + ), + ), + simulation.NewWeightedProposalContent( + WeightUpdateAdminProposal, + params.DefaultWeightUpdateAdminProposal, + SimulateUpdateAdminProposal( + wasmKeeper, + DefaultSimulateUpdateAdminProposalContractSelector, + ), + ), + simulation.NewWeightedProposalContent( + WeightExeContractProposal, + params.DefaultWeightExecuteContractProposal, + SimulateExecuteContractProposal( + bk, + wasmKeeper, + DefaultSimulationExecuteContractSelector, + DefaultSimulationExecuteSenderSelector, + DefaultSimulationExecutePayloader, + ), + ), + simulation.NewWeightedProposalContent( + WeightClearAdminProposal, + params.DefaultWeightClearAdminProposal, + SimulateClearAdminProposal( + wasmKeeper, + DefaultSimulateClearAdminProposalContractSelector, + ), + ), + } +} + +// simulate store code proposal (unused now) +// Current problem: out of gas (defaul gaswanted config of gov SimulateMsgSubmitProposal is 10_000_000) +// but this proposal may need more than it +func SimulateStoreCodeProposal(wasmKeeper WasmKeeper) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + simAccount, _ := simtypes.RandomAcc(r, accs) + + wasmBz := testdata.ReflectContractWasm() + + permission := wasmKeeper.GetParams(ctx).InstantiateDefaultPermission.With(simAccount.Address) + + return types.NewStoreCodeProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 10), + simAccount.Address.String(), + wasmBz, + &permission, + false, + ) + } +} + +// Simulate instantiate contract proposal +func SimulateInstantiateContractProposal(bk BankKeeper, wasmKeeper WasmKeeper, codeSelector CodeIDSelector) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + simAccount, _ := simtypes.RandomAcc(r, accs) + // admin + adminAccount, _ := simtypes.RandomAcc(r, accs) + // get codeID + codeID := codeSelector(ctx, wasmKeeper) + if codeID == 0 { + return nil + } + + return types.NewInstantiateContractProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 10), + simAccount.Address.String(), + adminAccount.Address.String(), + codeID, + simtypes.RandStringOfLength(r, 10), + []byte(`{}`), + sdk.Coins{}, + ) + } +} + +// Simulate execute contract proposal +func SimulateExecuteContractProposal( + bk BankKeeper, + wasmKeeper WasmKeeper, + contractSelector MsgExecuteContractSelector, + senderSelector MsgExecuteSenderSelector, + payloader MsgExecutePayloader, +) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + ctAddress := contractSelector(ctx, wasmKeeper) + if ctAddress == nil { + return nil + } + + simAccount, err := senderSelector(wasmKeeper, ctx, ctAddress, accs) + if err != nil { + return nil + } + + msg := types.MsgExecuteContract{ + Sender: simAccount.Address.String(), + Contract: ctAddress.String(), + Funds: sdk.Coins{}, + } + + if err := payloader(&msg); err != nil { + return nil + } + + return types.NewExecuteContractProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 10), + simAccount.Address.String(), + ctAddress.String(), + msg.Msg, + sdk.Coins{}, + ) + } +} + +type UpdateAdminContractSelector func(sdk.Context, WasmKeeper, string) (sdk.AccAddress, types.ContractInfo) + +func DefaultSimulateUpdateAdminProposalContractSelector( + ctx sdk.Context, + wasmKeeper WasmKeeper, + adminAddress string, +) (sdk.AccAddress, types.ContractInfo) { + var contractAddr sdk.AccAddress + var contractInfo types.ContractInfo + wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info types.ContractInfo) bool { + if info.Admin != adminAddress { + return false + } + contractAddr = address + contractInfo = info + return true + }) + return contractAddr, contractInfo +} + +// Simulate update admin contract proposal +func SimulateUpdateAdminProposal(wasmKeeper WasmKeeper, contractSelector UpdateAdminContractSelector) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + simAccount, _ := simtypes.RandomAcc(r, accs) + ctAddress, _ := contractSelector(ctx, wasmKeeper, simAccount.Address.String()) + if ctAddress == nil { + return nil + } + + return types.NewUpdateAdminProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 10), + simtypes.RandomAccounts(r, 1)[0].Address.String(), + ctAddress.String(), + ) + } +} + +type ClearAdminContractSelector func(sdk.Context, WasmKeeper) sdk.AccAddress + +func DefaultSimulateClearAdminProposalContractSelector( + ctx sdk.Context, + wasmKeeper WasmKeeper, +) sdk.AccAddress { + var contractAddr sdk.AccAddress + wasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info types.ContractInfo) bool { + contractAddr = address + return true + }) + return contractAddr +} + +// Simulate clear admin proposal +func SimulateClearAdminProposal(wasmKeeper WasmKeeper, contractSelector ClearAdminContractSelector) simtypes.ContentSimulatorFn { + return func(r *rand.Rand, ctx sdk.Context, accs []simtypes.Account) simtypes.Content { + ctAddress := contractSelector(ctx, wasmKeeper) + if ctAddress == nil { + return nil + } + + return types.NewClearAdminProposal( + simtypes.RandStringOfLength(r, 10), + simtypes.RandStringOfLength(r, 10), + ctAddress.String(), + ) + } +} diff --git a/x/wasm/types/proposal.go b/x/wasm/types/proposal.go index ed5c43b0ec..b76f14ac56 100644 --- a/x/wasm/types/proposal.go +++ b/x/wasm/types/proposal.go @@ -84,6 +84,17 @@ func init() { // register new content types with the sdk govv1beta1.ModuleCdc.LegacyAmino.RegisterConcrete(&UpdateInstantiateConfigProposal{}, "wasm/UpdateInstantiateConfigProposal", nil) } +func NewStoreCodeProposal( + title string, + description string, + runAs string, + wasmBz []byte, + permission *AccessConfig, + unpinCode bool, +) *StoreCodeProposal { + return &StoreCodeProposal{title, description, runAs, wasmBz, permission, unpinCode} +} + // ProposalRoute returns the routing key of a parameter change proposal. func (p StoreCodeProposal) ProposalRoute() string { return RouterKey } @@ -144,6 +155,19 @@ func (p StoreCodeProposal) MarshalYAML() (interface{}, error) { }, nil } +func NewInstantiateContractProposal( + title string, + description string, + runAs string, + admin string, + codeID uint64, + label string, + msg RawContractMessage, + funds sdk.Coins, +) *InstantiateContractProposal { + return &InstantiateContractProposal{title, description, runAs, admin, codeID, label, msg, funds} +} + // ProposalRoute returns the routing key of a parameter change proposal. func (p InstantiateContractProposal) ProposalRoute() string { return RouterKey } @@ -335,6 +359,17 @@ func (p SudoContractProposal) MarshalYAML() (interface{}, error) { }, nil } +func NewExecuteContractProposal( + title string, + description string, + runAs string, + contract string, + msg RawContractMessage, + funds sdk.Coins, +) *ExecuteContractProposal { + return &ExecuteContractProposal{title, description, runAs, contract, msg, funds} +} + // ProposalRoute returns the routing key of a parameter change proposal. func (p ExecuteContractProposal) ProposalRoute() string { return RouterKey } @@ -398,6 +433,15 @@ func (p ExecuteContractProposal) MarshalYAML() (interface{}, error) { }, nil } +func NewUpdateAdminProposal( + title string, + description string, + newAdmin string, + contract string, +) *UpdateAdminProposal { + return &UpdateAdminProposal{title, description, newAdmin, contract} +} + // ProposalRoute returns the routing key of a parameter change proposal. func (p UpdateAdminProposal) ProposalRoute() string { return RouterKey } @@ -434,6 +478,14 @@ func (p UpdateAdminProposal) String() string { `, p.Title, p.Description, p.Contract, p.NewAdmin) } +func NewClearAdminProposal( + title string, + description string, + contract string, +) *ClearAdminProposal { + return &ClearAdminProposal{title, description, contract} +} + // ProposalRoute returns the routing key of a parameter change proposal. func (p ClearAdminProposal) ProposalRoute() string { return RouterKey }