Gov proposal types and handler (#173)

* Gov proposal types and handler

* Review feedback

* Store code proposal and test

* Accept only valid proposals

* Complete proposal types
This commit is contained in:
Alexander Peters
2020-07-07 17:55:11 +02:00
committed by GitHub
parent 939525f468
commit 8a547179d2
13 changed files with 1612 additions and 57 deletions

View File

@@ -7,12 +7,7 @@ import (
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
dbm "github.com/tendermint/tm-db"
"github.com/CosmWasm/wasmd/x/wasm"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
@@ -35,8 +30,11 @@ import (
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/cosmos/cosmos-sdk/x/upgrade"
upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client"
"github.com/CosmWasm/wasmd/x/wasm"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
dbm "github.com/tendermint/tm-db"
)
const appName = "WasmApp"
@@ -228,10 +226,6 @@ func NewWasmApp(
AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)).
AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)).
AddRoute(upgrade.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.upgradeKeeper))
app.govKeeper = gov.NewKeeper(
app.cdc, keys[gov.StoreKey], app.subspaces[gov.ModuleName],
app.supplyKeeper, &stakingKeeper, govRouter,
)
// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@@ -257,6 +251,15 @@ func NewWasmApp(
supportedFeatures := "staking"
app.wasmKeeper = wasm.NewKeeper(app.cdc, keys[wasm.StoreKey], app.accountKeeper, app.bankKeeper, app.stakingKeeper, wasmRouter, wasmDir, wasmConfig, supportedFeatures, nil, nil)
if len(wasm.DefaultEnabledProposals) != 0 {
govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.wasmKeeper, wasm.DefaultEnabledProposals))
}
app.govKeeper = gov.NewKeeper(
app.cdc, keys[gov.StoreKey], app.subspaces[gov.ModuleName],
app.supplyKeeper, &stakingKeeper, govRouter,
)
// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
app.mm = module.NewManager(

View File

@@ -66,24 +66,26 @@ var (
MakeTestCodec = keeper.MakeTestCodec
CreateTestInput = keeper.CreateTestInput
TestHandler = keeper.TestHandler
NewWasmProposalHandler = keeper.NewWasmProposalHandler
// variable aliases
ModuleCdc = types.ModuleCdc
DefaultCodespace = types.DefaultCodespace
ErrCreateFailed = types.ErrCreateFailed
ErrAccountExists = types.ErrAccountExists
ErrInstantiateFailed = types.ErrInstantiateFailed
ErrExecuteFailed = types.ErrExecuteFailed
ErrGasLimit = types.ErrGasLimit
ErrInvalidGenesis = types.ErrInvalidGenesis
ErrNotFound = types.ErrNotFound
ErrQueryFailed = types.ErrQueryFailed
ErrInvalidMsg = types.ErrInvalidMsg
KeyLastCodeID = types.KeyLastCodeID
KeyLastInstanceID = types.KeyLastInstanceID
CodeKeyPrefix = types.CodeKeyPrefix
ContractKeyPrefix = types.ContractKeyPrefix
ContractStorePrefix = types.ContractStorePrefix
ModuleCdc = types.ModuleCdc
DefaultCodespace = types.DefaultCodespace
ErrCreateFailed = types.ErrCreateFailed
ErrAccountExists = types.ErrAccountExists
ErrInstantiateFailed = types.ErrInstantiateFailed
ErrExecuteFailed = types.ErrExecuteFailed
ErrGasLimit = types.ErrGasLimit
ErrInvalidGenesis = types.ErrInvalidGenesis
ErrNotFound = types.ErrNotFound
ErrQueryFailed = types.ErrQueryFailed
ErrInvalidMsg = types.ErrInvalidMsg
KeyLastCodeID = types.KeyLastCodeID
KeyLastInstanceID = types.KeyLastInstanceID
CodeKeyPrefix = types.CodeKeyPrefix
ContractKeyPrefix = types.ContractKeyPrefix
ContractStorePrefix = types.ContractStorePrefix
DefaultEnabledProposals = types.DefaultEnabledProposals
)
type (

View File

@@ -0,0 +1,42 @@
package keeper
import (
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
type AuthorizationPolicy interface {
CanCreateCode(c types.AccessConfig, creator sdk.AccAddress) bool
CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool
CanModifyContract(admin, actor sdk.AccAddress) bool
}
type DefaultAuthorizationPolicy struct {
}
func (p DefaultAuthorizationPolicy) CanCreateCode(config types.AccessConfig, actor sdk.AccAddress) bool {
return config.Allowed(actor)
}
func (p DefaultAuthorizationPolicy) CanInstantiateContract(config types.AccessConfig, actor sdk.AccAddress) bool {
return config.Allowed(actor)
}
func (p DefaultAuthorizationPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool {
return admin != nil && admin.Equals(actor)
}
type GovAuthorizationPolicy struct {
}
func (p GovAuthorizationPolicy) CanCreateCode(types.AccessConfig, sdk.AccAddress) bool {
return true
}
func (p GovAuthorizationPolicy) CanInstantiateContract(types.AccessConfig, sdk.AccAddress) bool {
return true
}
func (p GovAuthorizationPolicy) CanModifyContract(sdk.AccAddress, sdk.AccAddress) bool {
return true
}

View File

@@ -49,6 +49,7 @@ type Keeper struct {
messenger MessageHandler
// queryGasLimit is the max wasm gas that can be spent on executing a query with a contract
queryGasLimit uint64
authZPolicy AuthorizationPolicy
}
// NewKeeper creates a new contract Keeper instance
@@ -71,6 +72,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.Accou
bankKeeper: bankKeeper,
messenger: messenger,
queryGasLimit: wasmConfig.SmartQueryGasLimit,
authZPolicy: DefaultAuthorizationPolicy{},
}
keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, keeper).Merge(customPlugins)
return keeper
@@ -241,18 +243,20 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller
// Migrate allows to upgrade a contract to a new code with data migration.
func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte) (*sdk.Result, error) {
return k.migrate(ctx, contractAddress, caller, newCodeID, msg, k.authZPolicy)
}
func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) (*sdk.Result, error) {
ctx.GasMeter().ConsumeGas(InstanceCost, "Loading CosmWasm module: migrate")
contractInfo := k.GetContractInfo(ctx, contractAddress)
if contractInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract")
}
if contractInfo.Admin == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "migration not supported by this contract")
}
if !contractInfo.Admin.Equals(caller) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "no permission")
if !authZ.CanModifyContract(contractInfo.Admin, caller) {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not migrate")
}
newCodeInfo := k.GetCodeInfo(ctx, newCodeID)
if newCodeInfo == nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "unknown code")
@@ -294,34 +298,23 @@ func (k Keeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller
// UpdateContractAdmin sets the admin value on the ContractInfo. It must be a valid address (use ClearContractAdmin to remove it)
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
return k.setContractAdmin(ctx, contractAddress, caller, newAdmin, k.authZPolicy)
}
// ClearContractAdmin sets the admin value on the ContractInfo to nil, to disable further migrations/ updates.
func (k Keeper) ClearContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress) error {
return k.setContractAdmin(ctx, contractAddress, caller, nil, k.authZPolicy)
}
func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) 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 !authZ.CanModifyContract(contractInfo.Admin, caller) {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract")
}
if !contractInfo.Admin.Equals(caller) {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "no permission")
}
contractInfo.Admin = nil
contractInfo.Admin = newAdmin
k.setContractInfo(ctx, contractAddress, contractInfo)
return nil
}

