diff --git a/x/wasm/keeper/authz_policy.go b/x/wasm/keeper/authz_policy.go index 74c029e969..069bd08613 100644 --- a/x/wasm/keeper/authz_policy.go +++ b/x/wasm/keeper/authz_policy.go @@ -10,20 +10,28 @@ var _ types.AuthorizationPolicy = DefaultAuthorizationPolicy{} type DefaultAuthorizationPolicy struct{} -func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { +func (p DefaultAuthorizationPolicy) CanCreateCode(checksum []byte, chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { return chainConfigs.Upload.Allowed(actor) && contractConfig.IsSubset(chainConfigs.Instantiate) } -func (p DefaultAuthorizationPolicy) CanInstantiateContract(config types.AccessConfig, actor sdk.AccAddress) bool { - return config.Allowed(actor) +func (p DefaultAuthorizationPolicy) CanInstantiateContract(i *types.CodeInfo, actor sdk.AccAddress) bool { + return i.InstantiateConfig.Allowed(actor) } -func (p DefaultAuthorizationPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool { +func (p DefaultAuthorizationPolicy) CanModifyContract(contract *types.ContractInfo, actor sdk.AccAddress) bool { + admin, err := sdk.AccAddressFromBech32(contract.Admin) + if err != nil { + return false + } return admin != nil && admin.Equals(actor) } -func (p DefaultAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { +func (p DefaultAuthorizationPolicy) CanModifyCodeAccessConfig(code *types.CodeInfo, actor sdk.AccAddress, isSubset bool) bool { + creator, err := sdk.AccAddressFromBech32(code.Creator) + if err != nil { + return false + } return creator != nil && creator.Equals(actor) && isSubset } @@ -55,19 +63,19 @@ func newGovAuthorizationPolicy(propagate map[types.AuthorizationPolicyAction]str } // CanCreateCode implements AuthorizationPolicy.CanCreateCode to allow gov actions. Always returns true. -func (p GovAuthorizationPolicy) CanCreateCode(types.ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool { +func (p GovAuthorizationPolicy) CanCreateCode([]byte, types.ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool { return true } -func (p GovAuthorizationPolicy) CanInstantiateContract(types.AccessConfig, sdk.AccAddress) bool { +func (p GovAuthorizationPolicy) CanInstantiateContract(*types.CodeInfo, sdk.AccAddress) bool { return true } -func (p GovAuthorizationPolicy) CanModifyContract(sdk.AccAddress, sdk.AccAddress) bool { +func (p GovAuthorizationPolicy) CanModifyContract(*types.ContractInfo, sdk.AccAddress) bool { return true } -func (p GovAuthorizationPolicy) CanModifyCodeAccessConfig(sdk.AccAddress, sdk.AccAddress, bool) bool { +func (p GovAuthorizationPolicy) CanModifyCodeAccessConfig(*types.CodeInfo, sdk.AccAddress, bool) bool { return true } @@ -96,26 +104,26 @@ func NewPartialGovAuthorizationPolicy(defaultPolicy types.AuthorizationPolicy, e 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) CanCreateCode(checksum []byte, chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { + return p.defaultPolicy.CanCreateCode(checksum, chainConfigs, actor, contractConfig) } -func (p PartialGovAuthorizationPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool { +func (p PartialGovAuthorizationPolicy) CanInstantiateContract(c *types.CodeInfo, 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 { +func (p PartialGovAuthorizationPolicy) CanModifyContract(contract *types.ContractInfo, actor sdk.AccAddress) bool { if p.action == types.AuthZActionMigrateContract { return true } - return p.defaultPolicy.CanModifyContract(admin, actor) + return p.defaultPolicy.CanModifyContract(contract, actor) } -func (p PartialGovAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { - return p.defaultPolicy.CanModifyCodeAccessConfig(creator, actor, isSubset) +func (p PartialGovAuthorizationPolicy) CanModifyCodeAccessConfig(code *types.CodeInfo, actor sdk.AccAddress, isSubset bool) bool { + return p.defaultPolicy.CanModifyCodeAccessConfig(code, actor, isSubset) } // SubMessageAuthorizationPolicy always returns self diff --git a/x/wasm/keeper/authz_policy_test.go b/x/wasm/keeper/authz_policy_test.go index 4d918e512a..6cafe0e144 100644 --- a/x/wasm/keeper/authz_policy_test.go +++ b/x/wasm/keeper/authz_policy_test.go @@ -60,12 +60,12 @@ func TestDefaultAuthzPolicyCanCreateCode(t *testing.T) { t.Run(name, func(t *testing.T) { policy := DefaultAuthorizationPolicy{} if !spec.panics { - got := policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) + got := policy.CanCreateCode([]byte{}, spec.chainConfigs, myActorAddress, spec.contractInstConf) assert.Equal(t, spec.exp, got) return } assert.Panics(t, func() { - policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) + policy.CanCreateCode([]byte{}, spec.chainConfigs, myActorAddress, spec.contractInstConf) }) }) } @@ -104,13 +104,15 @@ func TestDefaultAuthzPolicyCanInstantiateContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := DefaultAuthorizationPolicy{} + info := types.NewCodeInfo([]byte{}, RandomAccountAddress(t), spec.config) + if !spec.panics { - got := policy.CanInstantiateContract(spec.config, myActorAddress) + got := policy.CanInstantiateContract(&info, myActorAddress) assert.Equal(t, spec.exp, got) return } assert.Panics(t, func() { - policy.CanInstantiateContract(spec.config, myActorAddress) + policy.CanInstantiateContract(&info, myActorAddress) }) }) } @@ -139,7 +141,8 @@ func TestDefaultAuthzPolicyCanModifyContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := DefaultAuthorizationPolicy{} - got := policy.CanModifyContract(spec.admin, myActorAddress) + contract := types.NewContractInfo(1, spec.admin, spec.admin, "", nil) + got := policy.CanModifyContract(&contract, myActorAddress) assert.Equal(t, spec.exp, got) }) } @@ -175,7 +178,8 @@ func TestDefaultAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := DefaultAuthorizationPolicy{} - got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset) + info := types.NewCodeInfo([]byte{}, spec.admin, types.AccessConfig{}) + got := policy.CanModifyCodeAccessConfig(&info, myActorAddress, spec.subset) assert.Equal(t, spec.exp, got) }) } @@ -229,7 +233,7 @@ func TestGovAuthzPolicyCanCreateCode(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := GovAuthorizationPolicy{} - got := policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) + got := policy.CanCreateCode([]byte{}, spec.chainConfigs, myActorAddress, spec.contractInstConf) assert.True(t, got) }) } @@ -261,7 +265,8 @@ func TestGovAuthzPolicyCanInstantiateContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := GovAuthorizationPolicy{} - got := policy.CanInstantiateContract(spec.config, myActorAddress) + info := types.NewCodeInfo([]byte{}, RandomAccountAddress(t), spec.config) + got := policy.CanInstantiateContract(&info, myActorAddress) assert.True(t, got) }) } @@ -285,7 +290,8 @@ func TestGovAuthzPolicyCanModifyContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := GovAuthorizationPolicy{} - got := policy.CanModifyContract(spec.admin, myActorAddress) + contract := types.NewContractInfo(1, spec.admin, spec.admin, "", nil) + got := policy.CanModifyContract(&contract, myActorAddress) assert.True(t, got) }) } @@ -315,7 +321,8 @@ func TestGovAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := newGovAuthorizationPolicy(nil) - got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset) + info := types.NewCodeInfo([]byte{}, spec.admin, types.AccessConfig{}) + got := policy.CanModifyCodeAccessConfig(&info, myActorAddress, spec.subset) assert.True(t, got) }) } @@ -373,7 +380,8 @@ func TestPartialGovAuthorizationPolicyCanInstantiateContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction) - got := policy.CanInstantiateContract(types.AccessConfig{}, nil) + info := types.NewCodeInfo([]byte{}, RandomAccountAddress(t), types.AccessConfig{}) + got := policy.CanInstantiateContract(&info, nil) assert.Equal(t, spec.exp, got) }) } @@ -399,22 +407,25 @@ func TestPartialGovAuthorizationPolicyCanModifyContract(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { policy := NewPartialGovAuthorizationPolicy(AlwaysRejectTestAuthZPolicy{}, spec.allowedAction) - got := policy.CanModifyContract(nil, nil) + contract := types.NewContractInfo(1, nil, nil, "", nil) + got := policy.CanModifyContract(&contract, nil) assert.Equal(t, spec.exp, got) }) } } func TestPartialGovAuthorizationPolicyDelegatedOnly(t *testing.T) { + info := types.NewCodeInfo([]byte{}, RandomAccountAddress(t), types.AccessConfig{}) + 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{}) + got := policy.CanCreateCode([]byte{}, types.ChainAccessConfigs{}, nil, types.AccessConfig{}) + exp := v.CanCreateCode([]byte{}, types.ChainAccessConfigs{}, nil, types.AccessConfig{}) assert.Equal(t, exp, got) - got = policy.CanModifyCodeAccessConfig(nil, nil, false) - exp = v.CanModifyCodeAccessConfig(nil, nil, false) + got = policy.CanModifyCodeAccessConfig(&info, nil, false) + exp = v.CanModifyCodeAccessConfig(&info, nil, false) assert.Equal(t, exp, got) } } @@ -431,19 +442,19 @@ var _ types.AuthorizationPolicy = AlwaysRejectTestAuthZPolicy{} type AlwaysRejectTestAuthZPolicy struct{} -func (a AlwaysRejectTestAuthZPolicy) CanCreateCode(chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { +func (a AlwaysRejectTestAuthZPolicy) CanCreateCode(checksum []byte, chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { return false } -func (a AlwaysRejectTestAuthZPolicy) CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool { +func (a AlwaysRejectTestAuthZPolicy) CanInstantiateContract(code *types.CodeInfo, actor sdk.AccAddress) bool { return false } -func (a AlwaysRejectTestAuthZPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool { +func (a AlwaysRejectTestAuthZPolicy) CanModifyContract(contract *types.ContractInfo, actor sdk.AccAddress) bool { return false } -func (a AlwaysRejectTestAuthZPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { +func (a AlwaysRejectTestAuthZPolicy) CanModifyCodeAccessConfig(code *types.CodeInfo, actor sdk.AccAddress, isSubset bool) bool { return false } diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 40d9cc1767..43fbf9ac75 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -108,6 +108,8 @@ type Keeper struct { // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string + + customAuthPolicy func(ctx context.Context, actor string) (types.AuthorizationPolicy, bool) } func (k Keeper) getUploadAccessConfig(ctx context.Context) types.AccessConfig { @@ -157,10 +159,6 @@ func (k Keeper) create(ctx context.Context, creator sdk.AccAddress, wasmCode []b Upload: k.getUploadAccessConfig(sdkCtx), } - if !authZ.CanCreateCode(chainConfigs, creator, *instantiateAccess) { - return 0, checksum, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not create code") - } - if ioutils.IsGzip(wasmCode) { sdkCtx.GasMeter().ConsumeGas(k.gasRegister.UncompressCosts(len(wasmCode)), "Uncompress gzip bytecode") wasmCode, err = ioutils.Uncompress(wasmCode, int64(types.MaxWasmSize)) @@ -171,6 +169,11 @@ func (k Keeper) create(ctx context.Context, creator sdk.AccAddress, wasmCode []b gasLeft := k.runtimeGasForContract(sdkCtx) checksum, gasUsed, err := k.wasmVM.StoreCode(wasmCode, gasLeft) + + if !authZ.CanCreateCode(checksum, chainConfigs, creator, *instantiateAccess) { + return 0, checksum, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not create code") + } + k.consumeRuntimeGas(sdkCtx, gasUsed) if err != nil { return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error()) @@ -258,7 +261,7 @@ func (k Keeper) instantiate( if codeInfo == nil { return nil, nil, types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID) } - if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) { + if !authPolicy.CanInstantiateContract(codeInfo, creator) { return nil, nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate") } contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash) @@ -452,16 +455,17 @@ func (k Keeper) migrate( if contractInfo == nil { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") } - if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not migrate") - } newCodeInfo := k.GetCodeInfo(ctx, newCodeID) if newCodeInfo == nil { return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown code") } - if !authZ.CanInstantiateContract(newCodeInfo.InstantiateConfig, caller) { + if !authZ.CanModifyContract(contractInfo, caller) { + return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not migrate") + } + + if !authZ.CanInstantiateContract(newCodeInfo, caller) { return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "to use new code") } @@ -723,7 +727,7 @@ func (k Keeper) setContractAdmin(ctx context.Context, contractAddress, caller, n if contractInfo == nil { return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") } - if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { + if !authZ.CanModifyContract(contractInfo, caller) { return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract") } newAdminStr := newAdmin.String() @@ -744,7 +748,7 @@ func (k Keeper) setContractLabel(ctx context.Context, contractAddress, caller sd if contractInfo == nil { return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") } - if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { + if !authZ.CanModifyContract(contractInfo, caller) { return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract") } contractInfo.Label = newLabel @@ -1178,7 +1182,7 @@ func (k Keeper) setAccessConfig(ctx context.Context, codeID uint64, caller sdk.A return types.ErrNoSuchCodeFn(codeID).Wrapf("code id %d", codeID) } isSubset := newConfig.Permission.IsSubset(k.getInstantiateAccessConfig(ctx)) - if !authz.CanModifyCodeAccessConfig(sdk.MustAccAddressFromBech32(info.Creator), caller, isSubset) { + if !authz.CanModifyCodeAccessConfig(info, caller, isSubset) { return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify code access config") } diff --git a/x/wasm/keeper/msg_server.go b/x/wasm/keeper/msg_server.go index 691ffa445c..0ddd64de16 100644 --- a/x/wasm/keeper/msg_server.go +++ b/x/wasm/keeper/msg_server.go @@ -418,6 +418,11 @@ func contains[T comparable](src []T, o T) bool { } func (m msgServer) selectAuthorizationPolicy(ctx context.Context, actor string) types.AuthorizationPolicy { + if m.keeper.customAuthPolicy != nil { + if policy, ok := m.keeper.customAuthPolicy(ctx, actor); ok { + return policy + } + } if actor == m.keeper.GetAuthority() { return newGovAuthorizationPolicy(m.keeper.propagateGovAuthorization) } diff --git a/x/wasm/keeper/msg_server_test.go b/x/wasm/keeper/msg_server_test.go index ff34162ccf..d7dc203fbf 100644 --- a/x/wasm/keeper/msg_server_test.go +++ b/x/wasm/keeper/msg_server_test.go @@ -1,6 +1,7 @@ package keeper import ( + "context" "testing" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -57,3 +58,51 @@ func TestSelectAuthorizationPolicy(t *testing.T) { }) } } + +var _ types.AuthorizationPolicy = TestCustomAuthorizationPolicy{} + +type TestCustomAuthorizationPolicy struct{} + +func (p TestCustomAuthorizationPolicy) CanCreateCode(checksum []byte, chainConfigs types.ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { + return true +} + +func (p TestCustomAuthorizationPolicy) CanInstantiateContract(code *types.CodeInfo, actor sdk.AccAddress) bool { + return true +} + +func (p TestCustomAuthorizationPolicy) CanModifyContract(contract *types.ContractInfo, actor sdk.AccAddress) bool { + return true +} + +func (p TestCustomAuthorizationPolicy) CanModifyCodeAccessConfig(code *types.CodeInfo, actor sdk.AccAddress, isSubset bool) bool { + return true +} + +func (p TestCustomAuthorizationPolicy) SubMessageAuthorizationPolicy(_ types.AuthorizationPolicyAction) types.AuthorizationPolicy { + return p +} + +func CustomAuthPolicy(ctx context.Context, actor string) (types.AuthorizationPolicy, bool) { + return TestCustomAuthorizationPolicy{}, true +} + +func TestSelectCustomAuthorizationPolicy(t *testing.T) { + myGovAuthority := RandomAccountAddress(t) + m := msgServer{keeper: &Keeper{ + propagateGovAuthorization: map[types.AuthorizationPolicyAction]struct{}{ + types.AuthZActionMigrateContract: {}, + types.AuthZActionInstantiate: {}, + }, + authority: myGovAuthority.String(), + customAuthPolicy: CustomAuthPolicy, + }} + + ms := store.NewCommitMultiStore(dbm.NewMemDB(), log.NewTestLogger(t), storemetrics.NewNoOpMetrics()) + ctx := sdk.NewContext(ms, tmproto.Header{}, false, log.NewNopLogger()) + + t.Run("TestSelectCustomAuthorizationPolicy", func(t *testing.T) { + got := m.selectAuthorizationPolicy(ctx, RandomAccountAddress(t).String()) + assert.Equal(t, TestCustomAuthorizationPolicy{}, got) + }) +} diff --git a/x/wasm/types/authz_policy.go b/x/wasm/types/authz_policy.go index 335f315d03..93b3f48fd1 100644 --- a/x/wasm/types/authz_policy.go +++ b/x/wasm/types/authz_policy.go @@ -26,10 +26,10 @@ const ( // 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 + CanCreateCode(checksum []byte, chainConfigs ChainAccessConfigs, actor types.AccAddress, contractConfig AccessConfig) bool + CanInstantiateContract(code *CodeInfo, actor types.AccAddress) bool + CanModifyContract(contract *ContractInfo, actor types.AccAddress) bool + CanModifyCodeAccessConfig(code *CodeInfo, actor types.AccAddress, isSubset bool) bool // SubMessageAuthorizationPolicy returns authorization policy to be used for submessages. Must never be nil SubMessageAuthorizationPolicy(entrypoint AuthorizationPolicyAction) AuthorizationPolicy }