Update contract admin (#124)

* Add update administrator

* Review comments
This commit is contained in:
Alexander Peters 2020-06-05 14:02:01 +02:00 committed by GitHub
parent c5dc55fee6
commit 9a16d583d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 4 deletions

View File

@ -50,7 +50,8 @@ Base64 encoded transactions.
- `raw-bytes` convert raw-bytes to hex
* (wasmcli) [\#191](https://github.com/cosmwasm/wasmd/pull/191) Add cmd `decode-tx`, decodes a tx from hex or base64
* (wasmd) [\#9](https://github.com/cosmwasm/wasmd/pull/9) Allow gzip data in tx body on Create
* (wasmd)[\#124](https://github.com/CosmWasm/wasmd/pull/124) Update contract admin
## [v2.0.3] - 2019-11-04
### Improvements

View File

@ -94,6 +94,7 @@ type (
MsgInstantiateContract = types.MsgInstantiateContract
MsgExecuteContract = types.MsgExecuteContract
MsgMigrateContract = types.MsgMigrateContract
MsgUpdateAdministrator = types.MsgUpdateAdministrator
Model = types.Model
CodeInfo = types.CodeInfo
ContractInfo = types.ContractInfo

View File

@ -39,6 +39,11 @@ func NewHandler(k Keeper) sdk.Handler {
case MsgMigrateContract:
return handleMigration(ctx, k, &msg)
case *MsgUpdateAdministrator:
return handleUpdateContractAdmin(ctx, k, msg)
case MsgUpdateAdministrator:
return handleUpdateContractAdmin(ctx, k, &msg)
default:
errMsg := fmt.Sprintf("unrecognized wasm message type: %T", msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
@ -139,3 +144,19 @@ func handleMigration(ctx sdk.Context, k Keeper, msg *MsgMigrateContract) (*sdk.R
res.Events = append(events, ourEvent)
return res, nil
}
func handleUpdateContractAdmin(ctx sdk.Context, k Keeper, msg *MsgUpdateAdministrator) (*sdk.Result, error) {
if err := k.UpdateContractAdmin(ctx, msg.Contract, msg.Sender, msg.NewAdmin); err != nil {
return nil, err
}
events := ctx.EventManager().Events()
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName),
sdk.NewAttribute(AttributeSigner, msg.Sender.String()),
sdk.NewAttribute(AttributeKeyContract, msg.Contract.String()),
)
return &sdk.Result{
Events: append(events, ourEvent),
}, nil
}

View File

@ -25,7 +25,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) {
}
for _, contract := range data.Contracts {
keeper.setContractInfo(ctx, contract.ContractAddress, contract.ContractInfo)
keeper.setContractInfo(ctx, contract.ContractAddress, &contract.ContractInfo)
keeper.setContractState(ctx, contract.ContractAddress, contract.ContractState)
}

View File

@ -246,7 +246,7 @@ func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
value.Events = nil
contractInfo.UpdateCodeID(ctx, newCodeID)
k.setContractInfo(ctx, contractAddress, *contractInfo)
k.setContractInfo(ctx, contractAddress, contractInfo)
if err := k.dispatchMessages(ctx, contractAddress, res.Messages); err != nil {
return nil, sdkerrors.Wrap(err, "dispatch")
@ -255,6 +255,23 @@ func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
return &value, nil
}
// UpdateContractAdmin sets the admin value on the ContractInfo. New admin can be nil to disable further migrations/ updates.
func (k Keeper) UpdateContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newAdmin sdk.AccAddress) error {
contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if contractInfo.Admin == nil {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "migration not supported by this contract")
}
if !contractInfo.Admin.Equals(caller) {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "no permission")
}
contractInfo.Admin = newAdmin
k.setContractInfo(ctx, contractAddress, contractInfo)
return nil
}
// QuerySmart queries the smart contract itself.
func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) {
ctx = ctx.WithGasMeter(sdk.NewGasMeter(k.queryGasLimit))
@ -326,7 +343,7 @@ func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress)
return &contract
}
func (k Keeper) setContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract types.ContractInfo) {
func (k Keeper) setContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshalBinaryBare(contract))
}

View File