View File

@@ -0,0 +1,141 @@
package keeper
import (
"fmt"
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
const ( // TODO: same as in handler
AttributeKeyContract = "contract_address"
AttributeKeyCodeID = "code_id"
AttributeSigner = "signer"
)
// NewWasmProposalHandler creates a new governance Handler for wasm proposals
func NewWasmProposalHandler(k Keeper, enabledTypes map[string]struct{}) govtypes.Handler {
return func(ctx sdk.Context, content govtypes.Content) error {
if content == nil {
return sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "content must not be empty")
}
if _, ok := enabledTypes[content.ProposalType()]; !ok {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unsupported wasm proposal content type: %q", content.ProposalType())
}
switch c := content.(type) {
case *types.StoreCodeProposal:
return handleStoreCodeProposal(ctx, k, *c)
case *types.InstantiateContractProposal:
return handleInstantiateProposal(ctx, k, *c)
case *types.MigrateContractProposal:
return handleMigrateProposal(ctx, k, *c)
case *types.UpdateAdminProposal:
return handleUpdateAdminProposal(ctx, k, *c)
case *types.ClearAdminProposal:
return handleClearAdminProposal(ctx, k, *c)
default:
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized wasm proposal content type: %T", c)
}
}
}
func handleStoreCodeProposal(ctx sdk.Context, k Keeper, p types.StoreCodeProposal) error {
if err := p.ValidateBasic(); err != nil {
return err
}
codeID, err := k.Create(ctx, p.Creator, p.WASMByteCode, p.Source, p.Builder)
if err != nil {
return err
}
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
//sdk.NewAttribute(AttributeSigner, p.Creator.String()), // todo: creator is not signer. rename attribute?
sdk.NewAttribute(AttributeKeyCodeID, fmt.Sprintf("%d", codeID)),
)
ctx.EventManager().EmitEvent(ourEvent)
return nil
}
func handleInstantiateProposal(ctx sdk.Context, k Keeper, p types.InstantiateContractProposal) error {
if err := p.ValidateBasic(); err != nil {
return err
}
contractAddr, err := k.Instantiate(ctx, p.Code, p.Creator, p.Admin, p.InitMsg, p.Label, p.InitFunds)
if err != nil {
return err
}
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
//sdk.NewAttribute(AttributeSigner, p.Creator.String()),
sdk.NewAttribute(AttributeKeyCodeID, fmt.Sprintf("%d", p.Code)),
sdk.NewAttribute(AttributeKeyContract, contractAddr.String()),
)
ctx.EventManager().EmitEvent(ourEvent)
return nil
}
func handleMigrateProposal(ctx sdk.Context, k Keeper, p types.MigrateContractProposal) error {
if err := p.ValidateBasic(); err != nil {
return err
}
res, err := k.migrate(ctx, p.Contract, p.Sender, p.Code, p.MigrateMsg, GovAuthorizationPolicy{})
if err != nil {
return err
}
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
//sdk.NewAttribute(AttributeSigner, p.Creator.String()),
sdk.NewAttribute(AttributeKeyContract, p.Contract.String()),
)
ctx.EventManager().EmitEvents(append(res.Events, ourEvent))
return nil
}
func handleUpdateAdminProposal(ctx sdk.Context, k Keeper, p types.UpdateAdminProposal) error {
if err := p.ValidateBasic(); err != nil {
return err
}
if err := k.setContractAdmin(ctx, p.Contract, p.Sender, p.NewAdmin, GovAuthorizationPolicy{}); err != nil {
return err
}
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
//sdk.NewAttribute(AttributeSigner, p.Creator.String()),
sdk.NewAttribute(AttributeKeyContract, p.Contract.String()),
)
ctx.EventManager().EmitEvent(ourEvent)
return nil
}
func handleClearAdminProposal(ctx sdk.Context, k Keeper, p types.ClearAdminProposal) error {
if err := p.ValidateBasic(); err != nil {
return err
}
if err := k.setContractAdmin(ctx, p.Contract, p.Sender, nil, GovAuthorizationPolicy{}); err != nil {
return err
}
ourEvent := sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
//sdk.NewAttribute(AttributeSigner, p.Creator.String()),
sdk.NewAttribute(AttributeKeyContract, p.Contract.String()),
)
ctx.EventManager().EmitEvent(ourEvent)
return nil
}

