From 9ee2271cba692dbc25b0fdc16c822ab0f8d453f1 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 4 May 2021 16:18:12 +0200 Subject: [PATCH 1/9] Start gov proposal execution --- x/twasm/contract/incoming_msgs.go | 131 +++++++++++++++++++++++++- x/twasm/keeper/handler_plugin.go | 36 ++++++- x/twasm/keeper/handler_plugin_test.go | 7 +- x/twasm/keeper/keeper.go | 2 +- x/twasm/keeper/privileged_test.go | 2 +- x/twasm/types/callbacks.go | 6 ++ x/twasm/types/callbacks_test.go | 7 +- 7 files changed, 179 insertions(+), 12 deletions(-) diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index bff9b3ce..bc9b227d 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -1,8 +1,19 @@ package contract +import ( + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/confio/tgrade/x/twasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" +) + // TgradeMsg messages coming from a contract type TgradeMsg struct { - Hooks *Hooks `json:"hooks"` + Hooks *Hooks `json:"hooks"` + ExecuteGovProposal *ExecuteGovProposal `json:"execute_gov_proposal"` } // Hooks contains method to interact with system callbacks @@ -16,3 +27,121 @@ type Hooks struct { RegisterValidatorSetUpdate *struct{} `json:"register_validator_set_update"` UnregisterValidatorSetUpdate *struct{} `json:"unregister_validator_set_update"` } + +// ExecuteGovProposal will execute an approved proposal in the Cosmos SDK "Gov Router". +// That allows access to many of the system internals, like sdk params or x/upgrade, +// as well as privileged access to the wasm module (eg. mark module privileged) +type ExecuteGovProposal struct { + Title string `json:"title"` + Description string `json:"description"` + Proposal GovProposal `json:"proposal"` +} + +func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { + switch { + case p.Proposal.Text != nil: + p.Proposal.Text.Title = p.Title + p.Proposal.Text.Description = p.Description + return p.Proposal.Text + case p.Proposal.RegisterUpgrade != nil: + return &upgradetypes.SoftwareUpgradeProposal{ + Title: p.Title, + Description: p.Description, + Plan: *p.Proposal.RegisterUpgrade, + } + case p.Proposal.CancelUpgrade != nil: + p.Proposal.CancelUpgrade.Title = p.Title + p.Proposal.CancelUpgrade.Description = p.Description + return p.Proposal.CancelUpgrade + case p.Proposal.RawProtoProposal != nil: + panic("todo: map to a new type") + + case p.Proposal.IbcClientUpdate != nil: + p.Proposal.IbcClientUpdate.Title = p.Title + p.Proposal.IbcClientUpdate.Description = p.Description + return p.Proposal.IbcClientUpdate + case p.Proposal.PromoteToPrivilegedContract != nil: + p.Proposal.PromoteToPrivilegedContract.Title = p.Title + p.Proposal.PromoteToPrivilegedContract.Description = p.Description + return p.Proposal.PromoteToPrivilegedContract + case p.Proposal.DemotePrivilegedContract != nil: + p.Proposal.DemotePrivilegedContract.Title = p.Title + p.Proposal.DemotePrivilegedContract.Description = p.Description + return p.Proposal.DemotePrivilegedContract + case p.Proposal.InstantiateContract != nil: + p.Proposal.InstantiateContract.Title = p.Title + p.Proposal.InstantiateContract.Description = p.Description + return p.Proposal.InstantiateContract + case p.Proposal.MigrateContract != nil: + p.Proposal.MigrateContract.Title = p.Title + p.Proposal.MigrateContract.Description = p.Description + return p.Proposal.MigrateContract + case p.Proposal.SetContractAdmin != nil: + p.Proposal.SetContractAdmin.Title = p.Title + p.Proposal.SetContractAdmin.Description = p.Description + return p.Proposal.SetContractAdmin + case p.Proposal.ClearContractAdmin != nil: + p.Proposal.ClearContractAdmin.Title = p.Title + p.Proposal.ClearContractAdmin.Description = p.Description + return p.Proposal.ClearContractAdmin + case p.Proposal.PinCodes != nil: + p.Proposal.PinCodes.Title = p.Title + p.Proposal.PinCodes.Description = p.Description + return p.Proposal.PinCodes + case p.Proposal.UnpinCodes != nil: + p.Proposal.UnpinCodes.Title = p.Title + p.Proposal.UnpinCodes.Description = p.Description + return p.Proposal.UnpinCodes + default: + return nil + } +} + +type GovProposal struct { + // Signaling proposal, the text and description field will be recorded + Text *govtypes.TextProposal `json:"text"` + + // Register an "live upgrade" on the x/upgrade module + // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/upgrade/v1beta1/upgrade.proto#L12-L53 + RegisterUpgrade *upgradetypes.Plan `json:"register_upgrade"` + + // There can only be one pending upgrade at a given time. This cancels the pending upgrade, if any. + // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/upgrade/v1beta1/upgrade.proto#L57-L62 + CancelUpgrade *upgradetypes.CancelSoftwareUpgradeProposal `json:"cancel_upgrade"` + + // Defines a proposal to change one or more parameters. + // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/params/v1beta1/params.proto#L9-L27 + ChangeParams *[]proposaltypes.ParamChange `json:"change_params"` + + // Allows raw bytes (if client and wasmd are aware of something the contract is not) + // Like CosmosMsg::Stargate but for the governance router, not normal router + RawProtoProposal *codectypes.Any `json:"raw_proto_proposal"` + + // Updates the matching client to set a new trusted header. + // This can be used by governance to restore a client that has timed out or forked or otherwise broken. + // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/ibc/core/client/v1/client.proto#L36-L49 + IbcClientUpdate *ibcclienttypes.ClientUpdateProposal `json:"ibc_client_update"` + + // See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto + PromoteToPrivilegedContract *types.PromoteToPrivilegedContractProposal `json:"promote_to_privileged_contract"` + // See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto + DemotePrivilegedContract *types.DemotePrivilegedContractProposal `json:"demote_privileged_contract"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L32-L54 + InstantiateContract *wasmtypes.InstantiateContractProposal `json:"instantiate_contract"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L56-L70 + MigrateContract *wasmtypes.MigrateContractProposal `json:"migrate_contract"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L72-L82 + SetContractAdmin *wasmtypes.UpdateAdminProposal `json:"set_contract_admin"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L84-L93 + ClearContractAdmin *wasmtypes.ClearAdminProposal `json:"clear_contract_admin"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L95-L107 + PinCodes *wasmtypes.PinCodesProposal `json:"pin_codes"` + + // See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L109-L121 + UnpinCodes *wasmtypes.UnpinCodesProposal `json:"unpin_codes"` +} diff --git a/x/twasm/keeper/handler_plugin.go b/x/twasm/keeper/handler_plugin.go index 88d51c5f..2df29a6f 100644 --- a/x/twasm/keeper/handler_plugin.go +++ b/x/twasm/keeper/handler_plugin.go @@ -9,6 +9,7 @@ import ( "github.com/confio/tgrade/x/twasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) // tgradeKeeper defines a subset of Keeper @@ -24,12 +25,13 @@ var _ wasmkeeper.Messenger = TgradeHandler{} // TgradeHandler is a custom message handler plugin for wasmd. type TgradeHandler struct { - keeper tgradeKeeper + keeper tgradeKeeper + govRouter govtypes.Router } // NewTgradeHandler constructor -func NewTgradeHandler(keeper tgradeKeeper) *TgradeHandler { - return &TgradeHandler{keeper: keeper} +func NewTgradeHandler(keeper tgradeKeeper, govRouter govtypes.Router) *TgradeHandler { + return &TgradeHandler{keeper: keeper, govRouter: govRouter} } // DispatchMsg handles wasmVM message for privileged contracts @@ -47,7 +49,8 @@ func (h TgradeHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, switch { case tMsg.Hooks != nil: return nil, nil, h.handleHooks(ctx, contractAddr, tMsg.Hooks) - + case tMsg.ExecuteGovProposal != nil: + return nil, nil, h.handleGovProposalExecution(ctx, contractAddr, tMsg.ExecuteGovProposal, h.govRouter) } return nil, nil, wasmtypes.ErrUnknownMsg } @@ -106,3 +109,28 @@ func (h TgradeHandler) handleHooks(ctx sdk.Context, contractAddr sdk.AccAddress, return wasmtypes.ErrUnknownMsg } } + +func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr sdk.AccAddress, exec *contract.ExecuteGovProposal, router govtypes.Router) error { + contractInfo := h.keeper.GetContractInfo(ctx, contractAddr) + if contractInfo == nil { + return sdkerrors.Wrap(wasmtypes.ErrNotFound, "contract info") + } + + var details types.TgradeContractDetails + if err := contractInfo.ReadExtension(&details); err != nil { + return err + } + if !details.HasRegisteredContractCallback(types.CallbackTypeGovProposalExecutor) { + return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "requires: %s", types.CallbackTypeGovProposalExecutor.String()) + } + + content := exec.GetProposalContent() + if content == nil { + return sdkerrors.Wrap(wasmtypes.ErrUnknownMsg, "unsupported content type") + } + if !router.HasRoute(content.ProposalRoute()) { + return sdkerrors.Wrap(govtypes.ErrNoProposalHandlerExists, content.ProposalRoute()) + } + govHandler := router.GetRoute(content.ProposalRoute()) + return govHandler(ctx, content) +} diff --git a/x/twasm/keeper/handler_plugin_test.go b/x/twasm/keeper/handler_plugin_test.go index 1abd7122..ca34ab6c 100644 --- a/x/twasm/keeper/handler_plugin_test.go +++ b/x/twasm/keeper/handler_plugin_test.go @@ -7,6 +7,7 @@ import ( "github.com/confio/tgrade/x/twasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" @@ -53,9 +54,10 @@ func TestTgradeHandlesDispatchMsg(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { + govRouter := govtypes.NewRouter() mock := handlerTgradeKeeperMock{} spec.setup(&mock) - h := NewTgradeHandler(mock) + h := NewTgradeHandler(mock, govRouter) var ctx sdk.Context _, _, gotErr := h.DispatchMsg(ctx, contractAddr, "", spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) @@ -239,7 +241,8 @@ func TestTgradeHandlesHooks(t *testing.T) { capturedDetails, capturedRegistrations, capturedUnRegistrations = nil, nil, nil mock := handlerTgradeKeeperMock{} spec.setup(&mock) - h := NewTgradeHandler(mock) + govRouter := govtypes.NewRouter() + h := NewTgradeHandler(mock, govRouter) var ctx sdk.Context gotErr := h.handleHooks(ctx, myContractAddr, &spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) diff --git a/x/twasm/keeper/keeper.go b/x/twasm/keeper/keeper.go index e59769b0..7d362b59 100644 --- a/x/twasm/keeper/keeper.go +++ b/x/twasm/keeper/keeper.go @@ -59,7 +59,7 @@ func NewKeeper( portSource, ), // append our custom message handler - NewTgradeHandler(&result), + NewTgradeHandler(&result, govRouter), ) var queryPlugins wasmkeeper.WasmVMQueryHandler = wasmkeeper.DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, &result.Keeper) diff --git a/x/twasm/keeper/privileged_test.go b/x/twasm/keeper/privileged_test.go index b87aeec7..5fe57d9d 100644 --- a/x/twasm/keeper/privileged_test.go +++ b/x/twasm/keeper/privileged_test.go @@ -149,7 +149,7 @@ func TestUnsetPrivileged(t *testing.T) { k := keepers.TWasmKeeper codeID, contractAddr := seedTestContract(t, ctx, k) - h := NewTgradeHandler(k) + h := NewTgradeHandler(k, nil) // and privileged with a callback k.setPrivilegedFlag(ctx, contractAddr) err := h.handleHooks(ctx, contractAddr, &contract.Hooks{ diff --git a/x/twasm/types/callbacks.go b/x/twasm/types/callbacks.go index 8b8b48d6..ddbdcdfe 100644 --- a/x/twasm/types/callbacks.go +++ b/x/twasm/types/callbacks.go @@ -12,12 +12,18 @@ var ( // CallbackTypeBeginBlock called every block before the TX are processed // Multiple contracts can register for this callback CallbackTypeBeginBlock = registerCallbackType(0x1, "begin_block", false) + // CallbackTypeEndBlock called every block after the TX are processed // Multiple contracts can register for this callback CallbackTypeEndBlock = registerCallbackType(0x2, "end_block", false) + // CallbackTypeValidatorSetUpdate end-blocker that can modify the validator set // This callback is exclusive to one contract instance, only. CallbackTypeValidatorSetUpdate = registerCallbackType(0x3, "validator_set_update", true) + + // CallbackTypeGovProposalExecutor + // This is a permission to execute governance proposals. + CallbackTypeGovProposalExecutor = registerCallbackType(0x4, "gov_proposal_executor", false) ) var ( diff --git a/x/twasm/types/callbacks_test.go b/x/twasm/types/callbacks_test.go index d6f7bb68..a6e5b677 100644 --- a/x/twasm/types/callbacks_test.go +++ b/x/twasm/types/callbacks_test.go @@ -75,9 +75,10 @@ func TestPrivilegedCallbackTypeValidation(t *testing.T) { func TestPrivilegedCallbackTypeSingletons(t *testing.T) { // sanity check with manually curated list specs := map[PrivilegedCallbackType]bool{ - CallbackTypeBeginBlock: false, - CallbackTypeEndBlock: false, - CallbackTypeValidatorSetUpdate: true, + CallbackTypeBeginBlock: false, + CallbackTypeEndBlock: false, + CallbackTypeValidatorSetUpdate: true, + CallbackTypeGovProposalExecutor: false, } for c, exp := range specs { t.Run(c.String(), func(t *testing.T) { From 04d4bc5ecb091fcb9bd3d2f9d6af9fc213825096 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 4 May 2021 16:42:34 +0200 Subject: [PATCH 2/9] Add stargate proposal type --- docs/proto/proto-docs.md | 18 ++ proto/confio/twasm/v1beta1/proposal.proto | 13 + x/twasm/contract/incoming_msgs.go | 8 +- x/twasm/keeper/handler_plugin.go | 4 + x/twasm/keeper/keeper.go | 2 + x/twasm/keeper/proposal_handler.go | 14 +- x/twasm/keeper/proposal_handler_test.go | 2 +- x/twasm/types/codec.go | 2 + x/twasm/types/proposal.go | 60 ++++ x/twasm/types/proposal.pb.go | 343 ++++++++++++++++++++-- 10 files changed, 439 insertions(+), 27 deletions(-) diff --git a/docs/proto/proto-docs.md b/docs/proto/proto-docs.md index 5bfa483f..a2af5fce 100644 --- a/docs/proto/proto-docs.md +++ b/docs/proto/proto-docs.md @@ -14,6 +14,7 @@ - [confio/twasm/v1beta1/proposal.proto](#confio/twasm/v1beta1/proposal.proto) - [DemotePrivilegedContractProposal](#confio.twasm.v1beta1.DemotePrivilegedContractProposal) - [PromoteToPrivilegedContractProposal](#confio.twasm.v1beta1.PromoteToPrivilegedContractProposal) + - [StargateContentProposal](#confio.twasm.v1beta1.StargateContentProposal) - [confio/twasm/v1beta1/query.proto](#confio/twasm/v1beta1/query.proto) - [QueryContractsByCallbackTypeRequest](#confio.twasm.v1beta1.QueryContractsByCallbackTypeRequest) @@ -148,6 +149,23 @@ PromoteToPrivilegedContractProposal gov proposal content type to add + + + +### StargateContentProposal +StargateContentProposal is a wildcard proposal type. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `title` | [string](#string) | | Title is a short summary | +| `description` | [string](#string) | | Description is a human readable text | +| `content` | [google.protobuf.Any](#google.protobuf.Any) | | Content implements any `govtypes.Content` type | + + + + + diff --git a/proto/confio/twasm/v1beta1/proposal.proto b/proto/confio/twasm/v1beta1/proposal.proto index 4ce78250..966e6422 100644 --- a/proto/confio/twasm/v1beta1/proposal.proto +++ b/proto/confio/twasm/v1beta1/proposal.proto @@ -4,6 +4,8 @@ package confio.twasm.v1beta1; import "gogoproto/gogo.proto"; import "cosmos/base/v1beta1/coin.proto"; import "cosmwasm/wasm/v1beta1/types.proto"; +import "cosmos_proto/cosmos.proto"; +import "google/protobuf/any.proto"; option go_package = "github.com/confio/tgrade/x/twasm/types"; option (gogoproto.goproto_stringer_all) = false; @@ -31,3 +33,14 @@ message DemotePrivilegedContractProposal { // Contract is the address of the smart contract string contract = 3 [ (gogoproto.moretags) = "yaml:\"contract\"" ]; } + +// StargateContentProposal is a wildcard proposal type. +message StargateContentProposal { + // Title is a short summary + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + // Description is a human readable text + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + // Content implements any `govtypes.Content` type + google.protobuf.Any content = 3 + [ (cosmos_proto.accepts_interface) = "Content" ]; +} diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index bc9b227d..71124e8d 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -37,6 +37,7 @@ type ExecuteGovProposal struct { Proposal GovProposal `json:"proposal"` } +// GetProposalContent converts message payload to gov content type. returns `nil` when unknown func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { switch { case p.Proposal.Text != nil: @@ -54,8 +55,11 @@ func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { p.Proposal.CancelUpgrade.Description = p.Description return p.Proposal.CancelUpgrade case p.Proposal.RawProtoProposal != nil: - panic("todo: map to a new type") - + return &types.StargateContentProposal{ + Title: p.Title, + Description: p.Description, + Content: p.Proposal.RawProtoProposal, + } case p.Proposal.IbcClientUpdate != nil: p.Proposal.IbcClientUpdate.Title = p.Title p.Proposal.IbcClientUpdate.Description = p.Description diff --git a/x/twasm/keeper/handler_plugin.go b/x/twasm/keeper/handler_plugin.go index 2df29a6f..384aac47 100644 --- a/x/twasm/keeper/handler_plugin.go +++ b/x/twasm/keeper/handler_plugin.go @@ -110,6 +110,7 @@ func (h TgradeHandler) handleHooks(ctx sdk.Context, contractAddr sdk.AccAddress, } } +// handle gov proposal execution func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr sdk.AccAddress, exec *contract.ExecuteGovProposal, router govtypes.Router) error { contractInfo := h.keeper.GetContractInfo(ctx, contractAddr) if contractInfo == nil { @@ -128,6 +129,9 @@ func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr if content == nil { return sdkerrors.Wrap(wasmtypes.ErrUnknownMsg, "unsupported content type") } + if err := content.ValidateBasic(); err != nil { + return sdkerrors.Wrap(err, "content") + } if !router.HasRoute(content.ProposalRoute()) { return sdkerrors.Wrap(govtypes.ErrNoProposalHandlerExists, content.ProposalRoute()) } diff --git a/x/twasm/keeper/keeper.go b/x/twasm/keeper/keeper.go index 7d362b59..f13d2ef5 100644 --- a/x/twasm/keeper/keeper.go +++ b/x/twasm/keeper/keeper.go @@ -20,6 +20,7 @@ type Keeper struct { storeKey sdk.StoreKey contractKeeper wasmtypes.ContractOpsKeeper paramSpace paramtypes.Subspace + govRouter govtypes.Router } func NewKeeper( @@ -46,6 +47,7 @@ func NewKeeper( cdc: cdc, storeKey: storeKey, paramSpace: paramSpace, + govRouter: govRouter, } // configure wasm keeper via options diff --git a/x/twasm/keeper/proposal_handler.go b/x/twasm/keeper/proposal_handler.go index 0caefa03..5cd70232 100644 --- a/x/twasm/keeper/proposal_handler.go +++ b/x/twasm/keeper/proposal_handler.go @@ -18,11 +18,11 @@ type govKeeper interface { // NewProposalHandler creates a new governance Handler for wasm proposals func NewProposalHandler(k Keeper) govtypes.Handler { wasmProposalHandler := wasmkeeper.NewWasmProposalHandler(k, wasmtypes.EnableAllProposals) - return NewProposalHandlerX(k, wasmProposalHandler) + return NewProposalHandlerX(k, wasmProposalHandler, k.govRouter) } // NewProposalHandlerX creates a new governance Handler for wasm proposals -func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler) govtypes.Handler { +func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler, router govtypes.Router) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) error { err := wasmProposalHandler(ctx, content) switch { @@ -39,6 +39,16 @@ func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler) govt return handlePromoteContractProposal(ctx, k, *c) case *types.DemotePrivilegedContractProposal: return handleDemoteContractProposal(ctx, k, *c) + case *types.StargateContentProposal: + nestedContent, ok := c.Content.GetCachedValue().(govtypes.Content) + if !ok || nestedContent == nil { + return sdkerrors.Wrap(wasmtypes.ErrInvalid, "not gov content type") + } + if !router.HasRoute(nestedContent.ProposalRoute()) { + return sdkerrors.Wrap(govtypes.ErrNoProposalHandlerExists, nestedContent.ProposalRoute()) + } + govHandler := router.GetRoute(nestedContent.ProposalRoute()) + return govHandler(ctx, content) default: return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized twasm srcProposal content type: %T", c) } diff --git a/x/twasm/keeper/proposal_handler_test.go b/x/twasm/keeper/proposal_handler_test.go index 9468af85..8301628a 100644 --- a/x/twasm/keeper/proposal_handler_test.go +++ b/x/twasm/keeper/proposal_handler_test.go @@ -93,7 +93,7 @@ func TestGovHandler(t *testing.T) { spec.setupGovKeeper(&mock) } // when - h := NewProposalHandlerX(&mock, spec.wasmHandler) + h := NewProposalHandlerX(&mock, spec.wasmHandler, govtypes.NewRouter()) gotErr := h(ctx, spec.srcProposal) // then require.True(t, spec.expErr.Is(gotErr), "exp %v but got #+v", spec.expErr, gotErr) diff --git a/x/twasm/types/codec.go b/x/twasm/types/codec.go index d63c38b3..f694b3bf 100644 --- a/x/twasm/types/codec.go +++ b/x/twasm/types/codec.go @@ -13,6 +13,7 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { wasmtypes.RegisterLegacyAminoCodec(cdc) cdc.RegisterConcrete(&PromoteToPrivilegedContractProposal{}, "twasm/PromoteToPrivilegedContractProposal", nil) cdc.RegisterConcrete(&DemotePrivilegedContractProposal{}, "twasm/DemotePrivilegedContractProposal", nil) + cdc.RegisterConcrete(&StargateContentProposal{}, "twasm/StargateContentProposal", nil) } @@ -22,6 +23,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { (*govtypes.Content)(nil), &PromoteToPrivilegedContractProposal{}, &DemotePrivilegedContractProposal{}, + &StargateContentProposal{}, ) registry.RegisterImplementations( (*wasmtypes.ContractInfoExtension)(nil), diff --git a/x/twasm/types/proposal.go b/x/twasm/types/proposal.go index 0c21b8f8..d04a935d 100644 --- a/x/twasm/types/proposal.go +++ b/x/twasm/types/proposal.go @@ -2,6 +2,8 @@ package types import ( "fmt" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -13,20 +15,24 @@ type ProposalType string const ( ProposalTypePromoteContract ProposalType = "PromoteToPrivilegedContract" ProposalTypeDemoteContract ProposalType = "DemotePrivilegedContract" + ProposalTypeStargate ProposalType = "StargateContent" ) // EnableAllProposals contains all twasm gov types as keys. var EnableAllProposals = []ProposalType{ ProposalTypePromoteContract, ProposalTypeDemoteContract, + ProposalTypeStargate, } func init() { // register new content types with the sdk govtypes.RegisterProposalType(string(ProposalTypePromoteContract)) govtypes.RegisterProposalType(string(ProposalTypeDemoteContract)) + govtypes.RegisterProposalType(string(ProposalTypeStargate)) govtypes.RegisterProposalTypeCodec(&PromoteToPrivilegedContractProposal{}, "twasm/PromoteToPrivilegedContractProposal") govtypes.RegisterProposalTypeCodec(&DemotePrivilegedContractProposal{}, "twasm/DemotePrivilegedContractProposal") + govtypes.RegisterProposalTypeCodec(&StargateContentProposal{}, "twasm/StargateContentProposal") } // ProposalRoute returns the routing key of a parameter change proposal. @@ -129,3 +135,57 @@ func validateProposalCommons(title, description string) error { } return nil } + +var _ codectypes.UnpackInterfacesMessage = &StargateContentProposal{} + +// UnpackInterfaces implements codectypes.UnpackInterfaces +func (m *StargateContentProposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var content govtypes.Content + if err := unpacker.UnpackAny(m.Content, &content); err != nil { + return err + } + return codectypes.UnpackInterfaces(content, unpacker) +} + +// ProposalRoute returns the routing key of a parameter change proposal. +func (p StargateContentProposal) ProposalRoute() string { return RouterKey } + +// GetTitle returns the title of the proposal +func (p *StargateContentProposal) GetTitle() string { return p.Title } + +// GetDescription returns the human readable description of the proposal +func (p StargateContentProposal) GetDescription() string { return p.Description } + +// ProposalType returns the type +func (p StargateContentProposal) ProposalType() string { + return string(ProposalTypeStargate) +} + +// ValidateBasic validates the proposal +func (p StargateContentProposal) ValidateBasic() error { + if err := validateProposalCommons(p.Title, p.Description); err != nil { + return err + } + content, ok := p.Content.GetCachedValue().(govtypes.Content) + if !ok || content == nil { + return sdkerrors.Wrap(wasmtypes.ErrInvalid, "not gov content type") + } + if err := content.ValidateBasic(); err != nil { + return sdkerrors.Wrap(err, "content") + } + return nil +} + +// String implements the Stringer interface. +func (p StargateContentProposal) String() string { + return fmt.Sprintf(`Store Code Proposal: + Title: %s + Description: %s + Content: %s +`, p.Title, p.Description, p.Content) +} + +// MarshalYAML pretty prints the wasm byte code +func (p StargateContentProposal) MarshalYAML() (interface{}, error) { + return p, nil +} diff --git a/x/twasm/types/proposal.pb.go b/x/twasm/types/proposal.pb.go index 4eec6869..8d743bc6 100644 --- a/x/twasm/types/proposal.pb.go +++ b/x/twasm/types/proposal.pb.go @@ -6,9 +6,11 @@ package types import ( fmt "fmt" _ "github.com/CosmWasm/wasmd/x/wasm/types" + types "github.com/cosmos/cosmos-sdk/codec/types" _ "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" + _ "github.com/regen-network/cosmos-proto" io "io" math "math" math_bits "math/bits" @@ -111,9 +113,52 @@ func (m *DemotePrivilegedContractProposal) XXX_DiscardUnknown() { var xxx_messageInfo_DemotePrivilegedContractProposal proto.InternalMessageInfo +// StargateContentProposal is a wildcard proposal type. +type StargateContentProposal struct { + // Title is a short summary + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty" yaml:"title"` + // Description is a human readable text + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` + // Content implements any `govtypes.Content` type + Content *types.Any `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` +} + +func (m *StargateContentProposal) Reset() { *m = StargateContentProposal{} } +func (*StargateContentProposal) ProtoMessage() {} +func (*StargateContentProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_77ea8b6359ab7726, []int{2} +} +func (m *StargateContentProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StargateContentProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StargateContentProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *StargateContentProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_StargateContentProposal.Merge(m, src) +} +func (m *StargateContentProposal) XXX_Size() int { + return m.Size() +} +func (m *StargateContentProposal) XXX_DiscardUnknown() { + xxx_messageInfo_StargateContentProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_StargateContentProposal proto.InternalMessageInfo + func init() { proto.RegisterType((*PromoteToPrivilegedContractProposal)(nil), "confio.twasm.v1beta1.PromoteToPrivilegedContractProposal") proto.RegisterType((*DemotePrivilegedContractProposal)(nil), "confio.twasm.v1beta1.DemotePrivilegedContractProposal") + proto.RegisterType((*StargateContentProposal)(nil), "confio.twasm.v1beta1.StargateContentProposal") } func init() { @@ -121,28 +166,32 @@ func init() { } var fileDescriptor_77ea8b6359ab7726 = []byte{ - // 325 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x92, 0x41, 0x4a, 0x33, 0x31, - 0x14, 0xc7, 0x27, 0xdf, 0x87, 0xa2, 0x51, 0x50, 0xc6, 0x22, 0xa5, 0x8b, 0xb4, 0xa6, 0x50, 0x5c, - 0x4d, 0x28, 0x6e, 0xc4, 0x65, 0x75, 0xe9, 0xa2, 0x14, 0x57, 0xee, 0x32, 0x69, 0x1c, 0x03, 0x33, - 0xf3, 0x86, 0x24, 0x56, 0x7b, 0x0b, 0x8f, 0xe1, 0x05, 0x04, 0x8f, 0xd0, 0x65, 0x97, 0x5d, 0x15, - 0x3b, 0xbd, 0x41, 0x4f, 0x20, 0x4d, 0xa6, 0xa5, 0x1e, 0xc1, 0xdd, 0x30, 0xbf, 0xdf, 0xcb, 0xff, - 0x0f, 0xef, 0xe1, 0xb6, 0x80, 0xfc, 0x49, 0x01, 0xb3, 0xaf, 0xdc, 0x64, 0x6c, 0xd4, 0x8d, 0xa5, - 0xe5, 0x5d, 0x56, 0x68, 0x28, 0xc0, 0xf0, 0x34, 0x2a, 0x34, 0x58, 0x08, 0x6b, 0x5e, 0x8a, 0x9c, - 0x14, 0x55, 0x52, 0xa3, 0x96, 0x40, 0x02, 0x4e, 0x60, 0xeb, 0x2f, 0xef, 0x36, 0x88, 0x00, 0x93, - 0x81, 0x61, 0x31, 0x37, 0x72, 0xfb, 0x9e, 0x00, 0x95, 0x57, 0xfc, 0x62, 0xcd, 0x5d, 0xd8, 0xaf, - 0x44, 0x3b, 0x2e, 0xa4, 0xf1, 0x0a, 0xfd, 0x42, 0xb8, 0xdd, 0xd7, 0x90, 0x81, 0x95, 0x0f, 0xd0, - 0xd7, 0x6a, 0xa4, 0x52, 0x99, 0xc8, 0xe1, 0x2d, 0xe4, 0x56, 0x73, 0x61, 0xfb, 0x55, 0xb9, 0xb0, - 0x83, 0xf7, 0xac, 0xb2, 0xa9, 0xac, 0xa3, 0x16, 0xba, 0x3c, 0xec, 0x9d, 0xae, 0xe6, 0xcd, 0xe3, - 0x31, 0xcf, 0xd2, 0x1b, 0xea, 0x7e, 0xd3, 0x81, 0xc7, 0xe1, 0x35, 0x3e, 0x1a, 0x4a, 0x23, 0xb4, - 0x2a, 0xac, 0x82, 0xbc, 0xfe, 0xcf, 0xd9, 0xe7, 0xab, 0x79, 0x33, 0xf4, 0xf6, 0x0e, 0xa4, 0x83, - 0x5d, 0x35, 0x64, 0xf8, 0x40, 0x54, 0xa9, 0xf5, 0xff, 0x6e, 0xec, 0x6c, 0x35, 0x6f, 0x9e, 0xf8, - 0xb1, 0x0d, 0xa1, 0x83, 0xad, 0x44, 0x3f, 0x11, 0x6e, 0xdd, 0xc9, 0x75, 0xf3, 0x3f, 0xd5, 0xbb, - 0x77, 0x3f, 0x59, 0x90, 0x60, 0xb6, 0x20, 0xc1, 0x47, 0x49, 0xd0, 0xa4, 0x24, 0x68, 0x5a, 0x12, - 0xf4, 0x5d, 0x12, 0xf4, 0xbe, 0x24, 0xc1, 0x74, 0x49, 0x82, 0xd9, 0x92, 0x04, 0x8f, 0x9d, 0x44, - 0xd9, 0xe7, 0x97, 0x38, 0x12, 0x90, 0xb1, 0xcd, 0xdd, 0x24, 0x9a, 0x0f, 0x25, 0x7b, 0xab, 0x0e, - 0xc8, 0xad, 0x31, 0xde, 0x77, 0x7b, 0xbc, 0xfa, 0x09, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x49, 0x11, - 0xca, 0x5d, 0x02, 0x00, 0x00, + // 397 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x93, 0x31, 0xee, 0xd3, 0x30, + 0x14, 0xc6, 0x63, 0x10, 0xfc, 0xc1, 0x45, 0x02, 0x85, 0x0a, 0x4a, 0x07, 0xb7, 0xa4, 0x52, 0xc5, + 0x42, 0xac, 0xc2, 0x82, 0x60, 0xa2, 0x30, 0x32, 0x54, 0x81, 0x89, 0x05, 0x39, 0xae, 0x6b, 0x2c, + 0x25, 0x79, 0x91, 0xe3, 0x16, 0x72, 0x0b, 0x8e, 0xc1, 0x01, 0x40, 0xea, 0x11, 0x2a, 0xa6, 0x8e, + 0x9d, 0x2a, 0x9a, 0xde, 0xa0, 0x27, 0x40, 0xb1, 0x93, 0xaa, 0x5c, 0x00, 0x89, 0x2d, 0x2f, 0xdf, + 0xef, 0xbd, 0xef, 0xb3, 0xf4, 0x1e, 0x1e, 0x71, 0xc8, 0x16, 0x0a, 0xa8, 0xf9, 0xc2, 0x8a, 0x94, + 0xae, 0x26, 0xb1, 0x30, 0x6c, 0x42, 0x73, 0x0d, 0x39, 0x14, 0x2c, 0x09, 0x73, 0x0d, 0x06, 0xfc, + 0xae, 0x83, 0x42, 0x0b, 0x85, 0x0d, 0xd4, 0xef, 0x4a, 0x90, 0x60, 0x01, 0x5a, 0x7f, 0x39, 0xb6, + 0x4f, 0x38, 0x14, 0x29, 0x14, 0x34, 0x66, 0x85, 0x38, 0xcf, 0xe3, 0xa0, 0xb2, 0x46, 0x7f, 0x5c, + 0xeb, 0xd6, 0xec, 0x2f, 0x47, 0x53, 0xe6, 0xa2, 0x68, 0x90, 0x47, 0x6e, 0xc4, 0x27, 0x37, 0xdb, + 0x15, 0xad, 0x24, 0x01, 0x64, 0x22, 0xa8, 0xad, 0xe2, 0xe5, 0x82, 0xb2, 0xac, 0x74, 0x52, 0xb0, + 0x46, 0x78, 0x34, 0xd3, 0x90, 0x82, 0x11, 0x1f, 0x60, 0xa6, 0xd5, 0x4a, 0x25, 0x42, 0x8a, 0xf9, + 0x1b, 0xc8, 0x8c, 0x66, 0xdc, 0xcc, 0x9a, 0x27, 0xf9, 0x63, 0x7c, 0xc3, 0x28, 0x93, 0x88, 0x1e, + 0x1a, 0xa2, 0x27, 0xb7, 0xa7, 0xf7, 0x4e, 0xfb, 0xc1, 0x9d, 0x92, 0xa5, 0xc9, 0xcb, 0xc0, 0xfe, + 0x0e, 0x22, 0x27, 0xfb, 0x2f, 0x70, 0x67, 0x2e, 0x0a, 0xae, 0x55, 0x6e, 0x14, 0x64, 0xbd, 0x6b, + 0x96, 0x7e, 0x70, 0xda, 0x0f, 0x7c, 0x47, 0x5f, 0x88, 0x41, 0x74, 0x89, 0xfa, 0x14, 0xdf, 0xe2, + 0x8d, 0x6b, 0xef, 0xba, 0x6d, 0xbb, 0x7f, 0xda, 0x0f, 0xee, 0xba, 0xb6, 0x56, 0x09, 0xa2, 0x33, + 0x14, 0xfc, 0x44, 0x78, 0xf8, 0x56, 0xd4, 0xc9, 0xff, 0xaf, 0xdc, 0x6b, 0x84, 0x1f, 0xbe, 0x37, + 0x4c, 0x4b, 0x66, 0x44, 0x9d, 0x57, 0x64, 0xff, 0x32, 0xee, 0x2b, 0x7c, 0xc5, 0x9d, 0xa9, 0x4d, + 0xdb, 0x79, 0xd6, 0x0d, 0xdd, 0x76, 0x84, 0xed, 0x76, 0x84, 0xaf, 0xb3, 0x72, 0xda, 0xf9, 0xf5, + 0xe3, 0xe9, 0x55, 0x93, 0x2e, 0x6a, 0x3b, 0xa6, 0xef, 0x36, 0x07, 0xe2, 0xed, 0x0e, 0xc4, 0xfb, + 0x5e, 0x11, 0xb4, 0xa9, 0x08, 0xda, 0x56, 0x04, 0xfd, 0xae, 0x08, 0xfa, 0x76, 0x24, 0xde, 0xf6, + 0x48, 0xbc, 0xdd, 0x91, 0x78, 0x1f, 0xc7, 0x52, 0x99, 0xcf, 0xcb, 0x38, 0xe4, 0x90, 0xd2, 0xf6, + 0x50, 0xa4, 0x66, 0x73, 0x41, 0xbf, 0x36, 0x17, 0x63, 0xf7, 0x36, 0xbe, 0x69, 0x1d, 0x9f, 0xff, + 0x09, 0x00, 0x00, 0xff, 0xff, 0x3b, 0xc9, 0x58, 0xb7, 0x4e, 0x03, 0x00, 0x00, } func (this *PromoteToPrivilegedContractProposal) Equal(that interface{}) bool { @@ -205,6 +254,36 @@ func (this *DemotePrivilegedContractProposal) Equal(that interface{}) bool { } return true } +func (this *StargateContentProposal) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*StargateContentProposal) + if !ok { + that2, ok := that.(StargateContentProposal) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Title != that1.Title { + return false + } + if this.Description != that1.Description { + return false + } + if !this.Content.Equal(that1.Content) { + return false + } + return true +} func (m *PromoteToPrivilegedContractProposal) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -293,6 +372,55 @@ func (m *DemotePrivilegedContractProposal) MarshalToSizedBuffer(dAtA []byte) (in return len(dAtA) - i, nil } +func (m *StargateContentProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StargateContentProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StargateContentProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Content != nil { + { + size, err := m.Content.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintProposal(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintProposal(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintProposal(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintProposal(dAtA []byte, offset int, v uint64) int { offset -= sovProposal(v) base := offset @@ -346,6 +474,27 @@ func (m *DemotePrivilegedContractProposal) Size() (n int) { return n } +func (m *StargateContentProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovProposal(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovProposal(uint64(l)) + } + if m.Content != nil { + l = m.Content.Size() + n += 1 + l + sovProposal(uint64(l)) + } + return n +} + func sovProposal(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -644,6 +793,156 @@ func (m *DemotePrivilegedContractProposal) Unmarshal(dAtA []byte) error { } return nil } +func (m *StargateContentProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProposal + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StargateContentProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StargateContentProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProposal + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProposal + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProposal + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProposal + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthProposal + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthProposal + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Content", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProposal + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProposal + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthProposal + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Content == nil { + m.Content = &types.Any{} + } + if err := m.Content.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProposal(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthProposal + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipProposal(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From ea8c587a15cea07ef207b2b23cb3da583f6d75bb Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 4 May 2021 17:00:33 +0200 Subject: [PATCH 3/9] Handle gov proposal executor un/registration --- x/twasm/contract/incoming_msgs.go | 3 +++ x/twasm/keeper/handler_plugin.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index 71124e8d..fc846f17 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -26,6 +26,9 @@ type Hooks struct { // only max 1 contract can be registered here, this is called in EndBlock (after everything else) and can change the validator set. RegisterValidatorSetUpdate *struct{} `json:"register_validator_set_update"` UnregisterValidatorSetUpdate *struct{} `json:"unregister_validator_set_update"` + + RegisterGovProposalExecutor *struct{} `json:"register_gov_proposal_executor"` + UnregisterGovProposalExecutor *struct{} `json:"unregister_gov_proposal_executor"` } // ExecuteGovProposal will execute an approved proposal in the Cosmos SDK "Gov Router". diff --git a/x/twasm/keeper/handler_plugin.go b/x/twasm/keeper/handler_plugin.go index 384aac47..66e14f41 100644 --- a/x/twasm/keeper/handler_plugin.go +++ b/x/twasm/keeper/handler_plugin.go @@ -105,6 +105,10 @@ func (h TgradeHandler) handleHooks(ctx sdk.Context, contractAddr sdk.AccAddress, return register(types.CallbackTypeValidatorSetUpdate) case hooks.UnregisterValidatorSetUpdate != nil: return unregister(types.CallbackTypeValidatorSetUpdate) + case hooks.RegisterGovProposalExecutor != nil: + return register(types.CallbackTypeGovProposalExecutor) + case hooks.UnregisterGovProposalExecutor != nil: + return unregister(types.CallbackTypeGovProposalExecutor) default: return wasmtypes.ErrUnknownMsg } From 09383aaf6685cfe63c3d4b156e29eb0edfef869f Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 5 May 2021 14:59:33 +0200 Subject: [PATCH 4/9] Start testing all the pieces --- x/twasm/keeper/handler_plugin.go | 8 +- x/twasm/keeper/handler_plugin_test.go | 206 +++++++++++++++++++----- x/twasm/keeper/proposal_handler_test.go | 45 +++++- x/twasm/types/proposal.go | 23 +++ x/twasm/types/proposal_test.go | 52 ++++++ x/twasm/types/test_fixtures.go | 18 +++ 6 files changed, 302 insertions(+), 50 deletions(-) diff --git a/x/twasm/keeper/handler_plugin.go b/x/twasm/keeper/handler_plugin.go index 66e14f41..d9d90cea 100644 --- a/x/twasm/keeper/handler_plugin.go +++ b/x/twasm/keeper/handler_plugin.go @@ -50,7 +50,7 @@ func (h TgradeHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, case tMsg.Hooks != nil: return nil, nil, h.handleHooks(ctx, contractAddr, tMsg.Hooks) case tMsg.ExecuteGovProposal != nil: - return nil, nil, h.handleGovProposalExecution(ctx, contractAddr, tMsg.ExecuteGovProposal, h.govRouter) + return nil, nil, h.handleGovProposalExecution(ctx, contractAddr, tMsg.ExecuteGovProposal) } return nil, nil, wasmtypes.ErrUnknownMsg } @@ -115,7 +115,7 @@ func (h TgradeHandler) handleHooks(ctx sdk.Context, contractAddr sdk.AccAddress, } // handle gov proposal execution -func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr sdk.AccAddress, exec *contract.ExecuteGovProposal, router govtypes.Router) error { +func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr sdk.AccAddress, exec *contract.ExecuteGovProposal) error { contractInfo := h.keeper.GetContractInfo(ctx, contractAddr) if contractInfo == nil { return sdkerrors.Wrap(wasmtypes.ErrNotFound, "contract info") @@ -136,9 +136,9 @@ func (h TgradeHandler) handleGovProposalExecution(ctx sdk.Context, contractAddr if err := content.ValidateBasic(); err != nil { return sdkerrors.Wrap(err, "content") } - if !router.HasRoute(content.ProposalRoute()) { + if !h.govRouter.HasRoute(content.ProposalRoute()) { return sdkerrors.Wrap(govtypes.ErrNoProposalHandlerExists, content.ProposalRoute()) } - govHandler := router.GetRoute(content.ProposalRoute()) + govHandler := h.govRouter.GetRoute(content.ProposalRoute()) return govHandler(ctx, content) } diff --git a/x/twasm/keeper/handler_plugin_test.go b/x/twasm/keeper/handler_plugin_test.go index ca34ab6c..0659e0f1 100644 --- a/x/twasm/keeper/handler_plugin_test.go +++ b/x/twasm/keeper/handler_plugin_test.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" @@ -16,18 +17,33 @@ import ( func TestTgradeHandlesDispatchMsg(t *testing.T) { contractAddr := RandomAddress(t) specs := map[string]struct { - setup func(m *handlerTgradeKeeperMock) - src wasmvmtypes.CosmosMsg - expErr *sdkerrors.Error + setup func(m *handlerTgradeKeeperMock) + src wasmvmtypes.CosmosMsg + expErr *sdkerrors.Error + expCapturedGovContent []govtypes.Content }{ "handle hook msg": { src: wasmvmtypes.CosmosMsg{ Custom: []byte(`{"hooks":{"register_begin_block":{}}}`), }, - setup: func(m *handlerTgradeKeeperMock) { // noopRegisterHook + setup: func(m *handlerTgradeKeeperMock) { noopRegisterHook(m) }, }, + "handle execute gov proposal msg": { + src: wasmvmtypes.CosmosMsg{ + Custom: []byte(`{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"text":{}}}}`), + }, + setup: func(m *handlerTgradeKeeperMock) { + noopRegisterHook(m, func(info *wasmtypes.ContractInfo) { + var details types.TgradeContractDetails + require.NoError(t, info.ReadExtension(&details)) + details.AddRegisteredCallback(types.CallbackTypeGovProposalExecutor, 1) + require.NoError(t, info.SetExtension(&details)) + }) + }, + expCapturedGovContent: []govtypes.Content{&govtypes.TextProposal{Title: "foo", Description: "bar"}}, + }, "non custom msg rejected": { src: wasmvmtypes.CosmosMsg{}, setup: func(m *handlerTgradeKeeperMock) {}, @@ -54,13 +70,14 @@ func TestTgradeHandlesDispatchMsg(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { - govRouter := govtypes.NewRouter() + govRouter := &CapturingGovRouter{} mock := handlerTgradeKeeperMock{} spec.setup(&mock) h := NewTgradeHandler(mock, govRouter) var ctx sdk.Context _, _, gotErr := h.DispatchMsg(ctx, contractAddr, "", spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) + assert.Equal(t, spec.expCapturedGovContent, govRouter.captured) }) } } @@ -124,35 +141,6 @@ func TestTgradeHandlesHooks(t *testing.T) { }, expRegistrations: []registration{{cb: types.CallbackTypeBeginBlock, addr: myContractAddr}}, }, - "register end block": { - src: contract.Hooks{RegisterEndBlock: &struct{}{}}, - setup: captureWithMock(), - expDetails: &types.TgradeContractDetails{ - RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "end_block"}}, - }, - expRegistrations: []registration{{cb: types.CallbackTypeEndBlock, addr: myContractAddr}}, - }, - "register validator set update block": { - src: contract.Hooks{RegisterValidatorSetUpdate: &struct{}{}}, - setup: captureWithMock(), - expDetails: &types.TgradeContractDetails{ - RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "validator_set_update"}}, - }, - expRegistrations: []registration{{cb: types.CallbackTypeValidatorSetUpdate, addr: myContractAddr}}, - }, - "register hook fails": { - src: contract.Hooks{RegisterValidatorSetUpdate: &struct{}{}}, - setup: func(m *handlerTgradeKeeperMock) { - m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { - r := wasmtypes.ContractInfoFixture() - return &r - } - m.appendToPrivilegedContractCallbacksFn = func(ctx sdk.Context, callbackType types.PrivilegedCallbackType, contractAddress sdk.AccAddress) (uint8, error) { - return 0, wasmtypes.ErrDuplicate - } - }, - expErr: wasmtypes.ErrDuplicate, - }, "unregister begin block": { src: contract.Hooks{UnregisterBeginBlock: &struct{}{}}, setup: captureWithMock(func(info *wasmtypes.ContractInfo) { @@ -164,6 +152,14 @@ func TestTgradeHandlesHooks(t *testing.T) { expDetails: &types.TgradeContractDetails{RegisteredCallbacks: []*types.RegisteredCallback{}}, expUnRegistrations: []unregistration{{cb: types.CallbackTypeBeginBlock, pos: 1, addr: myContractAddr}}, }, + "register end block": { + src: contract.Hooks{RegisterEndBlock: &struct{}{}}, + setup: captureWithMock(), + expDetails: &types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "end_block"}}, + }, + expRegistrations: []registration{{cb: types.CallbackTypeEndBlock, addr: myContractAddr}}, + }, "unregister end block": { src: contract.Hooks{UnregisterEndBlock: &struct{}{}}, setup: captureWithMock(func(info *wasmtypes.ContractInfo) { @@ -175,6 +171,14 @@ func TestTgradeHandlesHooks(t *testing.T) { expDetails: &types.TgradeContractDetails{RegisteredCallbacks: []*types.RegisteredCallback{}}, expUnRegistrations: []unregistration{{cb: types.CallbackTypeEndBlock, pos: 1, addr: myContractAddr}}, }, + "register validator set update block": { + src: contract.Hooks{RegisterValidatorSetUpdate: &struct{}{}}, + setup: captureWithMock(), + expDetails: &types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "validator_set_update"}}, + }, + expRegistrations: []registration{{cb: types.CallbackTypeValidatorSetUpdate, addr: myContractAddr}}, + }, "unregister validator set update block": { src: contract.Hooks{UnregisterValidatorSetUpdate: &struct{}{}}, setup: captureWithMock(func(info *wasmtypes.ContractInfo) { @@ -186,6 +190,38 @@ func TestTgradeHandlesHooks(t *testing.T) { expDetails: &types.TgradeContractDetails{RegisteredCallbacks: []*types.RegisteredCallback{}}, expUnRegistrations: []unregistration{{cb: types.CallbackTypeValidatorSetUpdate, pos: 1, addr: myContractAddr}}, }, + "register gov proposal executor": { + src: contract.Hooks{RegisterGovProposalExecutor: &struct{}{}}, + setup: captureWithMock(), + expDetails: &types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "gov_proposal_executor"}}, + }, + expRegistrations: []registration{{cb: types.CallbackTypeGovProposalExecutor, addr: myContractAddr}}, + }, + "unregister gov proposal executor": { + src: contract.Hooks{UnregisterGovProposalExecutor: &struct{}{}}, + setup: captureWithMock(func(info *wasmtypes.ContractInfo) { + ext := &types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "gov_proposal_executor"}}, + } + info.SetExtension(ext) + }), + expDetails: &types.TgradeContractDetails{RegisteredCallbacks: []*types.RegisteredCallback{}}, + expUnRegistrations: []unregistration{{cb: types.CallbackTypeGovProposalExecutor, pos: 1, addr: myContractAddr}}, + }, + "register hook fails": { + src: contract.Hooks{RegisterValidatorSetUpdate: &struct{}{}}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + r := wasmtypes.ContractInfoFixture() + return &r + } + m.appendToPrivilegedContractCallbacksFn = func(ctx sdk.Context, callbackType types.PrivilegedCallbackType, contractAddress sdk.AccAddress) (uint8, error) { + return 0, wasmtypes.ErrDuplicate + } + }, + expErr: wasmtypes.ErrDuplicate, + }, "register begin block with existing registration": { src: contract.Hooks{RegisterBeginBlock: &struct{}{}}, setup: captureWithMock(func(info *wasmtypes.ContractInfo) { @@ -241,8 +277,7 @@ func TestTgradeHandlesHooks(t *testing.T) { capturedDetails, capturedRegistrations, capturedUnRegistrations = nil, nil, nil mock := handlerTgradeKeeperMock{} spec.setup(&mock) - govRouter := govtypes.NewRouter() - h := NewTgradeHandler(mock, govRouter) + h := NewTgradeHandler(mock, nil) var ctx sdk.Context gotErr := h.handleHooks(ctx, myContractAddr, &spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) @@ -256,15 +291,108 @@ func TestTgradeHandlesHooks(t *testing.T) { } } +func TestHandleGovProposalExecution(t *testing.T) { + myContractAddr := RandomAddress(t) + specs := map[string]struct { + src contract.ExecuteGovProposal + setup func(m *handlerTgradeKeeperMock) + expErr *sdkerrors.Error + expCapturedGovContent []govtypes.Content + }{ + "all good": { + src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ + Text: &govtypes.TextProposal{}, + }}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + c := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { + info.SetExtension(&types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "gov_proposal_executor"}}, + }) + }) + return &c + } + }, + expCapturedGovContent: []govtypes.Content{&govtypes.TextProposal{Title: "foo", Description: "bar"}}, + }, + "unauthorized contract": { + src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ + Text: &govtypes.TextProposal{}, + }}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + c := wasmtypes.ContractInfoFixture() + return &c + } + }, + expErr: sdkerrors.ErrUnauthorized, + }, + "invalid content": { + src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ + RegisterUpgrade: &upgradetypes.Plan{}, + }}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + c := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { + info.SetExtension(&types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "gov_proposal_executor"}}, + }) + }) + return &c + } + }, + expErr: sdkerrors.ErrInvalidRequest, + }, + "no content": { + src: contract.ExecuteGovProposal{Title: "foo", Description: "bar"}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + c := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { + info.SetExtension(&types.TgradeContractDetails{ + RegisteredCallbacks: []*types.RegisteredCallback{{Position: 1, CallbackType: "gov_proposal_executor"}}, + }) + }) + return &c + } + }, + expErr: wasmtypes.ErrUnknownMsg, + }, + "unknown origin contract": { + src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ + Text: &govtypes.TextProposal{}, + }}, + setup: func(m *handlerTgradeKeeperMock) { + m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { + return nil + } + }, + expErr: wasmtypes.ErrNotFound, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + mock := handlerTgradeKeeperMock{} + spec.setup(&mock) + router := &CapturingGovRouter{} + h := NewTgradeHandler(mock, router) + var ctx sdk.Context + gotErr := h.handleGovProposalExecution(ctx, myContractAddr, &spec.src) + require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) + assert.Equal(t, spec.expCapturedGovContent, router.captured) + }) + } + +} + // noopRegisterHook does nothing and but all methods for registration -func noopRegisterHook(m *handlerTgradeKeeperMock) { +func noopRegisterHook(m *handlerTgradeKeeperMock, mutators ...func(*wasmtypes.ContractInfo)) { m.IsPrivilegedFn = func(ctx sdk.Context, contract sdk.AccAddress) bool { return true } m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { - v := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { + v := wasmtypes.ContractInfoFixture(append([]func(*wasmtypes.ContractInfo){func(info *wasmtypes.ContractInfo) { info.SetExtension(&types.TgradeContractDetails{}) - }) + }}, mutators...)...) return &v } m.appendToPrivilegedContractCallbacksFn = func(ctx sdk.Context, callbackType types.PrivilegedCallbackType, contractAddress sdk.AccAddress) (uint8, error) { diff --git a/x/twasm/keeper/proposal_handler_test.go b/x/twasm/keeper/proposal_handler_test.go index 8301628a..b5c92240 100644 --- a/x/twasm/keeper/proposal_handler_test.go +++ b/x/twasm/keeper/proposal_handler_test.go @@ -1,6 +1,7 @@ package keeper import ( + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/confio/tgrade/x/twasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -21,11 +22,12 @@ func TestGovHandler(t *testing.T) { } specs := map[string]struct { - wasmHandler govtypes.Handler - setupGovKeeper func(*MockGovKeeper) - srcProposal govtypes.Content - expErr *sdkerrors.Error - expCapturedAddrs []sdk.AccAddress + wasmHandler govtypes.Handler + setupGovKeeper func(*MockGovKeeper) + srcProposal govtypes.Content + expErr *sdkerrors.Error + expCapturedAddrs []sdk.AccAddress + expCapturedGovContent []govtypes.Content }{ "handled in wasm": { wasmHandler: func(ctx sdk.Context, content govtypes.Content) error { @@ -79,6 +81,18 @@ func TestGovHandler(t *testing.T) { srcProposal: &types.DemotePrivilegedContractProposal{}, expErr: govtypes.ErrInvalidProposalContent, }, + "stargate proposal": { + srcProposal: types.StargateContentProposalFixture(), + wasmHandler: notHandler, + expCapturedGovContent: []govtypes.Content{types.StargateContentProposalFixture()}, + }, + "stargate empty content rejected": { + srcProposal: types.StargateContentProposalFixture(func(p *types.StargateContentProposal) { + p.Content.ClearCachedValue() + }), + wasmHandler: notHandler, + expErr: wasmtypes.ErrInvalid, + }, "nil content": { wasmHandler: notHandler, expErr: sdkerrors.ErrUnknownRequest, @@ -93,13 +107,14 @@ func TestGovHandler(t *testing.T) { spec.setupGovKeeper(&mock) } // when - h := NewProposalHandlerX(&mock, spec.wasmHandler, govtypes.NewRouter()) + router := &CapturingGovRouter{} + h := NewProposalHandlerX(&mock, spec.wasmHandler, router) gotErr := h(ctx, spec.srcProposal) // then require.True(t, spec.expErr.Is(gotErr), "exp %v but got #+v", spec.expErr, gotErr) assert.Equal(t, spec.expCapturedAddrs, capturedContractAddrs) + assert.Equal(t, spec.expCapturedGovContent, router.captured) }) - } } @@ -121,3 +136,19 @@ func (m MockGovKeeper) UnsetPrivileged(ctx sdk.Context, contractAddr sdk.AccAddr } return m.UnsetPrivilegedFn(ctx, contractAddr) } + +type CapturingGovRouter struct { + govtypes.Router + captured []govtypes.Content +} + +func (m CapturingGovRouter) HasRoute(r string) bool { + return true +} + +func (m *CapturingGovRouter) GetRoute(path string) (h govtypes.Handler) { + return func(ctx sdk.Context, content govtypes.Content) error { + m.captured = append(m.captured, content) + return nil + } +} diff --git a/x/twasm/types/proposal.go b/x/twasm/types/proposal.go index d04a935d..037f835a 100644 --- a/x/twasm/types/proposal.go +++ b/x/twasm/types/proposal.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/gogo/protobuf/proto" "strings" ) @@ -138,6 +139,25 @@ func validateProposalCommons(title, description string) error { var _ codectypes.UnpackInterfacesMessage = &StargateContentProposal{} +// NewStargateContentProposal constructor +func NewStargateContentProposal(title, description string, content govtypes.Content) (*StargateContentProposal, error) { + msg, ok := content.(proto.Message) + if !ok { + return nil, sdkerrors.Wrapf(wasmtypes.ErrInvalid, "%T does not implement proto.Message", content) + } + + any, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrPackAny, err.Error()) + } + + return &StargateContentProposal{ + Title: title, + Description: description, + Content: any, + }, nil +} + // UnpackInterfaces implements codectypes.UnpackInterfaces func (m *StargateContentProposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { var content govtypes.Content @@ -166,6 +186,9 @@ func (p StargateContentProposal) ValidateBasic() error { if err := validateProposalCommons(p.Title, p.Description); err != nil { return err } + if p.Content == nil { + return sdkerrors.Wrap(wasmtypes.ErrEmpty, "content") + } content, ok := p.Content.GetCachedValue().(govtypes.Content) if !ok || content == nil { return sdkerrors.Wrap(wasmtypes.ErrInvalid, "not gov content type") diff --git a/x/twasm/types/proposal_test.go b/x/twasm/types/proposal_test.go index 04354623..fd335740 100644 --- a/x/twasm/types/proposal_test.go +++ b/x/twasm/types/proposal_test.go @@ -1,6 +1,7 @@ package types import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -48,6 +49,7 @@ func TestValidatePromoteToPrivilegedContractProposal(t *testing.T) { }) } } + func TestValidateDemotePrivilegedContractProposal(t *testing.T) { specs := map[string]struct { src *DemotePrivilegedContractProposal @@ -87,6 +89,56 @@ func TestValidateDemotePrivilegedContractProposal(t *testing.T) { }) } } + +func TestValidateStargateContentProposal(t *testing.T) { + specs := map[string]struct { + src *StargateContentProposal + expErr bool + }{ + "all good": { + src: StargateContentProposalFixture(), + }, + "without title": { + src: StargateContentProposalFixture(func(p *StargateContentProposal) { + p.Title = "" + }), + expErr: true, + }, + "with empty content": { + src: StargateContentProposalFixture(func(p *StargateContentProposal) { + p.Content = nil + }), + expErr: true, + }, + "unsupported type": { + src: StargateContentProposalFixture(func(p *StargateContentProposal) { + var err error + p.Content, err = codectypes.NewAnyWithValue(&TgradeContractDetails{}) + require.NoError(t, err) + }), + expErr: true, + }, + "invalid type": { + src: StargateContentProposalFixture(func(p *StargateContentProposal) { + var err error + p.Content, err = codectypes.NewAnyWithValue(&govtypes.TextProposal{}) + require.NoError(t, err) + }), + expErr: true, + }, + } + for msg, spec := range specs { + t.Run(msg, func(t *testing.T) { + err := spec.src.ValidateBasic() + if spec.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestProposalYaml(t *testing.T) { specs := map[string]struct { src govtypes.Content diff --git a/x/twasm/types/test_fixtures.go b/x/twasm/types/test_fixtures.go index 10e9bae9..6bd81f90 100644 --- a/x/twasm/types/test_fixtures.go +++ b/x/twasm/types/test_fixtures.go @@ -3,6 +3,7 @@ package types import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/tendermint/tendermint/libs/rand" "testing" ) @@ -33,6 +34,23 @@ func DemoteProposalFixture(mutators ...func(proposal *DemotePrivilegedContractPr return p } +func StargateContentProposalFixture(mutators ...func(proposal *StargateContentProposal)) *StargateContentProposal { + anyProto, err := NewStargateContentProposal("nested", "proto", &govtypes.TextProposal{Title: "another nested", Description: "3rd level"}) + if err != nil { + panic(err) + } + + // new stargate with a protobuf type that implements govtypes.Content and has another Any + p, err := NewStargateContentProposal("foo", "bar", anyProto) + if err != nil { + panic(err) + } + for _, m := range mutators { + m(p) + } + return p +} + func GenesisStateFixture(t *testing.T, mutators ...func(*GenesisState)) GenesisState { t.Helper() anyContractAddr := RandomBech32Address(t) From 8205a2e23fc3aae4e5f27396698c858d9c8ec48c Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 11 May 2021 16:35:34 +0200 Subject: [PATCH 5/9] Without stargate --- docs/proto/proto-docs.md | 18 -- proto/confio/twasm/v1beta1/proposal.proto | 11 - x/twasm/contract/incoming_msgs.go | 11 - x/twasm/contract/incoming_msgs_test.go | 57 ++++ x/twasm/keeper/proposal_handler.go | 14 +- x/twasm/keeper/proposal_handler_test.go | 15 +- x/twasm/types/codec.go | 2 - x/twasm/types/proposal.go | 85 +----- x/twasm/types/proposal.pb.go | 344 ++-------------------- x/twasm/types/proposal_test.go | 50 ---- x/twasm/types/test_fixtures.go | 18 -- 11 files changed, 85 insertions(+), 540 deletions(-) create mode 100644 x/twasm/contract/incoming_msgs_test.go diff --git a/docs/proto/proto-docs.md b/docs/proto/proto-docs.md index a2af5fce..5bfa483f 100644 --- a/docs/proto/proto-docs.md +++ b/docs/proto/proto-docs.md @@ -14,7 +14,6 @@ - [confio/twasm/v1beta1/proposal.proto](#confio/twasm/v1beta1/proposal.proto) - [DemotePrivilegedContractProposal](#confio.twasm.v1beta1.DemotePrivilegedContractProposal) - [PromoteToPrivilegedContractProposal](#confio.twasm.v1beta1.PromoteToPrivilegedContractProposal) - - [StargateContentProposal](#confio.twasm.v1beta1.StargateContentProposal) - [confio/twasm/v1beta1/query.proto](#confio/twasm/v1beta1/query.proto) - [QueryContractsByCallbackTypeRequest](#confio.twasm.v1beta1.QueryContractsByCallbackTypeRequest) @@ -149,23 +148,6 @@ PromoteToPrivilegedContractProposal gov proposal content type to add - - - -### StargateContentProposal -StargateContentProposal is a wildcard proposal type. - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| `title` | [string](#string) | | Title is a short summary | -| `description` | [string](#string) | | Description is a human readable text | -| `content` | [google.protobuf.Any](#google.protobuf.Any) | | Content implements any `govtypes.Content` type | - - - - - diff --git a/proto/confio/twasm/v1beta1/proposal.proto b/proto/confio/twasm/v1beta1/proposal.proto index 966e6422..cf93044f 100644 --- a/proto/confio/twasm/v1beta1/proposal.proto +++ b/proto/confio/twasm/v1beta1/proposal.proto @@ -33,14 +33,3 @@ message DemotePrivilegedContractProposal { // Contract is the address of the smart contract string contract = 3 [ (gogoproto.moretags) = "yaml:\"contract\"" ]; } - -// StargateContentProposal is a wildcard proposal type. -message StargateContentProposal { - // Title is a short summary - string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; - // Description is a human readable text - string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; - // Content implements any `govtypes.Content` type - google.protobuf.Any content = 3 - [ (cosmos_proto.accepts_interface) = "Content" ]; -} diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index fc846f17..a2ab0182 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -3,7 +3,6 @@ package contract import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/confio/tgrade/x/twasm/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" @@ -57,12 +56,6 @@ func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { p.Proposal.CancelUpgrade.Title = p.Title p.Proposal.CancelUpgrade.Description = p.Description return p.Proposal.CancelUpgrade - case p.Proposal.RawProtoProposal != nil: - return &types.StargateContentProposal{ - Title: p.Title, - Description: p.Description, - Content: p.Proposal.RawProtoProposal, - } case p.Proposal.IbcClientUpdate != nil: p.Proposal.IbcClientUpdate.Title = p.Title p.Proposal.IbcClientUpdate.Description = p.Description @@ -120,10 +113,6 @@ type GovProposal struct { // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/params/v1beta1/params.proto#L9-L27 ChangeParams *[]proposaltypes.ParamChange `json:"change_params"` - // Allows raw bytes (if client and wasmd are aware of something the contract is not) - // Like CosmosMsg::Stargate but for the governance router, not normal router - RawProtoProposal *codectypes.Any `json:"raw_proto_proposal"` - // Updates the matching client to set a new trusted header. // This can be used by governance to restore a client that has timed out or forked or otherwise broken. // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/ibc/core/client/v1/client.proto#L36-L49 diff --git a/x/twasm/contract/incoming_msgs_test.go b/x/twasm/contract/incoming_msgs_test.go new file mode 100644 index 00000000..a69b4d46 --- /dev/null +++ b/x/twasm/contract/incoming_msgs_test.go @@ -0,0 +1,57 @@ +package contract + +import ( + "bytes" + "encoding/json" + "fmt" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "github.com/gogo/protobuf/jsonpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestGetProposalContent(t *testing.T) { + cs, err := clienttypes.PackClientState(&ibctmtypes.ClientState{}) + require.NoError(t, err) + + require.NoError(t, err) + anyType.ClearCachedValue() + jm := &jsonpb.Marshaler{OrigName: false, EmitDefaults: true, AnyResolver: nil} + var buf bytes.Buffer + require.NoError(t, jm.Marshal(&buf, anyType)) + require.NoError(t, err) + xxx := fmt.Sprintf(`{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"register_upgrade":{"height":1, "info":"any information", "upgraded_client_state": %s}}}}`, buf.String()) + t.Log("___ ", xxx) + specs := map[string]struct { + src string + exp govtypes.Content + }{ + "text": { + src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"text":{}}}}`, + exp: &govtypes.TextProposal{Title: "foo", Description: "bar"}, + }, + "register upgrade": { + src: xxx, + exp: &upgradetypes.SoftwareUpgradeProposal{Title: "foo", Description: "bar", Plan: upgradetypes.Plan{ + Name: "", + Time: time.Time{}, + Height: 1, + Info: "any information", + UpgradedClientState: anyType, + }}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + var msg TgradeMsg + require.NoError(t, json.Unmarshal([]byte(spec.src), &msg)) + gov := msg.ExecuteGovProposal + require.NotNil(t, gov) + assert.Equal(t, spec.exp, gov.GetProposalContent()) + }) + } + +} diff --git a/x/twasm/keeper/proposal_handler.go b/x/twasm/keeper/proposal_handler.go index 5cd70232..0caefa03 100644 --- a/x/twasm/keeper/proposal_handler.go +++ b/x/twasm/keeper/proposal_handler.go @@ -18,11 +18,11 @@ type govKeeper interface { // NewProposalHandler creates a new governance Handler for wasm proposals func NewProposalHandler(k Keeper) govtypes.Handler { wasmProposalHandler := wasmkeeper.NewWasmProposalHandler(k, wasmtypes.EnableAllProposals) - return NewProposalHandlerX(k, wasmProposalHandler, k.govRouter) + return NewProposalHandlerX(k, wasmProposalHandler) } // NewProposalHandlerX creates a new governance Handler for wasm proposals -func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler, router govtypes.Router) govtypes.Handler { +func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) error { err := wasmProposalHandler(ctx, content) switch { @@ -39,16 +39,6 @@ func NewProposalHandlerX(k govKeeper, wasmProposalHandler govtypes.Handler, rout return handlePromoteContractProposal(ctx, k, *c) case *types.DemotePrivilegedContractProposal: return handleDemoteContractProposal(ctx, k, *c) - case *types.StargateContentProposal: - nestedContent, ok := c.Content.GetCachedValue().(govtypes.Content) - if !ok || nestedContent == nil { - return sdkerrors.Wrap(wasmtypes.ErrInvalid, "not gov content type") - } - if !router.HasRoute(nestedContent.ProposalRoute()) { - return sdkerrors.Wrap(govtypes.ErrNoProposalHandlerExists, nestedContent.ProposalRoute()) - } - govHandler := router.GetRoute(nestedContent.ProposalRoute()) - return govHandler(ctx, content) default: return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized twasm srcProposal content type: %T", c) } diff --git a/x/twasm/keeper/proposal_handler_test.go b/x/twasm/keeper/proposal_handler_test.go index b5c92240..ca90c2ae 100644 --- a/x/twasm/keeper/proposal_handler_test.go +++ b/x/twasm/keeper/proposal_handler_test.go @@ -1,7 +1,6 @@ package keeper import ( - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/confio/tgrade/x/twasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -81,18 +80,6 @@ func TestGovHandler(t *testing.T) { srcProposal: &types.DemotePrivilegedContractProposal{}, expErr: govtypes.ErrInvalidProposalContent, }, - "stargate proposal": { - srcProposal: types.StargateContentProposalFixture(), - wasmHandler: notHandler, - expCapturedGovContent: []govtypes.Content{types.StargateContentProposalFixture()}, - }, - "stargate empty content rejected": { - srcProposal: types.StargateContentProposalFixture(func(p *types.StargateContentProposal) { - p.Content.ClearCachedValue() - }), - wasmHandler: notHandler, - expErr: wasmtypes.ErrInvalid, - }, "nil content": { wasmHandler: notHandler, expErr: sdkerrors.ErrUnknownRequest, @@ -108,7 +95,7 @@ func TestGovHandler(t *testing.T) { } // when router := &CapturingGovRouter{} - h := NewProposalHandlerX(&mock, spec.wasmHandler, router) + h := NewProposalHandlerX(&mock, spec.wasmHandler) gotErr := h(ctx, spec.srcProposal) // then require.True(t, spec.expErr.Is(gotErr), "exp %v but got #+v", spec.expErr, gotErr) diff --git a/x/twasm/types/codec.go b/x/twasm/types/codec.go index f694b3bf..d63c38b3 100644 --- a/x/twasm/types/codec.go +++ b/x/twasm/types/codec.go @@ -13,7 +13,6 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { wasmtypes.RegisterLegacyAminoCodec(cdc) cdc.RegisterConcrete(&PromoteToPrivilegedContractProposal{}, "twasm/PromoteToPrivilegedContractProposal", nil) cdc.RegisterConcrete(&DemotePrivilegedContractProposal{}, "twasm/DemotePrivilegedContractProposal", nil) - cdc.RegisterConcrete(&StargateContentProposal{}, "twasm/StargateContentProposal", nil) } @@ -23,7 +22,6 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { (*govtypes.Content)(nil), &PromoteToPrivilegedContractProposal{}, &DemotePrivilegedContractProposal{}, - &StargateContentProposal{}, ) registry.RegisterImplementations( (*wasmtypes.ContractInfoExtension)(nil), diff --git a/x/twasm/types/proposal.go b/x/twasm/types/proposal.go index 037f835a..4e48610e 100644 --- a/x/twasm/types/proposal.go +++ b/x/twasm/types/proposal.go @@ -2,12 +2,9 @@ package types import ( "fmt" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/gogo/protobuf/proto" "strings" ) @@ -16,24 +13,20 @@ type ProposalType string const ( ProposalTypePromoteContract ProposalType = "PromoteToPrivilegedContract" ProposalTypeDemoteContract ProposalType = "DemotePrivilegedContract" - ProposalTypeStargate ProposalType = "StargateContent" ) // EnableAllProposals contains all twasm gov types as keys. var EnableAllProposals = []ProposalType{ ProposalTypePromoteContract, ProposalTypeDemoteContract, - ProposalTypeStargate, } func init() { // register new content types with the sdk govtypes.RegisterProposalType(string(ProposalTypePromoteContract)) govtypes.RegisterProposalType(string(ProposalTypeDemoteContract)) - govtypes.RegisterProposalType(string(ProposalTypeStargate)) govtypes.RegisterProposalTypeCodec(&PromoteToPrivilegedContractProposal{}, "twasm/PromoteToPrivilegedContractProposal") govtypes.RegisterProposalTypeCodec(&DemotePrivilegedContractProposal{}, "twasm/DemotePrivilegedContractProposal") - govtypes.RegisterProposalTypeCodec(&StargateContentProposal{}, "twasm/StargateContentProposal") } // ProposalRoute returns the routing key of a parameter change proposal. @@ -135,80 +128,4 @@ func validateProposalCommons(title, description string) error { return sdkerrors.Wrapf(govtypes.ErrInvalidProposalContent, "proposal description is longer than max length of %d", govtypes.MaxDescriptionLength) } return nil -} - -var _ codectypes.UnpackInterfacesMessage = &StargateContentProposal{} - -// NewStargateContentProposal constructor -func NewStargateContentProposal(title, description string, content govtypes.Content) (*StargateContentProposal, error) { - msg, ok := content.(proto.Message) - if !ok { - return nil, sdkerrors.Wrapf(wasmtypes.ErrInvalid, "%T does not implement proto.Message", content) - } - - any, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrPackAny, err.Error()) - } - - return &StargateContentProposal{ - Title: title, - Description: description, - Content: any, - }, nil -} - -// UnpackInterfaces implements codectypes.UnpackInterfaces -func (m *StargateContentProposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { - var content govtypes.Content - if err := unpacker.UnpackAny(m.Content, &content); err != nil { - return err - } - return codectypes.UnpackInterfaces(content, unpacker) -} - -// ProposalRoute returns the routing key of a parameter change proposal. -func (p StargateContentProposal) ProposalRoute() string { return RouterKey } - -// GetTitle returns the title of the proposal -func (p *StargateContentProposal) GetTitle() string { return p.Title } - -// GetDescription returns the human readable description of the proposal -func (p StargateContentProposal) GetDescription() string { return p.Description } - -// ProposalType returns the type -func (p StargateContentProposal) ProposalType() string { - return string(ProposalTypeStargate) -} - -// ValidateBasic validates the proposal -func (p StargateContentProposal) ValidateBasic() error { - if err := validateProposalCommons(p.Title, p.Description); err != nil { - return err - } - if p.Content == nil { - return sdkerrors.Wrap(wasmtypes.ErrEmpty, "content") - } - content, ok := p.Content.GetCachedValue().(govtypes.Content) - if !ok || content == nil { - return sdkerrors.Wrap(wasmtypes.ErrInvalid, "not gov content type") - } - if err := content.ValidateBasic(); err != nil { - return sdkerrors.Wrap(err, "content") - } - return nil -} - -// String implements the Stringer interface. -func (p StargateContentProposal) String() string { - return fmt.Sprintf(`Store Code Proposal: - Title: %s - Description: %s - Content: %s -`, p.Title, p.Description, p.Content) -} - -// MarshalYAML pretty prints the wasm byte code -func (p StargateContentProposal) MarshalYAML() (interface{}, error) { - return p, nil -} +} \ No newline at end of file diff --git a/x/twasm/types/proposal.pb.go b/x/twasm/types/proposal.pb.go index 8d743bc6..e2cf742d 100644 --- a/x/twasm/types/proposal.pb.go +++ b/x/twasm/types/proposal.pb.go @@ -6,7 +6,7 @@ package types import ( fmt "fmt" _ "github.com/CosmWasm/wasmd/x/wasm/types" - types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/cosmos-sdk/codec/types" _ "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" @@ -113,52 +113,9 @@ func (m *DemotePrivilegedContractProposal) XXX_DiscardUnknown() { var xxx_messageInfo_DemotePrivilegedContractProposal proto.InternalMessageInfo -// StargateContentProposal is a wildcard proposal type. -type StargateContentProposal struct { - // Title is a short summary - Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty" yaml:"title"` - // Description is a human readable text - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` - // Content implements any `govtypes.Content` type - Content *types.Any `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` -} - -func (m *StargateContentProposal) Reset() { *m = StargateContentProposal{} } -func (*StargateContentProposal) ProtoMessage() {} -func (*StargateContentProposal) Descriptor() ([]byte, []int) { - return fileDescriptor_77ea8b6359ab7726, []int{2} -} -func (m *StargateContentProposal) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *StargateContentProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_StargateContentProposal.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *StargateContentProposal) XXX_Merge(src proto.Message) { - xxx_messageInfo_StargateContentProposal.Merge(m, src) -} -func (m *StargateContentProposal) XXX_Size() int { - return m.Size() -} -func (m *StargateContentProposal) XXX_DiscardUnknown() { - xxx_messageInfo_StargateContentProposal.DiscardUnknown(m) -} - -var xxx_messageInfo_StargateContentProposal proto.InternalMessageInfo - func init() { proto.RegisterType((*PromoteToPrivilegedContractProposal)(nil), "confio.twasm.v1beta1.PromoteToPrivilegedContractProposal") proto.RegisterType((*DemotePrivilegedContractProposal)(nil), "confio.twasm.v1beta1.DemotePrivilegedContractProposal") - proto.RegisterType((*StargateContentProposal)(nil), "confio.twasm.v1beta1.StargateContentProposal") } func init() { @@ -166,32 +123,29 @@ func init() { } var fileDescriptor_77ea8b6359ab7726 = []byte{ - // 397 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x93, 0x31, 0xee, 0xd3, 0x30, - 0x14, 0xc6, 0x63, 0x10, 0xfc, 0xc1, 0x45, 0x02, 0x85, 0x0a, 0x4a, 0x07, 0xb7, 0xa4, 0x52, 0xc5, - 0x42, 0xac, 0xc2, 0x82, 0x60, 0xa2, 0x30, 0x32, 0x54, 0x81, 0x89, 0x05, 0x39, 0xae, 0x6b, 0x2c, - 0x25, 0x79, 0x91, 0xe3, 0x16, 0x72, 0x0b, 0x8e, 0xc1, 0x01, 0x40, 0xea, 0x11, 0x2a, 0xa6, 0x8e, - 0x9d, 0x2a, 0x9a, 0xde, 0xa0, 0x27, 0x40, 0xb1, 0x93, 0xaa, 0x5c, 0x00, 0x89, 0x2d, 0x2f, 0xdf, - 0xef, 0xbd, 0xef, 0xb3, 0xf4, 0x1e, 0x1e, 0x71, 0xc8, 0x16, 0x0a, 0xa8, 0xf9, 0xc2, 0x8a, 0x94, - 0xae, 0x26, 0xb1, 0x30, 0x6c, 0x42, 0x73, 0x0d, 0x39, 0x14, 0x2c, 0x09, 0x73, 0x0d, 0x06, 0xfc, - 0xae, 0x83, 0x42, 0x0b, 0x85, 0x0d, 0xd4, 0xef, 0x4a, 0x90, 0x60, 0x01, 0x5a, 0x7f, 0x39, 0xb6, - 0x4f, 0x38, 0x14, 0x29, 0x14, 0x34, 0x66, 0x85, 0x38, 0xcf, 0xe3, 0xa0, 0xb2, 0x46, 0x7f, 0x5c, - 0xeb, 0xd6, 0xec, 0x2f, 0x47, 0x53, 0xe6, 0xa2, 0x68, 0x90, 0x47, 0x6e, 0xc4, 0x27, 0x37, 0xdb, - 0x15, 0xad, 0x24, 0x01, 0x64, 0x22, 0xa8, 0xad, 0xe2, 0xe5, 0x82, 0xb2, 0xac, 0x74, 0x52, 0xb0, - 0x46, 0x78, 0x34, 0xd3, 0x90, 0x82, 0x11, 0x1f, 0x60, 0xa6, 0xd5, 0x4a, 0x25, 0x42, 0x8a, 0xf9, - 0x1b, 0xc8, 0x8c, 0x66, 0xdc, 0xcc, 0x9a, 0x27, 0xf9, 0x63, 0x7c, 0xc3, 0x28, 0x93, 0x88, 0x1e, - 0x1a, 0xa2, 0x27, 0xb7, 0xa7, 0xf7, 0x4e, 0xfb, 0xc1, 0x9d, 0x92, 0xa5, 0xc9, 0xcb, 0xc0, 0xfe, - 0x0e, 0x22, 0x27, 0xfb, 0x2f, 0x70, 0x67, 0x2e, 0x0a, 0xae, 0x55, 0x6e, 0x14, 0x64, 0xbd, 0x6b, - 0x96, 0x7e, 0x70, 0xda, 0x0f, 0x7c, 0x47, 0x5f, 0x88, 0x41, 0x74, 0x89, 0xfa, 0x14, 0xdf, 0xe2, - 0x8d, 0x6b, 0xef, 0xba, 0x6d, 0xbb, 0x7f, 0xda, 0x0f, 0xee, 0xba, 0xb6, 0x56, 0x09, 0xa2, 0x33, - 0x14, 0xfc, 0x44, 0x78, 0xf8, 0x56, 0xd4, 0xc9, 0xff, 0xaf, 0xdc, 0x6b, 0x84, 0x1f, 0xbe, 0x37, - 0x4c, 0x4b, 0x66, 0x44, 0x9d, 0x57, 0x64, 0xff, 0x32, 0xee, 0x2b, 0x7c, 0xc5, 0x9d, 0xa9, 0x4d, - 0xdb, 0x79, 0xd6, 0x0d, 0xdd, 0x76, 0x84, 0xed, 0x76, 0x84, 0xaf, 0xb3, 0x72, 0xda, 0xf9, 0xf5, - 0xe3, 0xe9, 0x55, 0x93, 0x2e, 0x6a, 0x3b, 0xa6, 0xef, 0x36, 0x07, 0xe2, 0xed, 0x0e, 0xc4, 0xfb, - 0x5e, 0x11, 0xb4, 0xa9, 0x08, 0xda, 0x56, 0x04, 0xfd, 0xae, 0x08, 0xfa, 0x76, 0x24, 0xde, 0xf6, - 0x48, 0xbc, 0xdd, 0x91, 0x78, 0x1f, 0xc7, 0x52, 0x99, 0xcf, 0xcb, 0x38, 0xe4, 0x90, 0xd2, 0xf6, - 0x50, 0xa4, 0x66, 0x73, 0x41, 0xbf, 0x36, 0x17, 0x63, 0xf7, 0x36, 0xbe, 0x69, 0x1d, 0x9f, 0xff, - 0x09, 0x00, 0x00, 0xff, 0xff, 0x3b, 0xc9, 0x58, 0xb7, 0x4e, 0x03, 0x00, 0x00, + // 347 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x92, 0x4f, 0x4e, 0xf2, 0x40, + 0x14, 0xc0, 0x3b, 0xdf, 0x17, 0x8d, 0x56, 0x13, 0x4d, 0x25, 0x06, 0x59, 0x0c, 0x58, 0x12, 0xe2, + 0xaa, 0x13, 0xe2, 0xc6, 0xb8, 0x44, 0x97, 0x2e, 0x08, 0x71, 0xe5, 0xc6, 0x4c, 0x87, 0x61, 0x9c, + 0xa4, 0xed, 0x6b, 0x66, 0x06, 0x94, 0x5b, 0x78, 0x0c, 0x2f, 0x60, 0xe2, 0x11, 0x58, 0xb2, 0x64, + 0x45, 0xa4, 0xdc, 0x80, 0x13, 0x18, 0x66, 0x0a, 0xc1, 0x23, 0xb8, 0xeb, 0xeb, 0xef, 0xf7, 0xfe, + 0x4c, 0xde, 0xf3, 0x9b, 0x0c, 0xb2, 0x81, 0x04, 0x62, 0x5e, 0xa9, 0x4e, 0xc9, 0xa8, 0x1d, 0x73, + 0x43, 0xdb, 0x24, 0x57, 0x90, 0x83, 0xa6, 0x49, 0x94, 0x2b, 0x30, 0x10, 0x54, 0x9c, 0x14, 0x59, + 0x29, 0x2a, 0xa5, 0x5a, 0x45, 0x80, 0x00, 0x2b, 0x90, 0xf5, 0x97, 0x73, 0x6b, 0x98, 0x81, 0x4e, + 0x41, 0x93, 0x98, 0x6a, 0xbe, 0xad, 0xc7, 0x40, 0x66, 0x25, 0xbf, 0x5c, 0x73, 0xdb, 0xec, 0x57, + 0x47, 0x33, 0xce, 0xb9, 0x2e, 0x95, 0x0b, 0x57, 0xe2, 0xd9, 0xd5, 0x76, 0xc1, 0x06, 0x09, 0x00, + 0x91, 0x70, 0x62, 0xa3, 0x78, 0x38, 0x20, 0x34, 0x1b, 0x3b, 0x14, 0x7e, 0x21, 0xbf, 0xd9, 0x55, + 0x90, 0x82, 0xe1, 0x8f, 0xd0, 0x55, 0x72, 0x24, 0x13, 0x2e, 0x78, 0xff, 0x0e, 0x32, 0xa3, 0x28, + 0x33, 0xdd, 0xf2, 0x49, 0x41, 0xcb, 0xdf, 0x33, 0xd2, 0x24, 0xbc, 0x8a, 0x1a, 0xe8, 0xea, 0xb0, + 0x73, 0xba, 0x9a, 0xd7, 0x8f, 0xc7, 0x34, 0x4d, 0x6e, 0x43, 0xfb, 0x3b, 0xec, 0x39, 0x1c, 0xdc, + 0xf8, 0x47, 0x7d, 0xae, 0x99, 0x92, 0xb9, 0x91, 0x90, 0x55, 0xff, 0x59, 0xfb, 0x7c, 0x35, 0xaf, + 0x07, 0xce, 0xde, 0x81, 0x61, 0x6f, 0x57, 0x0d, 0x88, 0x7f, 0xc0, 0xca, 0xae, 0xd5, 0xff, 0x36, + 0xed, 0x6c, 0x35, 0xaf, 0x9f, 0xb8, 0xb4, 0x0d, 0x09, 0x7b, 0x5b, 0x29, 0xfc, 0x44, 0x7e, 0xe3, + 0x9e, 0xaf, 0x27, 0xff, 0x53, 0x73, 0x77, 0x1e, 0x26, 0x0b, 0xec, 0xcd, 0x16, 0xd8, 0xfb, 0x28, + 0x30, 0x9a, 0x14, 0x18, 0x4d, 0x0b, 0x8c, 0xbe, 0x0b, 0x8c, 0xde, 0x97, 0xd8, 0x9b, 0x2e, 0xb1, + 0x37, 0x5b, 0x62, 0xef, 0xa9, 0x25, 0xa4, 0x79, 0x19, 0xc6, 0x11, 0x83, 0x94, 0x6c, 0xae, 0x4d, + 0x28, 0xda, 0xe7, 0xe4, 0xad, 0x3c, 0x3b, 0xbb, 0xfc, 0x78, 0xdf, 0xee, 0xf1, 0xfa, 0x27, 0x00, + 0x00, 0xff, 0xff, 0x59, 0xc2, 0x3d, 0x9a, 0x93, 0x02, 0x00, 0x00, } func (this *PromoteToPrivilegedContractProposal) Equal(that interface{}) bool { @@ -254,36 +208,6 @@ func (this *DemotePrivilegedContractProposal) Equal(that interface{}) bool { } return true } -func (this *StargateContentProposal) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*StargateContentProposal) - if !ok { - that2, ok := that.(StargateContentProposal) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Title != that1.Title { - return false - } - if this.Description != that1.Description { - return false - } - if !this.Content.Equal(that1.Content) { - return false - } - return true -} func (m *PromoteToPrivilegedContractProposal) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -372,55 +296,6 @@ func (m *DemotePrivilegedContractProposal) MarshalToSizedBuffer(dAtA []byte) (in return len(dAtA) - i, nil } -func (m *StargateContentProposal) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *StargateContentProposal) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *StargateContentProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Content != nil { - { - size, err := m.Content.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintProposal(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - } - if len(m.Description) > 0 { - i -= len(m.Description) - copy(dAtA[i:], m.Description) - i = encodeVarintProposal(dAtA, i, uint64(len(m.Description))) - i-- - dAtA[i] = 0x12 - } - if len(m.Title) > 0 { - i -= len(m.Title) - copy(dAtA[i:], m.Title) - i = encodeVarintProposal(dAtA, i, uint64(len(m.Title))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func encodeVarintProposal(dAtA []byte, offset int, v uint64) int { offset -= sovProposal(v) base := offset @@ -474,27 +349,6 @@ func (m *DemotePrivilegedContractProposal) Size() (n int) { return n } -func (m *StargateContentProposal) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Title) - if l > 0 { - n += 1 + l + sovProposal(uint64(l)) - } - l = len(m.Description) - if l > 0 { - n += 1 + l + sovProposal(uint64(l)) - } - if m.Content != nil { - l = m.Content.Size() - n += 1 + l + sovProposal(uint64(l)) - } - return n -} - func sovProposal(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -793,156 +647,6 @@ func (m *DemotePrivilegedContractProposal) Unmarshal(dAtA []byte) error { } return nil } -func (m *StargateContentProposal) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProposal - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StargateContentProposal: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StargateContentProposal: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProposal - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProposal - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProposal - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Title = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProposal - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthProposal - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthProposal - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Content", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProposal - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthProposal - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthProposal - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Content == nil { - m.Content = &types.Any{} - } - if err := m.Content.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProposal(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthProposal - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipProposal(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/twasm/types/proposal_test.go b/x/twasm/types/proposal_test.go index fd335740..b2144036 100644 --- a/x/twasm/types/proposal_test.go +++ b/x/twasm/types/proposal_test.go @@ -1,7 +1,6 @@ package types import ( - codectypes "github.com/cosmos/cosmos-sdk/codec/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -90,55 +89,6 @@ func TestValidateDemotePrivilegedContractProposal(t *testing.T) { } } -func TestValidateStargateContentProposal(t *testing.T) { - specs := map[string]struct { - src *StargateContentProposal - expErr bool - }{ - "all good": { - src: StargateContentProposalFixture(), - }, - "without title": { - src: StargateContentProposalFixture(func(p *StargateContentProposal) { - p.Title = "" - }), - expErr: true, - }, - "with empty content": { - src: StargateContentProposalFixture(func(p *StargateContentProposal) { - p.Content = nil - }), - expErr: true, - }, - "unsupported type": { - src: StargateContentProposalFixture(func(p *StargateContentProposal) { - var err error - p.Content, err = codectypes.NewAnyWithValue(&TgradeContractDetails{}) - require.NoError(t, err) - }), - expErr: true, - }, - "invalid type": { - src: StargateContentProposalFixture(func(p *StargateContentProposal) { - var err error - p.Content, err = codectypes.NewAnyWithValue(&govtypes.TextProposal{}) - require.NoError(t, err) - }), - expErr: true, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - err := spec.src.ValidateBasic() - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - func TestProposalYaml(t *testing.T) { specs := map[string]struct { src govtypes.Content diff --git a/x/twasm/types/test_fixtures.go b/x/twasm/types/test_fixtures.go index 6bd81f90..10e9bae9 100644 --- a/x/twasm/types/test_fixtures.go +++ b/x/twasm/types/test_fixtures.go @@ -3,7 +3,6 @@ package types import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/tendermint/tendermint/libs/rand" "testing" ) @@ -34,23 +33,6 @@ func DemoteProposalFixture(mutators ...func(proposal *DemotePrivilegedContractPr return p } -func StargateContentProposalFixture(mutators ...func(proposal *StargateContentProposal)) *StargateContentProposal { - anyProto, err := NewStargateContentProposal("nested", "proto", &govtypes.TextProposal{Title: "another nested", Description: "3rd level"}) - if err != nil { - panic(err) - } - - // new stargate with a protobuf type that implements govtypes.Content and has another Any - p, err := NewStargateContentProposal("foo", "bar", anyProto) - if err != nil { - panic(err) - } - for _, m := range mutators { - m(p) - } - return p -} - func GenesisStateFixture(t *testing.T, mutators ...func(*GenesisState)) GenesisState { t.Helper() anyContractAddr := RandomBech32Address(t) From 9e8ca284548560274903ab626c62edacacf2bb12 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 17 May 2021 11:25:45 +0200 Subject: [PATCH 6/9] Test gov propsal msgs --- x/twasm/contract/incoming_msgs.go | 124 ++- x/twasm/contract/incoming_msgs_test.go | 238 +++++- x/twasm/contract/schema/README.md | 6 + .../get_validator_set_updater_response.json | 23 + .../schema/list_begin_blockers_response.json | 22 + .../schema/list_end_blockers_response.json | 22 + x/twasm/contract/schema/tgrade_msg.json | 727 ++++++++++++++++++ x/twasm/contract/schema/tgrade_query.json | 74 ++ x/twasm/contract/schema/tgrade_sudo_msg.json | 168 ++++ x/twasm/contract/schema/validator_diff.json | 91 +++ .../schema/validator_vote_response.json | 44 ++ x/twasm/contract/test_fixtures.go | 27 + x/twasm/keeper/handler_plugin.go | 11 +- x/twasm/keeper/handler_plugin_test.go | 28 +- x/twasm/keeper/keeper.go | 2 +- x/twasm/keeper/privileged_test.go | 2 +- x/twasm/types/proposal.go | 2 +- 17 files changed, 1561 insertions(+), 50 deletions(-) create mode 100644 x/twasm/contract/schema/README.md create mode 100644 x/twasm/contract/schema/get_validator_set_updater_response.json create mode 100644 x/twasm/contract/schema/list_begin_blockers_response.json create mode 100644 x/twasm/contract/schema/list_end_blockers_response.json create mode 100644 x/twasm/contract/schema/tgrade_msg.json create mode 100644 x/twasm/contract/schema/tgrade_query.json create mode 100644 x/twasm/contract/schema/tgrade_sudo_msg.json create mode 100644 x/twasm/contract/schema/validator_diff.json create mode 100644 x/twasm/contract/schema/validator_vote_response.json create mode 100644 x/twasm/contract/test_fixtures.go diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index a2ab0182..b794f83b 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -1,12 +1,16 @@ package contract import ( + "encoding/json" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/confio/tgrade/x/twasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + "time" ) // TgradeMsg messages coming from a contract @@ -15,6 +19,18 @@ type TgradeMsg struct { ExecuteGovProposal *ExecuteGovProposal `json:"execute_gov_proposal"` } +// UnmarshalWithAny from json to Go objects with cosmos-sdk Any types +func (p *TgradeMsg) UnmarshalWithAny(bz []byte, unpacker codectypes.AnyUnpacker) error { + if err := json.Unmarshal(bz, p); err != nil { + return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + // unpack interfaces in protobuf Any types + if p.ExecuteGovProposal != nil { + return sdkerrors.Wrap(p.ExecuteGovProposal.unpackInterfaces(unpacker), "execute_gov_proposal") + } + return nil +} + // Hooks contains method to interact with system callbacks type Hooks struct { RegisterBeginBlock *struct{} `json:"register_begin_block"` @@ -56,10 +72,16 @@ func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { p.Proposal.CancelUpgrade.Title = p.Title p.Proposal.CancelUpgrade.Description = p.Description return p.Proposal.CancelUpgrade - case p.Proposal.IbcClientUpdate != nil: - p.Proposal.IbcClientUpdate.Title = p.Title - p.Proposal.IbcClientUpdate.Description = p.Description - return p.Proposal.IbcClientUpdate + case p.Proposal.ChangeParams != nil: + return &proposaltypes.ParameterChangeProposal{ + Title: p.Title, + Description: p.Description, + Changes: *p.Proposal.ChangeParams, + } + case p.Proposal.IBCClientUpdate != nil: + p.Proposal.IBCClientUpdate.Title = p.Title + p.Proposal.IBCClientUpdate.Description = p.Description + return p.Proposal.IBCClientUpdate case p.Proposal.PromoteToPrivilegedContract != nil: p.Proposal.PromoteToPrivilegedContract.Title = p.Title p.Proposal.PromoteToPrivilegedContract.Description = p.Description @@ -97,7 +119,98 @@ func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { } } +// unpackInterfaces unpacks the Any type into the interface type in `Any.cachedValue` +func (p *ExecuteGovProposal) unpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var err error + switch { + case p.Proposal.RegisterUpgrade != nil: + return p.Proposal.RegisterUpgrade.UnpackInterfaces(unpacker) + case p.Proposal.IBCClientUpdate != nil: + return p.Proposal.IBCClientUpdate.UnpackInterfaces(unpacker) + } + return err +} + +type ProtoAny struct { + TypeUrl string `json:"type_url"` + Value []byte `json:"value"` +} + +// GovProposal bridge to unmarshal json to proposal content types type GovProposal struct { + proposalContent +} + +// UnmarshalJSON is a custom unmarshaler that supports the cosmos-sdk Any types. +func (p *GovProposal) UnmarshalJSON(b []byte) error { + var raws map[string]json.RawMessage + if err := json.Unmarshal(b, &raws); err != nil { + return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + + // sdk protobuf Any types don't map back nicely to Go structs. So we do this manually + var result GovProposal + var customUnmarshalers = map[string]func(b []byte) error{ + "ibc_client_update": func(b []byte) error { + var proxy = struct { + ClientId string `json:"client_id"` + Header *ProtoAny `json:"header"` + }{} + if err := json.Unmarshal(b, &proxy); err != nil { + return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + result.IBCClientUpdate = &ibcclienttypes.ClientUpdateProposal{ + ClientId: proxy.ClientId, + Header: &codectypes.Any{ + TypeUrl: proxy.Header.TypeUrl, + Value: proxy.Header.Value, + }, + } + return nil + }, + "register_upgrade": func(b []byte) error { + var proxy = struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Time time.Time `protobuf:"bytes,2,opt,name=time,proto3,stdtime" json:"time"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` + UpgradedClientState ProtoAny `protobuf:"bytes,5,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty" yaml:"upgraded_client_state"` + }{} + if err := json.Unmarshal(b, &proxy); err != nil { + return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + result.RegisterUpgrade = &upgradetypes.Plan{ + Name: proxy.Name, + Time: proxy.Time.UTC(), + Height: proxy.Height, + Info: proxy.Info, + UpgradedClientState: &codectypes.Any{ + TypeUrl: proxy.UpgradedClientState.TypeUrl, + Value: proxy.UpgradedClientState.Value, + }, + } + return nil + }, + } + for field, unmarshaler := range customUnmarshalers { + if bz, ok := raws[field]; ok { + if err := unmarshaler(bz); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrJSONUnmarshal, "proposal: %q: %s", field, err.Error()) + } + *p = result + return nil + } + } + // default: use vanilla json unmarshaler when no custom one exists + if err := json.Unmarshal(b, &result.proposalContent); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + *p = result + return nil +} + +// proposalContent contains the concrete cosmos-sdk/ tgrade gov proposal types +type proposalContent struct { // Signaling proposal, the text and description field will be recorded Text *govtypes.TextProposal `json:"text"` @@ -116,10 +229,11 @@ type GovProposal struct { // Updates the matching client to set a new trusted header. // This can be used by governance to restore a client that has timed out or forked or otherwise broken. // See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/ibc/core/client/v1/client.proto#L36-L49 - IbcClientUpdate *ibcclienttypes.ClientUpdateProposal `json:"ibc_client_update"` + IBCClientUpdate *ibcclienttypes.ClientUpdateProposal `json:"ibc_client_update"` // See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto PromoteToPrivilegedContract *types.PromoteToPrivilegedContractProposal `json:"promote_to_privileged_contract"` + // See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto DemotePrivilegedContract *types.DemotePrivilegedContractProposal `json:"demote_privileged_contract"` diff --git a/x/twasm/contract/incoming_msgs_test.go b/x/twasm/contract/incoming_msgs_test.go index a69b4d46..dfc2c6b1 100644 --- a/x/twasm/contract/incoming_msgs_test.go +++ b/x/twasm/contract/incoming_msgs_test.go @@ -1,12 +1,16 @@ package contract import ( - "bytes" - "encoding/json" - "fmt" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/confio/tgrade/x/twasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" + ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/07-tendermint/types" + proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/gogo/protobuf/jsonpb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" @@ -14,44 +18,234 @@ import ( ) func TestGetProposalContent(t *testing.T) { - cs, err := clienttypes.PackClientState(&ibctmtypes.ClientState{}) - require.NoError(t, err) + ir := codectypes.NewInterfaceRegistry() + clienttypes.RegisterInterfaces(ir) + ibctmtypes.RegisterInterfaces(ir) + ib, err := ibcclienttypes.PackHeader(&ibctmtypes.Header{}) require.NoError(t, err) - anyType.ClearCachedValue() - jm := &jsonpb.Marshaler{OrigName: false, EmitDefaults: true, AnyResolver: nil} - var buf bytes.Buffer - require.NoError(t, jm.Marshal(&buf, anyType)) + cs, err := clienttypes.PackClientState(&ibctmtypes.ClientState{}) require.NoError(t, err) - xxx := fmt.Sprintf(`{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"register_upgrade":{"height":1, "info":"any information", "upgraded_client_state": %s}}}}`, buf.String()) - t.Log("___ ", xxx) + specs := map[string]struct { - src string - exp govtypes.Content + src string + expGovProposal govtypes.Content + expNotGovType bool }{ "text": { - src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"text":{}}}}`, - exp: &govtypes.TextProposal{Title: "foo", Description: "bar"}, + src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"text":{}}}}`, + expGovProposal: &govtypes.TextProposal{Title: "foo", Description: "bar"}, }, "register upgrade": { - src: xxx, - exp: &upgradetypes.SoftwareUpgradeProposal{Title: "foo", Description: "bar", Plan: upgradetypes.Plan{ + src: `{ +"execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "register_upgrade": { + "height": 1, + "info": "any information", + "upgraded_client_state": { + "type_url": "/ibc.lightclients.tendermint.v1.ClientState", "value": "EgAaACIAKgAyADoA" + } + }}}}`, + expGovProposal: &upgradetypes.SoftwareUpgradeProposal{Title: "foo", Description: "bar", Plan: upgradetypes.Plan{ Name: "", Time: time.Time{}, Height: 1, Info: "any information", - UpgradedClientState: anyType, + UpgradedClientState: cs, }}, }, + "cancel upgrade": { + src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"cancel_upgrade":{}}}}`, + expGovProposal: &upgradetypes.CancelSoftwareUpgradeProposal{Title: "foo", Description: "bar"}, + }, + "change param": { + src: `{ +"execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "change_params": [ + {"subspace": "mySubspace","key": "myKey","value": "myValue"}, + {"subspace": "myOtherSubspace", "key": "myOtherKey","value": "myOtherValue"} + ] + }}}`, + expGovProposal: &proposaltypes.ParameterChangeProposal{Title: "foo", Description: "bar", Changes: []proposaltypes.ParamChange{ + {Subspace: "mySubspace", Key: "myKey", Value: "myValue"}, + {Subspace: "myOtherSubspace", Key: "myOtherKey", Value: "myOtherValue"}, + }}, + }, + "ibc client update": { + src: `{ +"execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "ibc_client_update": { + "client_id": "myClientID", + "header": {"type_url": "/ibc.lightclients.tendermint.v1.Header","value": "GgA="} + }}}}`, + expGovProposal: &ibcclienttypes.ClientUpdateProposal{ + Title: "foo", + Description: "bar", + ClientId: "myClientID", + Header: ib, + }, + }, + "promote to privileged contract": { + src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"promote_to_privileged_contract":{"contract":"myContractAddress"}}}}`, + expGovProposal: &types.PromoteToPrivilegedContractProposal{ + Title: "foo", + Description: "bar", + Contract: "myContractAddress", + }, + }, + "demote privileged contract": { + src: `{"execute_gov_proposal":{"title":"foo", "description":"bar", "proposal":{"demote_privileged_contract":{"contract":"myContractAddress"}}}}`, + expGovProposal: &types.DemotePrivilegedContractProposal{ + Title: "foo", + Description: "bar", + Contract: "myContractAddress", + }, + }, + "instantiate contract": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "instantiate_contract": { + "admin": "myAdminAddress", + "code_id": 1, + "funds": [{"denom": "ALX", "amount": "2"},{"denom": "BLX","amount": "3"}], + "init_msg": {}, + "label": "testing", + "run_as": "myRunAsAddress" + }}}}`, + expGovProposal: &wasmtypes.InstantiateContractProposal{ + Title: "foo", + Description: "bar", + RunAs: "myRunAsAddress", + Admin: "myAdminAddress", + CodeID: 1, + Label: "testing", + InitMsg: []byte("{}"), + Funds: sdk.NewCoins(sdk.NewCoin("ALX", sdk.NewInt(2)), sdk.NewCoin("BLX", sdk.NewInt(3))), + }, + }, + "migrate contract": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "migrate_contract": { + "code_id": 1, + "contract": "myContractAddr", + "migrate_msg": {}, + "run_as": "myRunAsAddress" + }}}}`, + expGovProposal: &wasmtypes.MigrateContractProposal{ + Title: "foo", + Description: "bar", + RunAs: "myRunAsAddress", + Contract: "myContractAddr", + CodeID: 1, + MigrateMsg: []byte("{}"), + }, + }, + "set contract admin": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "set_contract_admin": { + "contract": "myContractAddr", + "new_admin": "myNewAdminAddress" + }}}}`, + expGovProposal: &wasmtypes.UpdateAdminProposal{ + Title: "foo", + Description: "bar", + NewAdmin: "myNewAdminAddress", + Contract: "myContractAddr", + }, + }, + "clear contract admin": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "clear_contract_admin": { + "contract": "myContractAddr" + }}}}`, + expGovProposal: &wasmtypes.ClearAdminProposal{ + Title: "foo", + Description: "bar", + Contract: "myContractAddr", + }, + }, + "pin codes": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "pin_codes": { + "code_ids": [3,2,1] + }}}}`, + expGovProposal: &wasmtypes.PinCodesProposal{ + Title: "foo", + Description: "bar", + CodeIDs: []uint64{3, 2, 1}, + }, + }, + "unpin codes": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "unpin_codes": { + "code_ids": [3,2,1] + }}}}`, + expGovProposal: &wasmtypes.UnpinCodesProposal{ + Title: "foo", + Description: "bar", + CodeIDs: []uint64{3, 2, 1}, + }, + }, + "unsupported proposal type": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar", + "proposal": { + "any_unknown": { + "foo": "bar" + }}}}`, + expGovProposal: nil, + }, + "no proposal type": { + src: `{ + "execute_gov_proposal": { + "title": "foo", "description": "bar" +}}`, + expGovProposal: nil, + }, + "no gov type": { + src: `{ + "anything": { + "foo": "bar" +}}`, + expNotGovType: true, + }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { var msg TgradeMsg - require.NoError(t, json.Unmarshal([]byte(spec.src), &msg)) + require.NoError(t, msg.UnmarshalWithAny([]byte(spec.src), ir)) gov := msg.ExecuteGovProposal + if spec.expNotGovType { + assert.Nil(t, gov) + return + } require.NotNil(t, gov) - assert.Equal(t, spec.exp, gov.GetProposalContent()) + assert.Equal(t, spec.expGovProposal, gov.GetProposalContent()) + }) } - } diff --git a/x/twasm/contract/schema/README.md b/x/twasm/contract/schema/README.md new file mode 100644 index 00000000..54365c8b --- /dev/null +++ b/x/twasm/contract/schema/README.md @@ -0,0 +1,6 @@ +# Json schemas +For documentation purpose + +Copied from https://github.com/confio/tgrade-contracts/tree/main/packages/bindings/schema + +Current version: `adba90d` diff --git a/x/twasm/contract/schema/get_validator_set_updater_response.json b/x/twasm/contract/schema/get_validator_set_updater_response.json new file mode 100644 index 00000000..f4cd5892 --- /dev/null +++ b/x/twasm/contract/schema/get_validator_set_updater_response.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetValidatorSetUpdaterResponse", + "type": "object", + "properties": { + "updater": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/x/twasm/contract/schema/list_begin_blockers_response.json b/x/twasm/contract/schema/list_begin_blockers_response.json new file mode 100644 index 00000000..1c4b8802 --- /dev/null +++ b/x/twasm/contract/schema/list_begin_blockers_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListBeginBlockersResponse", + "type": "object", + "required": [ + "begin_blockers" + ], + "properties": { + "begin_blockers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/x/twasm/contract/schema/list_end_blockers_response.json b/x/twasm/contract/schema/list_end_blockers_response.json new file mode 100644 index 00000000..d48e7243 --- /dev/null +++ b/x/twasm/contract/schema/list_end_blockers_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListEndBlockersResponse", + "type": "object", + "required": [ + "end_blockers" + ], + "properties": { + "end_blockers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/x/twasm/contract/schema/tgrade_msg.json b/x/twasm/contract/schema/tgrade_msg.json new file mode 100644 index 00000000..fa56c795 --- /dev/null +++ b/x/twasm/contract/schema/tgrade_msg.json @@ -0,0 +1,727 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TgradeMsg", + "description": "A number of Custom messages that can be returned by 'privileged' contracts. Returning them from any other contract will return an error and abort the transaction.", + "anyOf": [ + { + "description": "un/register for begin or end block hooks", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "$ref": "#/definitions/HooksMsg" + } + }, + "additionalProperties": false + }, + { + "description": "privileged contracts can mint arbitrary native tokens (extends BankMsg)", + "type": "object", + "required": [ + "mint_tokens" + ], + "properties": { + "mint_tokens": { + "type": "object", + "required": [ + "amount", + "denom", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + }, + "recipient": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "as well as adjust tendermint consensus params", + "type": "object", + "required": [ + "consensus_params" + ], + "properties": { + "consensus_params": { + "$ref": "#/definitions/ConsensusParams" + } + }, + "additionalProperties": false + }, + { + "description": "Run another contract in \"sudo\" mode (extends WasmMsg)", + "type": "object", + "required": [ + "wasm_sudo" + ], + "properties": { + "wasm_sudo": { + "type": "object", + "required": [ + "contract_addr", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded SudoMsg struct (as raw Binary). Note the contract may support different variants than the base TgradeSudoMsg, which defines the base chain->contract interface", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will execute an approved proposal in the Cosmos SDK \"Gov Router\". That allows access to many of the system internals, like sdk params or x/upgrade, as well as privileged access to the wasm module (eg. mark module privileged)", + "type": "object", + "required": [ + "execute_gov_proposal" + ], + "properties": { + "execute_gov_proposal": { + "type": "object", + "required": [ + "description", + "proposal", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "proposal": { + "$ref": "#/definitions/GovProposal" + }, + "title": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "BlockParams": { + "type": "object", + "properties": { + "max_bytes": { + "description": "Maximum number of bytes (over all tx) to be included in a block", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "max_gas": { + "description": "Maximum gas (over all tx) to be executed in one block. If set, more txs may be included in a block, but when executing, all tx after this is limit are consumed will immediately error", + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "ConsensusParams": { + "description": "See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L282-L289 These are various Tendermint Consensus Params that can be adjusted by EndBlockers If any of them are set to Some, then the blockchain will set those as new parameter for tendermint consensus.\n\nNote: we are not including ValidatorParams, which is used to change the allowed pubkey types for validators", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/BlockParams" + }, + { + "type": "null" + } + ] + }, + "evidence": { + "anyOf": [ + { + "$ref": "#/definitions/EvidenceParams" + }, + { + "type": "null" + } + ] + } + } + }, + "EvidenceParams": { + "type": "object", + "properties": { + "max_age_duration": { + "description": "Max age of evidence, in seconds. It should correspond with an app's \"unbonding period\"", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "max_age_num_blocks": { + "description": "Max age of evidence, in blocks.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "max_bytes": { + "description": "Maximum number of bytes of evidence to be included in a block", + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "GovProposal": { + "anyOf": [ + { + "description": "Signaling proposal, the text and description field will be recorded", + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Register an \"live upgrade\" on the x/upgrade module See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/upgrade/v1beta1/upgrade.proto#L12-L53", + "type": "object", + "required": [ + "register_upgrade" + ], + "properties": { + "register_upgrade": { + "type": "object", + "required": [ + "height", + "info", + "name", + "upgraded_client_state" + ], + "properties": { + "height": { + "description": "The height at which the upgrade must be performed. (Time-based upgrades are not supported due to instability)", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "info": { + "description": "Any application specific upgrade info to be included on-chain such as a git commit that validators could automatically upgrade to", + "type": "string" + }, + "name": { + "description": "Sets the name for the upgrade. This name will be used by the upgraded version of the software to apply any special \"on-upgrade\" commands during the first BeginBlock method after the upgrade is applied.", + "type": "string" + }, + "upgraded_client_state": { + "$ref": "#/definitions/ProtoAny" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "There can only be one pending upgrade at a given time. This cancels the pending upgrade, if any. See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/upgrade/v1beta1/upgrade.proto#L57-L62", + "type": "object", + "required": [ + "cancel_upgrade" + ], + "properties": { + "cancel_upgrade": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Defines a proposal to change one or more parameters. See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/cosmos/params/v1beta1/params.proto#L9-L27", + "type": "object", + "required": [ + "change_params" + ], + "properties": { + "change_params": { + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows raw bytes (if client and wasmd are aware of something the contract is not) Like CosmosMsg::Stargate but for the governance router, not normal router", + "type": "object", + "required": [ + "raw_proto_proposal" + ], + "properties": { + "raw_proto_proposal": { + "$ref": "#/definitions/ProtoAny" + } + }, + "additionalProperties": false + }, + { + "description": "Updates the matching client to set a new trusted header. This can be used by governance to restore a client that has timed out or forked or otherwise broken. See https://github.com/cosmos/cosmos-sdk/blob/v0.42.3/proto/ibc/core/client/v1/client.proto#L36-L49", + "type": "object", + "required": [ + "ibc_client_update" + ], + "properties": { + "ibc_client_update": { + "type": "object", + "required": [ + "client_id", + "header" + ], + "properties": { + "client_id": { + "type": "string" + }, + "header": { + "$ref": "#/definitions/ProtoAny" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto", + "type": "object", + "required": [ + "promote_to_privileged_contract" + ], + "properties": { + "promote_to_privileged_contract": { + "type": "object", + "required": [ + "contract" + ], + "properties": { + "contract": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/confio/tgrade/blob/privileged_contracts_5/proto/confio/twasm/v1beta1/proposal.proto", + "type": "object", + "required": [ + "demote_privileged_contract" + ], + "properties": { + "demote_privileged_contract": { + "type": "object", + "required": [ + "contract" + ], + "properties": { + "contract": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L32-L54", + "type": "object", + "required": [ + "instantiate_contract" + ], + "properties": { + "instantiate_contract": { + "type": "object", + "required": [ + "admin", + "code_id", + "funds", + "init_msg", + "label", + "run_as" + ], + "properties": { + "admin": { + "description": "Admin is an optional address that can execute migrations", + "type": "string" + }, + "code_id": { + "description": "the reference to the stored WASM code", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "funds": { + "description": "coins that are transferred to the contract on instantiation", + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "init_msg": { + "description": "json encoded message to be passed to the contract on instantiation", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "label": { + "description": "metadata to be stored with a contract instance.", + "type": "string" + }, + "run_as": { + "description": "the address that is passed to the contract's environment as sender", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L56-L70", + "type": "object", + "required": [ + "migrate_contract" + ], + "properties": { + "migrate_contract": { + "type": "object", + "required": [ + "code_id", + "contract", + "migrate_msg", + "run_as" + ], + "properties": { + "code_id": { + "description": "a reference to the new WASM code that it should be migrated to", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "contract": { + "description": "the contract address to be migrated", + "type": "string" + }, + "migrate_msg": { + "description": "json encoded message to be passed to the new WASM code to perform the migration", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "run_as": { + "description": "the address that is passed to the contract's environment as sender", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L72-L82", + "type": "object", + "required": [ + "set_contract_admin" + ], + "properties": { + "set_contract_admin": { + "type": "object", + "required": [ + "contract", + "new_admin" + ], + "properties": { + "contract": { + "description": "the contract address to be updated", + "type": "string" + }, + "new_admin": { + "description": "the account address to become admin of this contract", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L84-L93", + "type": "object", + "required": [ + "clear_contract_admin" + ], + "properties": { + "clear_contract_admin": { + "type": "object", + "required": [ + "contract" + ], + "properties": { + "contract": { + "description": "the contract address to be cleared", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L95-L107", + "type": "object", + "required": [ + "pin_codes" + ], + "properties": { + "pin_codes": { + "type": "object", + "required": [ + "code_ids" + ], + "properties": { + "code_ids": { + "description": "all code ideas that should be pinned in cache for high performance", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "See https://github.com/CosmWasm/wasmd/blob/master/proto/cosmwasm/wasm/v1beta1/proposal.proto#L109-L121", + "type": "object", + "required": [ + "unpin_codes" + ], + "properties": { + "unpin_codes": { + "type": "object", + "required": [ + "code_ids" + ], + "properties": { + "code_ids": { + "description": "all code ideas that should be removed from cache to free space", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "HooksMsg": { + "anyOf": [ + { + "description": "contracts registered here are called the beginning of each block with possible double-sign evidence", + "type": "object", + "required": [ + "register_begin_block" + ], + "properties": { + "register_begin_block": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unregister_begin_block" + ], + "properties": { + "unregister_begin_block": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "contracts registered here are called the end of every block", + "type": "object", + "required": [ + "register_end_block" + ], + "properties": { + "register_end_block": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unregister_end_block" + ], + "properties": { + "unregister_end_block": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "only max 1 contract can be registered here, this is called in EndBlock (after everything else) and can change the validator set.", + "type": "object", + "required": [ + "register_validator_set_update" + ], + "properties": { + "register_validator_set_update": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unregister_validator_set_update" + ], + "properties": { + "unregister_validator_set_update": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "contracts registered here are allowed to call ExecuteGovProposal{} (Any privileged contract *can* register, but this means you must explicitly request permission before sending such a message)", + "type": "object", + "required": [ + "register_gov_proposal_executor" + ], + "properties": { + "register_gov_proposal_executor": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unregister_gov_proposal_executor" + ], + "properties": { + "unregister_gov_proposal_executor": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines an individual parameter change, for use in ParameterChangeProposal.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "subspace": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "ProtoAny": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/x/twasm/contract/schema/tgrade_query.json b/x/twasm/contract/schema/tgrade_query.json new file mode 100644 index 00000000..c4bd20dd --- /dev/null +++ b/x/twasm/contract/schema/tgrade_query.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TgradeQuery", + "anyOf": [ + { + "description": "Returns the current tendermint validator set, along with their voting status from last block", + "type": "object", + "required": [ + "validator_votes" + ], + "properties": { + "validator_votes": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "$ref": "#/definitions/HooksQuery" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "HooksQuery": { + "anyOf": [ + { + "description": "List all registered contracts for each category", + "type": "object", + "required": [ + "list_begin_blockers" + ], + "properties": { + "list_begin_blockers": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_end_blockers" + ], + "properties": { + "list_end_blockers": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_validator_set_updater" + ], + "properties": { + "get_validator_set_updater": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/x/twasm/contract/schema/tgrade_sudo_msg.json b/x/twasm/contract/schema/tgrade_sudo_msg.json new file mode 100644 index 00000000..ce9d4e87 --- /dev/null +++ b/x/twasm/contract/schema/tgrade_sudo_msg.json @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TgradeSudoMsg", + "anyOf": [ + { + "description": "This will be delivered every block if the contract is currently registered for Begin Block types based on subset of https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L81", + "type": "object", + "required": [ + "begin_block" + ], + "properties": { + "begin_block": { + "type": "object", + "required": [ + "evidence" + ], + "properties": { + "evidence": { + "description": "This is proven evidence of malice and the basis for slashing validators", + "type": "array", + "items": { + "$ref": "#/definitions/Evidence" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will be delivered every block if the contract is currently registered for End Block Block height and time is already in Env.", + "type": "object", + "required": [ + "end_block" + ], + "properties": { + "end_block": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "This will be delivered after all end blockers if this is registered for ValidatorUpdates. If it sets Response.data, it must be a JSON-encoded ValidatorDiff, which will be used to change the validator set.", + "type": "object", + "required": [ + "end_with_validator_update" + ], + "properties": { + "end_with_validator_update": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "privilege_change" + ], + "properties": { + "privilege_change": { + "$ref": "#/definitions/PrivilegeChangeMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Evidence": { + "description": "See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L354-L375", + "type": "object", + "required": [ + "evidence_type", + "height", + "time", + "total_voting_power", + "validator" + ], + "properties": { + "evidence_type": { + "$ref": "#/definitions/EvidenceType" + }, + "height": { + "description": "the block height the offense occurred", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "time": { + "description": "the time when the offense occurred (in seconds UNIX time, like env.block.time)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_voting_power": { + "description": "the total voting power of the validator set at the time the offense occurred", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "validator": { + "$ref": "#/definitions/Validator" + } + } + }, + "EvidenceType": { + "type": "string", + "enum": [ + "duplicate_vote", + "light_client_attack" + ] + }, + "PrivilegeChangeMsg": { + "description": "These are called on a contract when it is made privileged or demoted", + "anyOf": [ + { + "description": "This is called when a contract gets \"privileged status\". It is a proper place to call `RegisterXXX` methods that require this status. Contracts that require this should be in a \"frozen\" state until they get this callback.", + "type": "object", + "required": [ + "promoted" + ], + "properties": { + "promoted": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "This is called when a contract looses \"privileged status\"", + "type": "object", + "required": [ + "demoted" + ], + "properties": { + "demoted": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Validator": { + "description": "This is returned by most queries from Tendermint See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L336-L340", + "type": "object", + "required": [ + "address", + "power" + ], + "properties": { + "address": { + "$ref": "#/definitions/Binary" + }, + "power": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } +} diff --git a/x/twasm/contract/schema/validator_diff.json b/x/twasm/contract/schema/validator_diff.json new file mode 100644 index 00000000..371cb3ae --- /dev/null +++ b/x/twasm/contract/schema/validator_diff.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ValidatorDiff", + "description": "See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L229-L235 A `EndWithValidatorUpdate{}` call may return a JSON-encoded ValidatorDiff in Response.data if it wishes to change the validator set.", + "type": "object", + "required": [ + "diffs" + ], + "properties": { + "diffs": { + "type": "array", + "items": { + "$ref": "#/definitions/ValidatorUpdate" + } + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Pubkey": { + "description": "A Tendermint validator pubkey.\n\nSee https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto for a list of available types. Sr25519 is added here as it is likely to join the party.\n\nThis type is optimized for the JSON interface. No data validation on the enum cases is performed. If you don't trust the data source, you can create a `ValidatedPubkey` enum that mirrors this type and uses fixed sized data fields.", + "anyOf": [ + { + "description": "32 bytes Ed25519 pubkey", + "type": "object", + "required": [ + "ed25519" + ], + "properties": { + "ed25519": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Must use 33 bytes 0x02/0x03 prefixed compressed pubkey format", + "type": "object", + "required": [ + "secp256k1" + ], + "properties": { + "secp256k1": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "32 bytes Sr25519 pubkey", + "type": "object", + "required": [ + "sr25519" + ], + "properties": { + "sr25519": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "ValidatorUpdate": { + "description": "This is used to update the validator set See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L343-L346", + "type": "object", + "required": [ + "power", + "pubkey" + ], + "properties": { + "power": { + "description": "This is the voting power in the consensus rounds", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pubkey": { + "description": "This is the pubkey used in Tendermint consensus", + "allOf": [ + { + "$ref": "#/definitions/Pubkey" + } + ] + } + } + } + } +} diff --git a/x/twasm/contract/schema/validator_vote_response.json b/x/twasm/contract/schema/validator_vote_response.json new file mode 100644 index 00000000..e9350a22 --- /dev/null +++ b/x/twasm/contract/schema/validator_vote_response.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ValidatorVoteResponse", + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "$ref": "#/definitions/ValidatorVote" + } + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "ValidatorVote": { + "description": "This is taken from BeginBlock.LastCommitInfo See https://github.com/tendermint/tendermint/blob/v0.34.8/proto/tendermint/abci/types.proto#L348-L352", + "type": "object", + "required": [ + "address", + "power", + "voted" + ], + "properties": { + "address": { + "$ref": "#/definitions/Binary" + }, + "power": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voted": { + "type": "boolean" + } + } + } + } +} diff --git a/x/twasm/contract/test_fixtures.go b/x/twasm/contract/test_fixtures.go new file mode 100644 index 00000000..5a6b4ee4 --- /dev/null +++ b/x/twasm/contract/test_fixtures.go @@ -0,0 +1,27 @@ +package contract + +import govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + +// ExecuteGovProposalFixture text proposal type +func ExecuteGovProposalFixture(mutators ...func(proposal *ExecuteGovProposal)) ExecuteGovProposal { + r := ExecuteGovProposal{ + Title: "foo", + Description: "bar", + Proposal: GovProposalFixture(func(p *GovProposal) { + p.Text = &govtypes.TextProposal{} + }), + } + for _, m := range mutators { + m(&r) + } + return r +} + +// GovProposalFixture empty gov proposal type +func GovProposalFixture(mutators ...func(proposal *GovProposal)) GovProposal { + r := GovProposal{} + for _, m := range mutators { + m(&r) + } + return r +} diff --git a/x/twasm/keeper/handler_plugin.go b/x/twasm/keeper/handler_plugin.go index d9d90cea..285498ed 100644 --- a/x/twasm/keeper/handler_plugin.go +++ b/x/twasm/keeper/handler_plugin.go @@ -1,12 +1,12 @@ package keeper import ( - "encoding/json" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/confio/tgrade/x/twasm/contract" "github.com/confio/tgrade/x/twasm/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -27,15 +27,16 @@ var _ wasmkeeper.Messenger = TgradeHandler{} type TgradeHandler struct { keeper tgradeKeeper govRouter govtypes.Router + cdc codec.Marshaler } // NewTgradeHandler constructor -func NewTgradeHandler(keeper tgradeKeeper, govRouter govtypes.Router) *TgradeHandler { - return &TgradeHandler{keeper: keeper, govRouter: govRouter} +func NewTgradeHandler(cdc codec.Marshaler, keeper tgradeKeeper, govRouter govtypes.Router) *TgradeHandler { + return &TgradeHandler{cdc: cdc, keeper: keeper, govRouter: govRouter} } // DispatchMsg handles wasmVM message for privileged contracts -func (h TgradeHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { +func (h TgradeHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, _ string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { if msg.Custom == nil { return nil, nil, wasmtypes.ErrUnknownMsg } @@ -43,7 +44,7 @@ func (h TgradeHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, return nil, nil, wasmtypes.ErrUnknownMsg } var tMsg contract.TgradeMsg - if err := json.Unmarshal(msg.Custom, &tMsg); err != nil { + if err := tMsg.UnmarshalWithAny(msg.Custom, h.cdc); err != nil { return nil, nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } switch { diff --git a/x/twasm/keeper/handler_plugin_test.go b/x/twasm/keeper/handler_plugin_test.go index 0659e0f1..70ffbebc 100644 --- a/x/twasm/keeper/handler_plugin_test.go +++ b/x/twasm/keeper/handler_plugin_test.go @@ -70,10 +70,11 @@ func TestTgradeHandlesDispatchMsg(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { + cdc := MakeEncodingConfig(t).Marshaler govRouter := &CapturingGovRouter{} mock := handlerTgradeKeeperMock{} spec.setup(&mock) - h := NewTgradeHandler(mock, govRouter) + h := NewTgradeHandler(cdc, mock, govRouter) var ctx sdk.Context _, _, gotErr := h.DispatchMsg(ctx, contractAddr, "", spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) @@ -277,7 +278,7 @@ func TestTgradeHandlesHooks(t *testing.T) { capturedDetails, capturedRegistrations, capturedUnRegistrations = nil, nil, nil mock := handlerTgradeKeeperMock{} spec.setup(&mock) - h := NewTgradeHandler(mock, nil) + h := NewTgradeHandler(nil, mock, nil) var ctx sdk.Context gotErr := h.handleHooks(ctx, myContractAddr, &spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) @@ -300,9 +301,7 @@ func TestHandleGovProposalExecution(t *testing.T) { expCapturedGovContent []govtypes.Content }{ "all good": { - src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ - Text: &govtypes.TextProposal{}, - }}, + src: contract.ExecuteGovProposalFixture(), setup: func(m *handlerTgradeKeeperMock) { m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { c := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { @@ -316,9 +315,7 @@ func TestHandleGovProposalExecution(t *testing.T) { expCapturedGovContent: []govtypes.Content{&govtypes.TextProposal{Title: "foo", Description: "bar"}}, }, "unauthorized contract": { - src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ - Text: &govtypes.TextProposal{}, - }}, + src: contract.ExecuteGovProposalFixture(), setup: func(m *handlerTgradeKeeperMock) { m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { c := wasmtypes.ContractInfoFixture() @@ -328,9 +325,11 @@ func TestHandleGovProposalExecution(t *testing.T) { expErr: sdkerrors.ErrUnauthorized, }, "invalid content": { - src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ - RegisterUpgrade: &upgradetypes.Plan{}, - }}, + src: contract.ExecuteGovProposalFixture(func(p *contract.ExecuteGovProposal) { + p.Proposal = contract.GovProposalFixture(func(x *contract.GovProposal) { + x.RegisterUpgrade = &upgradetypes.Plan{} + }) + }), setup: func(m *handlerTgradeKeeperMock) { m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { c := wasmtypes.ContractInfoFixture(func(info *wasmtypes.ContractInfo) { @@ -358,9 +357,7 @@ func TestHandleGovProposalExecution(t *testing.T) { expErr: wasmtypes.ErrUnknownMsg, }, "unknown origin contract": { - src: contract.ExecuteGovProposal{Title: "foo", Description: "bar", Proposal: contract.GovProposal{ - Text: &govtypes.TextProposal{}, - }}, + src: contract.ExecuteGovProposalFixture(), setup: func(m *handlerTgradeKeeperMock) { m.GetContractInfoFn = func(ctx sdk.Context, contractAddress sdk.AccAddress) *wasmtypes.ContractInfo { return nil @@ -371,10 +368,11 @@ func TestHandleGovProposalExecution(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { + cdc := MakeEncodingConfig(t).Marshaler mock := handlerTgradeKeeperMock{} spec.setup(&mock) router := &CapturingGovRouter{} - h := NewTgradeHandler(mock, router) + h := NewTgradeHandler(cdc, mock, router) var ctx sdk.Context gotErr := h.handleGovProposalExecution(ctx, myContractAddr, &spec.src) require.True(t, spec.expErr.Is(gotErr), "expected %v but got %#+v", spec.expErr, gotErr) diff --git a/x/twasm/keeper/keeper.go b/x/twasm/keeper/keeper.go index f13d2ef5..edf2bbc0 100644 --- a/x/twasm/keeper/keeper.go +++ b/x/twasm/keeper/keeper.go @@ -61,7 +61,7 @@ func NewKeeper( portSource, ), // append our custom message handler - NewTgradeHandler(&result, govRouter), + NewTgradeHandler(cdc, &result, govRouter), ) var queryPlugins wasmkeeper.WasmVMQueryHandler = wasmkeeper.DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, channelKeeper, queryRouter, &result.Keeper) diff --git a/x/twasm/keeper/privileged_test.go b/x/twasm/keeper/privileged_test.go index 5fe57d9d..7cb694ee 100644 --- a/x/twasm/keeper/privileged_test.go +++ b/x/twasm/keeper/privileged_test.go @@ -149,7 +149,7 @@ func TestUnsetPrivileged(t *testing.T) { k := keepers.TWasmKeeper codeID, contractAddr := seedTestContract(t, ctx, k) - h := NewTgradeHandler(k, nil) + h := NewTgradeHandler(nil, k, nil) // and privileged with a callback k.setPrivilegedFlag(ctx, contractAddr) err := h.handleHooks(ctx, contractAddr, &contract.Hooks{ diff --git a/x/twasm/types/proposal.go b/x/twasm/types/proposal.go index 4e48610e..0c21b8f8 100644 --- a/x/twasm/types/proposal.go +++ b/x/twasm/types/proposal.go @@ -128,4 +128,4 @@ func validateProposalCommons(title, description string) error { return sdkerrors.Wrapf(govtypes.ErrInvalidProposalContent, "proposal description is longer than max length of %d", govtypes.MaxDescriptionLength) } return nil -} \ No newline at end of file +} From dd0af2c661366658d29ad245d47422bc87e62246 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 17 May 2021 15:12:28 +0200 Subject: [PATCH 7/9] Add gov system test --- testing/contracts/tgrade_gov_reflect.wasm | Bin 0 -> 275317 bytes testing/gov_test.go | 63 ++++++++++++++++++++++ x/twasm/contract/incoming_msgs.go | 3 +- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 testing/contracts/tgrade_gov_reflect.wasm create mode 100644 testing/gov_test.go diff --git a/testing/contracts/tgrade_gov_reflect.wasm b/testing/contracts/tgrade_gov_reflect.wasm new file mode 100644 index 0000000000000000000000000000000000000000..73844731e2fa6e057d40091038bee64c8314ff91 GIT binary patch literal 275317 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 bidEtin^=urYFQ69P^cm}k`AFaf9n4M569{A literal 0 HcmV?d00001 diff --git a/testing/gov_test.go b/testing/gov_test.go new file mode 100644 index 00000000..f7b9ac98 --- /dev/null +++ b/testing/gov_test.go @@ -0,0 +1,63 @@ +// +build system_test + +package testing + +import ( + "fmt" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + "strings" + "testing" +) + +// Scenario: add reflect contract to genesis and set privileged +// trigger gov proposal to unset privileges +// then verify that callback permission was removed +func TestGovProposal(t *testing.T) { + sut.ResetChain(t) + cli := NewTgradeCli(t, sut, verbose) + myKey := strings.Trim(cli.Keys("keys", "show", "-a", "node0"), "\n ") + t.Logf("key: %q", myKey) + commands := [][]string{ + { + "wasm-genesis-message", + "store", + "testing/contracts/tgrade_gov_reflect.wasm", + "--instantiate-everybody=true", + "--builder=foo/bar:latest", + fmt.Sprintf("--run-as=%s", myKey), + }, + { + "wasm-genesis-message", + "instantiate-contract", + "1", + `{}`, + "--label=testing", + fmt.Sprintf("--run-as=%s", myKey), + }, + { + "wasm-genesis-flags", + "set-privileged", + "tgrade18vd8fpwxzck93qlwghaj6arh4p7c5n89hzs8hy", + }, + } + sut.ModifyGenesis(t, commands...) + sut.StartChain(t) + + qResult := cli.CustomQuery("q", "wasm", "callback-contracts", "gov_proposal_executor") + contracts := gjson.Get(qResult, "contracts").Array() + require.Len(t, contracts, 1, qResult) + require.Equal(t, "tgrade18vd8fpwxzck93qlwghaj6arh4p7c5n89hzs8hy", contracts[0].String()) + t.Log("got query result", qResult) + + // when + t.Log("Send a proposal to be returned") + excecMsg := `{"proposal":{"title":"foo", "description":"bar", "proposal":{"demote_privileged_contract":{"contract":"tgrade18vd8fpwxzck93qlwghaj6arh4p7c5n89hzs8hy"}}}}` + txResult := cli.CustomCommand("tx", "wasm", "execute", "tgrade18vd8fpwxzck93qlwghaj6arh4p7c5n89hzs8hy", excecMsg, fmt.Sprintf("--from=%s", myKey), "--gas=1500000") + RequireTxSuccess(t, txResult) + + // then should not be privileged anymore + qResult = cli.CustomQuery("q", "wasm", "callback-contracts", "gov_proposal_executor") + contracts = gjson.Get(qResult, "contracts").Array() + require.Len(t, contracts, 0, qResult) +} diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index b794f83b..876f8dbe 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -55,7 +55,8 @@ type ExecuteGovProposal struct { Proposal GovProposal `json:"proposal"` } -// GetProposalContent converts message payload to gov content type. returns `nil` when unknown +// GetProposalContent converts message payload to gov content type. returns `nil` when unknown. +// The response is not guaranteed to be valid content. func (p ExecuteGovProposal) GetProposalContent() govtypes.Content { switch { case p.Proposal.Text != nil: From b91c0c28ce7f633451d5597230c3d34a4cd8c6eb Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Fri, 21 May 2021 14:16:55 +0200 Subject: [PATCH 8/9] Review comments --- go.mod | 4 +- go.sum | 56 +++++++++++++------------- x/twasm/contract/incoming_msgs.go | 39 +++++++++--------- x/twasm/contract/incoming_msgs_test.go | 11 ++--- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index e2df33ab..bef46719 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/confio/tgrade go 1.15 require ( - github.com/CosmWasm/wasmd v0.16.0 + github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903 github.com/CosmWasm/wasmvm v0.14.0 - github.com/cosmos/cosmos-sdk v0.42.4 + github.com/cosmos/cosmos-sdk v0.42.5 github.com/gogo/protobuf v1.3.3 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index 0136f32a..24d77f3c 100644 --- a/go.sum +++ b/go.sum @@ -12,13 +12,12 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/99designs/keyring v1.1.6 h1:kVDC2uCgVwecxCk+9zoCt2uEL6dt+dfVzMvGgnVcIuM= github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmd v0.16.0 h1:37b/cKIq37z1YFCVKl4PrBjRt2np5y2kE9LinhK+lQI= -github.com/CosmWasm/wasmd v0.16.0/go.mod h1:EmtH0lN3ZlHyU5Sn1VzbZ9guf0BMBSPifzodBcK6d2E= +github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903 h1:exQX7Qv0zbC3zfZd8XfDBp210r88SLne1yp9UgFLXDI= +github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903/go.mod h1:Coq6rhAce0LBWlzg618XTjqSrcD5ij2qvgafxauEPA8= github.com/CosmWasm/wasmvm v0.14.0 h1:oceacwdwD9d9GzqElOrB8Qu1topx4+zM47VEqnJ/9Jo= github.com/CosmWasm/wasmvm v0.14.0/go.mod h1:Id107qllDJyJjVQQsKMOy2YYF98sqPJ2t+jX1QES40A= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -47,8 +46,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.6 h1:x/tmtOF9cDBoXH7XoAGOz2qqm1DknFD1590XmD/DUJ8= -github.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.8 h1:oOxq3KPj0WhCuy50EhzwiyMyG2ovRQZpZLXQuOh2a/M= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -91,8 +90,9 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/confio/ics23/go v0.0.0-20200817220745-f173e6211efb/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= -github.com/confio/ics23/go v0.6.3 h1:PuGK2V1NJWZ8sSkNDq91jgT/cahFEW9RGp4Y5jxulf0= github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.6.6 h1:pkOy18YxxJ/r0XFDCnrl4Bjv6h4LkBSpLS6F38mrKL8= +github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -103,15 +103,16 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/cosmos-sdk v0.42.4 h1:yaD4PyOx0LnyfiWasC5egg1U76lT83GRxjJjupPo7Gk= -github.com/cosmos/cosmos-sdk v0.42.4/go.mod h1:I1Zw1zmU4rA/NITaakTb71pXQnQrWyFBhqo3WSeg0vA= +github.com/cosmos/cosmos-sdk v0.42.5 h1:tXttRklrAUUrFopfe9aMXpfiArF6MH2QRRqq0Lxyd5w= +github.com/cosmos/cosmos-sdk v0.42.5/go.mod h1:3JyT+Ud7QRTO1/ikncNqVhaF526ZKNqh2HGMqXn+QHE= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/iavl v0.15.0-rc3.0.20201009144442-230e9bdf52cd/go.mod h1:3xOIaNNX19p0QrX0VqWa6voPRoJRGGYtny+DH8NEPvE= github.com/cosmos/iavl v0.15.0-rc5/go.mod h1:WqoPL9yPTQ85QBMT45OOUzPxG/U/JcJoN7uMjgxke/I= -github.com/cosmos/iavl v0.15.3 h1:xE9r6HW8GeKeoYJN4zefpljZ1oukVScP/7M8oj6SUts= github.com/cosmos/iavl v0.15.3/go.mod h1:OLjQiAQ4fGD2KDZooyJG9yz+p2ao2IAYSbke8mVvSA4= +github.com/cosmos/iavl v0.16.0 h1:ICIOB8xysirTX27GmVAaoeSpeozzgSu9d49w36xkVJA= +github.com/cosmos/iavl v0.16.0/go.mod h1:2A8O/Jz9YwtjqXMO0CjnnbTYEEaovE8jWcwrakH3PoE= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= @@ -213,8 +214,8 @@ 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/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -228,6 +229,7 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -256,8 +258,9 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.1/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= @@ -346,8 +349,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= -github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -369,8 +372,9 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -413,8 +417,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/otiai10/copy v1.4.2 h1:RTiz2sol3eoXPLF4o+YWqEybwfUa/Q2Nkc4ZIUs3fwI= -github.com/otiai10/copy v1.4.2/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= +github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= +github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= @@ -427,8 +431,8 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= @@ -468,10 +472,9 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.21.0 h1:SMvI2JVldvfUvRVlP64jkIJEC6WiGHJcN2e5tB+ztF8= -github.com/prometheus/common v0.21.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlPDGM= +github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -497,7 +500,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM= github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -576,7 +578,6 @@ github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM github.com/tendermint/tendermint v0.34.0-rc4/go.mod h1:yotsojf2C1QBOw4dZrTcxbyxmPUrT4hNuOQWX9XUwB4= github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX6mjJTgbLHTwi17VDVg= github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ= -github.com/tendermint/tendermint v0.34.9/go.mod h1:kl4Z1JwGx1I+u1SXIzMDy7Z3T8LiMeCAOnzNn6AIMT4= github.com/tendermint/tendermint v0.34.10 h1:wBOc/It8sh/pVH9np2V5fBvRmIyFN/bUrGPx+eAHexs= github.com/tendermint/tendermint v0.34.10/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0= github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= @@ -749,8 +750,9 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -777,7 +779,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -850,8 +851,9 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10= +gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/x/twasm/contract/incoming_msgs.go b/x/twasm/contract/incoming_msgs.go index 876f8dbe..20878439 100644 --- a/x/twasm/contract/incoming_msgs.go +++ b/x/twasm/contract/incoming_msgs.go @@ -10,7 +10,6 @@ import ( ibcclienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "time" ) // TgradeMsg messages coming from a contract @@ -19,7 +18,8 @@ type TgradeMsg struct { ExecuteGovProposal *ExecuteGovProposal `json:"execute_gov_proposal"` } -// UnmarshalWithAny from json to Go objects with cosmos-sdk Any types +// UnmarshalWithAny from json to Go objects with cosmos-sdk Any types that have their objects/ interfaces unpacked and +// set in the `cachedValue` attribute. func (p *TgradeMsg) UnmarshalWithAny(bz []byte, unpacker codectypes.AnyUnpacker) error { if err := json.Unmarshal(bz, p); err != nil { return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) @@ -132,11 +132,20 @@ func (p *ExecuteGovProposal) unpackInterfaces(unpacker codectypes.AnyUnpacker) e return err } +// ProtoAny data type to map from json to cosmos-sdk Any type. type ProtoAny struct { TypeUrl string `json:"type_url"` Value []byte `json:"value"` } +// Encode convertes to a cosmos-sdk Any type. +func (a ProtoAny) Encode() *codectypes.Any { + return &codectypes.Any{ + TypeUrl: a.TypeUrl, + Value: a.Value, + } +} + // GovProposal bridge to unmarshal json to proposal content types type GovProposal struct { proposalContent @@ -162,33 +171,25 @@ func (p *GovProposal) UnmarshalJSON(b []byte) error { } result.IBCClientUpdate = &ibcclienttypes.ClientUpdateProposal{ ClientId: proxy.ClientId, - Header: &codectypes.Any{ - TypeUrl: proxy.Header.TypeUrl, - Value: proxy.Header.Value, - }, + Header: proxy.Header.Encode(), } return nil }, "register_upgrade": func(b []byte) error { var proxy = struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Time time.Time `protobuf:"bytes,2,opt,name=time,proto3,stdtime" json:"time"` - Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` - Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` - UpgradedClientState ProtoAny `protobuf:"bytes,5,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty" yaml:"upgraded_client_state"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` + UpgradedClientState ProtoAny `protobuf:"bytes,5,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty" yaml:"upgraded_client_state"` }{} if err := json.Unmarshal(b, &proxy); err != nil { return sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } result.RegisterUpgrade = &upgradetypes.Plan{ - Name: proxy.Name, - Time: proxy.Time.UTC(), - Height: proxy.Height, - Info: proxy.Info, - UpgradedClientState: &codectypes.Any{ - TypeUrl: proxy.UpgradedClientState.TypeUrl, - Value: proxy.UpgradedClientState.Value, - }, + Name: proxy.Name, + Height: proxy.Height, + Info: proxy.Info, + UpgradedClientState: proxy.UpgradedClientState.Encode(), } return nil }, diff --git a/x/twasm/contract/incoming_msgs_test.go b/x/twasm/contract/incoming_msgs_test.go index dfc2c6b1..8e0b3110 100644 --- a/x/twasm/contract/incoming_msgs_test.go +++ b/x/twasm/contract/incoming_msgs_test.go @@ -39,17 +39,18 @@ func TestGetProposalContent(t *testing.T) { "register upgrade": { src: `{ "execute_gov_proposal": { - "title": "foo", "description": "bar", + "title": "myTitle", "description": "myDescription", "proposal": { "register_upgrade": { + "name": "myUpgradeName", "height": 1, "info": "any information", "upgraded_client_state": { "type_url": "/ibc.lightclients.tendermint.v1.ClientState", "value": "EgAaACIAKgAyADoA" } }}}}`, - expGovProposal: &upgradetypes.SoftwareUpgradeProposal{Title: "foo", Description: "bar", Plan: upgradetypes.Plan{ - Name: "", + expGovProposal: &upgradetypes.SoftwareUpgradeProposal{Title: "myTitle", Description: "myDescription", Plan: upgradetypes.Plan{ + Name: "myUpgradeName", Time: time.Time{}, Height: 1, Info: "any information", @@ -116,7 +117,7 @@ func TestGetProposalContent(t *testing.T) { "admin": "myAdminAddress", "code_id": 1, "funds": [{"denom": "ALX", "amount": "2"},{"denom": "BLX","amount": "3"}], - "init_msg": {}, + "init_msg": "e30=", "label": "testing", "run_as": "myRunAsAddress" }}}}`, @@ -139,7 +140,7 @@ func TestGetProposalContent(t *testing.T) { "migrate_contract": { "code_id": 1, "contract": "myContractAddr", - "migrate_msg": {}, + "migrate_msg": "e30=", "run_as": "myRunAsAddress" }}}}`, expGovProposal: &wasmtypes.MigrateContractProposal{ From e5a4d19774b24159a46137603d2a2ffb81a37733 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Fri, 28 May 2021 09:06:42 +0200 Subject: [PATCH 9/9] Update wasmd version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bef46719..fa46edbe 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/confio/tgrade go 1.15 require ( - github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903 + github.com/CosmWasm/wasmd v0.17.0 github.com/CosmWasm/wasmvm v0.14.0 github.com/cosmos/cosmos-sdk v0.42.5 github.com/gogo/protobuf v1.3.3 diff --git a/go.sum b/go.sum index 24d77f3c..1678bea0 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903 h1:exQX7Qv0zbC3zfZd8XfDBp210r88SLne1yp9UgFLXDI= -github.com/CosmWasm/wasmd v0.16.1-0.20210521120544-98431c6dc903/go.mod h1:Coq6rhAce0LBWlzg618XTjqSrcD5ij2qvgafxauEPA8= +github.com/CosmWasm/wasmd v0.17.0 h1:eRgXvTZvGfJC4uekmE4rCCi4g/H/Mg8dbj/T2wNhA0o= +github.com/CosmWasm/wasmd v0.17.0/go.mod h1:Coq6rhAce0LBWlzg618XTjqSrcD5ij2qvgafxauEPA8= github.com/CosmWasm/wasmvm v0.14.0 h1:oceacwdwD9d9GzqElOrB8Qu1topx4+zM47VEqnJ/9Jo= github.com/CosmWasm/wasmvm v0.14.0/go.mod h1:Id107qllDJyJjVQQsKMOy2YYF98sqPJ2t+jX1QES40A= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=