@ -568,6 +568,84 @@ func TestMigrate(t *testing.T) {
}
}
func TestUpdateContractAdmin(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
accKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
_, _, anyAddr := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
instAdmin sdk.AccAddress
newAdmin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
expErr *sdkerrors.Error
}{
"all good with admin set": {
instAdmin: fred,
newAdmin: anyAddr,
caller: fred,
},
"all good with new admin empty": {
instAdmin: fred,
newAdmin: nil,
caller: fred,
},
"prevent update when admin was not set on instantiate": {
caller: creator,
newAdmin: fred,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent updates from non admin address": {
instAdmin: creator,
newAdmin: fred,
caller: fred,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing contract addr": {
instAdmin: creator,
caller: creator,
overrideContractAddr: anyAddr,
expErr: sdkerrors.ErrInvalidRequest,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
addr, err := keeper.Instantiate(ctx, originalContractID, creator, spec.instAdmin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
}
err = keeper.UpdateContractAdmin(ctx, addr, spec.caller, spec.newAdmin)
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
cInfo := keeper.GetContractInfo(ctx, addr)
assert.Equal(t, spec.newAdmin, cInfo.Admin)
})
}
}
type InitMsg struct {
Verifier sdk.AccAddress `json:"verifier"`
Beneficiary sdk.AccAddress `json:"beneficiary"`

View File

@ -11,6 +11,7 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&MsgInstantiateContract{}, "wasm/instantiate", nil)
cdc.RegisterConcrete(&MsgExecuteContract{}, "wasm/execute", nil)
cdc.RegisterConcrete(&MsgMigrateContract{}, "wasm/migrate", nil)
cdc.RegisterConcrete(&MsgUpdateAdministrator{}, "wasm/update-contract-admin", nil)
}
// ModuleCdc generic sealed codec to be used throughout module

View File

@ -227,3 +227,43 @@ func (msg MsgMigrateContract) GetSignBytes() []byte {
func (msg MsgMigrateContract) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}
type MsgUpdateAdministrator struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
NewAdmin sdk.AccAddress `json:"new_admin,omitempty" yaml:"new_admin"`
Contract sdk.AccAddress `json:"contract" yaml:"contract"`
}
func (msg MsgUpdateAdministrator) Route() string {
return RouterKey
}
func (msg MsgUpdateAdministrator) Type() string {
return "update-contract-admin"
}
func (msg MsgUpdateAdministrator) ValidateBasic() error {
if err := sdk.VerifyAddressFormat(msg.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
if err := sdk.VerifyAddressFormat(msg.Contract); err != nil {
return sdkerrors.Wrap(err, "contract")
}
if len(msg.NewAdmin) != 0 {
if err := sdk.VerifyAddressFormat(msg.NewAdmin); err != nil {
return sdkerrors.Wrap(err, "new admin")
}
if msg.Sender.Equals(msg.NewAdmin) {
return sdkerrors.Wrap(ErrInvalidMsg, "new admin is the same as the old")
}
}
return nil
}
func (msg MsgUpdateAdministrator) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg))
}
func (msg MsgUpdateAdministrator) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Sender}
}

View File

@ -209,7 +209,76 @@ func TestInstantiateContractValidation(t *testing.T) {
}
})
}
}
func TestMsgUpdateAdministrator(t *testing.T) {
badAddress, err := sdk.AccAddressFromHex("012345")
require.NoError(t, err)
// proper address size
goodAddress := sdk.AccAddress(make([]byte, 20))
otherGoodAddress := sdk.AccAddress(bytes.Repeat([]byte{0x1}, 20))
anotherGoodAddress := sdk.AccAddress(bytes.Repeat([]byte{0x2}, 20))
specs := map[string]struct {
src MsgUpdateAdministrator
expErr bool
}{
"all good": {
src: MsgUpdateAdministrator{
Sender: goodAddress,
NewAdmin: otherGoodAddress,
Contract: anotherGoodAddress,
},
},
"new admin optional": {
src: MsgUpdateAdministrator{
Sender: goodAddress,
Contract: anotherGoodAddress,
},
},
"bad sender": {
src: MsgUpdateAdministrator{
Sender: badAddress,
NewAdmin: otherGoodAddress,
Contract: anotherGoodAddress,
},
expErr: true,
},
"bad new admin": {
src: MsgUpdateAdministrator{
Sender: goodAddress,
NewAdmin: badAddress,
Contract: anotherGoodAddress,
},
expErr: true,
},
"bad contract addr": {
src: MsgUpdateAdministrator{
Sender: goodAddress,
NewAdmin: otherGoodAddress,
Contract: badAddress,
},
expErr: true,
},
"new admin same as old admin": {
src: MsgUpdateAdministrator{
Sender: goodAddress,
NewAdmin: goodAddress,
Contract: anotherGoodAddress,
},
expErr: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
err := spec.src.ValidateBasic()
if spec.expErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}
func TestMsgMigrateContract(t *testing.T) {