View File

@@ -0,0 +1,270 @@
package keeper
import (
"bytes"
"encoding/hex"
"encoding/json"
"io/ioutil"
"os"
"testing"
"github.com/CosmWasm/wasmd/x/wasm/internal/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStoreCodeProposal(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, "staking", nil, nil)
govKeeper, wasmKeeper := keepers.GovKeeper, keepers.WasmKeeper
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
var anyAddress sdk.AccAddress = make([]byte, sdk.AddrLen)
src := types.StoreCodeProposalFixture(func(p *types.StoreCodeProposal) {
p.Creator = anyAddress
p.WASMByteCode = wasmCode
p.Source = "https://example.com/mysource"
p.Builder = "foo/bar:v0.0.0"
})
// when stored
storedProposal, err := govKeeper.SubmitProposal(ctx, &src)
require.NoError(t, err)
// and proposal execute
handler := govKeeper.Router().GetRoute(storedProposal.ProposalRoute())
err = handler(ctx, storedProposal.Content)
require.NoError(t, err)
// then
cInfo := wasmKeeper.GetCodeInfo(ctx, 1)
require.NotNil(t, cInfo)
assert.Equal(t, anyAddress, cInfo.Creator)
assert.Equal(t, "foo/bar:v0.0.0", cInfo.Builder)
assert.Equal(t, "https://example.com/mysource", cInfo.Source)
storedCode, err := wasmKeeper.GetByteCode(ctx, 1)
require.NoError(t, err)
assert.Equal(t, wasmCode, storedCode)
}
func TestInstantiateProposal(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, "staking", nil, nil)
govKeeper, wasmKeeper := keepers.GovKeeper, keepers.WasmKeeper
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
require.NoError(t, wasmKeeper.importCode(ctx, 1,
types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)),
wasmCode),
)
var (
oneAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, sdk.AddrLen)
)
src := types.InstantiateContractProposalFixture(func(p *types.InstantiateContractProposal) {
p.Code = 1
p.Creator = oneAddress
p.Admin = otherAddress
p.Label = "testing"
})
// when stored
storedProposal, err := govKeeper.SubmitProposal(ctx, &src)
require.NoError(t, err)
// and proposal execute
handler := govKeeper.Router().GetRoute(storedProposal.ProposalRoute())
err = handler(ctx, storedProposal.Content)
require.NoError(t, err)
// then
contractAddr, err := sdk.AccAddressFromBech32("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5")
require.NoError(t, err)
cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, cInfo)
assert.Equal(t, uint64(1), cInfo.CodeID)
assert.Equal(t, oneAddress, cInfo.Creator)
assert.Equal(t, otherAddress, cInfo.Admin)
assert.Equal(t, "testing", cInfo.Label)
assert.Equal(t, src.InitMsg, cInfo.InitMsg)
}
func TestMigrateProposal(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, "staking", nil, nil)
govKeeper, wasmKeeper := keepers.GovKeeper, keepers.WasmKeeper
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
codeInfoFixture := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode))
require.NoError(t, wasmKeeper.importCode(ctx, 1, codeInfoFixture, wasmCode))
require.NoError(t, wasmKeeper.importCode(ctx, 2, codeInfoFixture, wasmCode))
var (
anyAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, sdk.AddrLen)
contractAddr = contractAddress(1, 1)
)
contractInfoFixture := types.ContractInfoFixture(func(c *types.ContractInfo) {
c.Label = "testing"
c.Admin = anyAddress
})
key, err := hex.DecodeString("636F6E666967")
require.NoError(t, err)
m := types.Model{Key: key, Value: []byte(`{"verifier":"AAAAAAAAAAAAAAAAAAAAAAAAAAA=","beneficiary":"AAAAAAAAAAAAAAAAAAAAAAAAAAA=","funder":"AQEBAQEBAQEBAQEBAQEBAQEBAQE="}`)}
require.NoError(t, wasmKeeper.importContract(ctx, contractAddr, &contractInfoFixture, []types.Model{m}))
migMsg := struct {
Verifier sdk.AccAddress `json:"verifier"`
}{Verifier: otherAddress}
migMsgBz, err := json.Marshal(migMsg)
require.NoError(t, err)
src := types.MigrateContractProposal{
WasmProposal: types.WasmProposal{
Title: "Foo",
Description: "Bar",
},
Code: 2,
Contract: contractAddr,
MigrateMsg: migMsgBz,
Sender: otherAddress,
}
// when stored
storedProposal, err := govKeeper.SubmitProposal(ctx, &src)
require.NoError(t, err)
// and proposal execute
handler := govKeeper.Router().GetRoute(storedProposal.ProposalRoute())
err = handler(ctx, storedProposal.Content)
require.NoError(t, err)
// then
require.NoError(t, err)
cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, cInfo)
assert.Equal(t, uint64(2), cInfo.CodeID)
assert.Equal(t, uint64(1), cInfo.PreviousCodeID)
assert.Equal(t, anyAddress, cInfo.Admin)
assert.Equal(t, "testing", cInfo.Label)
}
func TestAdminProposals(t *testing.T) {
var (
anyAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, sdk.AddrLen)
contractAddr = contractAddress(1, 1)
)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
specs := map[string]struct {
state types.ContractInfo
srcProposal gov.Content
expAdmin sdk.AccAddress
}{
"update with different admin": {
state: types.ContractInfoFixture(),
srcProposal: &types.UpdateAdminProposal{
WasmProposal: types.WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Sender: anyAddress,
NewAdmin: otherAddress,
},
expAdmin: otherAddress,
},
"update with old admin empty": {
state: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Admin = nil
}),
srcProposal: &types.UpdateAdminProposal{
WasmProposal: types.WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Sender: anyAddress,
NewAdmin: otherAddress,
},
expAdmin: otherAddress,
},
"clear admin": {
state: types.ContractInfoFixture(),
srcProposal: &types.ClearAdminProposal{
WasmProposal: types.WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Sender: anyAddress,
},
expAdmin: nil,
},
"clear with old admin empty": {
state: types.ContractInfoFixture(func(info *types.ContractInfo) {
info.Admin = nil
}),
srcProposal: &types.ClearAdminProposal{
WasmProposal: types.WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Sender: anyAddress,
},
expAdmin: nil,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, keepers := CreateTestInput(t, false, tempDir, "staking", nil, nil)
govKeeper, wasmKeeper := keepers.GovKeeper, keepers.WasmKeeper
codeInfoFixture := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode))
require.NoError(t, wasmKeeper.importCode(ctx, 1, codeInfoFixture, wasmCode))
require.NoError(t, wasmKeeper.importContract(ctx, contractAddr, &spec.state, []types.Model{}))
// when stored
storedProposal, err := govKeeper.SubmitProposal(ctx, spec.srcProposal)
require.NoError(t, err)
// and execute proposal
handler := govKeeper.Router().GetRoute(storedProposal.ProposalRoute())
err = handler(ctx, storedProposal.Content)
require.NoError(t, err)
// then
cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr)
require.NotNil(t, cInfo)
assert.Equal(t, spec.expAdmin, cInfo.Admin)
})
}
}

