Gov authorization propagation for sub-messages (#1482)

* Add gov authorization propagation for sub-messages

* Minor update
This commit is contained in:
Alexander Peters
2023-07-06 11:42:01 +02:00
committed by GitHub
parent 1763477831
commit 63f73d3e6c
14 changed files with 620 additions and 109 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}{

View File

@@ -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{}
}

View File

@@ -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)
})
}
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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)
})
}
}

View File

@@ -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
}

View File

@@ -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
}

54
x/wasm/types/context.go Normal file
View File

@@ -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
}