From 63f73d3e6c5431ac0a3983270a100ed84eec4e46 Mon Sep 17 00:00:00 2001 From: Alexander Peters Date: Thu, 6 Jul 2023 11:42:01 +0200 Subject: [PATCH] Gov authorization propagation for sub-messages (#1482) * Add gov authorization propagation for sub-messages * Minor update --- x/wasm/keeper/authz_policy.go | 101 +++++++++++++---- x/wasm/keeper/authz_policy_test.go | 172 ++++++++++++++++++++++++++--- x/wasm/keeper/contract_keeper.go | 16 +-- x/wasm/keeper/keeper.go | 55 ++++----- x/wasm/keeper/keeper_cgo.go | 5 +- x/wasm/keeper/keeper_test.go | 6 +- x/wasm/keeper/msg_server.go | 23 ++-- x/wasm/keeper/msg_server_test.go | 53 +++++++++ x/wasm/keeper/options.go | 14 +++ x/wasm/keeper/options_test.go | 10 ++ x/wasm/keeper/submsg_test.go | 161 +++++++++++++++++++++++++++ x/wasm/types/ante.go | 24 ---- x/wasm/types/authz_policy.go | 35 ++++++ x/wasm/types/context.go | 54 +++++++++ 14 files changed, 620 insertions(+), 109 deletions(-) create mode 100644 x/wasm/keeper/msg_server_test.go delete mode 100644 x/wasm/types/ante.go create mode 100644 x/wasm/types/authz_policy.go create mode 100644 x/wasm/types/context.go diff --git a/x/wasm/keeper/authz_policy.go b/x/wasm/keeper/authz_policy.go index 19f5320a9e..74c029e969 100644 --- a/x/wasm/keeper/authz_policy.go +++ b/x/wasm/keeper/authz_policy.go @@ -6,27 +6,11 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/types" ) -// ChainAccessConfigs chain settings -type ChainAccessConfigs struct { - Upload types.AccessConfig - Instantiate types.AccessConfig -} - -// NewChainAccessConfigs constructor -func NewChainAccessConfigs(upload types.AccessConfig, instantiate types.AccessConfig) ChainAccessConfigs { - return ChainAccessConfigs{Upload: upload, Instantiate: instantiate} -} - -type AuthorizationPolicy interface { - CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool - CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool - CanModifyContract(admin, actor sdk.AccAddress) bool - CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool -} +var _ types.AuthorizationPolicy = DefaultAuthorizationPolicy{} type DefaultAuthorizationPolicy struct{} -func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { +func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { return chainConfigs.Upload.Allowed(actor) && contractConfig.IsSubset(chainConfigs.Instantiate) } @@ -43,10 +27,35 @@ func (p DefaultAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk return creator != nil && creator.Equals(actor) && isSubset } -type GovAuthorizationPolicy struct{} +// SubMessageAuthorizationPolicy always returns the default policy +func (p DefaultAuthorizationPolicy) SubMessageAuthorizationPolicy(_ types.AuthorizationPolicyAction) types.AuthorizationPolicy { + return p +} + +var _ types.AuthorizationPolicy = GovAuthorizationPolicy{} + +type GovAuthorizationPolicy struct { + propagate map[types.AuthorizationPolicyAction]struct{} +} + +// NewGovAuthorizationPolicy public constructor +func NewGovAuthorizationPolicy(actions ...types.AuthorizationPolicyAction) types.AuthorizationPolicy { + propagate := make(map[types.AuthorizationPolicyAction]struct{}, len(actions)) + for _, a := range actions { + propagate[a] = struct{}{} + } + return newGovAuthorizationPolicy(propagate) +} + +// newGovAuthorizationPolicy internal constructor +func newGovAuthorizationPolicy(propagate map[types.AuthorizationPolicyAction]struct{}) types.AuthorizationPolicy { + return GovAuthorizationPolicy{ + propagate: propagate, + } +} // CanCreateCode implements AuthorizationPolicy.CanCreateCode to allow gov actions. Always returns true. -func (p GovAuthorizationPolicy) CanCreateCode(ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool { +func (p GovAuthorizationPolicy) CanCreateCode(types.ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool { return true } @@ -61,3 +70,55 @@ func (p GovAuthorizationPolicy) CanModifyContract(sdk.AccAddress, sdk.AccAddress func (p GovAuthorizationPolicy) CanModifyCodeAccessConfig(sdk.AccAddress, sdk.AccAddress, bool) bool { return true } + +// SubMessageAuthorizationPolicy returns new policy with fine-grained gov permission for given action only +func (p GovAuthorizationPolicy) SubMessageAuthorizationPolicy(action types.AuthorizationPolicyAction) types.AuthorizationPolicy { + defaultPolicy := DefaultAuthorizationPolicy{} + if p.propagate != nil && len(p.propagate) != 0 { + if _, ok := p.propagate[action]; ok { + return NewPartialGovAuthorizationPolicy(defaultPolicy, action) + } + } + return defaultPolicy +} + +var _ types.AuthorizationPolicy = PartialGovAuthorizationPolicy{} + +// PartialGovAuthorizationPolicy decorates the given default policy to add fine-grained gov permissions +// to the defined action +type PartialGovAuthorizationPolicy struct { + action types.AuthorizationPolicyAction + defaultPolicy types.AuthorizationPolicy +} + +// NewPartialGovAuthorizationPolicy constructor +func NewPartialGovAuthorizationPolicy(defaultPolicy types.AuthorizationPolicy, entrypoint types.AuthorizationPolicyAction) PartialGovAuthorizationPolicy { + return PartialGovAuthorizationPolicy{action: entrypoint, defaultPolicy: defaultPolicy} +} + +func (p PartialGovAuthorizationPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { + return p.defaultPolicy.CanCreateCode(chainConfigs, actor, contractConfig) +} + +func (p PartialGovAuthorizationPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool { + if p.action == types.AuthZActionInstantiate { + return true + } + return p.defaultPolicy.CanInstantiateContract(c, actor) +} + +func (p PartialGovAuthorizationPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool { + if p.action == types.AuthZActionMigrateContract { + return true + } + return p.defaultPolicy.CanModifyContract(admin, actor) +} + +func (p PartialGovAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { + return p.defaultPolicy.CanModifyCodeAccessConfig(creator, actor, isSubset) +} + +// SubMessageAuthorizationPolicy always returns self +func (p PartialGovAuthorizationPolicy) SubMessageAuthorizationPolicy(_ types.AuthorizationPolicyAction) types.AuthorizationPolicy { + return p +} diff --git a/x/wasm/keeper/authz_policy_test.go b/x/wasm/keeper/authz_policy_test.go index 1418913bc8..2230620daf 100644 --- a/x/wasm/keeper/authz_policy_test.go +++ b/x/wasm/keeper/authz_policy_test.go @@ -13,44 +13,44 @@ func TestDefaultAuthzPolicyCanCreateCode(t *testing.T) { myActorAddress := RandomAccountAddress(t) otherAddress := RandomAccountAddress(t) specs := map[string]struct { - chainConfigs ChainAccessConfigs + chainConfigs types.ChainAccessConfigs contractInstConf types.AccessConfig actor sdk.AccAddress exp bool panics bool }{ "upload nobody": { - chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), contractInstConf: types.AllowEverybody, exp: false, }, "upload everybody": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), contractInstConf: types.AllowEverybody, exp: true, }, "upload any address - included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), contractInstConf: types.AllowEverybody, exp: true, }, "upload any address - not included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), contractInstConf: types.AllowEverybody, exp: false, }, "contract config - subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress), exp: true, }, "contract config - not subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), contractInstConf: types.AllowEverybody, exp: false, }, "upload undefined config - panics": { - chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), contractInstConf: types.AllowEverybody, panics: true, }, @@ -180,40 +180,48 @@ func TestDefaultAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { } } +func TestDefaultAuthzPolicySubMessageAuthorizationPolicy(t *testing.T) { + policy := DefaultAuthorizationPolicy{} + for _, v := range []types.AuthorizationPolicyAction{types.AuthZActionInstantiate, types.AuthZActionMigrateContract} { + got := policy.SubMessageAuthorizationPolicy(v) + assert.Equal(t, policy, got) + } +} + func TestGovAuthzPolicyCanCreateCode(t *testing.T) { myActorAddress := RandomAccountAddress(t) otherAddress := RandomAccountAddress(t) specs := map[string]struct { - chainConfigs ChainAccessConfigs + chainConfigs types.ChainAccessConfigs contractInstConf types.AccessConfig actor sdk.AccAddress }{ "upload nobody": { - chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), contractInstConf: types.AllowEverybody, }, "upload everybody": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), contractInstConf: types.AllowEverybody, }, "upload any address - included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), contractInstConf: types.AllowEverybody, }, "upload any address - not included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), contractInstConf: types.AllowEverybody, }, "contract config - subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress), }, "contract config - not subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), + chainConfigs: types.NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), contractInstConf: types.AllowEverybody, }, "upload undefined config - not panics": { - chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), + chainConfigs: types.NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), contractInstConf: types.AllowEverybody, }, } @@ -305,9 +313,139 @@ func TestGovAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { } for name, spec := range specs { t.Run(name, func(t *testing.T) { - policy := GovAuthorizationPolicy{} + policy := newGovAuthorizationPolicy(nil) got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset) assert.True(t, got) }) } } + +func TestGovAuthorizationPolicySubMessageAuthorizationPolicy(t *testing.T) { + specs := map[string]struct { + propagate map[types.AuthorizationPolicyAction]struct{} + entrypoint types.AuthorizationPolicyAction + exp types.AuthorizationPolicy + }{ + "non propagating": { + exp: DefaultAuthorizationPolicy{}, + }, + "propagating with matching action": { + propagate: map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + }, + entrypoint: types.AuthZActionMigrateContract, + exp: NewPartialGovAuthorizationPolicy(DefaultAuthorizationPolicy{}, types.AuthZActionMigrateContract), + }, + "propagating for non matching action": { + propagate: map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + }, + entrypoint: types.AuthZActionInstantiate, + exp: DefaultAuthorizationPolicy{}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := newGovAuthorizationPolicy(spec.propagate).SubMessageAuthorizationPolicy(spec.entrypoint) + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestPartialGovAuthorizationPolicyCanInstantiateContract(t *testing.T) { + specs := map[string]struct { + allowedAction types.AuthorizationPolicyAction + exp bool + }{ + "instantiation granted": { + allowedAction: types.AuthZActionInstantiate, + exp: true, + }, + "decorated policy when instantiation not granted ": { + allowedAction: types.AuthZActionMigrateContract, + exp: false, + }, + "decorated policy when nothing set": { + exp: false, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction) + got := policy.CanInstantiateContract(types.AccessConfig{}, nil) + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestPartialGovAuthorizationPolicyCanModifyContract(t *testing.T) { + specs := map[string]struct { + allowedAction types.AuthorizationPolicyAction + exp bool + }{ + "migration granted": { + allowedAction: types.AuthZActionMigrateContract, + exp: true, + }, + "decorated policy when migration not granted ": { + allowedAction: types.AuthZActionInstantiate, + exp: false, + }, + "decorated policy when nothing set": { + exp: false, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction) + got := policy.CanModifyContract(nil, nil) + assert.Equal(t, spec.exp, got) + }) + } +} + +func TestPartialGovAuthorizationPolicyDelegatedOnly(t *testing.T) { + for _, v := range []types.AuthorizationPolicy{AlwaysRejectTestAuthZPolicy{}, NewGovAuthorizationPolicy()} { + policy := NewPartialGovAuthorizationPolicy(v, types.AuthZActionInstantiate) + + got := policy.CanCreateCode(types.ChainAccessConfigs{}, nil, types.AccessConfig{}) + exp := v.CanCreateCode(types.ChainAccessConfigs{}, nil, types.AccessConfig{}) + assert.Equal(t, exp, got) + + got = policy.CanModifyCodeAccessConfig(nil, nil, false) + exp = v.CanModifyCodeAccessConfig(nil, nil, false) + assert.Equal(t, exp, got) + } +} + +func TestPartialGovAuthorizationPolicySubMessageAuthorizationPolicy(t *testing.T) { + policy := NewPartialGovAuthorizationPolicy(DefaultAuthorizationPolicy{}, types.AuthZActionInstantiate) + for _, v := range []types.AuthorizationPolicyAction{types.AuthZActionInstantiate, types.AuthZActionMigrateContract} { + got := policy.SubMessageAuthorizationPolicy(v) + assert.Equal(t, policy, got) + } +} + +var _ types.AuthorizationPolicy = AlwaysRejectTestAuthZPolicy{} + +type AlwaysRejectTestAuthZPolicy struct{} + +func (a AlwaysRejectTestAuthZPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { + return false +} + +func (a AlwaysRejectTestAuthZPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool { + return false +} + +func (a AlwaysRejectTestAuthZPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool { + return false +} + +func (a AlwaysRejectTestAuthZPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { + return false +} + +func (a AlwaysRejectTestAuthZPolicy) SubMessageAuthorizationPolicy(entrypoint types.AuthorizationPolicyAction) types.AuthorizationPolicy { + return a +} diff --git a/x/wasm/keeper/contract_keeper.go b/x/wasm/keeper/contract_keeper.go index 564adc5532..bebe8a7e94 100644 --- a/x/wasm/keeper/contract_keeper.go +++ b/x/wasm/keeper/contract_keeper.go @@ -10,7 +10,7 @@ var _ types.ContractOpsKeeper = PermissionedKeeper{} // decoratedKeeper contains a subset of the wasm keeper that are already or can be guarded by an authorization policy in the future type decoratedKeeper interface { - create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error) + create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ types.AuthorizationPolicy) (codeID uint64, checksum []byte, err error) instantiate( ctx sdk.Context, @@ -20,26 +20,26 @@ type decoratedKeeper interface { label string, deposit sdk.Coins, addressGenerator AddressGenerator, - authZ AuthorizationPolicy, + authZ types.AuthorizationPolicy, ) (sdk.AccAddress, []byte, error) - migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) - setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error + migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ types.AuthorizationPolicy) ([]byte, error) + setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ types.AuthorizationPolicy) error pinCode(ctx sdk.Context, codeID uint64) error unpinCode(ctx sdk.Context, codeID uint64) error execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) setContractInfoExtension(ctx sdk.Context, contract sdk.AccAddress, extra types.ContractInfoExtension) error - setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, autz AuthorizationPolicy) error + setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, autz types.AuthorizationPolicy) error ClassicAddressGenerator() AddressGenerator } type PermissionedKeeper struct { - authZPolicy AuthorizationPolicy + authZPolicy types.AuthorizationPolicy nested decoratedKeeper } -func NewPermissionedKeeper(nested decoratedKeeper, authZPolicy AuthorizationPolicy) *PermissionedKeeper { +func NewPermissionedKeeper(nested decoratedKeeper, authZPolicy types.AuthorizationPolicy) *PermissionedKeeper { return &PermissionedKeeper{authZPolicy: authZPolicy, nested: nested} } @@ -55,7 +55,7 @@ func (p PermissionedKeeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasm return p.nested.create(ctx, creator, wasmCode, instantiateAccess, p.authZPolicy) } -// Instantiate creates an instance of a WASM contract using the classic sequence based address generator +// AuthZActionInstantiate creates an instance of a WASM contract using the classic sequence based address generator func (p PermissionedKeeper) Instantiate( ctx sdk.Context, codeID uint64, diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 32c982ea51..e5498ba06b 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "context" "encoding/binary" "encoding/hex" "fmt" @@ -34,13 +33,6 @@ import ( // constant value so all nodes run with the same limit. const contractMemoryLimit = 32 -type contextKey int - -const ( - // private type creates an interface key for Context that cannot be accessed by any other package - contextKeyQueryStackSize contextKey = iota -) - // Option is an extension point to instantiate keeper with non default values type Option interface { apply(*Keeper) @@ -102,6 +94,9 @@ type Keeper struct { maxQueryStackSize uint32 acceptedAccountTypes map[reflect.Type]struct{} accountPruner AccountPruner + // propagate gov authZ to sub-messages + propagateGovAuthorization map[types.AuthorizationPolicyAction]struct{} + // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string @@ -149,7 +144,7 @@ func (k Keeper) GetAuthority() string { return k.authority } -func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error) { +func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ types.AuthorizationPolicy) (codeID uint64, checksum []byte, err error) { if creator == nil { return 0, checksum, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil") } @@ -159,7 +154,7 @@ func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, if instantiateAccess == nil { instantiateAccess = &defaultAccessConfig } - chainConfigs := ChainAccessConfigs{ + chainConfigs := types.ChainAccessConfigs{ Instantiate: defaultAccessConfig, Upload: k.getUploadAccessConfig(ctx), } @@ -243,7 +238,7 @@ func (k Keeper) instantiate( label string, deposit sdk.Coins, addressGenerator AddressGenerator, - authPolicy AuthorizationPolicy, + authPolicy types.AuthorizationPolicy, ) (sdk.AccAddress, []byte, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate") @@ -260,7 +255,6 @@ func (k Keeper) instantiate( if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) { return nil, nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate") } - contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash) if k.HasContractInfo(ctx, contractAddress) { return nil, nil, types.ErrDuplicate.Wrap("instance with this code id, sender and label exists: try a different label") @@ -356,6 +350,7 @@ func (k Keeper) instantiate( sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), )) + ctx = types.WithSubMsgAuthzPolicy(ctx, authPolicy.SubMessageAuthorizationPolicy(types.AuthZActionInstantiate)) data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) if err != nil { return nil, nil, errorsmod.Wrap(err, "dispatch") @@ -407,7 +402,14 @@ func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller return data, nil } -func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) { +func (k Keeper) migrate( + ctx sdk.Context, + contractAddress sdk.AccAddress, + caller sdk.AccAddress, + newCodeID uint64, + msg []byte, + authZ types.AuthorizationPolicy, +) ([]byte, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate") migrateSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, newCodeID), len(msg)) ctx.GasMeter().ConsumeGas(migrateSetupCosts, "Loading CosmWasm module: migrate") @@ -472,6 +474,7 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), )) + ctx = types.WithSubMsgAuthzPolicy(ctx, authZ.SubMessageAuthorizationPolicy(types.AuthZActionMigrateContract)) data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) if err != nil { return nil, errorsmod.Wrap(err, "dispatch") @@ -480,9 +483,14 @@ func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller return data, nil } -// Sudo allows priviledged access to a contract. This can never be called by an external tx, but only by +// Sudo allows privileged access to a contract. This can never be called by an external tx, but only by // another native Go module directly, or on-chain governance (if sudo proposals are enabled). Thus, the keeper doesn't // place any access controls on it, that is the responsibility or the app developer (who passes the wasm.Keeper in app.go) +// +// Sub-messages returned from the sudo call to the contract are executed with the default authorization policy. This can be +// customized though by passing a new policy with the context. See types.WithSubMsgAuthzPolicy. +// The policy will be read in msgServer.selectAuthorizationPolicy and used for sub-message executions. +// This is an extension point for some very advanced scenarios only. Use with care! func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) { defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "sudo") contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) @@ -509,6 +517,7 @@ func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), )) + // sudo submessages are executed with the default authorization policy data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) if err != nil { return nil, errorsmod.Wrap(err, "dispatch") @@ -595,7 +604,7 @@ func (k Keeper) IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(a } } -func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error { +func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ types.AuthorizationPolicy) error { contractInfo := k.GetContractInfo(ctx, contractAddress) if contractInfo == nil { return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") @@ -703,13 +712,9 @@ func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []b } func checkAndIncreaseQueryStackSize(ctx sdk.Context, maxQueryStackSize uint32) (sdk.Context, error) { - var queryStackSize uint32 - - // read current value - if size := ctx.Context().Value(contextKeyQueryStackSize); size != nil { - queryStackSize = size.(uint32) - } else { - queryStackSize = 0 + var queryStackSize uint32 = 0 + if size, ok := types.QueryStackSize(ctx); ok { + queryStackSize = size } // increase @@ -721,9 +726,7 @@ func checkAndIncreaseQueryStackSize(ctx sdk.Context, maxQueryStackSize uint32) ( } // set updated stack size - ctx = ctx.WithContext(context.WithValue(ctx.Context(), contextKeyQueryStackSize, queryStackSize)) - - return ctx, nil + return types.WithQueryStackSize(ctx, queryStackSize), nil } // QueryRaw returns the contract's state for give key. Returns `nil` when key is `nil`. @@ -951,7 +954,7 @@ func (k Keeper) setContractInfoExtension(ctx sdk.Context, contractAddr sdk.AccAd } // setAccessConfig updates the access config of a code id. -func (k Keeper) setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, authz AuthorizationPolicy) error { +func (k Keeper) setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, authz types.AuthorizationPolicy) error { info := k.GetCodeInfo(ctx, codeID) if info == nil { return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID) diff --git a/x/wasm/keeper/keeper_cgo.go b/x/wasm/keeper/keeper_cgo.go index 78d9edde0a..7f306516f0 100644 --- a/x/wasm/keeper/keeper_cgo.go +++ b/x/wasm/keeper/keeper_cgo.go @@ -54,7 +54,10 @@ func NewKeeper( gasRegister: NewDefaultWasmGasRegister(), maxQueryStackSize: types.DefaultMaxQueryStackSize, acceptedAccountTypes: defaultAcceptedAccountTypes, - authority: authority, + propagateGovAuthorization: map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionInstantiate: {}, + }, + authority: authority, } keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distrKeeper, channelKeeper, keeper) for _, o := range opts { diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go index d59b62ce4f..7d3a0fa1b2 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -149,7 +149,7 @@ func TestCreateWithParamPermissions(t *testing.T) { otherAddr := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) specs := map[string]struct { - policy AuthorizationPolicy + policy types.AuthorizationPolicy chainUpload types.AccessConfig expError *errorsmod.Error }{ @@ -2052,7 +2052,7 @@ func TestSetAccessConfig(t *testing.T) { const codeID = 1 specs := map[string]struct { - authz AuthorizationPolicy + authz types.AuthorizationPolicy chainPermission types.AccessType newConfig types.AccessConfig caller sdk.AccAddress @@ -2368,7 +2368,7 @@ func TestSetContractAdmin(t *testing.T) { specs := map[string]struct { newAdmin sdk.AccAddress caller sdk.AccAddress - policy AuthorizationPolicy + policy types.AuthorizationPolicy expAdmin string expErr bool }{ diff --git a/x/wasm/keeper/msg_server.go b/x/wasm/keeper/msg_server.go index 3c5c3e72e8..18bfc024cf 100644 --- a/x/wasm/keeper/msg_server.go +++ b/x/wasm/keeper/msg_server.go @@ -32,7 +32,7 @@ func (m msgServer) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*t return nil, errorsmod.Wrap(err, "sender") } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) codeID, checksum, err := m.keeper.create(ctx, senderAddr, msg.WASMByteCode, msg.InstantiatePermission, policy) if err != nil { @@ -63,7 +63,7 @@ func (m msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInst } } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) contractAddr, data, err := m.keeper.instantiate(ctx, msg.CodeID, senderAddr, adminAddr, msg.Msg, msg.Label, msg.Funds, m.keeper.ClassicAddressGenerator(), policy) if err != nil { @@ -94,7 +94,7 @@ func (m msgServer) InstantiateContract2(goCtx context.Context, msg *types.MsgIns } } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) addrGenerator := PredicableAddressGenerator(senderAddr, msg.Salt, msg.Msg, msg.FixMsg) @@ -149,7 +149,7 @@ func (m msgServer) MigrateContract(goCtx context.Context, msg *types.MsgMigrateC return nil, errorsmod.Wrap(err, "contract") } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) data, err := m.keeper.migrate(ctx, contractAddr, senderAddr, msg.CodeID, msg.Msg, policy) if err != nil { @@ -180,7 +180,7 @@ func (m msgServer) UpdateAdmin(goCtx context.Context, msg *types.MsgUpdateAdmin) return nil, errorsmod.Wrap(err, "new admin") } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) if err := m.keeper.setContractAdmin(ctx, contractAddr, senderAddr, newAdminAddr, policy); err != nil { return nil, err @@ -204,7 +204,7 @@ func (m msgServer) ClearAdmin(goCtx context.Context, msg *types.MsgClearAdmin) ( return nil, errorsmod.Wrap(err, "contract") } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) if err := m.keeper.setContractAdmin(ctx, contractAddr, senderAddr, nil, policy); err != nil { return nil, err @@ -223,7 +223,7 @@ func (m msgServer) UpdateInstantiateConfig(goCtx context.Context, msg *types.Msg if err != nil { return nil, errorsmod.Wrap(err, "sender") } - policy := m.selectAuthorizationPolicy(msg.Sender) + policy := m.selectAuthorizationPolicy(ctx, msg.Sender) if err := m.keeper.setAccessConfig(ctx, msg.CodeID, senderAddr, *msg.NewInstantiatePermission, policy); err != nil { return nil, err @@ -339,7 +339,7 @@ func (m msgServer) StoreAndInstantiateContract(goCtx context.Context, req *types } ctx := sdk.UnwrapSDKContext(goCtx) - policy := m.selectAuthorizationPolicy(req.Authority) + policy := m.selectAuthorizationPolicy(ctx, req.Authority) codeID, _, err := m.keeper.create(ctx, authorityAddr, req.WASMByteCode, req.InstantiatePermission, policy) if err != nil { @@ -357,9 +357,12 @@ func (m msgServer) StoreAndInstantiateContract(goCtx context.Context, req *types }, nil } -func (m msgServer) selectAuthorizationPolicy(actor string) AuthorizationPolicy { +func (m msgServer) selectAuthorizationPolicy(ctx sdk.Context, actor string) types.AuthorizationPolicy { if actor == m.keeper.GetAuthority() { - return GovAuthorizationPolicy{} + return newGovAuthorizationPolicy(m.keeper.propagateGovAuthorization) + } + if policy, ok := types.SubMsgAuthzPolicy(ctx); ok { + return policy } return DefaultAuthorizationPolicy{} } diff --git a/x/wasm/keeper/msg_server_test.go b/x/wasm/keeper/msg_server_test.go new file mode 100644 index 0000000000..6a064e50bf --- /dev/null +++ b/x/wasm/keeper/msg_server_test.go @@ -0,0 +1,53 @@ +package keeper + +import ( + "testing" + + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +func TestSelectAuthorizationPolicy(t *testing.T) { + myGovAuthority := RandomAccountAddress(t) + m := msgServer{keeper: &Keeper{ + propagateGovAuthorization: map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + types.AuthZActionInstantiate: {}, + }, + authority: myGovAuthority.String(), + }} + ctx := sdk.NewContext(store.NewCommitMultiStore(nil), tmproto.Header{}, false, log.NewNopLogger()) + + specs := map[string]struct { + ctx sdk.Context + actor sdk.AccAddress + exp types.AuthorizationPolicy + }{ + "always gov policy for gov authority sender": { + ctx: types.WithSubMsgAuthzPolicy(ctx, NewPartialGovAuthorizationPolicy(nil, types.AuthZActionMigrateContract)), + actor: myGovAuthority, + exp: NewGovAuthorizationPolicy(types.AuthZActionMigrateContract, types.AuthZActionInstantiate), + }, + "pick from context when set": { + ctx: types.WithSubMsgAuthzPolicy(ctx, NewPartialGovAuthorizationPolicy(nil, types.AuthZActionMigrateContract)), + actor: RandomAccountAddress(t), + exp: NewPartialGovAuthorizationPolicy(nil, types.AuthZActionMigrateContract), + }, + "fallback to default policy": { + ctx: ctx, + actor: RandomAccountAddress(t), + exp: DefaultAuthorizationPolicy{}, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := m.selectAuthorizationPolicy(spec.ctx, spec.actor.String()) + assert.Equal(t, spec.exp, got) + }) + } +} diff --git a/x/wasm/keeper/options.go b/x/wasm/keeper/options.go index 4537c7969e..144ded5167 100644 --- a/x/wasm/keeper/options.go +++ b/x/wasm/keeper/options.go @@ -161,6 +161,20 @@ func WithAcceptedAccountTypesOnContractInstantiation(accts ...authtypes.AccountI }) } +// WitGovSubMsgAuthZPropagated overwrites the default gov authorization policy for sub-messages +func WitGovSubMsgAuthZPropagated(entries ...types.AuthorizationPolicyAction) Option { + x := make(map[types.AuthorizationPolicyAction]struct{}, len(entries)) + for _, e := range entries { + x[e] = struct{}{} + } + if got, exp := len(x), len(entries); got != exp { + panic(fmt.Sprintf("duplicates in %#v", entries)) + } + return optsFn(func(k *Keeper) { + k.propagateGovAuthorization = x + }) +} + func asTypeMap(accts []authtypes.AccountI) map[reflect.Type]struct{} { m := make(map[reflect.Type]struct{}, len(accts)) for _, a := range accts { diff --git a/x/wasm/keeper/options_test.go b/x/wasm/keeper/options_test.go index e6d055e915..de2f0fde1c 100644 --- a/x/wasm/keeper/options_test.go +++ b/x/wasm/keeper/options_test.go @@ -110,6 +110,16 @@ func TestConstructorOptions(t *testing.T) { assert.Equal(t, VestingCoinBurner{}, k.accountPruner) }, }, + "gov propagation": { + srcOpt: WitGovSubMsgAuthZPropagated(types.AuthZActionInstantiate, types.AuthZActionMigrateContract), + verify: func(t *testing.T, k Keeper) { + exp := map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionInstantiate: {}, + types.AuthZActionMigrateContract: {}, + } + assert.Equal(t, exp, k.propagateGovAuthorization) + }, + }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { diff --git a/x/wasm/keeper/submsg_test.go b/x/wasm/keeper/submsg_test.go index 5dcdf4a98b..f7a075f3c1 100644 --- a/x/wasm/keeper/submsg_test.go +++ b/x/wasm/keeper/submsg_test.go @@ -7,6 +7,13 @@ import ( "strconv" "testing" + errorsmod "cosmossdk.io/errors" + + wasmvm "github.com/CosmWasm/wasmvm" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" @@ -550,3 +557,157 @@ func TestDispatchSubMsgConditionalReplyOn(t *testing.T) { }) } } + +func TestInstantiateGovSubMsgAuthzPropagated(t *testing.T) { + mockWasmVM := &wasmtesting.MockWasmer{} + wasmtesting.MakeInstantiable(mockWasmVM) + var instanceLevel int + // mock wasvm to return new instantiate msgs with the response + mockWasmVM.InstantiateFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + if instanceLevel == 2 { + return &wasmvmtypes.Response{}, 0, nil + } + instanceLevel++ + submsgPayload := fmt.Sprintf(`{"sub":%d}`, instanceLevel) + return &wasmvmtypes.Response{ + Messages: []wasmvmtypes.SubMsg{ + { + ReplyOn: wasmvmtypes.ReplyNever, + Msg: wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{Instantiate: &wasmvmtypes.InstantiateMsg{ + CodeID: 1, Msg: []byte(submsgPayload), Label: "from sub-msg", + }}, + }, + }, + }, + }, 0, nil + } + + ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(mockWasmVM)) + k := keepers.WasmKeeper + + // make chain restricted so that nobody can create instances + newParams := types.DefaultParams() + newParams.InstantiateDefaultPermission = types.AccessTypeNobody + require.NoError(t, k.SetParams(ctx, newParams)) + + example1 := StoreRandomContract(t, ctx, keepers, mockWasmVM) + + specs := map[string]struct { + policy types.AuthorizationPolicy + expErr *errorsmod.Error + }{ + "default policy - rejected": { + policy: DefaultAuthorizationPolicy{}, + expErr: sdkerrors.ErrUnauthorized, + }, + "propagating gov policy - accepted": { + policy: newGovAuthorizationPolicy(map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionInstantiate: {}, + }), + }, + "non propagating gov policy - rejected in sub-msg": { + policy: newGovAuthorizationPolicy(nil), + expErr: sdkerrors.ErrUnauthorized, + }, + "propagating gov policy with diff action - rejected": { + policy: newGovAuthorizationPolicy(map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + }), + expErr: sdkerrors.ErrUnauthorized, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + tCtx, _ := ctx.CacheContext() + instanceLevel = 0 + + _, _, gotErr := k.instantiate(tCtx, example1.CodeID, example1.CreatorAddr, nil, []byte(`{"first":{}}`), "from ext msg", nil, k.ClassicAddressGenerator(), spec.policy) + if spec.expErr != nil { + assert.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + var instanceCount int + k.IterateContractsByCode(tCtx, example1.CodeID, func(address sdk.AccAddress) bool { + instanceCount++ + return false + }) + assert.Equal(t, 3, instanceCount) + assert.Equal(t, 2, instanceLevel) + }) + } +} + +func TestMigrateGovSubMsgAuthzPropagated(t *testing.T) { + mockWasmVM := &wasmtesting.MockWasmer{} + wasmtesting.MakeInstantiable(mockWasmVM) + ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(mockWasmVM)) + k := keepers.WasmKeeper + + example1 := InstantiateHackatomExampleContract(t, ctx, keepers) + example2 := InstantiateIBCReflectContract(t, ctx, keepers) + + var instanceLevel int + // mock wasvm to return new migrate msgs with the response + mockWasmVM.MigrateFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { + if instanceLevel == 1 { + return &wasmvmtypes.Response{}, 0, nil + } + instanceLevel++ + submsgPayload := fmt.Sprintf(`{"sub":%d}`, instanceLevel) + return &wasmvmtypes.Response{ + Messages: []wasmvmtypes.SubMsg{ + { + ReplyOn: wasmvmtypes.ReplyNever, + Msg: wasmvmtypes.CosmosMsg{ + Wasm: &wasmvmtypes.WasmMsg{Migrate: &wasmvmtypes.MigrateMsg{ + ContractAddr: example1.Contract.String(), + NewCodeID: example2.CodeID, + Msg: []byte(submsgPayload), + }}, + }, + }, + }, + }, 0, nil + } + + specs := map[string]struct { + policy types.AuthorizationPolicy + expErr *errorsmod.Error + }{ + "default policy - rejected": { + policy: DefaultAuthorizationPolicy{}, + expErr: sdkerrors.ErrUnauthorized, + }, + "propagating gov policy - accepted": { + policy: newGovAuthorizationPolicy(map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + }), + }, + "non propagating gov policy - rejected in sub-msg": { + policy: newGovAuthorizationPolicy(nil), + expErr: sdkerrors.ErrUnauthorized, + }, + "propagating gov policy with diff action - rejected": { + policy: newGovAuthorizationPolicy(map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionInstantiate: {}, + }), + expErr: sdkerrors.ErrUnauthorized, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + tCtx, _ := ctx.CacheContext() + instanceLevel = 0 + + _, gotErr := k.migrate(tCtx, example1.Contract, RandomAccountAddress(t), example2.CodeID, []byte(`{}`), spec.policy) + if spec.expErr != nil { + assert.ErrorIs(t, gotErr, spec.expErr) + return + } + require.NoError(t, gotErr) + assert.Equal(t, 1, instanceLevel) + }) + } +} diff --git a/x/wasm/types/ante.go b/x/wasm/types/ante.go deleted file mode 100644 index 4c76efdf45..0000000000 --- a/x/wasm/types/ante.go +++ /dev/null @@ -1,24 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type contextKey int - -const ( - // private type creates an interface key for Context that cannot be accessed by any other package - contextKeyTXCount contextKey = iota -) - -// WithTXCounter stores a transaction counter value in the context -func WithTXCounter(ctx sdk.Context, counter uint32) sdk.Context { - return ctx.WithValue(contextKeyTXCount, counter) -} - -// TXCounter returns the tx counter value and found bool from the context. -// The result will be (0, false) for external queries or simulations where no counter available. -func TXCounter(ctx sdk.Context) (uint32, bool) { - val, ok := ctx.Value(contextKeyTXCount).(uint32) - return val, ok -} diff --git a/x/wasm/types/authz_policy.go b/x/wasm/types/authz_policy.go new file mode 100644 index 0000000000..8e812f2b93 --- /dev/null +++ b/x/wasm/types/authz_policy.go @@ -0,0 +1,35 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +// ChainAccessConfigs chain settings +type ChainAccessConfigs struct { + Upload AccessConfig + Instantiate AccessConfig +} + +// NewChainAccessConfigs constructor +func NewChainAccessConfigs(upload AccessConfig, instantiate AccessConfig) ChainAccessConfigs { + return ChainAccessConfigs{Upload: upload, Instantiate: instantiate} +} + +type AuthorizationPolicyAction uint64 + +const ( + _ AuthorizationPolicyAction = iota + AuthZActionInstantiate + AuthZActionMigrateContract +) + +// AuthorizationPolicy is an abstract authorization ruleset defined as an extension point that can be customized by +// chains +type AuthorizationPolicy interface { + CanCreateCode(chainConfigs ChainAccessConfigs, actor types.AccAddress, contractConfig AccessConfig) bool + CanInstantiateContract(c AccessConfig, actor types.AccAddress) bool + CanModifyContract(admin, actor types.AccAddress) bool + CanModifyCodeAccessConfig(creator, actor types.AccAddress, isSubset bool) bool + // SubMessageAuthorizationPolicy returns authorization policy to be used for submessages. Must never be nil + SubMessageAuthorizationPolicy(entrypoint AuthorizationPolicyAction) AuthorizationPolicy +} diff --git a/x/wasm/types/context.go b/x/wasm/types/context.go new file mode 100644 index 0000000000..0463e3ae1a --- /dev/null +++ b/x/wasm/types/context.go @@ -0,0 +1,54 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// private type creates an interface key for Context that cannot be accessed by any other package +type contextKey int + +const ( + // position counter of the TX in the block + contextKeyTXCount contextKey = iota + // smart query stack counter to abort query loops + contextKeyQueryStackSize contextKey = iota + // authorization policy for sub-messages + contextKeySubMsgAuthzPolicy = iota +) + +// WithTXCounter stores a transaction counter value in the context +func WithTXCounter(ctx sdk.Context, counter uint32) sdk.Context { + return ctx.WithValue(contextKeyTXCount, counter) +} + +// TXCounter returns the tx counter value and found bool from the context. +// The result will be (0, false) for external queries or simulations where no counter available. +func TXCounter(ctx sdk.Context) (uint32, bool) { + val, ok := ctx.Value(contextKeyTXCount).(uint32) + return val, ok +} + +// WithQueryStackSize stores the stack position for smart queries in the context returned +func WithQueryStackSize(ctx sdk.Context, counter uint32) sdk.Context { + return ctx.WithValue(contextKeyQueryStackSize, counter) +} + +// QueryStackSize reads the stack position for smart queries from the context +func QueryStackSize(ctx sdk.Context) (uint32, bool) { + val, ok := ctx.Value(contextKeyQueryStackSize).(uint32) + return val, ok +} + +// WithSubMsgAuthzPolicy stores the authorization policy for submessages into the context returned +func WithSubMsgAuthzPolicy(ctx sdk.Context, policy AuthorizationPolicy) sdk.Context { + if policy == nil { + panic("policy must not be nil") + } + return ctx.WithValue(contextKeySubMsgAuthzPolicy, policy) +} + +// SubMsgAuthzPolicy reads the authorization policy for submessages from the context +func SubMsgAuthzPolicy(ctx sdk.Context) (AuthorizationPolicy, bool) { + val, ok := ctx.Value(contextKeySubMsgAuthzPolicy).(AuthorizationPolicy) + return val, ok +}