View File

@@ -2,10 +2,13 @@ package keeper
import (
"fmt"
"github.com/cosmos/cosmos-sdk/x/distribution"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
@@ -39,6 +42,7 @@ func MakeTestCodec() *codec.Codec {
supply.AppModuleBasic{}.RegisterCodec(cdc)
staking.AppModuleBasic{}.RegisterCodec(cdc)
distribution.AppModuleBasic{}.RegisterCodec(cdc)
gov.RegisterCodec(cdc)
wasmTypes.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
@@ -60,6 +64,8 @@ type TestKeepers struct {
WasmKeeper Keeper
DistKeeper distribution.Keeper
SupplyKeeper supply.Keeper
GovKeeper gov.Keeper
BankKeeper bank.Keeper
}
// encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default)
@@ -71,6 +77,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat
keyDistro := sdk.NewKVStoreKey(distribution.StoreKey)
keyParams := sdk.NewKVStoreKey(params.StoreKey)
tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey)
keyGov := sdk.NewKVStoreKey(govtypes.StoreKey)
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db)
@@ -81,6 +88,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat
ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyDistro, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
ms.MountStoreWithDB(keyGov, sdk.StoreTypeIAVL, db)
err := ms.LoadLatestVersion()
require.Nil(t, err)
@@ -113,7 +121,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat
//mint.ModuleName: {supply.Minter},
staking.BondedPoolName: {supply.Burner, supply.Staking},
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
//gov.ModuleName: {supply.Burner},
gov.ModuleName: {supply.Burner},
}
supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bankKeeper, maccPerms)
@@ -164,12 +172,27 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat
// add wasm handler so we can loop-back (contracts calling contracts)
router.AddRoute(wasmTypes.RouterKey, TestHandler(keeper))
govRouter := gov.NewRouter().
AddRoute(govtypes.RouterKey, govtypes.ProposalHandler).
AddRoute(wasmTypes.RouterKey, NewWasmProposalHandler(keeper, wasmTypes.DefaultEnabledProposals))
govKeeper := gov.NewKeeper(
cdc, keyGov, pk.Subspace(govtypes.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()), supplyKeeper, stakingKeeper, govRouter,
)
govKeeper.SetProposalID(ctx, govtypes.DefaultStartingProposalID)
govKeeper.SetDepositParams(ctx, govtypes.DefaultDepositParams())
govKeeper.SetVotingParams(ctx, govtypes.DefaultVotingParams())
govKeeper.SetTallyParams(ctx, govtypes.DefaultTallyParams())
keepers := TestKeepers{
AccountKeeper: accountKeeper,
SupplyKeeper: supplyKeeper,
StakingKeeper: stakingKeeper,
DistKeeper: distKeeper,
WasmKeeper: keeper,
GovKeeper: govKeeper,
BankKeeper: bankKeeper,
}
return ctx, keepers
}

View File

@@ -2,7 +2,6 @@ package types
import (
"github.com/cosmos/cosmos-sdk/codec"
// "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// RegisterCodec registers the account types and interface
@@ -13,6 +12,12 @@ func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&MsgMigrateContract{}, "wasm/migrate", nil)
cdc.RegisterConcrete(&MsgUpdateAdmin{}, "wasm/update-contract-admin", nil)
cdc.RegisterConcrete(&MsgClearAdmin{}, "wasm/clear-contract-admin", nil)
cdc.RegisterConcrete(&StoreCodeProposal{}, "wasm/store-proposal", nil)
cdc.RegisterConcrete(&InstantiateContractProposal{}, "wasm/instantiate-proposal", nil)
cdc.RegisterConcrete(&MigrateContractProposal{}, "wasm/migrate-proposal", nil)
cdc.RegisterConcrete(&UpdateAdminProposal{}, "wasm/update-admin-proposal", nil)
cdc.RegisterConcrete(&ClearAdminProposal{}, "wasm/clear-admin-proposal", nil)
}
// ModuleCdc generic sealed codec to be used throughout module

View File

