Gov authorization propagation for sub-messages (#1482)
* Add gov authorization propagation for sub-messages * Minor update
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}{
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
53
x/wasm/keeper/msg_server_test.go
Normal file
53
x/wasm/keeper/msg_server_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
35
x/wasm/types/authz_policy.go
Normal file
35
x/wasm/types/authz_policy.go
Normal 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
54
x/wasm/types/context.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user