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:
27
app/app.go
27
app/app.go
@@ -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(
|
||||
|
||||
@@ -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 (
|
||||
|
||||
42
x/wasm/internal/keeper/authz_policy.go
Normal file
42
x/wasm/internal/keeper/authz_policy.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
141
x/wasm/internal/keeper/proposal_handler.go
Normal file
141
x/wasm/internal/keeper/proposal_handler.go
Normal 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
|
||||
}
|
||||
270
x/wasm/internal/keeper/proposal_integration_test.go
Normal file
270
x/wasm/internal/keeper/proposal_integration_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
107
x/wasm/internal/types/params.go
Normal file
107
x/wasm/internal/types/params.go
Normal 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")
|
||||
}
|
||||
}
|
||||
292
x/wasm/internal/types/proposal.go
Normal file
292
x/wasm/internal/types/proposal.go
Normal 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)
|
||||
}
|
||||
534
x/wasm/internal/types/proposal_test.go
Normal file
534
x/wasm/internal/types/proposal_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user