@@ -83,8 +83,8 @@ func (msg MsgInstantiateContract) ValidateBasic() error {
return err
}
if msg.InitFunds.IsAnyNegative() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "negative InitFunds")
if !msg.InitFunds.IsValid() {
return sdkerrors.ErrInvalidCoins
}
if len(msg.Admin) != 0 {

View File

@@ -0,0 +1,107 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/params"
)
const (
// DefaultParamspace for params keeper
DefaultParamspace = ModuleName
)
var ParamStoreKeyUploadAccess = []byte("uploadAccess")
var ParamStoreKeyInstantiateAccess = []byte("instantiateAccess")
type AccessType uint8
const (
Undefined AccessType = 0
Nobody AccessType = 1
OnlyAddress AccessType = 2
Everybody AccessType = 3
)
func (a AccessType) With(addr sdk.AccAddress) AccessConfig {
switch a {
case Nobody:
return AllowNobody
case OnlyAddress:
if err := sdk.VerifyAddressFormat(addr); err != nil {
panic(err)
}
return AccessConfig{Type: OnlyAddress, Address: addr}
case Everybody:
return AllowEverybody
}
panic("unsupported access type")
}
type AccessConfig struct {
Type AccessType `json:"type"`
Address sdk.AccAddress `json:"address"`
}
var (
DefaultUploadAccess = AllowEverybody
AllowEverybody = AccessConfig{Type: Everybody}
AllowNobody = AccessConfig{Type: Nobody}
)
// ParamKeyTable type declaration for parameters
func ParamKeyTable() params.KeyTable {
return params.NewKeyTable(
params.NewParamSetPair(ParamStoreKeyUploadAccess, AllowEverybody, validateAccessConfig),
params.NewParamSetPair(ParamStoreKeyInstantiateAccess, Everybody, validateAccessType),
)
}
func validateAccessConfig(i interface{}) error {
v, ok := i.(AccessConfig)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
return v.ValidateBasic()
}
func validateAccessType(i interface{}) error {
v, ok := i.(AccessType)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if v == Undefined {
return sdkerrors.Wrap(ErrEmpty, "type")
}
// TODO: should we prevent Nobody here?
return nil
}
func (v AccessConfig) ValidateBasic() error {
switch v.Type {
case Undefined:
return sdkerrors.Wrap(ErrEmpty, "type")
case Nobody, Everybody:
if len(v.Address) != 0 {
return sdkerrors.Wrap(ErrInvalid, "address not allowed for this type")
}
case OnlyAddress:
return sdk.VerifyAddressFormat(v.Address)
}
return sdkerrors.Wrap(ErrInvalid, "unknown type")
}
func (v AccessConfig) Allowed(actor sdk.AccAddress) bool {
switch v.Type {
case Nobody:
return false
case Everybody:
return true
case OnlyAddress:
return v.Address.Equals(actor)
default:
panic("unknown type")
}
}

View File

