diff --git a/CHANGELOG.md b/CHANGELOG.md index e4acb30ff6..99be78ca75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased](https://github.com/Finschia/wasmd/compare/v0.1.3...HEAD) ### Features +* [\#46](https://github.com/Finschia/wasmd/pull/46) add admin-related events ### Improvements * [\#43](https://github.com/Finschia/wasmd/pull/43) delete unnecessary test diff --git a/x/wasm/README.md b/x/wasm/README.md index 6b769f5357..cd221877c7 100644 --- a/x/wasm/README.md +++ b/x/wasm/README.md @@ -293,16 +293,20 @@ find out the entire path for the events. | wasm-{customEventType} | {customContractAttributeKey} | {customContractAttributeKey} | (optional) Defined by wasm contract developer | #### MsgUpdateAdmin -| Type | Attribute Key | Attribute Value | Note | -|---------|-------------------|-------------------|---------------------------| -| message | module | wasm | | -| message | sender | {senderAddress} | | +| Type | Attribute Key | Attribute Value | Note | +|-----------------------|-------------------|--------------------|------| +| message | module | wasm | | +| message | sender | {senderAddress} | | +| update_contract_admin | _contract_address | {contract_address} | | +| update_contract_admin | new_admin_address | {adminAddress} | | #### MsgClearAdmin -| Type | Attribute Key | Attribute Value | Note | -|---------|-------------------|-------------------|---------------------------| -| message | module | wasm | | -| message | sender | {senderAddress} | | +| Type | Attribute Key | Attribute Value | Note | +|-----------------------|-------------------|--------------------|--------------| +| message | module | wasm | | +| message | sender | {senderAddress} | | +| update_contract_admin | _contract_address | {contract_address} | | +| update_contract_admin | new_admin_address | "" | empty string | ### Keeper Events In addition to message events, the wasm keeper will produce events when the following methods are called (or any method which ends up calling them) @@ -334,6 +338,20 @@ In addition to message events, the wasm keeper will produce events when the foll |------------|---------------|-----------------|------| | unpin_code | code_id | {codeID} | | +#### SetContractAdmin +| Type | Attribute Key | Attribute Value | Note | +|-----------------------|-------------------|--------------------|------| +| update_contract_admin | _contract_address | {contract_address} | | +| update_contract_admin | new_admin_address | {adminAddress} | | + +#### SetAccessConfig +By governance + +| Type | Attribute Key | Attribute Value | Note | +|---------------------------|-----------------|-----------------|------| +| update_code_access_config | code_permission | {String} | | +| update_code_access_config | code_id | {String} | | + ### Proposal Events If you use wasm proposal, it makes common event like below. @@ -341,7 +359,6 @@ If you use wasm proposal, it makes common event like below. |---------------------|---------------|--------------------|------| | gov_contract_result | result | {resultOfProposal} | | - ## Messages TODO diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 3734c0174f..b6a798f39b 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -619,8 +619,15 @@ func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAd if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract") } - contractInfo.Admin = newAdmin.String() + newAdminStr := newAdmin.String() + contractInfo.Admin = newAdminStr k.storeContractInfo(ctx, contractAddress, contractInfo) + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypeUpdateContractAdmin, + sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), + sdk.NewAttribute(types.AttributeKeyNewAdmin, newAdminStr), + )) + return nil } @@ -958,6 +965,16 @@ func (k Keeper) setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAd info.InstantiateConfig = newConfig k.storeCodeInfo(ctx, codeID, *info) + evt := sdk.NewEvent( + types.EventTypeUpdateCodeAccessConfig, + sdk.NewAttribute(types.AttributeKeyCodePermission, newConfig.Permission.String()), + sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), + ) + if addrs := newConfig.AllAuthorizedAddresses(); len(addrs) != 0 { + attr := sdk.NewAttribute(types.AttributeKeyAuthorizedAddresses, strings.Join(addrs, ",")) + evt.Attributes = append(evt.Attributes, attr.ToKVPair()) + } + ctx.EventManager().EmitEvent(evt) return nil } diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go index 3a26e68b51..85e9097842 100644 --- a/x/wasm/keeper/keeper_test.go +++ b/x/wasm/keeper/keeper_test.go @@ -13,6 +13,7 @@ import ( fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/Finschia/finschia-sdk/baseapp" @@ -2041,6 +2042,7 @@ func TestSetAccessConfig(t *testing.T) { k := keepers.WasmKeeper creatorAddr := RandomAccountAddress(t) nonCreatorAddr := RandomAccountAddress(t) + const codeID = 1 specs := map[string]struct { authz AuthorizationPolicy @@ -2048,18 +2050,27 @@ func TestSetAccessConfig(t *testing.T) { newConfig types.AccessConfig caller sdk.AccAddress expErr bool + expEvts map[string]string }{ "user with new permissions == chain permissions": { authz: DefaultAuthorizationPolicy{}, chainPermission: types.AccessTypeEverybody, newConfig: types.AllowEverybody, caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "Everybody", + }, }, "user with new permissions < chain permissions": { authz: DefaultAuthorizationPolicy{}, chainPermission: types.AccessTypeEverybody, newConfig: types.AllowNobody, caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "Nobody", + }, }, "user with new permissions > chain permissions": { authz: DefaultAuthorizationPolicy{}, @@ -2080,29 +2091,59 @@ func TestSetAccessConfig(t *testing.T) { chainPermission: types.AccessTypeEverybody, newConfig: types.AllowEverybody, caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "Everybody", + }, }, "gov with new permissions < chain permissions": { authz: GovAuthorizationPolicy{}, chainPermission: types.AccessTypeEverybody, newConfig: types.AllowNobody, caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "Nobody", + }, }, "gov with new permissions > chain permissions": { authz: GovAuthorizationPolicy{}, chainPermission: types.AccessTypeNobody, newConfig: types.AccessTypeOnlyAddress.With(creatorAddr), caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "OnlyAddress", + "authorized_addresses": creatorAddr.String(), + }, + }, + "gov with new permissions > chain permissions - multiple addresses": { + authz: GovAuthorizationPolicy{}, + chainPermission: types.AccessTypeNobody, + newConfig: types.AccessTypeAnyOfAddresses.With(creatorAddr, nonCreatorAddr), + caller: creatorAddr, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "AnyOfAddresses", + "authorized_addresses": creatorAddr.String() + "," + nonCreatorAddr.String(), + }, }, "gov without actor": { authz: GovAuthorizationPolicy{}, chainPermission: types.AccessTypeEverybody, newConfig: types.AllowEverybody, + expEvts: map[string]string{ + "code_id": "1", + "code_permission": "Everybody", + }, }, } - const codeID = 1 for name, spec := range specs { t.Run(name, func(t *testing.T) { ctx, _ := parentCtx.CacheContext() + em := sdk.NewEventManager() + ctx = ctx.WithEventManager(em) + newParams := types.DefaultParams() newParams.InstantiateDefaultPermission = spec.chainPermission k.SetParams(ctx, newParams) @@ -2115,6 +2156,10 @@ func TestSetAccessConfig(t *testing.T) { return } require.NoError(t, gotErr) + // and event emitted + require.Len(t, em.Events(), 1) + assert.Equal(t, "update_code_access_config", em.Events()[0].Type) + assert.Equal(t, spec.expEvts, attrsToStringMap(em.Events()[0].Attributes)) }) } } @@ -2233,3 +2278,77 @@ func TestKeeper_GetByteCode(t *testing.T) { }) } } + +func TestSetContractAdmin(t *testing.T) { + parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) + k := keepers.WasmKeeper + myAddr := RandomAccountAddress(t) + example := InstantiateReflectExampleContract(t, parentCtx, keepers) + specs := map[string]struct { + newAdmin sdk.AccAddress + caller sdk.AccAddress + policy AuthorizationPolicy + expAdmin string + expErr bool + }{ + "update admin": { + newAdmin: myAddr, + caller: example.CreatorAddr, + policy: DefaultAuthorizationPolicy{}, + expAdmin: myAddr.String(), + }, + "update admin - unauthorized": { + newAdmin: myAddr, + caller: RandomAccountAddress(t), + policy: DefaultAuthorizationPolicy{}, + expErr: true, + }, + "clear admin - default policy": { + caller: example.CreatorAddr, + policy: DefaultAuthorizationPolicy{}, + expAdmin: "", + }, + "clear admin - unauthorized": { + expAdmin: "", + policy: DefaultAuthorizationPolicy{}, + caller: RandomAccountAddress(t), + expErr: true, + }, + "clear admin - gov policy": { + newAdmin: nil, + policy: GovAuthorizationPolicy{}, + caller: example.CreatorAddr, + expAdmin: "", + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + ctx, _ := parentCtx.CacheContext() + em := sdk.NewEventManager() + ctx = ctx.WithEventManager(em) + gotErr := k.setContractAdmin(ctx, example.Contract, spec.caller, spec.newAdmin, spec.policy) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.Equal(t, spec.expAdmin, k.GetContractInfo(ctx, example.Contract).Admin) + // and event emitted + require.Len(t, em.Events(), 1) + assert.Equal(t, "update_contract_admin", em.Events()[0].Type) + exp := map[string]string{ + "_contract_address": example.Contract.String(), + "new_admin_address": spec.expAdmin, + } + assert.Equal(t, exp, attrsToStringMap(em.Events()[0].Attributes)) + }) + } +} + +func attrsToStringMap(attrs []abci.EventAttribute) map[string]string { + r := make(map[string]string, len(attrs)) + for _, v := range attrs { + r[string(v.Key)] = string(v.Value) + } + return r +} diff --git a/x/wasm/keeper/test_common.go b/x/wasm/keeper/test_common.go index 82916191ac..dfc929c2a1 100644 --- a/x/wasm/keeper/test_common.go +++ b/x/wasm/keeper/test_common.go @@ -670,7 +670,7 @@ func InstantiateReflectExampleContract(t testing.TB, ctx sdk.Context, keepers Te example := StoreReflectContract(t, ctx, keepers) initialAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) label := "demo contract to query" - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, []byte("{}"), label, initialAmount) + contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, example.CreatorAddr, []byte("{}"), label, initialAmount) require.NoError(t, err) return ExampleInstance{ diff --git a/x/wasm/types/events.go b/x/wasm/types/events.go index 579024ed0c..442c3ed369 100644 --- a/x/wasm/types/events.go +++ b/x/wasm/types/events.go @@ -6,24 +6,29 @@ const ( // CustomContractEventPrefix contracts can create custom events. To not mix them with other system events they got the `wasm-` prefix. CustomContractEventPrefix = "wasm-" - EventTypeStoreCode = "store_code" - EventTypeInstantiate = "instantiate" - EventTypeExecute = "execute" - EventTypeMigrate = "migrate" - EventTypePinCode = "pin_code" - EventTypeUnpinCode = "unpin_code" - EventTypeSudo = "sudo" - EventTypeReply = "reply" - EventTypeGovContractResult = "gov_contract_result" + EventTypeStoreCode = "store_code" + EventTypeInstantiate = "instantiate" + EventTypeExecute = "execute" + EventTypeMigrate = "migrate" + EventTypePinCode = "pin_code" + EventTypeUnpinCode = "unpin_code" + EventTypeSudo = "sudo" + EventTypeReply = "reply" + EventTypeGovContractResult = "gov_contract_result" + EventTypeUpdateContractAdmin = "update_contract_admin" + EventTypeUpdateCodeAccessConfig = "update_code_access_config" ) // event attributes returned from contract execution const ( AttributeReservedPrefix = "_" - AttributeKeyContractAddr = "_contract_address" - AttributeKeyCodeID = "code_id" - AttributeKeyChecksum = "code_checksum" - AttributeKeyResultDataHex = "result" - AttributeKeyRequiredCapability = "required_capability" + AttributeKeyContractAddr = "_contract_address" + AttributeKeyCodeID = "code_id" + AttributeKeyChecksum = "code_checksum" + AttributeKeyResultDataHex = "result" + AttributeKeyRequiredCapability = "required_capability" + AttributeKeyNewAdmin = "new_admin_address" + AttributeKeyCodePermission = "code_permission" + AttributeKeyAuthorizedAddresses = "authorized_addresses" ) diff --git a/x/wasm/types/types.pb.go b/x/wasm/types/types.pb.go index 41415d707e..b202ec1ce8 100644 --- a/x/wasm/types/types.pb.go +++ b/x/wasm/types/types.pb.go @@ -1572,6 +1572,17 @@ func (m *AccessConfig) Unmarshal(dAtA []byte) error { return nil } +// si +func (a AccessConfig) AllAuthorizedAddresses() []string { + switch a.Permission { + case AccessTypeAnyOfAddresses: + return a.Addresses + case AccessTypeOnlyAddress: + return []string{a.Address} + } + return []string{} +} + func (m *Params) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0