@@ -0,0 +1,292 @@
package types
import (
"encoding/json"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
const (
ProposalTypeStoreCode = "StoreCode"
ProposalTypeStoreInstantiateContract = "InstantiateContract"
ProposalTypeMigrateContract = "MigrateContract"
ProposalTypeUpdateAdmin = "UpdateAdmin"
ProposalTypeClearAdmin = "ClearAdmin"
)
var DefaultEnabledProposals = map[string]struct{}{
ProposalTypeStoreCode: {},
ProposalTypeStoreInstantiateContract: {},
ProposalTypeMigrateContract: {},
ProposalTypeUpdateAdmin: {},
ProposalTypeClearAdmin: {},
}
type WasmProposal struct {
Title string `json:"title" yaml:"title"`
Description string `json:"description" yaml:"description"`
}
// GetTitle returns the title of a parameter change proposal.
func (p WasmProposal) GetTitle() string { return p.Title }
// GetDescription returns the description of a parameter change proposal.
func (p WasmProposal) GetDescription() string { return p.Description }
// ProposalRoute returns the routing key of a parameter change proposal.
func (p WasmProposal) ProposalRoute() string { return RouterKey }
// ValidateBasic validates the proposal
func (p WasmProposal) ValidateBasic() error {
if strings.TrimSpace(p.Title) != p.Title {
return sdkerrors.Wrap(govtypes.ErrInvalidProposalContent, "proposal title must not start/end with white spaces")
}
if len(p.Title) == 0 {
return sdkerrors.Wrap(govtypes.ErrInvalidProposalContent, "proposal title cannot be blank")
}
if len(p.Title) > govtypes.MaxTitleLength {
return sdkerrors.Wrapf(govtypes.ErrInvalidProposalContent, "proposal title is longer than max length of %d", govtypes.MaxTitleLength)
}
if strings.TrimSpace(p.Description) != p.Description {
return sdkerrors.Wrap(govtypes.ErrInvalidProposalContent, "proposal description must not start/end with white spaces")
}
if len(p.Description) == 0 {
return sdkerrors.Wrap(govtypes.ErrInvalidProposalContent, "proposal description cannot be blank")
}
if len(p.Description) > govtypes.MaxDescriptionLength {
return sdkerrors.Wrapf(govtypes.ErrInvalidProposalContent, "proposal description is longer than max length of %d", govtypes.MaxDescriptionLength)
}
return nil
}
type StoreCodeProposal struct {
WasmProposal
// Creator is the address that "owns" the code object
Creator sdk.AccAddress `json:"creator" yaml:"creator"`
// WASMByteCode can be raw or gzip compressed
WASMByteCode []byte `json:"wasm_byte_code" yaml:"wasm_byte_code"`
// Source is a valid absolute HTTPS URI to the contract's source code, optional
Source string `json:"source" yaml:"source"`
// Builder is a valid docker image name with tag, optional
Builder string `json:"builder" yaml:"builder"`
}
// ProposalType returns the type
func (p StoreCodeProposal) ProposalType() string { return ProposalTypeStoreCode }
// ValidateBasic validates the proposal
func (p StoreCodeProposal) ValidateBasic() error {
if err := p.WasmProposal.ValidateBasic(); err != nil {
return err
}
if err := sdk.VerifyAddressFormat(p.Creator); err != nil {
return err
}
if err := validateWasmCode(p.WASMByteCode); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "code bytes %s", err.Error())
}
if err := validateSourceURL(p.Source); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "source %s", err.Error())
}
if err := validateBuilder(p.Builder); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "builder %s", err.Error())
}
return nil
}
// String implements the Stringer interface.
func (p StoreCodeProposal) String() string {
return fmt.Sprintf(`Store Code Proposal:
Title: %s
Description: %s
Creator: %s
WasmCode: %X
Source: %s
Builder: %s
`, p.Title, p.Description, p.Creator, p.WASMByteCode, p.Source, p.Builder)
}
type InstantiateContractProposal struct {
WasmProposal
// Creator is the address that pays the init funds
Creator sdk.AccAddress `json:"sender" yaml:"sender"`
// Admin is an optional address that can execute migrations
Admin sdk.AccAddress `json:"admin,omitempty" yaml:"admin"`
Code uint64 `json:"code_id" yaml:"code_id"`
Label string `json:"label" yaml:"label"`
InitMsg json.RawMessage `json:"init_msg" yaml:"init_msg"`
InitFunds sdk.Coins `json:"init_funds" yaml:"init_funds"`
}
// ProposalType returns the type
func (p InstantiateContractProposal) ProposalType() string {
return ProposalTypeStoreInstantiateContract
}
// ValidateBasic validates the proposal
func (p InstantiateContractProposal) ValidateBasic() error {
if err := p.WasmProposal.ValidateBasic(); err != nil {
return err
}
if err := sdk.VerifyAddressFormat(p.Creator); err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "creator is required")
}
if p.Code == 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "code_id is required")
}
if err := validateLabel(p.Label); err != nil {
return err
}
if !p.InitFunds.IsValid() {
return sdkerrors.ErrInvalidCoins
}
if len(p.Admin) != 0 {
if err := sdk.VerifyAddressFormat(p.Admin); err != nil {
return err
}
}
return nil
}
// String implements the Stringer interface.
func (p InstantiateContractProposal) String() string {
return fmt.Sprintf(`Instantiate Code Proposal:
Title: %s
Description: %s
Creator: %s
Admin: %s
Code id: %d
Label: %s
InitMsg: %q
InitFunds: %s
`, p.Title, p.Description, p.Creator, p.Admin, p.Code, p.Label, p.InitMsg, p.InitFunds)
}
type MigrateContractProposal struct {
WasmProposal
Contract sdk.AccAddress `json:"contract" yaml:"contract"`
Code uint64 `json:"code_id" yaml:"code_id"`
MigrateMsg json.RawMessage `json:"msg" yaml:"msg"`
// Sender is the role that is passed to the contract's environment
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
}
// ProposalType returns the type
func (p MigrateContractProposal) ProposalType() string { return ProposalTypeMigrateContract }
// ValidateBasic validates the proposal
func (p MigrateContractProposal) ValidateBasic() error {
if err := p.WasmProposal.ValidateBasic(); err != nil {
return err
}
if p.Code == 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "code_id is required")
}
if err := sdk.VerifyAddressFormat(p.Contract); err != nil {
return sdkerrors.Wrap(err, "contract")
}
if err := sdk.VerifyAddressFormat(p.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
return nil
}
// String implements the Stringer interface.
func (p MigrateContractProposal) String() string {
return fmt.Sprintf(`Migrate Contract Proposal:
Title: %s
Description: %s
Contract: %s
Code id: %d
Sender: %s
MigrateMsg %q
`, p.Title, p.Description, p.Contract, p.Code, p.Sender, p.MigrateMsg)
}
type UpdateAdminProposal struct {
WasmProposal
NewAdmin sdk.AccAddress `json:"new_admin" yaml:"new_admin"`
Contract sdk.AccAddress `json:"contract" yaml:"contract"`
// Sender is the role that is passed to the contract's environment
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
}
// ProposalType returns the type
func (p UpdateAdminProposal) ProposalType() string { return ProposalTypeUpdateAdmin }
// ValidateBasic validates the proposal
func (p UpdateAdminProposal) ValidateBasic() error {
if err := p.WasmProposal.ValidateBasic(); err != nil {
return err
}
if err := sdk.VerifyAddressFormat(p.Contract); err != nil {
return sdkerrors.Wrap(err, "contract")
}
if err := sdk.VerifyAddressFormat(p.NewAdmin); err != nil {
return sdkerrors.Wrap(err, "new admin")
}
if err := sdk.VerifyAddressFormat(p.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
return nil
}
// String implements the Stringer interface.
func (p UpdateAdminProposal) String() string {
return fmt.Sprintf(`Update Contract Admin Proposal:
Title: %s
Description: %s
Contract: %s
Sender: %s
New Admin: %s
`, p.Title, p.Description, p.Contract, p.Sender, p.NewAdmin)
}
type ClearAdminProposal struct {
WasmProposal
Contract sdk.AccAddress `json:"contract" yaml:"contract"`
// Sender is the role that is passed to the contract's environment
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
}
// ProposalType returns the type
func (p ClearAdminProposal) ProposalType() string { return ProposalTypeClearAdmin }
// ValidateBasic validates the proposal
func (p ClearAdminProposal) ValidateBasic() error {
if err := p.WasmProposal.ValidateBasic(); err != nil {
return err
}
if err := sdk.VerifyAddressFormat(p.Contract); err != nil {
return sdkerrors.Wrap(err, "contract")
}
if err := sdk.VerifyAddressFormat(p.Sender); err != nil {
return sdkerrors.Wrap(err, "sender")
}
return nil
}
// String implements the Stringer interface.
func (p ClearAdminProposal) String() string {
return fmt.Sprintf(`Clear Contract Admin Proposal:
Title: %s
Description: %s
Contract: %s
Sender: %s
`, p.Title, p.Description, p.Contract, p.Sender)
}

View File

@@ -0,0 +1,534 @@
package types
import (
"bytes"
"strings"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidateWasmProposal(t *testing.T) {
specs := map[string]struct {
src WasmProposal
expErr bool
}{
"all good": {src: WasmProposal{
Title: "Foo",
Description: "Bar",
}},
"prevent empty title": {
src: WasmProposal{
Description: "Bar",
},
expErr: true,
},
"prevent white space only title": {
src: WasmProposal{
Title: " ",
Description: "Bar",
},
expErr: true,
},
"prevent leading white spaces in title": {
src: WasmProposal{
Title: " Foo",
Description: "Bar",
},
expErr: true,
},
"prevent title exceeds max length ": {
src: WasmProposal{
Title: strings.Repeat("a", govtypes.MaxTitleLength+1),
Description: "Bar",
},
expErr: true,
},
"prevent empty description": {
src: WasmProposal{
Title: "Foo",
},
expErr: true,
},
"prevent leading white spaces in description": {
src: WasmProposal{
Title: "Foo",
Description: " Bar",
},
expErr: true,
},
"prevent white space only description": {
src: WasmProposal{
Title: "Foo",
Description: " ",
},
expErr: true,
},
"prevent descr exceeds max length ": {
src: WasmProposal{
Title: "Foo",
Description: strings.Repeat("a", govtypes.MaxDescriptionLength+1),
},
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateStoreCodeProposal(t *testing.T) {
var (
invalidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen-1)
)
specs := map[string]struct {
src StoreCodeProposal
expErr bool
}{
"all good": {
src: StoreCodeProposalFixture(),
},
"without source": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.Source = ""
}),
},
"base data missing": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.WasmProposal = WasmProposal{}
}),
expErr: true,
},
"creator missing": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.Creator = nil
}),
expErr: true,
},
"creator invalid": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.Creator = invalidAddress
}),
expErr: true,
},
"wasm code missing": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.WASMByteCode = nil
}),
expErr: true,
},
"wasm code invalid": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.WASMByteCode = bytes.Repeat([]byte{0x0}, MaxWasmSize+1)
}),
expErr: true,
},
"source invalid": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.Source = "not an url"
}),
expErr: true,
},
"builder invalid": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.Builder = "not a builder"
}),
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateInstantiateContractProposal(t *testing.T) {
var (
invalidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen-1)
)
specs := map[string]struct {
src InstantiateContractProposal
expErr bool
}{
"all good": {
src: InstantiateContractProposalFixture(),
},
"without admin": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Admin = nil
}),
},
"without init msg": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.InitMsg = nil
}),
},
"without init funds": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.InitFunds = nil
}),
},
"base data missing": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.WasmProposal = WasmProposal{}
}),
expErr: true,
},
"creator missing": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Creator = nil
}),
expErr: true,
},
"creator invalid": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Creator = invalidAddress
}),
expErr: true,
},
"admin invalid": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Admin = invalidAddress
}),
expErr: true,
},
"code id empty": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Code = 0
}),
expErr: true,
},
"label empty": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.Label = ""
}),
expErr: true,
},
"init funds negative": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.InitFunds = sdk.Coins{{Denom: "foo", Amount: sdk.NewInt(-1)}}
}),
expErr: true,
},
"init funds with duplicates": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.InitFunds = sdk.Coins{{Denom: "foo", Amount: sdk.NewInt(1)}, {Denom: "foo", Amount: sdk.NewInt(2)}}
}),
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateMigrateContractProposal(t *testing.T) {
var (
invalidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen-1)
)
specs := map[string]struct {
src MigrateContractProposal
expErr bool
}{
"all good": {
src: MigrateContractProposalFixture(),
},
"without migrate msg": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.MigrateMsg = nil
}),
},
"base data missing": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.WasmProposal = WasmProposal{}
}),
expErr: true,
},
"contract missing": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.Contract = nil
}),
expErr: true,
},
"contract invalid": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.Contract = invalidAddress
}),
expErr: true,
},
"code id empty": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.Code = 0
}),
expErr: true,
},
"sender missing": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.Sender = nil
}),
expErr: true,
},
"sender invalid": {
src: MigrateContractProposalFixture(func(p *MigrateContractProposal) {
p.Sender = invalidAddress
}),
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateUpdateAdminProposal(t *testing.T) {
var (
invalidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen-1)
)
specs := map[string]struct {
src UpdateAdminProposal
expErr bool
}{
"all good": {
src: UpdateAdminProposalFixture(),
},
"base data missing": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.WasmProposal = WasmProposal{}
}),
expErr: true,
},
"contract missing": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.Contract = nil
}),
expErr: true,
},
"contract invalid": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.Contract = invalidAddress
}),
expErr: true,
},
"admin missing": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.NewAdmin = nil
}),
expErr: true,
},
"admin invalid": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.NewAdmin = invalidAddress
}),
expErr: true,
},
"sender missing": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.Sender = nil
}),
expErr: true,
},
"sender invalid": {
src: UpdateAdminProposalFixture(func(p *UpdateAdminProposal) {
p.Sender = invalidAddress
}),
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestValidateClearAdminProposal(t *testing.T) {
var (
invalidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen-1)
)
specs := map[string]struct {
src ClearAdminProposal
expErr bool
}{
"all good": {
src: ClearAdminProposalFixture(),
},
"base data missing": {
src: ClearAdminProposalFixture(func(p *ClearAdminProposal) {
p.WasmProposal = WasmProposal{}
}),
expErr: true,
},
"contract missing": {
src: ClearAdminProposalFixture(func(p *ClearAdminProposal) {
p.Contract = nil
}),
expErr: true,
},
"contract invalid": {
src: ClearAdminProposalFixture(func(p *ClearAdminProposal) {
p.Contract = invalidAddress
}),
expErr: true,
},
"sender missing": {
src: ClearAdminProposalFixture(func(p *ClearAdminProposal) {
p.Sender = nil
}),
expErr: true,
},
"sender invalid": {
src: ClearAdminProposalFixture(func(p *ClearAdminProposal) {
p.Sender = invalidAddress
}),
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)
} else {
require.NoError(t, err)
}
})
}
}
func TestProposalStrings(t *testing.T) {
specs := map[string]struct {
src gov.Content
exp string
}{
"store code": {
src: StoreCodeProposalFixture(func(p *StoreCodeProposal) {
p.WASMByteCode = []byte{01, 02, 03, 04, 05, 06, 07, 0x08, 0x09, 0x0a}
}),
exp: `Store Code Proposal:
Title: Foo
Description: Bar
Creator: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
WasmCode: 0102030405060708090A
Source: https://example.com/code
Builder: foo/bar:latest
`,
},
"instantiate contract": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) {
p.InitFunds = sdk.Coins{{Denom: "foo", Amount: sdk.NewInt(1)}, {Denom: "bar", Amount: sdk.NewInt(2)}}
}),
exp: `Instantiate Code Proposal:
Title: Foo
Description: Bar
Creator: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
Admin: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
Code id: 1
Label: testing
InitMsg: "{\"verifier\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\",\"beneficiary\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\"}"
InitFunds: 1foo,2bar
`,
},
"instantiate contract without funds": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) { p.InitFunds = nil }),
exp: `Instantiate Code Proposal:
Title: Foo
Description: Bar
Creator: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
Admin: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
Code id: 1
Label: testing
InitMsg: "{\"verifier\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\",\"beneficiary\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\"}"
InitFunds:
`,
},
"instantiate contract without admin": {
src: InstantiateContractProposalFixture(func(p *InstantiateContractProposal) { p.Admin = nil }),
exp: `Instantiate Code Proposal:
Title: Foo
Description: Bar
Creator: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
Admin:
Code id: 1
Label: testing
InitMsg: "{\"verifier\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\",\"beneficiary\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\"}"
InitFunds:
`,
},
"migrate contract": {
src: MigrateContractProposalFixture(),
exp: `Migrate Contract Proposal:
Title: Foo
Description: Bar
Contract: cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5
Code id: 1
Sender: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
MigrateMsg "{\"verifier\":\"cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du\"}"
`,
},
"update admin": {
src: UpdateAdminProposalFixture(),
exp: `Update Contract Admin Proposal:
Title: Foo
Description: Bar
Contract: cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5
Sender: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
New Admin: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
`,
},
"clear admin": {
src: ClearAdminProposalFixture(),
exp: `Clear Contract Admin Proposal:
Title: Foo
Description: Bar
Contract: cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5
Sender: cosmos1qyqszqgpqyqszqgpqyqszqgpqyqszqgpjnp7du
`,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
assert.Equal(t, spec.exp, spec.src.String())
})
}
}

View File

@@ -3,7 +3,9 @@ package types
import (
"bytes"
"crypto/sha256"
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/libs/rand"
)
@@ -101,3 +103,144 @@ func ContractInfoFixture(mutators ...func(*ContractInfo)) ContractInfo {
}
return fixture
}
func WithSHA256CodeHash(wasmCode []byte) func(info *CodeInfo) {
return func(info *CodeInfo) {
codeHash := sha256.Sum256(wasmCode)
info.CodeHash = codeHash[:]
}
}
func StoreCodeProposalFixture(mutators ...func(*StoreCodeProposal)) StoreCodeProposal {
var anyValidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
p := StoreCodeProposal{
WasmProposal: WasmProposal{
Title: "Foo",
Description: "Bar",
},
Creator: anyValidAddress,
WASMByteCode: []byte{0x0},
Source: "https://example.com/code",
Builder: "foo/bar:latest",
}
for _, m := range mutators {
m(&p)
}
return p
}
func InstantiateContractProposalFixture(mutators ...func(p *InstantiateContractProposal)) InstantiateContractProposal {
var (
anyValidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
initMsg = struct {
Verifier sdk.AccAddress `json:"verifier"`
Beneficiary sdk.AccAddress `json:"beneficiary"`
}{
Verifier: anyValidAddress,
Beneficiary: anyValidAddress,
}
)
initMsgBz, err := json.Marshal(initMsg)
if err != nil {
panic(err)
}
p := InstantiateContractProposal{
WasmProposal: WasmProposal{
Title: "Foo",
Description: "Bar",
},
Creator: anyValidAddress,
Admin: anyValidAddress,
Code: 1,
Label: "testing",
InitMsg: initMsgBz,
InitFunds: nil,
}
for _, m := range mutators {
m(&p)
}
return p
}
func MigrateContractProposalFixture(mutators ...func(p *MigrateContractProposal)) MigrateContractProposal {
var (
anyValidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
migMsg = struct {
Verifier sdk.AccAddress `json:"verifier"`
}{Verifier: anyValidAddress}
)
migMsgBz, err := json.Marshal(migMsg)
if err != nil {
panic(err)
}
contractAddr, err := sdk.AccAddressFromBech32("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5")
if err != nil {
panic(err)
}
p := MigrateContractProposal{
WasmProposal: WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Code: 1,
MigrateMsg: migMsgBz,
Sender: anyValidAddress,
}
for _, m := range mutators {
m(&p)
}
return p
}
func UpdateAdminProposalFixture(mutators ...func(p *UpdateAdminProposal)) UpdateAdminProposal {
var anyValidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
contractAddr, err := sdk.AccAddressFromBech32("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5")
if err != nil {
panic(err)
}
p := UpdateAdminProposal{
WasmProposal: WasmProposal{
Title: "Foo",
Description: "Bar",
},
NewAdmin: anyValidAddress,
Contract: contractAddr,
Sender: anyValidAddress,
}
for _, m := range mutators {
m(&p)
}
return p
}
func ClearAdminProposalFixture(mutators ...func(p *ClearAdminProposal)) ClearAdminProposal {
var anyValidAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, sdk.AddrLen)
contractAddr, err := sdk.AccAddressFromBech32("cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5")
if err != nil {
panic(err)
}
p := ClearAdminProposal{
WasmProposal: WasmProposal{
Title: "Foo",
Description: "Bar",
},
Contract: contractAddr,
Sender: anyValidAddress,
}
for _, m := range mutators {
m(&p)
}
return p
}