Merge PR #4206: Param Change Proposal

* Add params error types

* Update param module keeper to take a codespace

* Update imports

* Implement SetRaw and SetRawWithSubkey

* Implement ParamChange and update aliases

* Add types codec

* Implement ParameterChangeProposal

* Implement TestParameterChangeProposal

* Fix linting errors

* Update tags

* Implement content

* Updata params aliases

* Finish params handler and proposal types

* Move deposit and vote logic to types package

* Move proposal type to types package

* Move errors to types package

* Update proposal

* Move gov messages to types package

* Minor updates to naming

* Move keys to types package

* Move codec to types package

* Move proposal types to types package

* Update aliases

* Add governance alias types

* Implement governance router

* Update gov aliases

* Update gov keeper

* Update private functions needed for the keeper

* Update godocs

* Update the gov message handler

* Update Gaia app

* Make updates to auth

* Update the message codec in the keeper

* Update gov end blocker

* Update types tests

* Minor tweaks

* Add legacy genesis logic

* Update gov aliases

* Move gov keys to types package

* Revertt to using gov/types in params

* Implement params handler test

* Update governance tests

* Fix endblocker tests

* Fix governance querier tests

* Add seal support to gov router

* Update simulationCreateMsgSubmitProposal

* Disable software upgrade proposals

* Move params keys to types package

* Implement param module proposal client logic

* Update gov client logic

* Update gaia app client hooks

* Fix linting errors

* Fix ValidateBasic

* Remove legacy files

* Update paramchange to use strings

* Update paramchange cli cmd

* Update ValidateBasic and errors

* Use PostCommands when adding child cmds

* Fix codec logic

* Update params client and handler

* Update IsValidProposalType

* Update SubmitProposal to test exec

* Implement TestGaiaCLISubmitParamChangeProposal

* Implement TestSubmitParamChangeProposal

* Update swagger.yaml

* Update gaiacli.md

* Update gov spec docs

* Fix linting errors

* Fix unit tests

* Add pending log entries

* Update docs

* Update docs

* Update client/lcd/swagger-ui/swagger.yaml

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update cmd/gaia/cli_test/test_helpers.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update client/lcd/test_helpers.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update x/gov/types/proposal.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Address PR comments

* Update docs/cosmos-hub/gaiacli.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update gov docs to include quorum notes

* Add logs to handleParameterChangeProposal

* Update docs/spec/governance/02_state.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Support and use new StatusFailed when proposal passes but fails exec

* Add docs/notes warning on param validity

* Update docs

* Update docs/spec/governance/02_state.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Update docs/spec/governance/02_state.md

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Minor doc update

* Update x/gov/client/cli/tx.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Fix usage of fromAddr

* Rige code style  suggestion

* Update x/params/types/proposal.go

Co-Authored-By: alexanderbez <alexanderbez@users.noreply.github.com>

* Fix CI lint errors

* Update NewModuleClient godoc

* Add godoc to rtr.Seal() call

* Rename files

* Rename NewProposalHandler
This commit is contained in:
Alexander Bezobchuk 2019-04-30 12:31:38 -04:00 committed by frog power 4000
parent 1306a25e42
commit 5ca93ac574
74 changed files with 2812 additions and 1411 deletions

View File

@ -0,0 +1,4 @@
#3565 Updates to the governance module:
* Rename JSON field from `proposal_content` to `content`
* Rename JSON field from `proposal_id` to `id`
* Disable `ProposalTypeSoftwareUpgrade` temporarily

View File

@ -0,0 +1,3 @@
#3565 Implement parameter change proposal support.
Parameter change proposals can be submitted through the CLI
or a REST endpoint. See docs for further usage.

View File

@ -630,6 +630,43 @@ func TestSubmitProposal(t *testing.T) {
require.Equal(t, proposalID, proposer.ProposalID)
}
func TestSubmitParamChangeProposal(t *testing.T) {
kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, ""))
require.NoError(t, err)
addr, seed := CreateAddr(t, name1, pw, kb)
cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true)
defer cleanup()
acc := getAccount(t, port, addr)
initialBalance := acc.GetCoins()
// create proposal tx
proposalTokens := sdk.TokensFromTendermintPower(5)
resultTx := doSubmitParamChangeProposal(t, port, seed, name1, pw, addr, proposalTokens, fees)
tests.WaitForHeight(resultTx.Height+1, port)
// check if tx was committed
require.Equal(t, uint32(0), resultTx.Code)
var proposalID uint64
bz, err := hex.DecodeString(resultTx.Data)
require.NoError(t, err)
cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
// verify balance
acc = getAccount(t, port, addr)
expectedBalance := initialBalance[0].Sub(fees[0])
require.Equal(t, expectedBalance.Amount.Sub(proposalTokens), acc.GetCoins().AmountOf(sdk.DefaultBondDenom))
// query proposal
proposal := getProposal(t, port, proposalID)
require.Equal(t, "Test", proposal.GetTitle())
proposer := getProposer(t, port, proposalID)
require.Equal(t, addr.String(), proposer.Proposer)
require.Equal(t, proposalID, proposer.ProposalID)
}
func TestDeposit(t *testing.T) {
kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, ""))
require.NoError(t, err)

View File

@ -1122,6 +1122,49 @@ paths:
description: Invalid query parameters
500:
description: Internal Server Error
/gov/proposals/param_change:
post:
summary: Generate a parameter change proposal transaction
description: Generate a parameter change proposal transaction
consumes:
- application/json
produces:
- application/json
tags:
- ICS22
parameters:
- description: The parameter change proposal body that contains all parameter changes
name: post_proposal_body
in: body
required: true
schema:
type: object
properties:
base_req:
$ref: "#/definitions/BaseReq"
title:
type: string
description:
type: string
proposer:
$ref: "#/definitions/Address"
deposit:
type: array
items:
$ref: "#/definitions/Coin"
changes:
type: array
items:
$ref: "#/definitions/ParamChange"
responses:
200:
description: The transaction was succesfully generated
schema:
$ref: "#/definitions/StdTx"
400:
description: Invalid proposal body
500:
description: Internal Server Error
/gov/proposals/{proposalId}:
get:
summary: Query a proposal
@ -2367,3 +2410,17 @@ definitions:
type: string
missed_blocks_counter:
type: string
ParamChange:
type: object
subspace:
type: string
description: The parameter module subspace
key:
type: string
description: The parameter key
subkey:
type: string
description: An optional parameter subkey
value:
type: string
description: The new parameter value

View File

@ -4,13 +4,12 @@ import (
"bytes"
"encoding/json"
"fmt"
"regexp"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
@ -22,6 +21,7 @@ import (
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
@ -43,6 +43,8 @@ import (
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest"
paramsrest "github.com/cosmos/cosmos-sdk/x/params/client/rest"
paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingrest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
"github.com/cosmos/cosmos-sdk/x/staking"
@ -411,7 +413,7 @@ func registerRoutes(rs *RestServer) {
distrrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey)
stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, paramsrest.ProposalRESTHandler(rs.CliCtx, rs.Cdc))
mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
}
@ -1151,6 +1153,46 @@ func doSubmitProposal(
return txResp
}
func doSubmitParamChangeProposal(
t *testing.T, port, seed, name, pwd string, proposerAddr sdk.AccAddress,
amount sdk.Int, fees sdk.Coins,
) sdk.TxResponse {
acc := getAccount(t, port, proposerAddr)
accnum := acc.GetAccountNumber()
sequence := acc.GetSequence()
chainID := viper.GetString(client.FlagChainID)
from := acc.GetAddress().String()
baseReq := rest.NewBaseReq(from, "", chainID, "", "", accnum, sequence, fees, nil, false)
pr := paramscutils.ParamChangeProposalReq{
BaseReq: baseReq,
Title: "Test",
Description: "test",
Proposer: proposerAddr,
Deposit: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, amount)},
Changes: []params.ParamChange{
params.NewParamChange("staking", "MaxValidators", "", "105"),
},
}
req, err := cdc.MarshalJSON(pr)
require.NoError(t, err)
resp, body := Request(t, port, "POST", "/gov/proposals/param_change", req)
fmt.Println(resp)
require.Equal(t, http.StatusOK, resp.StatusCode, body)
resp, body = signAndBroadcastGenTx(t, port, name, pwd, body, acc, client.DefaultGasAdjustment, false)
require.Equal(t, http.StatusOK, resp.StatusCode, body)
var txResp sdk.TxResponse
err = cdc.UnmarshalJSON([]byte(body), &txResp)
require.NoError(t, err)
return txResp
}
// GET /gov/proposals Query proposals
func getProposalsAll(t *testing.T, port string) []gov.Proposal {
res, body := Request(t, port, "GET", "/gov/proposals", nil)

View File

@ -6,11 +6,6 @@ import (
"os"
"sort"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -23,6 +18,11 @@ import (
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
)
const (
@ -97,7 +97,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey),
}
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams)
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace)
// define the accountKeeper
app.accountKeeper = auth.NewAccountKeeper(
@ -140,11 +140,17 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
&stakingKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace),
slashing.DefaultCodespace,
)
govRouter := gov.NewRouter()
govRouter.AddRoute(gov.RouterKey, gov.ProposalHandler).
AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper))
app.govKeeper = gov.NewKeeper(
app.cdc,
app.keyGov,
app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakingKeeper,
gov.DefaultCodespace,
govRouter,
)
app.crisisKeeper = crisis.NewKeeper(
app.paramsKeeper.Subspace(crisis.DefaultParamspace),
@ -209,6 +215,7 @@ func MakeCodec() *codec.Codec {
staking.RegisterCodec(cdc)
distr.RegisterCodec(cdc)
slashing.RegisterCodec(cdc)
params.RegisterCodec(cdc)
gov.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
crisis.RegisterCodec(cdc)

View File

@ -602,6 +602,67 @@ func TestGaiaCLISubmitProposal(t *testing.T) {
f.Cleanup()
}
func TestGaiaCLISubmitParamChangeProposal(t *testing.T) {
t.Parallel()
f := InitFixtures(t)
proc := f.GDStart()
defer proc.Stop(false)
fooAddr := f.KeyAddress(keyFoo)
fooAcc := f.QueryAccount(fooAddr)
startTokens := sdk.TokensFromTendermintPower(50)
require.Equal(t, startTokens, fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom))
// write proposal to file
proposalTokens := sdk.TokensFromTendermintPower(5)
proposal := fmt.Sprintf(`{
"title": "Param Change",
"description": "Update max validators",
"changes": [
{
"subspace": "staking",
"key": "MaxValidators",
"value": "105"
}
],
"deposit": [
{
"denom": "stake",
"amount": "%s"
}
]
}
`, proposalTokens.String())
proposalFile := WriteToNewTempFile(t, proposal)
// create the param change proposal
f.TxGovSubmitParamChangeProposal(keyFoo, proposalFile.Name(), sdk.NewCoin(denom, proposalTokens), "-y")
tests.WaitForNextNBlocksTM(1, f.Port)
// ensure transaction tags can be queried
txs := f.QueryTxs(1, 50, "action:submit_proposal", fmt.Sprintf("sender:%s", fooAddr))
require.Len(t, txs, 1)
// ensure deposit was deducted
fooAcc = f.QueryAccount(fooAddr)
require.Equal(t, startTokens.Sub(proposalTokens).String(), fooAcc.GetCoins().AmountOf(sdk.DefaultBondDenom).String())
// ensure proposal is directly queryable
proposal1 := f.QueryGovProposal(1)
require.Equal(t, uint64(1), proposal1.ProposalID)
require.Equal(t, gov.StatusDepositPeriod, proposal1.Status)
// ensure correct query proposals result
proposalsQuery := f.QueryGovProposals()
require.Equal(t, uint64(1), proposalsQuery[0].ProposalID)
// ensure the correct deposit amount on the proposal
deposit := f.QueryGovDeposit(1, fooAddr)
require.Equal(t, proposalTokens, deposit.Amount.AmountOf(denom))
}
func TestGaiaCLIQueryTxPagination(t *testing.T) {
t.Parallel()
f := InitFixtures(t)

View File

@ -387,6 +387,20 @@ func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string,
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
}
// TxGovSubmitParamChangeProposal executes a CLI parameter change proposal
// submission.
func (f *Fixtures) TxGovSubmitParamChangeProposal(
from, proposalPath string, deposit sdk.Coin, flags ...string,
) (bool, string, string) {
cmd := fmt.Sprintf(
"%s tx gov submit-proposal param-change %s --from=%s %v",
f.GaiacliBinary, proposalPath, from, f.Flags(),
)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
}
//___________________________________________________________________________________
// gaiacli query account

View File

@ -6,15 +6,6 @@ import (
"os"
"path"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/rakyll/statik/fs"
"github.com/spf13/cobra"
"github.com/spf13/viper"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
@ -23,28 +14,36 @@ import (
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
at "github.com/cosmos/cosmos-sdk/x/auth"
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
gv "github.com/cosmos/cosmos-sdk/x/gov"
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest"
sl "github.com/cosmos/cosmos-sdk/x/slashing"
slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
st "github.com/cosmos/cosmos-sdk/x/staking"
staking "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
crisisclient "github.com/cosmos/cosmos-sdk/x/crisis/client"
distcmd "github.com/cosmos/cosmos-sdk/x/distribution"
distClient "github.com/cosmos/cosmos-sdk/x/distribution/client"
dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest"
gv "github.com/cosmos/cosmos-sdk/x/gov"
govClient "github.com/cosmos/cosmos-sdk/x/gov/client"
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
"github.com/cosmos/cosmos-sdk/x/mint"
mintclient "github.com/cosmos/cosmos-sdk/x/mint/client"
mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest"
paramcli "github.com/cosmos/cosmos-sdk/x/params/client/cli"
paramsrest "github.com/cosmos/cosmos-sdk/x/params/client/rest"
sl "github.com/cosmos/cosmos-sdk/x/slashing"
slashingclient "github.com/cosmos/cosmos-sdk/x/slashing/client"
slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest"
st "github.com/cosmos/cosmos-sdk/x/staking"
stakingclient "github.com/cosmos/cosmos-sdk/x/staking/client"
staking "github.com/cosmos/cosmos-sdk/x/staking/client/rest"
"github.com/rakyll/statik/fs"
"github.com/spf13/cobra"
"github.com/spf13/viper"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/libs/cli"
_ "github.com/cosmos/cosmos-sdk/client/lcd/statik"
)
@ -70,7 +69,7 @@ func main() {
// Module clients hold cli commnads (tx,query) and lcd routes
// TODO: Make the lcd command take a list of ModuleClient
mc := []sdk.ModuleClients{
govClient.NewModuleClient(gv.StoreKey, cdc),
govClient.NewModuleClient(gv.StoreKey, cdc, paramcli.GetCmdSubmitProposal(cdc)),
distClient.NewModuleClient(distcmd.StoreKey, cdc),
stakingclient.NewModuleClient(st.StoreKey, cdc),
mintclient.NewModuleClient(mint.StoreKey, cdc),
@ -175,7 +174,7 @@ func registerRoutes(rs *lcd.RestServer) {
dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey)
staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, paramsrest.ProposalRESTHandler(rs.CliCtx, rs.Cdc))
mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
}

View File

@ -167,7 +167,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, baseAppOptions ...func(*bam.BaseAp
tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey),
}
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams)
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace)
// define the accountKeeper
app.accountKeeper = auth.NewAccountKeeper(

View File

@ -536,37 +536,100 @@ You can also query all of the delegations to a particular validator:
### Governance
Governance is the process from which users in the Cosmos Hub can come to consensus on software upgrades, parameters of the mainnet or on custom text proposals. This is done through voting on proposals, which will be submitted by `Atom` holders on the mainnet.
Governance is the process from which users in the Cosmos Hub can come to consensus
on software upgrades, parameters of the mainnet or signaling mechanisms through
text proposals. This is done through voting on proposals, which will be submitted
by `ATOM` holders on the mainnet.
Some considerations about the voting process:
- Voting is done by bonded `Atom` holders on a 1 bonded `Atom` 1 vote basis
- Voting is done by bonded `ATOM` holders on a 1 bonded `ATOM` 1 vote basis
- Delegators inherit the vote of their validator if they don't vote
- **Validators MUST vote on every proposal**. If a validator does not vote on a proposal, they will be **partially slashed**
- Votes are tallied at the end of the voting period (2 weeks on mainnet). Each address can vote multiple times to update its `Option` value (paying the transaction fee each time), only the last casted vote will count as valid
- Votes are tallied at the end of the voting period (2 weeks on mainnet) where
each address can vote multiple times to update its `Option` value (paying the transaction fee each time),
only the most recently cast vote will count as valid
- Voters can choose between options `Yes`, `No`, `NoWithVeto` and `Abstain`
At the end of the voting period, a proposal is accepted if `(YesVotes/(YesVotes+NoVotes+NoWithVetoVotes))>1/2` and `(NoWithVetoVotes/(YesVotes+NoVotes+NoWithVetoVotes))<1/3`. It is rejected otherwise
- At the end of the voting period, a proposal is accepted iff:
- `(YesVotes / (YesVotes+NoVotes+NoWithVetoVotes)) > 1/2`
- `(NoWithVetoVotes / (YesVotes+NoVotes+NoWithVetoVotes)) < 1/3`
- `((YesVotes+NoVotes+NoWithVetoVotes) / totalBondedStake) >= quorum`
For more information about the governance process and how it works, please check out the Governance module [specification](./../spec/governance).
For more information about the governance process and how it works, please check
out the Governance module [specification](./../spec/governance).
#### Create a Governance Proposal
In order to create a governance proposal, you must submit an initial deposit along with the proposal details:
In order to create a governance proposal, you must submit an initial deposit
along with a title and description. Various modules outside of governance may
implement their own proposal types and handlers (eg. parameter changes), where
the governance module itself supports `Text` proposals. Any module
outside of governance has it's command mounted on top of `submit-proposal`.
- `title`: Title of the proposal
- `description`: Description of the proposal
- `type`: Type of proposal. Must be of value _Text_ (types _SoftwareUpgrade_ and _ParameterChange_ not supported yet).
To submit a `Text` proposal:
```bash
gaiacli tx gov submit-proposal \
--title=<title> \
--description=<description> \
--type=<Text/ParameterChange/SoftwareUpgrade> \
--type="Text" \
--deposit="1000000uatom" \
--from=<name> \
--chain-id=<chain_id>
```
You may also provide the proposal directly through the `--proposal` flag which
points to a JSON file containing the proposal.
To submit a parameter change proposal, you must provide a proposal file as its
contents are less friendly to CLI input:
```bash
gaiacli tx gov submit-proposal param-change <path/to/proposal.json> \
--from=<name> \
--chain-id=<chain_id>
```
Where `proposal.json` contains the following:
```json
{
"title": "Param Change",
"description": "Update max validators",
"changes": [
{
"subspace": "staking",
"key": "MaxValidators",
"value": "105"
}
],
"deposit": [
{
"denom": "stake",
"amount": "10000000"
}
]
}
```
::: danger Warning
Currently parameter changes are _evaluated_ but not _validated_, so it is very important
that any `value` change is valid (ie. correct type and within bounds) for its
respective parameter, eg. `MaxValidators` should be an integer and not a decimal.
Proper vetting of a parameter change proposal should prevent this from happening
(no deposits should occur during the governance process), but it should be noted
regardless.
:::
::: tip Note
The `SoftwareUpgrade` is currently not supported as it's not implemented and
currently does not differ from the semantics of a `Text` proposal.
:::
##### Query Proposals
Once created, you can now query information of the proposal:

View File

@ -58,6 +58,11 @@ proposal:
`PlainTextProposals`, but actual software upgrades must be performed via
`SoftwareUpgradeProposals`.
Other modules may expand upon the governance module by implementing their own
proposal types and handlers. These types are registered and processed through the
governance module (eg. `ParamChangeProposal`), which then execute the respective
module's proposal handler when a proposal passes. This custom handler may perform
arbitrary state changes.
## Vote

View File

@ -33,7 +33,6 @@ Parameters are stored in a global `GlobalParams` KVStore.
Additionally, we introduce some basic types:
```go
type Vote byte
const (
@ -43,22 +42,23 @@ const (
VoteAbstain = 0x4
)
type ProposalType byte
type ProposalType string
const (
ProposalTypePlainText = 0x1 // Plain text proposals
ProposalTypeSoftwareUpgrade = 0x2 // Text proposal inducing a software upgrade
ProposalTypePlainText = "Text"
ProposalTypeSoftwareUpgrade = "SoftwareUpgrade"
)
type ProposalStatus byte
const (
ProposalStatusOpen = 0x1 // Proposal is submitted. Participants can deposit on it but not vote
ProposalStatusActive = 0x2 // MinDeposit is reached, participants can vote
ProposalStatusAccepted = 0x3 // Proposal has been accepted
ProposalStatusRejected = 0x4 // Proposal has been rejected
ProposalStatusClosed = 0x5 // Proposal never reached MinDeposit
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01 // Proposal is submitted. Participants can deposit on it but not vote
StatusVotingPeriod ProposalStatus = 0x02 // MinDeposit is reached, participants can vote
StatusPassed ProposalStatus = 0x03 // Proposal passed and successfully executed
StatusRejected ProposalStatus = 0x04 // Proposal has been rejected
StatusFailed ProposalStatus = 0x05 // Proposal passed but failed execution
)
```
@ -84,39 +84,55 @@ This type is used in a temp map when tallying
## Proposals
`Proposals` are an item to be voted on. It contains the `ProposalContent` which denotes what this proposal is about, and the other fields, which are the mutable state of the governance process.
`Proposal` objects are used to account votes and generally track the proposal's state. They contain `Content` which denotes
what this proposal is about, and other fields, which are the mutable state of
the governance process.
```go
type Proposal struct {
ProposalContent // Proposal content interface
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
Deposits []Deposit // List of deposits on the proposal
SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time // Time that the DepositPeriod of a proposal would expire
Submitter sdk.AccAddress // Address of the submitter
Content // Proposal content interface
VotingStartTime time.Time // Time of the block where MinDeposit was reached. time.Time{} if MinDeposit is not reached
VotingEndTime time.Time // Time of the block that the VotingPeriod for a proposal will end.
CurrentStatus ProposalStatus // Current status of the proposal
ProposalID uint64
Status ProposalStatus // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult // Result of Tallies
YesVotes sdk.Dec
NoVotes sdk.Dec
NoWithVetoVotes sdk.Dec
AbstainVotes sdk.Dec
SubmitTime time.Time // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
```
`ProposalContent`s are an interface which contains the information about the `Proposal` where it is provided from an external source, including the proposer. Governance process itself does not evaluate about the internal content.
```go
type ProposalContent interface {
GetTitle() string
GetDescription() string
ProposalType() ProposalKind
type Content interface {
GetTitle() string
GetDescription() string
ProposalRoute() string
ProposalType() string
ValidateBasic() sdk.Error
String() string
}
```
The `Content` on a proposal is an interface which contains the information about
the `Proposal` such as the tile, description, and any notable changes. Also, this
`Content` type can by implemented by any module. The `Content`'s `ProposalRoute`
returns a string which must be used to route the `Content`'s `Handler` in the
governance keeper. This allows the governance keeper to execute proposal logic
implemented by any module. If a proposal passes, the handler is executed. Only
if the handler is successful does the state get persisted and the proposal finally
passes. Otherwise, the proposal is rejected.
```go
type Handler func(ctx sdk.Context, content Content) sdk.Error
```
The `Handler` is responsible for actually executing the proposal and processing
any state changes specified by the proposal. It is executed only if a proposal
passes during `EndBlock`.
We also mention a method to update the tally for a given proposal:
```go
@ -125,12 +141,15 @@ We also mention a method to update the tally for a given proposal:
## Stores
*Stores are KVStores in the multistore. The key to find the store is the first parameter in the list*`
*Stores are KVStores in the multi-store. The key to find the store is the first
parameter in the list*`
We will use one KVStore `Governance` to store two mappings:
* A mapping from `proposalID|'proposal'` to `Proposal`
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows us to query all addresses that voted on the proposal along with their vote by doing a range query on `proposalID:addresses`
* A mapping from `proposalID|'proposal'` to `Proposal`.
* A mapping from `proposalID|'addresses'|address` to `Vote`. This mapping allows
us to query all addresses that voted on the proposal along with their vote by
doing a range query on `proposalID:addresses`.
For pseudocode purposes, here are the two function we will use to read or write in stores:
@ -142,11 +161,12 @@ For pseudocode purposes, here are the two function we will use to read or write
**Store:**
* `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the
`ProposalIDs` of proposals that reached `MinDeposit`. Each `EndBlock`, all the proposals
that have reached the end of their voting period are processed.
To process a finished proposal, the application tallies the votes, compute the votes of
each validator and checks if every validator in the valdiator set have voted.
If the proposal is accepted, deposits are refunded.
`ProposalIDs` of proposals that reached `MinDeposit`. During each `EndBlock`,
all the proposals that have reached the end of their voting period are processed.
To process a finished proposal, the application tallies the votes, computes the
votes of each validator and checks if every validator in the validator set has
voted. If the proposal is accepted, deposits are refunded. Finally, the proposal
content `Handler` is executed.
And the pseudocode for the `ProposalProcessingQueue`:
@ -191,10 +211,17 @@ And the pseudocode for the `ProposalProcessingQueue`:
if (proposal.Votes.YesVotes/totalNonAbstain > tallyingParam.Threshold AND proposal.Votes.NoWithVetoVotes/totalNonAbstain < tallyingParam.Veto)
// proposal was accepted at the end of the voting period
// refund deposits (non-voters already punished)
proposal.CurrentStatus = ProposalStatusAccepted
for each (amount, depositor) in proposal.Deposits
depositor.AtomBalance += amount
stateWriter, err := proposal.Handler()
if err != nil
// proposal passed but failed during state execution
proposal.CurrentStatus = ProposalStatusFailed
else
// proposal pass and state is persisted
proposal.CurrentStatus = ProposalStatusAccepted
stateWriter.save()
else
// proposal was rejected
proposal.CurrentStatus = ProposalStatusRejected

View File

@ -7,13 +7,15 @@ transaction.
```go
type TxGovSubmitProposal struct {
Title string // Title of the proposal
Description string // Description of the proposal
Type ProposalType // Type of proposal
InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive.
Content Content
InitialDeposit sdk.Coins
Proposer sdk.AccAddress
}
```
The `Content` of a `TxGovSubmitProposal` message must have an appropriate router
set in the governance module.
**State modifications:**
* Generate new `proposalID`
* Create new `Proposal`

View File

@ -21,10 +21,6 @@ governance module. Future improvements may include:
representative before they inherit the vote of their validator. In other
words, they would only inherit the vote of their validator if their other
appointed representative did not vote.
* **`ParameterProposals` and `WhitelistProposals`:** These proposals would
automatically change pre-defined parameters and whitelists. Upon acceptance,
these proposals would not require validators to do the signal and switch
process.
* **Better process for proposal review:** There would be two parts to
`proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to
reward third party auditors.

View File

@ -5,9 +5,9 @@ import (
"github.com/tendermint/tendermint/crypto"
codec "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
const (
@ -40,14 +40,14 @@ type AccountKeeper struct {
// The codec codec for binary encoding/decoding of accounts.
cdc *codec.Codec
paramSubspace params.Subspace
paramSubspace subspace.Subspace
}
// NewAccountKeeper returns a new sdk.AccountKeeper that uses go-amino to
// (binary) encode and decode concrete sdk.Accounts.
// nolint
func NewAccountKeeper(
cdc *codec.Codec, key sdk.StoreKey, paramstore params.Subspace, proto func() Account,
cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, proto func() Account,
) AccountKeeper {
return AccountKeeper{

View File

@ -6,6 +6,7 @@ import (
"strings"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
// DefaultParamspace defines the default auth module parameter subspace
@ -29,7 +30,7 @@ var (
KeySigVerifyCostSecp256k1 = []byte("SigVerifyCostSecp256k1")
)
var _ params.ParamSet = &Params{}
var _ subspace.ParamSet = &Params{}
// Params defines the parameters for the auth module.
type Params struct {
@ -48,8 +49,8 @@ func ParamKeyTable() params.KeyTable {
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
// pairs of auth module's parameters.
// nolint
func (p *Params) ParamSetPairs() params.ParamSetPairs {
return params.ParamSetPairs{
func (p *Params) ParamSetPairs() subspace.ParamSetPairs {
return subspace.ParamSetPairs{
{KeyMaxMemoCharacters, &p.MaxMemoCharacters},
{KeyTxSigLimit, &p.TxSigLimit},
{KeyTxSizeCostPerByte, &p.TxSizeCostPerByte},

View File

@ -11,7 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
type testInput struct {
@ -29,8 +29,8 @@ func setupTestInput() testInput {
authCapKey := sdk.NewKVStoreKey("authCapKey")
fckCapKey := sdk.NewKVStoreKey("fckCapKey")
keyParams := sdk.NewKVStoreKey("params")
tkeyParams := sdk.NewTransientStoreKey("transient_params")
keyParams := sdk.NewKVStoreKey("subspace")
tkeyParams := sdk.NewTransientStoreKey("transient_subspace")
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db)
@ -39,8 +39,8 @@ func setupTestInput() testInput {
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
ms.LoadLatestVersion()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
ak := NewAccountKeeper(cdc, authCapKey, pk.Subspace(DefaultParamspace), ProtoBaseAccount)
ps := subspace.NewSubspace(cdc, keyParams, tkeyParams, DefaultParamspace)
ak := NewAccountKeeper(cdc, authCapKey, ps, ProtoBaseAccount)
fck := NewFeeCollectionKeeper(cdc, fckCapKey)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger())

View File

@ -42,7 +42,7 @@ func setupTestInput() testInput {
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
ms.LoadLatestVersion()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
ak := auth.NewAccountKeeper(
cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount,
)

View File

@ -111,7 +111,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64,
require.Nil(t, err)
cdc := MakeTestCodec()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger())
accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount)

108
x/gov/alias.go Normal file
View File

@ -0,0 +1,108 @@
//nolint
package gov
import "github.com/cosmos/cosmos-sdk/x/gov/types"
const (
StatusNil = types.StatusNil
StatusDepositPeriod = types.StatusDepositPeriod
StatusVotingPeriod = types.StatusVotingPeriod
StatusPassed = types.StatusPassed
StatusRejected = types.StatusRejected
StatusFailed = types.StatusFailed
DefaultCodespace = types.DefaultCodespace
DefaultParamspace = types.DefaultParamspace
ModuleName = types.ModuleName
RouterKey = types.RouterKey
StoreKey = types.StoreKey
QuerierRoute = types.QuerierRoute
ProposalTypeText = types.ProposalTypeText
ProposalTypeSoftwareUpgrade = types.ProposalTypeSoftwareUpgrade
OptionEmpty = types.OptionEmpty
OptionYes = types.OptionYes
OptionAbstain = types.OptionAbstain
OptionNo = types.OptionNo
OptionNoWithVeto = types.OptionNoWithVeto
TypeMsgDeposit = types.TypeMsgDeposit
TypeMsgVote = types.TypeMsgVote
TypeMsgSubmitProposal = types.TypeMsgSubmitProposal
)
type (
Content = types.Content
Handler = types.Handler
Proposal = types.Proposal
Proposals = types.Proposals
Deposit = types.Deposit
Deposits = types.Deposits
Vote = types.Vote
Votes = types.Votes
VoteOption = types.VoteOption
TextProposal = types.TextProposal
SoftwareUpgradeProposal = types.SoftwareUpgradeProposal
ProposalStatus = types.ProposalStatus
MsgSubmitProposal = types.MsgSubmitProposal
MsgDeposit = types.MsgDeposit
MsgVote = types.MsgVote
TallyResult = types.TallyResult
)
var (
ErrUnknownProposal = types.ErrUnknownProposal
ErrInactiveProposal = types.ErrInactiveProposal
ErrAlreadyActiveProposal = types.ErrAlreadyActiveProposal
ErrAlreadyFinishedProposal = types.ErrAlreadyFinishedProposal
ErrAddressNotStaked = types.ErrAddressNotStaked
ErrInvalidProposalContent = types.ErrInvalidProposalContent
ErrInvalidProposalType = types.ErrInvalidProposalType
ErrInvalidVote = types.ErrInvalidVote
ErrInvalidGenesis = types.ErrInvalidGenesis
ErrNoProposalHandlerExists = types.ErrNoProposalHandlerExists
NewProposal = types.NewProposal
ProposalHandler = types.ProposalHandler
ValidVoteOption = types.ValidVoteOption
ValidProposalStatus = types.ValidProposalStatus
ValidateAbstract = types.ValidateAbstract
EmptyTallyResult = types.EmptyTallyResult
RegisterCodec = types.RegisterCodec
KeyDelimiter = types.KeyDelimiter
KeyNextProposalID = types.KeyNextProposalID
PrefixActiveProposalQueue = types.PrefixActiveProposalQueue
PrefixInactiveProposalQueue = types.PrefixInactiveProposalQueue
KeyProposal = types.KeyProposal
KeyDeposit = types.KeyDeposit
KeyVote = types.KeyVote
KeyDepositsSubspace = types.KeyDepositsSubspace
KeyVotesSubspace = types.KeyVotesSubspace
PrefixActiveProposalQueueTime = types.PrefixActiveProposalQueueTime
KeyActiveProposalQueueProposal = types.KeyActiveProposalQueueProposal
PrefixInactiveProposalQueueTime = types.PrefixInactiveProposalQueueTime
KeyInactiveProposalQueueProposal = types.KeyInactiveProposalQueueProposal
NewMsgSubmitProposal = types.NewMsgSubmitProposal
NewMsgDeposit = types.NewMsgDeposit
NewMsgVote = types.NewMsgVote
NewTextProposal = types.NewTextProposal
NewTallyResultFromMap = types.NewTallyResultFromMap
ContentFromProposalType = types.ContentFromProposalType
IsValidProposalType = types.IsValidProposalType
VoteOptionFromString = types.VoteOptionFromString
ProposalStatusFromString = types.ProposalStatusFromString
RegisterProposalType = types.RegisterProposalType
RegisterProposalTypeCodec = types.RegisterProposalTypeCodec
)

View File

@ -12,17 +12,17 @@ import (
func parseSubmitProposalFlags() (*proposal, error) {
proposal := &proposal{}
proposalFile := viper.GetString(flagProposal)
proposalFile := viper.GetString(FlagProposal)
if proposalFile == "" {
proposal.Title = viper.GetString(flagTitle)
proposal.Description = viper.GetString(flagDescription)
proposal.Title = viper.GetString(FlagTitle)
proposal.Description = viper.GetString(FlagDescription)
proposal.Type = govClientUtils.NormalizeProposalType(viper.GetString(flagProposalType))
proposal.Deposit = viper.GetString(flagDeposit)
proposal.Deposit = viper.GetString(FlagDeposit)
return proposal, nil
}
for _, flag := range proposalFlags {
for _, flag := range ProposalFlags {
if viper.GetString(flag) != "" {
return nil, fmt.Errorf("--%s flag provided alongside --proposal, which is a noop", flag)
}

View File

@ -25,17 +25,17 @@ func TestParseSubmitProposalFlags(t *testing.T) {
badJSON.WriteString("bad json")
// nonexistent json
viper.Set(flagProposal, "fileDoesNotExist")
viper.Set(FlagProposal, "fileDoesNotExist")
_, err = parseSubmitProposalFlags()
require.Error(t, err)
// invalid json
viper.Set(flagProposal, badJSON.Name())
viper.Set(FlagProposal, badJSON.Name())
_, err = parseSubmitProposalFlags()
require.Error(t, err)
// ok json
viper.Set(flagProposal, okJSON.Name())
viper.Set(FlagProposal, okJSON.Name())
proposal1, err := parseSubmitProposalFlags()
require.Nil(t, err, "unexpected error")
require.Equal(t, "Test Proposal", proposal1.Title)
@ -44,7 +44,7 @@ func TestParseSubmitProposalFlags(t *testing.T) {
require.Equal(t, "1000test", proposal1.Deposit)
// flags that can't be used with --proposal
for _, incompatibleFlag := range proposalFlags {
for _, incompatibleFlag := range ProposalFlags {
viper.Set(incompatibleFlag, "some value")
_, err := parseSubmitProposalFlags()
require.Error(t, err)
@ -52,11 +52,11 @@ func TestParseSubmitProposalFlags(t *testing.T) {
}
// no --proposal, only flags
viper.Set(flagProposal, "")
viper.Set(flagTitle, proposal1.Title)
viper.Set(flagDescription, proposal1.Description)
viper.Set(FlagProposal, "")
viper.Set(FlagTitle, proposal1.Title)
viper.Set(FlagDescription, proposal1.Description)
viper.Set(flagProposalType, proposal1.Type)
viper.Set(flagDeposit, proposal1.Deposit)
viper.Set(FlagDeposit, proposal1.Deposit)
proposal2, err := parseSubmitProposalFlags()
require.Nil(t, err, "unexpected error")
require.Equal(t, proposal1.Title, proposal2.Title)

View File

@ -18,17 +18,17 @@ import (
govClientUtils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
)
// Proposal flags
const (
flagTitle = "title"
flagDescription = "description"
FlagTitle = "title"
FlagDescription = "description"
flagProposalType = "type"
flagDeposit = "deposit"
FlagDeposit = "deposit"
flagVoter = "voter"
flagOption = "option"
flagDepositor = "depositor"
flagStatus = "status"
flagNumLimit = "limit"
flagProposal = "proposal"
FlagProposal = "proposal"
)
type proposal struct {
@ -38,11 +38,14 @@ type proposal struct {
Deposit string
}
var proposalFlags = []string{
flagTitle,
flagDescription,
// ProposalFlags defines the core required fields of a proposal. It is used to
// verify that these values are not provided in conjunction with a JSON proposal
// file.
var ProposalFlags = []string{
FlagTitle,
FlagDescription,
flagProposalType,
flagDeposit,
FlagDeposit,
}
// GetCmdSubmitProposal implements submitting a proposal transaction command.
@ -69,33 +72,25 @@ is equivalent to
$ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --deposit="10test" --from mykey
`),
RunE: func(cmd *cobra.Command, args []string) error {
proposal, err := parseSubmitProposalFlags()
if err != nil {
return err
}
txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(cdc)
// Get proposer address
from := cliCtx.GetFromAddress()
proposal, err := parseSubmitProposalFlags()
if err != nil {
return err
}
// Find deposit amount
amount, err := sdk.ParseCoins(proposal.Deposit)
if err != nil {
return err
}
proposalType, err := gov.ProposalTypeFromString(proposal.Type)
if err != nil {
return err
}
content := gov.ContentFromProposalType(proposal.Title, proposal.Description, proposal.Type)
msg := gov.NewMsgSubmitProposal(proposal.Title, proposal.Description, proposalType, from, amount)
err = msg.ValidateBasic()
if err != nil {
msg := gov.NewMsgSubmitProposal(content, amount, cliCtx.GetFromAddress())
if err := msg.ValidateBasic(); err != nil {
return err
}
@ -103,11 +98,11 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome
},
}
cmd.Flags().String(flagTitle, "", "title of proposal")
cmd.Flags().String(flagDescription, "", "description of proposal")
cmd.Flags().String(FlagTitle, "", "title of proposal")
cmd.Flags().String(FlagDescription, "", "description of proposal")
cmd.Flags().String(flagProposalType, "", "proposalType of proposal, types: text/parameter_change/software_upgrade")
cmd.Flags().String(flagDeposit, "", "deposit of proposal")
cmd.Flags().String(flagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)")
cmd.Flags().String(FlagDeposit, "", "deposit of proposal")
cmd.Flags().String(FlagProposal, "", "proposal file path (if this path is given, other proposal flags are ignored)")
return cmd
}

View File

@ -9,14 +9,19 @@ import (
govCli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
)
// ModuleClient exports all client functionality from this module
// ModuleClient exports all client functionality from the governance module. The
// governance ModuleClient is slightly different from other ModuleClients in that
// it contains a slice of "proposal" child commands. These commands are respective
// to proposal type handlers that are implemented in other modules but are mounted
// under the governance CLI (eg. parameter change proposals).
type ModuleClient struct {
storeKey string
cdc *amino.Codec
pcmds []*cobra.Command
}
func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
return ModuleClient{storeKey, cdc}
func NewModuleClient(storeKey string, cdc *amino.Codec, pcmds ...*cobra.Command) ModuleClient {
return ModuleClient{storeKey, cdc, pcmds}
}
// GetQueryCmd returns the cli query commands for this module
@ -49,10 +54,15 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command {
Short: "Governance transactions subcommands",
}
cmdSubmitProp := govCli.GetCmdSubmitProposal(mc.cdc)
for _, pcmd := range mc.pcmds {
cmdSubmitProp.AddCommand(client.PostCommands(pcmd)[0])
}
govTxCmd.AddCommand(client.PostCommands(
govCli.GetCmdDeposit(mc.storeKey, mc.cdc),
govCli.GetCmdVote(mc.storeKey, mc.cdc),
govCli.GetCmdSubmitProposal(mc.cdc),
cmdSubmitProp,
)...)
return govTxCmd

View File

@ -14,7 +14,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
govClientUtils "github.com/cosmos/cosmos-sdk/x/gov/client/utils"
)
// REST Variable names
@ -28,8 +27,20 @@ const (
RestNumLimit = "limit"
)
// ProposalRESTHandler defines a REST handler implemented in another module. The
// sub-route is mounted on the governance REST handler.
type ProposalRESTHandler struct {
SubRoute string
Handler func(http.ResponseWriter, *http.Request)
}
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) {
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, phs ...ProposalRESTHandler) {
propSubRtr := r.PathPrefix("/gov/proposals").Subrouter()
for _, ph := range phs {
propSubRtr.HandleFunc(fmt.Sprintf("/%s", ph.SubRoute), ph.Handler).Methods("POST")
}
r.HandleFunc("/gov/proposals", postProposalHandlerFn(cdc, cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/deposits", RestProposalID), depositHandlerFn(cdc, cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}/votes", RestProposalID), voteHandlerFn(cdc, cliCtx)).Methods("POST")
@ -88,14 +99,10 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han
return
}
proposalType, err := gov.ProposalTypeFromString(govClientUtils.NormalizeProposalType(req.ProposalType))
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
proposalType := gcutils.NormalizeProposalType(req.ProposalType)
content := gov.ContentFromProposalType(req.Title, req.Description, proposalType)
// create the message
msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit)
msg := gov.NewMsgSubmitProposal(content, req.InitialDeposit, req.Proposer)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
@ -168,7 +175,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc
return
}
voteOption, err := gov.VoteOptionFromString(govClientUtils.NormalizeVoteOption(req.Option))
voteOption, err := gov.VoteOptionFromString(gcutils.NormalizeVoteOption(req.Option))
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
@ -540,7 +547,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext)
}
if len(strProposalStatus) != 0 {
proposalStatus, err := gov.ProposalStatusFromString(govClientUtils.NormalizeProposalStatus(strProposalStatus))
proposalStatus, err := gov.ProposalStatusFromString(gcutils.NormalizeProposalStatus(strProposalStatus))
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return

View File

@ -1,31 +1,39 @@
package utils
import "github.com/cosmos/cosmos-sdk/x/gov"
// NormalizeVoteOption - normalize user specified vote option
func NormalizeVoteOption(option string) string {
switch option {
case "Yes", "yes":
return "Yes"
return gov.OptionYes.String()
case "Abstain", "abstain":
return "Abstain"
return gov.OptionAbstain.String()
case "No", "no":
return "No"
return gov.OptionNo.String()
case "NoWithVeto", "no_with_veto":
return "NoWithVeto"
return gov.OptionNoWithVeto.String()
default:
return ""
}
return ""
}
//NormalizeProposalType - normalize user specified proposal type
func NormalizeProposalType(proposalType string) string {
switch proposalType {
case "Text", "text":
return "Text"
case "ParameterChange", "parameter_change":
return "ParameterChange"
return gov.ProposalTypeText
case "SoftwareUpgrade", "software_upgrade":
return "SoftwareUpgrade"
return gov.ProposalTypeSoftwareUpgrade
default:
return ""
}
return ""
}
//NormalizeProposalStatus - normalize user specified proposal status

View File

@ -1,22 +0,0 @@
package gov
import (
"github.com/cosmos/cosmos-sdk/codec"
)
var msgCdc = codec.New()
// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil)
cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil)
cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil)
cdc.RegisterInterface((*ProposalContent)(nil), nil)
cdc.RegisterConcrete(TextProposal{}, "gov/TextProposal", nil)
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "gov/SoftwareUpgradeProposal", nil)
}
func init() {
RegisterCodec(msgCdc)
}

View File

@ -52,25 +52,47 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) sdk.Tags {
}
passes, tallyResults := tally(ctx, keeper, activeProposal)
var tagValue string
var tagValue, logMsg string
if passes {
keeper.RefundDeposits(ctx, activeProposal.ProposalID)
activeProposal.Status = StatusPassed
tagValue = tags.ActionProposalPassed
handler := keeper.router.GetRoute(activeProposal.ProposalRoute())
cacheCtx, writeCache := ctx.CacheContext()
// The proposal handler may execute state mutating logic depending
// on the proposal content. If the handler fails, no state mutation
// is written and the error message is logged.
err := handler(cacheCtx, activeProposal.Content)
if err == nil {
activeProposal.Status = StatusPassed
tagValue = tags.ActionProposalPassed
logMsg = "passed"
// write state to the underlying multi-store
writeCache()
} else {
activeProposal.Status = StatusFailed
tagValue = tags.ActionProposalFailed
logMsg = fmt.Sprintf("passed, but failed on execution: %s", err.ABCILog())
}
} else {
keeper.DeleteDeposits(ctx, activeProposal.ProposalID)
activeProposal.Status = StatusRejected
tagValue = tags.ActionProposalRejected
logMsg = "rejected"
}
activeProposal.FinalTallyResult = tallyResults
keeper.SetProposal(ctx, activeProposal)
keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.VotingEndTime, activeProposal.ProposalID)
logger.Info(
fmt.Sprintf(
"proposal %d (%s) tallied; passed: %v",
activeProposal.ProposalID, activeProposal.GetTitle(), passes,
"proposal %d (%s) tallied; result: %s",
activeProposal.ProposalID, activeProposal.GetTitle(), logMsg,
),
)

View File

@ -9,28 +9,34 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/tags"
"github.com/cosmos/cosmos-sdk/x/staking"
)
func TestTickExpiredDepositPeriod(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(keeper)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(input.keeper)
inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue := input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
newProposalMsg := NewMsgSubmitProposal(
ContentFromProposalType("test", "test", ProposalTypeText),
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)},
input.addrs[0],
)
res := govHandler(ctx, newProposalMsg)
require.True(t, res.IsOK())
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
@ -38,45 +44,49 @@ func TestTickExpiredDepositPeriod(t *testing.T) {
newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
newHeader = ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod)
newHeader.Time = ctx.BlockHeader().Time.Add(input.keeper.GetDepositParams(ctx).MaxDepositPeriod)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.True(t, inactiveQueue.Valid())
inactiveQueue.Close()
EndBlocker(ctx, keeper)
EndBlocker(ctx, input.keeper)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
}
func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(keeper)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(input.keeper)
inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue := input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
newProposalMsg := NewMsgSubmitProposal(
ContentFromProposalType("test", "test", ProposalTypeText),
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)},
input.addrs[0],
)
res := govHandler(ctx, newProposalMsg)
require.True(t, res.IsOK())
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
@ -84,23 +94,28 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(2) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
newProposalMsg2 := NewMsgSubmitProposal(
ContentFromProposalType("test2", "test2", ProposalTypeText),
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)},
input.addrs[0],
)
res = govHandler(ctx, newProposalMsg2)
require.True(t, res.IsOK())
newHeader = ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second)
newHeader.Time = ctx.BlockHeader().Time.Add(input.keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(time.Duration(-1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.True(t, inactiveQueue.Valid())
inactiveQueue.Close()
EndBlocker(ctx, keeper)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
EndBlocker(ctx, input.keeper)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
@ -108,40 +123,44 @@ func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(5) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.True(t, inactiveQueue.Valid())
inactiveQueue.Close()
EndBlocker(ctx, keeper)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
EndBlocker(ctx, input.keeper)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
}
func TestTickPassedDepositPeriod(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(keeper)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(input.keeper)
inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue := input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
activeQueue := input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, activeQueue.Valid())
activeQueue.Close()
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
newProposalMsg := NewMsgSubmitProposal(
ContentFromProposalType("test2", "test2", ProposalTypeText),
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)},
input.addrs[0],
)
res := govHandler(ctx, newProposalMsg)
require.True(t, res.IsOK())
var proposalID uint64
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
@ -149,76 +168,114 @@ func TestTickPassedDepositPeriod(t *testing.T) {
newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
newDepositMsg := NewMsgDeposit(input.addrs[1], proposalID, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 5)})
res = govHandler(ctx, newDepositMsg)
require.True(t, res.IsOK())
activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
activeQueue = input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, activeQueue.Valid())
activeQueue.Close()
}
func TestTickPassedVotingPeriod(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
SortAddresses(addrs)
input := getMockApp(t, 10, GenesisState{}, nil)
SortAddresses(input.addrs)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(keeper)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.keeper.ck.SetSendEnabled(ctx, true)
govHandler := NewHandler(input.keeper)
inactiveQueue := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue := input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
activeQueue := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
activeQueue := input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, activeQueue.Valid())
activeQueue.Close()
proposalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(5))}
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], proposalCoins)
newProposalMsg := NewMsgSubmitProposal(testProposal(), proposalCoins, input.addrs[0])
res := govHandler(ctx, newProposalMsg)
require.True(t, res.IsOK())
var proposalID uint64
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID)
newHeader := ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second)
ctx = ctx.WithBlockHeader(newHeader)
newDepositMsg := NewMsgDeposit(addrs[1], proposalID, proposalCoins)
newDepositMsg := NewMsgDeposit(input.addrs[1], proposalID, proposalCoins)
res = govHandler(ctx, newDepositMsg)
require.True(t, res.IsOK())
newHeader = ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(keeper.GetVotingParams(ctx).VotingPeriod)
newHeader.Time = ctx.BlockHeader().Time.Add(input.keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(input.keeper.GetVotingParams(ctx).VotingPeriod)
ctx = ctx.WithBlockHeader(newHeader)
inactiveQueue = keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
inactiveQueue = input.keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, inactiveQueue.Valid())
inactiveQueue.Close()
activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
activeQueue = input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.True(t, activeQueue.Valid())
var activeProposalID uint64
keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID)
proposal, ok := keeper.GetProposal(ctx, activeProposalID)
require.NoError(t, input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeQueue.Value(), &activeProposalID))
proposal, ok := input.keeper.GetProposal(ctx, activeProposalID)
require.True(t, ok)
require.Equal(t, StatusVotingPeriod, proposal.Status)
depositsIterator := keeper.GetDeposits(ctx, proposalID)
depositsIterator := input.keeper.GetDeposits(ctx, proposalID)
require.True(t, depositsIterator.Valid())
depositsIterator.Close()
activeQueue.Close()
EndBlocker(ctx, keeper)
EndBlocker(ctx, input.keeper)
activeQueue = keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
activeQueue = input.keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time)
require.False(t, activeQueue.Valid())
activeQueue.Close()
}
func TestProposalPassedEndblocker(t *testing.T) {
input := getMockApp(t, 1, GenesisState{}, nil)
SortAddresses(input.addrs)
handler := NewHandler(input.keeper)
stakingHandler := staking.NewHandler(input.sk)
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
valAddr := sdk.ValAddress(input.addrs[0])
input.keeper.ck.SetSendEnabled(ctx, true)
createValidators(t, stakingHandler, ctx, []sdk.ValAddress{valAddr}, []int64{10})
staking.EndBlocker(ctx, input.sk)
proposal, err := input.keeper.SubmitProposal(ctx, testProposal())
require.NoError(t, err)
proposalCoins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(10))}
newDepositMsg := NewMsgDeposit(input.addrs[0], proposal.ProposalID, proposalCoins)
res := handler(ctx, newDepositMsg)
require.True(t, res.IsOK())
err = input.keeper.AddVote(ctx, proposal.ProposalID, input.addrs[0], OptionYes)
require.NoError(t, err)
newHeader := ctx.BlockHeader()
newHeader.Time = ctx.BlockHeader().Time.Add(input.keeper.GetDepositParams(ctx).MaxDepositPeriod).Add(input.keeper.GetVotingParams(ctx).VotingPeriod)
ctx = ctx.WithBlockHeader(newHeader)
resTags := EndBlocker(ctx, input.keeper)
require.Equal(t, sdk.MakeTag(tags.ProposalResult, tags.ActionProposalPassed), resTags[1])
}

View File

@ -1,66 +0,0 @@
//nolint
package gov
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
DefaultCodespace sdk.CodespaceType = ModuleName
CodeUnknownProposal sdk.CodeType = 1
CodeInactiveProposal sdk.CodeType = 2
CodeAlreadyActiveProposal sdk.CodeType = 3
CodeAlreadyFinishedProposal sdk.CodeType = 4
CodeAddressNotStaked sdk.CodeType = 5
CodeInvalidTitle sdk.CodeType = 6
CodeInvalidDescription sdk.CodeType = 7
CodeInvalidProposalType sdk.CodeType = 8
CodeInvalidVote sdk.CodeType = 9
CodeInvalidGenesis sdk.CodeType = 10
CodeInvalidProposalStatus sdk.CodeType = 11
)
// Error constructors
func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal with id %d", proposalID))
}
func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal with id %d", proposalID))
}
func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("Proposal %d has been already active", proposalID))
}
func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("Proposal %d has already passed its voting period", proposalID))
}
func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error {
return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("Address %s is not staked and is thus ineligible to vote", address))
}
func ErrInvalidTitle(codespace sdk.CodespaceType, errorMsg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidTitle, errorMsg)
}
func ErrInvalidDescription(codespace sdk.CodespaceType, errorMsg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDescription, errorMsg)
}
func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType ProposalKind) sdk.Error {
return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("Proposal Type '%s' is not valid", proposalType))
}
func ErrInvalidVote(codespace sdk.CodespaceType, voteOption VoteOption) sdk.Error {
return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%v' is not a valid voting option", voteOption))
}
func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidVote, msg)
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -67,8 +68,12 @@ func DefaultGenesisState() GenesisState {
// Checks whether 2 GenesisState structs are equivalent.
func (data GenesisState) Equal(data2 GenesisState) bool {
b1 := msgCdc.MustMarshalBinaryBare(data)
b2 := msgCdc.MustMarshalBinaryBare(data2)
cdc := codec.New()
RegisterCodec(cdc)
b1 := cdc.MustMarshalBinaryBare(data)
b2 := cdc.MustMarshalBinaryBare(data2)
return bytes.Equal(b1, b2)
}

View File

@ -25,19 +25,19 @@ func TestEqualProposalID(t *testing.T) {
func TestEqualProposals(t *testing.T) {
// Generate mock app and keepers
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(addrs)
input := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(input.addrs)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
// Submit two proposals
proposal := testProposal()
proposal1, err := keeper.SubmitProposal(ctx, proposal)
proposal1, err := input.keeper.SubmitProposal(ctx, proposal)
require.NoError(t, err)
proposal2, err := keeper.SubmitProposal(ctx, proposal)
proposal2, err := input.keeper.SubmitProposal(ctx, proposal)
require.NoError(t, err)
// They are similar but their IDs should be different
@ -67,62 +67,62 @@ func TestEqualProposals(t *testing.T) {
func TestImportExportQueues(t *testing.T) {
// Generate mock app and keepers
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(addrs)
input := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(input.addrs)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
// Create two proposals, put the second into the voting period
proposal := testProposal()
proposal1, err := keeper.SubmitProposal(ctx, proposal)
proposal1, err := input.keeper.SubmitProposal(ctx, proposal)
require.NoError(t, err)
proposalID1 := proposal1.ProposalID
proposal2, err := keeper.SubmitProposal(ctx, proposal)
proposal2, err := input.keeper.SubmitProposal(ctx, proposal)
require.NoError(t, err)
proposalID2 := proposal2.ProposalID
_, votingStarted := keeper.AddDeposit(ctx, proposalID2, addrs[0], keeper.GetDepositParams(ctx).MinDeposit)
_, votingStarted := input.keeper.AddDeposit(ctx, proposalID2, input.addrs[0], input.keeper.GetDepositParams(ctx).MinDeposit)
require.True(t, votingStarted)
proposal1, ok := keeper.GetProposal(ctx, proposalID1)
proposal1, ok := input.keeper.GetProposal(ctx, proposalID1)
require.True(t, ok)
proposal2, ok = keeper.GetProposal(ctx, proposalID2)
proposal2, ok = input.keeper.GetProposal(ctx, proposalID2)
require.True(t, ok)
require.True(t, proposal1.Status == StatusDepositPeriod)
require.True(t, proposal2.Status == StatusVotingPeriod)
genAccs := mapp.AccountKeeper.GetAllAccounts(ctx)
genAccs := input.mApp.AccountKeeper.GetAllAccounts(ctx)
// Export the state and import it into a new Mock App
genState := ExportGenesis(ctx, keeper)
mapp2, keeper2, _, _, _, _ := getMockApp(t, 2, genState, genAccs)
genState := ExportGenesis(ctx, input.keeper)
input2 := getMockApp(t, 2, genState, genAccs)
header = abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp2.BeginBlock(abci.RequestBeginBlock{Header: header})
header = abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input2.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx2 := mapp2.BaseApp.NewContext(false, abci.Header{})
ctx2 := input2.mApp.BaseApp.NewContext(false, abci.Header{})
// Jump the time forward past the DepositPeriod and VotingPeriod
ctx2 = ctx2.WithBlockTime(ctx2.BlockHeader().Time.Add(keeper2.GetDepositParams(ctx2).MaxDepositPeriod).Add(keeper2.GetVotingParams(ctx2).VotingPeriod))
ctx2 = ctx2.WithBlockTime(ctx2.BlockHeader().Time.Add(input2.keeper.GetDepositParams(ctx2).MaxDepositPeriod).Add(input2.keeper.GetVotingParams(ctx2).VotingPeriod))
// Make sure that they are still in the DepositPeriod and VotingPeriod respectively
proposal1, ok = keeper2.GetProposal(ctx2, proposalID1)
proposal1, ok = input2.keeper.GetProposal(ctx2, proposalID1)
require.True(t, ok)
proposal2, ok = keeper2.GetProposal(ctx2, proposalID2)
proposal2, ok = input2.keeper.GetProposal(ctx2, proposalID2)
require.True(t, ok)
require.True(t, proposal1.Status == StatusDepositPeriod)
require.True(t, proposal2.Status == StatusVotingPeriod)
// Run the endblocker. Check to make sure that proposal1 is removed from state, and proposal2 is finished VotingPeriod.
EndBlocker(ctx2, keeper2)
EndBlocker(ctx2, input2.keeper)
proposal1, ok = keeper2.GetProposal(ctx2, proposalID1)
proposal1, ok = input2.keeper.GetProposal(ctx2, proposalID1)
require.False(t, ok)
proposal2, ok = keeper2.GetProposal(ctx2, proposalID2)
proposal2, ok = input2.keeper.GetProposal(ctx2, proposalID2)
require.True(t, ok)
require.True(t, proposal2.Status == StatusRejected)
}

View File

@ -28,27 +28,17 @@ func NewHandler(keeper Keeper) sdk.Handler {
}
func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result {
var content ProposalContent
switch msg.ProposalType {
case ProposalTypeText:
content = NewTextProposal(msg.Title, msg.Description)
case ProposalTypeSoftwareUpgrade:
content = NewSoftwareUpgradeProposal(msg.Title, msg.Description)
default:
return ErrInvalidProposalType(keeper.codespace, msg.ProposalType).Result()
}
proposal, err := keeper.SubmitProposal(ctx, content)
if err != nil {
return err.Result()
}
proposalID := proposal.ProposalID
proposalIDStr := fmt.Sprintf("%d", proposalID)
err, votingStarted := keeper.AddDeposit(ctx, proposalID, msg.Proposer, msg.InitialDeposit)
proposal, err := keeper.SubmitProposal(ctx, msg.Content)
if err != nil {
return err.Result()
}
err, votingStarted := keeper.AddDeposit(ctx, proposal.ProposalID, msg.Proposer, msg.InitialDeposit)
if err != nil {
return err.Result()
}
proposalIDStr := fmt.Sprintf("%d", proposal.ProposalID)
resTags := sdk.NewTags(
tags.ProposalID, proposalIDStr,
tags.Category, tags.TxCategory,
@ -60,7 +50,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos
}
return sdk.Result{
Data: keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID),
Data: keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal.ProposalID),
Tags: resTags,
}
}

View File

@ -3,7 +3,7 @@ package gov
import (
"time"
codec "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
@ -11,23 +11,6 @@ import (
"github.com/tendermint/tendermint/libs/log"
)
const (
// ModuleKey is the name of the module
ModuleName = "gov"
// StoreKey is the store key string for gov
StoreKey = ModuleName
// RouterKey is the message route for gov
RouterKey = ModuleName
// QuerierRoute is the querier route for gov
QuerierRoute = ModuleName
// Parameter store default namestore
DefaultParamspace = ModuleName
)
// Parameter store key
var (
ParamStoreKeyDepositParams = []byte("depositparams")
@ -73,6 +56,9 @@ type Keeper struct {
// Reserved codespace
codespace sdk.CodespaceType
// Proposal router
router Router
}
// NewKeeper returns a governance keeper. It handles:
@ -80,8 +66,15 @@ type Keeper struct {
// - depositing funds into proposals, and activating upon sufficient funds being deposited
// - users voting on proposals, with weight proportional to stake in the system
// - and tallying the result of the vote.
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper,
paramSpace params.Subspace, ck BankKeeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper {
func NewKeeper(
cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper, paramSpace params.Subspace,
ck BankKeeper, ds sdk.DelegationSet, codespace sdk.CodespaceType, rtr Router,
) Keeper {
// It is vital to seal the governance proposal router here as to not allow
// further handlers to be registered after the keeper is created since this
// could create invalid or non-deterministic behavior.
rtr.Seal()
return Keeper{
storeKey: key,
@ -92,36 +85,42 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramsKeeper params.Keeper,
vs: ds.GetValidatorSet(),
cdc: cdc,
codespace: codespace,
router: rtr,
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") }
func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/gov") }
// Proposals
func (keeper Keeper) SubmitProposal(ctx sdk.Context, content ProposalContent) (proposal Proposal, err sdk.Error) {
func (keeper Keeper) SubmitProposal(ctx sdk.Context, content Content) (Proposal, sdk.Error) {
if !keeper.router.HasRoute(content.ProposalRoute()) {
return Proposal{}, ErrNoProposalHandlerExists(keeper.codespace, content)
}
// Execute the proposal content in a cache-wrapped context to validate the
// actual parameter changes before the proposal proceeds through the
// governance process. State is not persisted.
cacheCtx, _ := ctx.CacheContext()
handler := keeper.router.GetRoute(content.ProposalRoute())
if err := handler(cacheCtx, content); err != nil {
return Proposal{}, ErrInvalidProposalContent(keeper.codespace, err.Result().Log)
}
proposalID, err := keeper.getNewProposalID(ctx)
if err != nil {
return
return Proposal{}, err
}
submitTime := ctx.BlockHeader().Time
depositPeriod := keeper.GetDepositParams(ctx).MaxDepositPeriod
proposal = Proposal{
ProposalContent: content,
ProposalID: proposalID,
Status: StatusDepositPeriod,
FinalTallyResult: EmptyTallyResult(),
TotalDeposit: sdk.NewCoins(),
SubmitTime: submitTime,
DepositEndTime: submitTime.Add(depositPeriod),
}
proposal := NewProposal(content, proposalID, submitTime, submitTime.Add(depositPeriod))
keeper.SetProposal(ctx, proposal)
keeper.InsertInactiveProposalQueue(ctx, proposal.DepositEndTime, proposalID)
return
return proposal, nil
}
// Get Proposal from store by ProposalID
@ -192,7 +191,7 @@ func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddr
continue
}
if validProposalStatus(status) {
if ValidProposalStatus(status) {
if proposal.Status != status {
continue
}
@ -208,7 +207,7 @@ func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sd
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz != nil {
return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set")
return ErrInvalidGenesis(keeper.codespace, "initial proposal ID already set")
}
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID)
store.Set(KeyNextProposalID, bz)
@ -230,7 +229,7 @@ func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err s
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID never set")
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1)
@ -243,7 +242,7 @@ func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64,
store := ctx.KVStore(keeper.storeKey)
bz := store.Get(KeyNextProposalID)
if bz == nil {
return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
return 0, ErrInvalidGenesis(keeper.codespace, "initial proposal ID never set")
}
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID)
return proposalID, nil
@ -307,7 +306,7 @@ func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.A
return ErrInactiveProposal(keeper.codespace, proposalID)
}
if !validVoteOption(option) {
if !ValidVoteOption(option) {
return ErrInvalidVote(keeper.codespace, option)
}

View File

@ -1,6 +1,7 @@
package gov
import (
"strings"
"testing"
"time"
@ -8,232 +9,233 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestGetSetProposal(t *testing.T) {
mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil)
input := getMockApp(t, 0, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
tp := testProposal()
proposal, err := keeper.SubmitProposal(ctx, tp)
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
gotProposal, ok := keeper.GetProposal(ctx, proposalID)
gotProposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.True(t, ProposalEqual(proposal, gotProposal))
}
func TestIncrementProposalNumber(t *testing.T) {
mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil)
input := getMockApp(t, 0, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
tp := testProposal()
keeper.SubmitProposal(ctx, tp)
keeper.SubmitProposal(ctx, tp)
keeper.SubmitProposal(ctx, tp)
keeper.SubmitProposal(ctx, tp)
keeper.SubmitProposal(ctx, tp)
proposal6, err := keeper.SubmitProposal(ctx, tp)
input.keeper.SubmitProposal(ctx, tp)
input.keeper.SubmitProposal(ctx, tp)
input.keeper.SubmitProposal(ctx, tp)
input.keeper.SubmitProposal(ctx, tp)
input.keeper.SubmitProposal(ctx, tp)
proposal6, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
require.Equal(t, uint64(6), proposal6.ProposalID)
}
func TestActivateVotingPeriod(t *testing.T) {
mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil)
input := getMockApp(t, 0, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
tp := testProposal()
proposal, err := keeper.SubmitProposal(ctx, tp)
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
require.True(t, proposal.VotingStartTime.Equal(time.Time{}))
keeper.activateVotingPeriod(ctx, proposal)
input.keeper.activateVotingPeriod(ctx, proposal)
require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time))
proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposal.ProposalID)
require.True(t, ok)
activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime)
activeIterator := input.keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime)
require.True(t, activeIterator.Valid())
var proposalID uint64
keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID)
input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID)
require.Equal(t, proposalID, proposal.ProposalID)
activeIterator.Close()
}
func TestDeposits(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(addrs)
input := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(input.addrs)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
tp := testProposal()
proposal, err := keeper.SubmitProposal(ctx, tp)
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
fourStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(4)))
fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.TokensFromTendermintPower(5)))
addr0Initial := keeper.ck.GetCoins(ctx, addrs[0])
addr1Initial := keeper.ck.GetCoins(ctx, addrs[1])
addr0Initial := input.keeper.ck.GetCoins(ctx, input.addrs[0])
addr1Initial := input.keeper.ck.GetCoins(ctx, input.addrs[1])
expTokens := sdk.TokensFromTendermintPower(42)
require.Equal(t, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, expTokens)), addr0Initial)
require.True(t, proposal.TotalDeposit.IsEqual(sdk.NewCoins()))
// Check no deposits at beginning
deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1])
deposit, found := input.keeper.GetDeposit(ctx, proposalID, input.addrs[1])
require.False(t, found)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.True(t, proposal.VotingStartTime.Equal(time.Time{}))
// Check first deposit
err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourStake)
err, votingStarted := input.keeper.AddDeposit(ctx, proposalID, input.addrs[0], fourStake)
require.Nil(t, err)
require.False(t, votingStarted)
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0])
deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[0])
require.True(t, found)
require.Equal(t, fourStake, deposit.Amount)
require.Equal(t, addrs[0], deposit.Depositor)
proposal, ok = keeper.GetProposal(ctx, proposalID)
require.Equal(t, input.addrs[0], deposit.Depositor)
proposal, ok = input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.Equal(t, fourStake, proposal.TotalDeposit)
require.Equal(t, addr0Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[0]))
require.Equal(t, addr0Initial.Sub(fourStake), input.keeper.ck.GetCoins(ctx, input.addrs[0]))
// Check a second deposit from same address
err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], fiveStake)
err, votingStarted = input.keeper.AddDeposit(ctx, proposalID, input.addrs[0], fiveStake)
require.Nil(t, err)
require.False(t, votingStarted)
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0])
deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[0])
require.True(t, found)
require.Equal(t, fourStake.Add(fiveStake), deposit.Amount)
require.Equal(t, addrs[0], deposit.Depositor)
proposal, ok = keeper.GetProposal(ctx, proposalID)
require.Equal(t, input.addrs[0], deposit.Depositor)
proposal, ok = input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.Equal(t, fourStake.Add(fiveStake), proposal.TotalDeposit)
require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), keeper.ck.GetCoins(ctx, addrs[0]))
require.Equal(t, addr0Initial.Sub(fourStake).Sub(fiveStake), input.keeper.ck.GetCoins(ctx, input.addrs[0]))
// Check third deposit from a new address
err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fourStake)
err, votingStarted = input.keeper.AddDeposit(ctx, proposalID, input.addrs[1], fourStake)
require.Nil(t, err)
require.True(t, votingStarted)
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1])
require.True(t, found)
require.Equal(t, addrs[1], deposit.Depositor)
require.Equal(t, input.addrs[1], deposit.Depositor)
require.Equal(t, fourStake, deposit.Amount)
proposal, ok = keeper.GetProposal(ctx, proposalID)
proposal, ok = input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.Equal(t, fourStake.Add(fiveStake).Add(fourStake), proposal.TotalDeposit)
require.Equal(t, addr1Initial.Sub(fourStake), keeper.ck.GetCoins(ctx, addrs[1]))
require.Equal(t, addr1Initial.Sub(fourStake), input.keeper.ck.GetCoins(ctx, input.addrs[1]))
// Check that proposal moved to voting period
proposal, ok = keeper.GetProposal(ctx, proposalID)
proposal, ok = input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time))
// Test deposit iterator
depositsIterator := keeper.GetDeposits(ctx, proposalID)
depositsIterator := input.keeper.GetDeposits(ctx, proposalID)
require.True(t, depositsIterator.Valid())
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit)
require.Equal(t, addrs[0], deposit.Depositor)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit)
require.Equal(t, input.addrs[0], deposit.Depositor)
require.Equal(t, fourStake.Add(fiveStake), deposit.Amount)
depositsIterator.Next()
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit)
require.Equal(t, addrs[1], deposit.Depositor)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit)
require.Equal(t, input.addrs[1], deposit.Depositor)
require.Equal(t, fourStake, deposit.Amount)
depositsIterator.Next()
require.False(t, depositsIterator.Valid())
depositsIterator.Close()
// Test Refund Deposits
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1])
require.True(t, found)
require.Equal(t, fourStake, deposit.Amount)
keeper.RefundDeposits(ctx, proposalID)
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
input.keeper.RefundDeposits(ctx, proposalID)
deposit, found = input.keeper.GetDeposit(ctx, proposalID, input.addrs[1])
require.False(t, found)
require.Equal(t, addr0Initial, keeper.ck.GetCoins(ctx, addrs[0]))
require.Equal(t, addr1Initial, keeper.ck.GetCoins(ctx, addrs[1]))
require.Equal(t, addr0Initial, input.keeper.ck.GetCoins(ctx, input.addrs[0]))
require.Equal(t, addr1Initial, input.keeper.ck.GetCoins(ctx, input.addrs[1]))
}
func TestVotes(t *testing.T) {
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(addrs)
input := getMockApp(t, 2, GenesisState{}, nil)
SortAddresses(input.addrs)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
tp := testProposal()
proposal, err := keeper.SubmitProposal(ctx, tp)
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
// Test first vote
keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
vote, found := keeper.GetVote(ctx, proposalID, addrs[0])
input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain)
vote, found := input.keeper.GetVote(ctx, proposalID, input.addrs[0])
require.True(t, found)
require.Equal(t, addrs[0], vote.Voter)
require.Equal(t, input.addrs[0], vote.Voter)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, OptionAbstain, vote.Option)
// Test change of vote
keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
vote, found = keeper.GetVote(ctx, proposalID, addrs[0])
input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
vote, found = input.keeper.GetVote(ctx, proposalID, input.addrs[0])
require.True(t, found)
require.Equal(t, addrs[0], vote.Voter)
require.Equal(t, input.addrs[0], vote.Voter)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, OptionYes, vote.Option)
// Test second vote
keeper.AddVote(ctx, proposalID, addrs[1], OptionNoWithVeto)
vote, found = keeper.GetVote(ctx, proposalID, addrs[1])
input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNoWithVeto)
vote, found = input.keeper.GetVote(ctx, proposalID, input.addrs[1])
require.True(t, found)
require.Equal(t, addrs[1], vote.Voter)
require.Equal(t, input.addrs[1], vote.Voter)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, OptionNoWithVeto, vote.Option)
// Test vote iterator
votesIterator := keeper.GetVotes(ctx, proposalID)
votesIterator := input.keeper.GetVotes(ctx, proposalID)
require.True(t, votesIterator.Valid())
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote)
require.True(t, votesIterator.Valid())
require.Equal(t, addrs[0], vote.Voter)
require.Equal(t, input.addrs[0], vote.Voter)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, OptionYes, vote.Option)
votesIterator.Next()
require.True(t, votesIterator.Valid())
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote)
input.keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote)
require.True(t, votesIterator.Valid())
require.Equal(t, addrs[1], vote.Voter)
require.Equal(t, input.addrs[1], vote.Voter)
require.Equal(t, proposalID, vote.ProposalID)
require.Equal(t, OptionNoWithVeto, vote.Option)
votesIterator.Next()
@ -242,34 +244,112 @@ func TestVotes(t *testing.T) {
}
func TestProposalQueues(t *testing.T) {
mapp, keeper, _, _, _, _ := getMockApp(t, 0, GenesisState{}, nil)
input := getMockApp(t, 0, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
mapp.InitChainer(ctx, abci.RequestInitChain{})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.mApp.InitChainer(ctx, abci.RequestInitChain{})
// create test proposals
tp := testProposal()
proposal, err := keeper.SubmitProposal(ctx, tp)
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, proposal.DepositEndTime)
inactiveIterator := input.keeper.InactiveProposalQueueIterator(ctx, proposal.DepositEndTime)
require.True(t, inactiveIterator.Valid())
var proposalID uint64
keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID)
input.keeper.cdc.UnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID)
require.Equal(t, proposalID, proposal.ProposalID)
inactiveIterator.Close()
keeper.activateVotingPeriod(ctx, proposal)
input.keeper.activateVotingPeriod(ctx, proposal)
proposal, ok := keeper.GetProposal(ctx, proposal.ProposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposal.ProposalID)
require.True(t, ok)
activeIterator := keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime)
activeIterator := input.keeper.ActiveProposalQueueIterator(ctx, proposal.VotingEndTime)
require.True(t, activeIterator.Valid())
keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID)
input.keeper.cdc.UnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID)
require.Equal(t, proposalID, proposal.ProposalID)
activeIterator.Close()
}
type validProposal struct{}
func (validProposal) GetTitle() string { return "title" }
func (validProposal) GetDescription() string { return "description" }
func (validProposal) ProposalRoute() string { return RouterKey }
func (validProposal) ProposalType() string { return ProposalTypeText }
func (validProposal) String() string { return "" }
func (validProposal) ValidateBasic() sdk.Error { return nil }
type invalidProposalTitle1 struct{ validProposal }
func (invalidProposalTitle1) GetTitle() string { return "" }
type invalidProposalTitle2 struct{ validProposal }
func (invalidProposalTitle2) GetTitle() string { return strings.Repeat("1234567890", 100) }
type invalidProposalDesc1 struct{ validProposal }
func (invalidProposalDesc1) GetDescription() string { return "" }
type invalidProposalDesc2 struct{ validProposal }
func (invalidProposalDesc2) GetDescription() string { return strings.Repeat("1234567890", 1000) }
type invalidProposalRoute struct{ validProposal }
func (invalidProposalRoute) ProposalRoute() string { return "nonexistingroute" }
type invalidProposalValidation struct{ validProposal }
func (invalidProposalValidation) ValidateBasic() sdk.Error {
return sdk.NewError(sdk.CodespaceUndefined, sdk.CodeInternal, "")
}
func registerTestCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(validProposal{}, "test/validproposal", nil)
cdc.RegisterConcrete(invalidProposalTitle1{}, "test/invalidproposalt1", nil)
cdc.RegisterConcrete(invalidProposalTitle2{}, "test/invalidproposalt2", nil)
cdc.RegisterConcrete(invalidProposalDesc1{}, "test/invalidproposald1", nil)
cdc.RegisterConcrete(invalidProposalDesc2{}, "test/invalidproposald2", nil)
cdc.RegisterConcrete(invalidProposalRoute{}, "test/invalidproposalr", nil)
cdc.RegisterConcrete(invalidProposalValidation{}, "test/invalidproposalv", nil)
}
func TestSubmitProposal(t *testing.T) {
input := getMockApp(t, 0, GenesisState{}, nil)
registerTestCodec(input.keeper.cdc)
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
input.mApp.InitChainer(ctx, abci.RequestInitChain{})
testCases := []struct {
content Content
expectedErr sdk.Error
}{
{validProposal{}, nil},
// Keeper does not check the validity of title and description, no error
{invalidProposalTitle1{}, nil},
{invalidProposalTitle2{}, nil},
{invalidProposalDesc1{}, nil},
{invalidProposalDesc2{}, nil},
// error only when invalid route
{invalidProposalRoute{}, ErrNoProposalHandlerExists(DefaultCodespace, invalidProposalRoute{})},
// Keeper does not call ValidateBasic, msg.ValidateBasic does
{invalidProposalValidation{}, nil},
}
for _, tc := range testCases {
_, err := input.keeper.SubmitProposal(ctx, tc.content)
require.Equal(t, tc.expectedErr, err, "unexpected type of error: %s", err)
}
}

View File

@ -1,361 +0,0 @@
package gov
import (
"encoding/json"
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Proposal is a struct used by gov module internally
// embedds ProposalContent with additional fields to record the status of the proposal process
type Proposal struct {
ProposalContent `json:"proposal_content"` // Proposal content interface
ProposalID uint64 `json:"proposal_id"` // ID of the proposal
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
// nolint
func (p Proposal) String() string {
return fmt.Sprintf(`Proposal %d:
Title: %s
Type: %s
Status: %s
Submit Time: %s
Deposit End Time: %s
Total Deposit: %s
Voting Start Time: %s
Voting End Time: %s
Description: %s`,
p.ProposalID, p.GetTitle(), p.ProposalType(),
p.Status, p.SubmitTime, p.DepositEndTime,
p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
)
}
// ProposalContent is an interface that has title, description, and proposaltype
// that the governance module can use to identify them and generate human readable messages
// ProposalContent can have additional fields, which will handled by ProposalHandlers
// via type assertion, e.g. parameter change amount in ParameterChangeProposal
type ProposalContent interface {
GetTitle() string
GetDescription() string
ProposalType() ProposalKind
}
// Proposals is an array of proposal
type Proposals []Proposal
// nolint
func (p Proposals) String() string {
out := "ID - (Status) [Type] Title\n"
for _, prop := range p {
out += fmt.Sprintf("%d - (%s) [%s] %s\n",
prop.ProposalID, prop.Status,
prop.ProposalType(), prop.GetTitle())
}
return strings.TrimSpace(out)
}
// Text Proposals
type TextProposal struct {
Title string `json:"title"` // Title of the proposal
Description string `json:"description"` // Description of the proposal
}
func NewTextProposal(title, description string) TextProposal {
return TextProposal{
Title: title,
Description: description,
}
}
// Implements Proposal Interface
var _ ProposalContent = TextProposal{}
// nolint
func (tp TextProposal) GetTitle() string { return tp.Title }
func (tp TextProposal) GetDescription() string { return tp.Description }
func (tp TextProposal) ProposalType() ProposalKind { return ProposalTypeText }
// Software Upgrade Proposals
type SoftwareUpgradeProposal struct {
TextProposal
}
func NewSoftwareUpgradeProposal(title, description string) SoftwareUpgradeProposal {
return SoftwareUpgradeProposal{
TextProposal: NewTextProposal(title, description),
}
}
// Implements Proposal Interface
var _ ProposalContent = SoftwareUpgradeProposal{}
// nolint
func (sup SoftwareUpgradeProposal) ProposalType() ProposalKind { return ProposalTypeSoftwareUpgrade }
// ProposalQueue
type ProposalQueue []uint64
// ProposalKind
// Type that represents Proposal Type as a byte
type ProposalKind byte
//nolint
const (
ProposalTypeNil ProposalKind = 0x00
ProposalTypeText ProposalKind = 0x01
ProposalTypeParameterChange ProposalKind = 0x02
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
)
// String to proposalType byte. Returns 0xff if invalid.
func ProposalTypeFromString(str string) (ProposalKind, error) {
switch str {
case "Text":
return ProposalTypeText, nil
case "ParameterChange":
return ProposalTypeParameterChange, nil
case "SoftwareUpgrade":
return ProposalTypeSoftwareUpgrade, nil
default:
return ProposalKind(0xff), fmt.Errorf("'%s' is not a valid proposal type", str)
}
}
// is defined ProposalType?
func validProposalType(pt ProposalKind) bool {
if pt == ProposalTypeText ||
pt == ProposalTypeParameterChange ||
pt == ProposalTypeSoftwareUpgrade {
return true
}
return false
}
// Marshal needed for protobuf compatibility
func (pt ProposalKind) Marshal() ([]byte, error) {
return []byte{byte(pt)}, nil
}
// Unmarshal needed for protobuf compatibility
func (pt *ProposalKind) Unmarshal(data []byte) error {
*pt = ProposalKind(data[0])
return nil
}
// Marshals to JSON using string
func (pt ProposalKind) MarshalJSON() ([]byte, error) {
return json.Marshal(pt.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (pt *ProposalKind) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalTypeFromString(s)
if err != nil {
return err
}
*pt = bz2
return nil
}
// Turns VoteOption byte to String
func (pt ProposalKind) String() string {
switch pt {
case ProposalTypeText:
return "Text"
case ProposalTypeParameterChange:
return "ParameterChange"
case ProposalTypeSoftwareUpgrade:
return "SoftwareUpgrade"
default:
return ""
}
}
// For Printf / Sprintf, returns bech32 when using %s
// nolint: errcheck
func (pt ProposalKind) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(pt.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(pt))))
}
}
// ProposalStatus
// Type that represents Proposal Status as a byte
type ProposalStatus byte
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
)
// ProposalStatusToString turns a string into a ProposalStatus
func ProposalStatusFromString(str string) (ProposalStatus, error) {
switch str {
case "DepositPeriod":
return StatusDepositPeriod, nil
case "VotingPeriod":
return StatusVotingPeriod, nil
case "Passed":
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
}
}
// is defined ProposalType?
func validProposalStatus(status ProposalStatus) bool {
if status == StatusDepositPeriod ||
status == StatusVotingPeriod ||
status == StatusPassed ||
status == StatusRejected {
return true
}
return false
}
// Marshal needed for protobuf compatibility
func (status ProposalStatus) Marshal() ([]byte, error) {
return []byte{byte(status)}, nil
}
// Unmarshal needed for protobuf compatibility
func (status *ProposalStatus) Unmarshal(data []byte) error {
*status = ProposalStatus(data[0])
return nil
}
// Marshals to JSON using string
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalStatusFromString(s)
if err != nil {
return err
}
*status = bz2
return nil
}
// Turns VoteStatus byte to String
func (status ProposalStatus) String() string {
switch status {
case StatusDepositPeriod:
return "DepositPeriod"
case StatusVotingPeriod:
return "VotingPeriod"
case StatusPassed:
return "Passed"
case StatusRejected:
return "Rejected"
default:
return ""
}
}
// For Printf / Sprintf, returns bech32 when using %s
// nolint: errcheck
func (status ProposalStatus) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(status.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
}
}
// Tally Results
type TallyResult struct {
Yes sdk.Int `json:"yes"`
Abstain sdk.Int `json:"abstain"`
No sdk.Int `json:"no"`
NoWithVeto sdk.Int `json:"no_with_veto"`
}
func NewTallyResult(yes, abstain, no, noWithVeto sdk.Int) TallyResult {
return TallyResult{
Yes: yes,
Abstain: abstain,
No: no,
NoWithVeto: noWithVeto,
}
}
func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult {
return TallyResult{
Yes: results[OptionYes].TruncateInt(),
Abstain: results[OptionAbstain].TruncateInt(),
No: results[OptionNo].TruncateInt(),
NoWithVeto: results[OptionNoWithVeto].TruncateInt(),
}
}
// checks if two proposals are equal
func EmptyTallyResult() TallyResult {
return TallyResult{
Yes: sdk.ZeroInt(),
Abstain: sdk.ZeroInt(),
No: sdk.ZeroInt(),
NoWithVeto: sdk.ZeroInt(),
}
}
// checks if two proposals are equal
func (tr TallyResult) Equals(comp TallyResult) bool {
return (tr.Yes.Equal(comp.Yes) &&
tr.Abstain.Equal(comp.Abstain) &&
tr.No.Equal(comp.No) &&
tr.NoWithVeto.Equal(comp.NoWithVeto))
}
func (tr TallyResult) String() string {
return fmt.Sprintf(`Tally Result:
Yes: %s
Abstain: %s
No: %s
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)
const custom = "custom"
@ -78,12 +79,12 @@ func getQueriedProposals(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querie
Data: cdc.MustMarshalJSON(NewQueryProposalsParams(status, limit, voter, depositor)),
}
bz, err := querier(ctx, []string{QueryProposal}, query)
bz, err := querier(ctx, []string{QueryProposals}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var proposals []Proposal
err2 := cdc.UnmarshalJSON(bz, proposals)
var proposals Proposals
err2 := cdc.UnmarshalJSON(bz, &proposals)
require.Nil(t, err2)
return proposals
}
@ -94,12 +95,12 @@ func getQueriedDeposit(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier
Data: cdc.MustMarshalJSON(NewQueryDepositParams(proposalID, depositor)),
}
bz, err := querier(ctx, []string{QueryDeposits}, query)
bz, err := querier(ctx, []string{QueryDeposit}, query)
require.Nil(t, err)
require.NotNil(t, bz)
var deposit Deposit
err2 := cdc.UnmarshalJSON(bz, deposit)
err2 := cdc.UnmarshalJSON(bz, &deposit)
require.Nil(t, err2)
return deposit
}
@ -168,66 +169,80 @@ func getQueriedTally(t *testing.T, ctx sdk.Context, cdc *codec.Codec, querier sd
return tally
}
func testQueryParams(t *testing.T) {
func TestQueryParams(t *testing.T) {
cdc := codec.New()
mapp, keeper, _, _, _, _ := getMockApp(t, 1000, GenesisState{}, nil)
querier := NewQuerier(keeper)
ctx := mapp.NewContext(false, abci.Header{})
input := getMockApp(t, 1000, GenesisState{}, nil)
querier := NewQuerier(input.keeper)
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := input.mApp.NewContext(false, abci.Header{})
getQueriedParams(t, ctx, cdc, querier)
}
func testQueries(t *testing.T) {
func TestQueries(t *testing.T) {
cdc := codec.New()
mapp, keeper, _, addrs, _, _ := getMockApp(t, 1000, GenesisState{}, nil)
querier := NewQuerier(keeper)
handler := NewHandler(keeper)
ctx := mapp.NewContext(false, abci.Header{})
input := getMockApp(t, 1000, GenesisState{}, nil)
querier := NewQuerier(input.keeper)
handler := NewHandler(input.keeper)
types.RegisterCodec(cdc)
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := input.mApp.NewContext(false, abci.Header{})
depositParams, _, _ := getQueriedParams(t, ctx, cdc, querier)
// addrs[0] proposes (and deposits) proposals #1 and #2
res := handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
// input.addrs[0] proposes (and deposits) proposals #1 and #2
res := handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, input.addrs[0]))
var proposalID1 uint64
require.True(t, res.IsOK())
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID1)
res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
res = handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 10000000)}, input.addrs[0]))
var proposalID2 uint64
require.True(t, res.IsOK())
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID2)
// addrs[1] proposes (and deposits) proposals #3
res = handler(ctx, NewMsgSubmitProposal("title", "description", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewInt64Coin("dummycoin", 1)}))
// input.addrs[1] proposes (and deposits) proposals #3
res = handler(ctx, NewMsgSubmitProposal(testProposal(), sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1)}, input.addrs[1]))
var proposalID3 uint64
require.True(t, res.IsOK())
cdc.MustUnmarshalBinaryLengthPrefixed(res.Data, &proposalID3)
// addrs[1] deposits on proposals #2 & #3
res = handler(ctx, NewMsgDeposit(addrs[1], proposalID2, depositParams.MinDeposit))
res = handler(ctx, NewMsgDeposit(addrs[1], proposalID3, depositParams.MinDeposit))
// input.addrs[1] deposits on proposals #2 & #3
res = handler(ctx, NewMsgDeposit(input.addrs[1], proposalID2, depositParams.MinDeposit))
res = handler(ctx, NewMsgDeposit(input.addrs[1], proposalID3, depositParams.MinDeposit))
// check deposits on proposal1 match individual deposits
deposits := getQueriedDeposits(t, ctx, cdc, querier, proposalID1)
require.Len(t, deposits, 1)
deposit := getQueriedDeposit(t, ctx, cdc, querier, proposalID1, addrs[0])
deposit := getQueriedDeposit(t, ctx, cdc, querier, proposalID1, input.addrs[0])
require.Equal(t, deposit, deposits[0])
// check deposits on proposal2 match individual deposits
deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID2)
require.Len(t, deposits, 2)
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[0])
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, input.addrs[0])
require.True(t, deposit.Equals(deposits[0]))
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, addrs[1])
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID2, input.addrs[1])
require.True(t, deposit.Equals(deposits[1]))
// check deposits on proposal3 match individual deposits
deposits = getQueriedDeposits(t, ctx, cdc, querier, proposalID3)
require.Len(t, deposits, 1)
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID3, addrs[1])
deposit = getQueriedDeposit(t, ctx, cdc, querier, proposalID3, input.addrs[1])
require.Equal(t, deposit, deposits[0])
// Only proposal #1 should be in Deposit Period
proposals := getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusDepositPeriod, 0)
require.Len(t, proposals, 1)
require.Equal(t, proposalID1, proposals[0].ProposalID)
// Only proposals #2 and #3 should be in Voting Period
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, nil, StatusVotingPeriod, 0)
require.Len(t, proposals, 2)
@ -235,29 +250,30 @@ func testQueries(t *testing.T) {
require.Equal(t, proposalID3, proposals[1].ProposalID)
// Addrs[0] votes on proposals #2 & #3
handler(ctx, NewMsgVote(addrs[0], proposalID2, OptionYes))
handler(ctx, NewMsgVote(addrs[0], proposalID3, OptionYes))
require.True(t, handler(ctx, NewMsgVote(input.addrs[0], proposalID2, OptionYes)).IsOK())
require.True(t, handler(ctx, NewMsgVote(input.addrs[0], proposalID3, OptionYes)).IsOK())
// Addrs[1] votes on proposal #3
handler(ctx, NewMsgVote(addrs[1], proposalID3, OptionYes))
handler(ctx, NewMsgVote(input.addrs[1], proposalID3, OptionYes))
// Test query voted by addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[0], StatusNil, 0)
// Test query voted by input.addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, input.addrs[0], StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).ProposalID)
require.Equal(t, proposalID3, (proposals[1]).ProposalID)
// Test query votes on Proposal 2
votes := getQueriedVotes(t, ctx, cdc, querier, proposalID2)
require.Len(t, votes, 1)
require.Equal(t, addrs[0], votes[0].Voter)
vote := getQueriedVote(t, ctx, cdc, querier, proposalID2, addrs[0])
require.Equal(t, input.addrs[0], votes[0].Voter)
vote := getQueriedVote(t, ctx, cdc, querier, proposalID2, input.addrs[0])
require.Equal(t, vote, votes[0])
// Test query votes on Proposal 3
votes = getQueriedVotes(t, ctx, cdc, querier, proposalID3)
require.Len(t, votes, 2)
require.True(t, addrs[0].String() == votes[0].Voter.String())
require.True(t, addrs[1].String() == votes[0].Voter.String())
require.True(t, input.addrs[0].String() == votes[0].Voter.String())
require.True(t, input.addrs[1].String() == votes[1].Voter.String())
// Test proposals queries with filters
@ -267,24 +283,20 @@ func testQueries(t *testing.T) {
require.Equal(t, proposalID2, (proposals[1]).ProposalID)
require.Equal(t, proposalID3, (proposals[2]).ProposalID)
// Test query voted by addrs[1]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, addrs[1], StatusNil, 0)
// Test query voted by input.addrs[1]
proposals = getQueriedProposals(t, ctx, cdc, querier, nil, input.addrs[1], StatusNil, 0)
require.Equal(t, proposalID3, (proposals[0]).ProposalID)
// Test query deposited by addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], nil, StatusNil, 0)
// Test query deposited by input.addrs[0]
proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[0], nil, StatusNil, 0)
require.Equal(t, proposalID1, (proposals[0]).ProposalID)
// Test query deposited by addr2
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[1], nil, StatusNil, 0)
proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[1], nil, StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).ProposalID)
require.Equal(t, proposalID3, (proposals[1]).ProposalID)
// Test query voted AND deposited by addr1
proposals = getQueriedProposals(t, ctx, cdc, querier, addrs[0], addrs[0], StatusNil, 0)
proposals = getQueriedProposals(t, ctx, cdc, querier, input.addrs[0], input.addrs[0], StatusNil, 0)
require.Equal(t, proposalID2, (proposals[0]).ProposalID)
// Test Tally Query
tally := getQueriedTally(t, ctx, cdc, querier, proposalID2)
require.True(t, !tally.Equals(EmptyTallyResult()))
}

74
x/gov/router.go Normal file
View File

@ -0,0 +1,74 @@
package gov
import (
"fmt"
"regexp"
)
var (
_ Router = (*router)(nil)
isAlphaNumeric = regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString
)
// Router implements a governance Handler router.
//
// TODO: Use generic router (ref #3976).
type Router interface {
AddRoute(r string, h Handler) (rtr Router)
HasRoute(r string) bool
GetRoute(path string) (h Handler)
Seal()
}
type router struct {
routes map[string]Handler
sealed bool
}
func NewRouter() Router {
return &router{
routes: make(map[string]Handler),
}
}
// Seal seals the router which prohibits any subsequent route handlers to be
// added. Seal will panic if called more than once.
func (rtr *router) Seal() {
if rtr.sealed {
panic("router already sealed")
}
rtr.sealed = true
}
// AddRoute adds a governance handler for a given path. It returns the Router
// so AddRoute calls can be linked. It will panic if the router is sealed.
func (rtr *router) AddRoute(path string, h Handler) Router {
if rtr.sealed {
panic("router sealed; cannot add route handler")
}
if !isAlphaNumeric(path) {
panic("route expressions can only contain alphanumeric characters")
}
if rtr.HasRoute(path) {
panic(fmt.Sprintf("route %s has already been initialized", path))
}
rtr.routes[path] = h
return rtr
}
// HasRoute returns true if the router has a path registered or false otherwise.
func (rtr *router) HasRoute(path string) bool {
return rtr.routes[path] != nil
}
// GetRoute returns a Handler for a given path.
func (rtr *router) GetRoute(path string) Handler {
if !rtr.HasRoute(path) {
panic(fmt.Sprintf("route \"%s\" does not exist", path))
}
return rtr.routes[path]
}

View File

@ -106,11 +106,12 @@ func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, handler sdk.Hand
func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) {
deposit := randomDeposit(r)
msg = gov.NewMsgSubmitProposal(
simulation.RandStringOfLength(r, 5),
simulation.RandStringOfLength(r, 5),
gov.ProposalTypeText,
sender.Address,
gov.NewTextProposal(
simulation.RandStringOfLength(r, 5),
simulation.RandStringOfLength(r, 5),
),
deposit,
sender.Address,
)
if msg.ValidateBasic() != nil {
err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes())

View File

@ -5,16 +5,21 @@ import (
)
// Governance tags
var (
ActionProposalDropped = "proposal-dropped"
ActionProposalPassed = "proposal-passed"
ActionProposalRejected = "proposal-rejected"
const (
ActionProposalDropped = "proposal-dropped" // didn't meet min deposit
ActionProposalPassed = "proposal-passed" // meet vote quorum
ActionProposalRejected = "proposal-rejected" // didn't meet vote quorum
ActionProposalFailed = "proposal-failed" // error on proposal handler
TxCategory = "governance"
Action = sdk.TagAction
Category = sdk.TagCategory
Sender = sdk.TagSender
ProposalID = "proposal-id"
VotingPeriodStart = "voting-period-start"
ProposalResult = "proposal-result"
)
// SDK tag aliases
var (
Action = sdk.TagAction
Category = sdk.TagCategory
Sender = sdk.TagSender
)

View File

@ -45,6 +45,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall
sdk.ZeroDec(),
OptionEmpty,
)
return false
})
@ -106,24 +107,28 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall
if keeper.vs.TotalBondedTokens(ctx).IsZero() {
return false, tallyResults
}
// If there is not enough quorum of votes, the proposal fails
percentVoting := totalVotingPower.Quo(keeper.vs.TotalBondedTokens(ctx).ToDec())
if percentVoting.LT(tallyParams.Quorum) {
return false, tallyResults
}
// If no one votes (everyone abstains), proposal fails
if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) {
return false, tallyResults
}
// If more than 1/3 of voters veto, proposal fails
if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyParams.Veto) {
return false, tallyResults
}
// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyParams.Threshold) {
return true, tallyResults
}
// If more than 1/2 of non-abstaining voters vote No, proposal fails
// If more than 1/2 of non-abstaining voters vote No, proposal fails
return false, tallyResults
}

View File

@ -6,607 +6,583 @@ import (
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
)
var (
pubkeys = []crypto.PubKey{ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey()}
testDescription = staking.NewDescription("T", "E", "S", "T")
testCommissionMsg = staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec())
)
func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, powerAmt []int64) {
require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.")
for i := 0; i < len(addrs); i++ {
valTokens := sdk.TokensFromTendermintPower(powerAmt[i])
valCreateMsg := staking.NewMsgCreateValidator(
addrs[i], pubkeys[i], sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
testDescription, testCommissionMsg, sdk.OneInt(),
)
res := stakingHandler(ctx, valCreateMsg)
require.True(t, res.IsOK())
}
}
func TestTallyNoOneVotes(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:2]))
for i, addr := range addrs[:2] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:2]))
for i, addr := range input.addrs[:2] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.True(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyNoQuorum(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:2]))
for i, addr := range addrs[:2] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:2]))
for i, addr := range input.addrs[:2] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{2, 5})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, _ := tally(ctx, keeper, proposal)
passes, _ := tally(ctx, input.keeper, proposal)
require.False(t, passes)
}
func TestTallyOnlyValidatorsAllYes(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:2]))
for i, addr := range addrs[:2] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:2]))
for i, addr := range input.addrs[:2] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 5})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.True(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyOnlyValidators51No(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:2]))
for i, addr := range addrs[:2] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:2]))
for i, addr := range input.addrs[:2] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, _ := tally(ctx, keeper, proposal)
passes, _ := tally(ctx, input.keeper, proposal)
require.False(t, passes)
}
func TestTallyOnlyValidators51Yes(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.True(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyOnlyValidatorsVetoed(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNoWithVeto)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.True(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyOnlyValidatorsAbstainFails(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionAbstain)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyOnlyValidatorsNonVoter(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{6, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyDelgatorOverride(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
delTokens := sdk.TokensFromTendermintPower(30)
delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[3], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyDelgatorInherit(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
delTokens := sdk.TokensFromTendermintPower(30)
delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionNo)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.True(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyDelgatorMultipleOverride(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{5, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
delTokens := sdk.TokensFromTendermintPower(10)
delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg)
delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg2)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[3], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyDelgatorMultipleInherit(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valTokens1 := sdk.TokensFromTendermintPower(25)
val1CreateMsg := staking.NewMsgCreateValidator(
sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens1), testDescription, testCommissionMsg, sdk.OneInt(),
sdk.ValAddress(input.addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens1), testDescription, testCommissionMsg, sdk.OneInt(),
)
stakingHandler(ctx, val1CreateMsg)
valTokens2 := sdk.TokensFromTendermintPower(6)
val2CreateMsg := staking.NewMsgCreateValidator(
sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens2), testDescription, testCommissionMsg, sdk.OneInt(),
sdk.ValAddress(input.addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens2), testDescription, testCommissionMsg, sdk.OneInt(),
)
stakingHandler(ctx, val2CreateMsg)
valTokens3 := sdk.TokensFromTendermintPower(7)
val3CreateMsg := staking.NewMsgCreateValidator(
sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens3), testDescription, testCommissionMsg, sdk.OneInt(),
sdk.ValAddress(input.addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewCoin(sdk.DefaultBondDenom, valTokens3), testDescription, testCommissionMsg, sdk.OneInt(),
)
stakingHandler(ctx, val3CreateMsg)
delTokens := sdk.TokensFromTendermintPower(10)
delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg)
delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg2)
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.False(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))
}
func TestTallyJailedValidator(t *testing.T) {
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10, GenesisState{}, nil)
input := getMockApp(t, 10, GenesisState{}, nil)
header := abci.Header{Height: mapp.LastBlockHeight() + 1}
mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
header := abci.Header{Height: input.mApp.LastBlockHeight() + 1}
input.mApp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(sk)
ctx := input.mApp.BaseApp.NewContext(false, abci.Header{})
stakingHandler := staking.NewHandler(input.sk)
valAddrs := make([]sdk.ValAddress, len(addrs[:3]))
for i, addr := range addrs[:3] {
valAddrs := make([]sdk.ValAddress, len(input.addrs[:3]))
for i, addr := range input.addrs[:3] {
valAddrs[i] = sdk.ValAddress(addr)
}
createValidators(t, stakingHandler, ctx, valAddrs, []int64{25, 6, 7})
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
delTokens := sdk.TokensFromTendermintPower(10)
delegator1Msg := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[2]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg)
delegator1Msg2 := staking.NewMsgDelegate(addrs[3], sdk.ValAddress(addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
delegator1Msg2 := staking.NewMsgDelegate(input.addrs[3], sdk.ValAddress(input.addrs[1]), sdk.NewCoin(sdk.DefaultBondDenom, delTokens))
stakingHandler(ctx, delegator1Msg2)
val2, found := sk.GetValidator(ctx, sdk.ValAddress(addrs[1]))
val2, found := input.sk.GetValidator(ctx, sdk.ValAddress(input.addrs[1]))
require.True(t, found)
sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address()))
input.sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address()))
staking.EndBlocker(ctx, sk)
staking.EndBlocker(ctx, input.sk)
tp := TextProposal{"Test", "description"}
proposal, err := keeper.SubmitProposal(ctx, tp)
tp := testProposal()
proposal, err := input.keeper.SubmitProposal(ctx, tp)
require.NoError(t, err)
proposalID := proposal.ProposalID
proposal.Status = StatusVotingPeriod
keeper.SetProposal(ctx, proposal)
input.keeper.SetProposal(ctx, proposal)
err = keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[0], OptionYes)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[1], OptionNo)
require.Nil(t, err)
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
err = input.keeper.AddVote(ctx, proposalID, input.addrs[2], OptionNo)
require.Nil(t, err)
proposal, ok := keeper.GetProposal(ctx, proposalID)
proposal, ok := input.keeper.GetProposal(ctx, proposalID)
require.True(t, ok)
passes, tallyResults := tally(ctx, keeper, proposal)
passes, tallyResults := tally(ctx, input.keeper, proposal)
require.True(t, passes)
require.False(t, tallyResults.Equals(EmptyTallyResult()))

View File

@ -7,10 +7,12 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
@ -18,42 +20,57 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking"
)
// initialize the mock application for this module
func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) (
mapp *mock.App, keeper Keeper, sk staking.Keeper, addrs []sdk.AccAddress,
pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) {
type testInput struct {
mApp *mock.App
keeper Keeper
router Router
sk staking.Keeper
addrs []sdk.AccAddress
pubKeys []crypto.PubKey
privKeys []crypto.PrivKey
}
mapp = mock.NewApp()
func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) testInput {
mApp := mock.NewApp()
staking.RegisterCodec(mapp.Cdc)
RegisterCodec(mapp.Cdc)
staking.RegisterCodec(mApp.Cdc)
RegisterCodec(mApp.Cdc)
keyStaking := sdk.NewKVStoreKey(staking.StoreKey)
tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey)
tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey)
keyGov := sdk.NewKVStoreKey(StoreKey)
pk := mapp.ParamsKeeper
ck := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
sk = staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
keeper = NewKeeper(mapp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace)
rtr := NewRouter().AddRoute(RouterKey, ProposalHandler)
mapp.Router().AddRoute(RouterKey, NewHandler(keeper))
mapp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper))
pk := mApp.ParamsKeeper
ck := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
sk := staking.NewKeeper(mApp.Cdc, keyStaking, tKeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
keeper := NewKeeper(mApp.Cdc, keyGov, pk, pk.Subspace("testgov"), ck, sk, DefaultCodespace, rtr)
mapp.SetEndBlocker(getEndBlocker(keeper))
mapp.SetInitChainer(getInitChainer(mapp, keeper, sk, genState))
mApp.Router().AddRoute(RouterKey, NewHandler(keeper))
mApp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper))
require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keyGov))
mApp.SetEndBlocker(getEndBlocker(keeper))
mApp.SetInitChainer(getInitChainer(mApp, keeper, sk, genState))
require.NoError(t, mApp.CompleteSetup(keyStaking, tKeyStaking, keyGov))
valTokens := sdk.TokensFromTendermintPower(42)
var (
addrs []sdk.AccAddress
pubKeys []crypto.PubKey
privKeys []crypto.PrivKey
)
if genAccs == nil || len(genAccs) == 0 {
genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs,
sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, valTokens)})
}
mock.SetGenesis(mapp, genAccs)
mock.SetGenesis(mApp, genAccs)
return mapp, keeper, sk, addrs, pubKeys, privKeys
return testInput{mApp, keeper, rtr, sk, addrs, pubKeys, privKeys}
}
// gov and staking endblocker
@ -90,20 +107,6 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper,
}
}
// TODO: Remove once address interface has been implemented (ref: #2186)
func SortValAddresses(addrs []sdk.ValAddress) {
var byteAddrs [][]byte
for _, addr := range addrs {
byteAddrs = append(byteAddrs, addr.Bytes())
}
SortByteArrays(byteAddrs)
for i, byteAddr := range byteAddrs {
addrs[i] = byteAddr
}
}
// Sorts Addresses
func SortAddresses(addrs []sdk.AccAddress) {
var byteAddrs [][]byte
@ -147,11 +150,40 @@ func SortByteArrays(src [][]byte) [][]byte {
return sorted
}
func testProposal() TextProposal {
func testProposal() Content {
return NewTextProposal("Test", "description")
}
// checks if two proposals are equal (note: slow, for tests only)
func ProposalEqual(proposalA Proposal, proposalB Proposal) bool {
return bytes.Equal(msgCdc.MustMarshalBinaryBare(proposalA), msgCdc.MustMarshalBinaryBare(proposalB))
cdc := codec.New()
RegisterCodec(cdc)
return bytes.Equal(cdc.MustMarshalBinaryBare(proposalA), cdc.MustMarshalBinaryBare(proposalB))
}
var (
pubkeys = []crypto.PubKey{
ed25519.GenPrivKey().PubKey(),
ed25519.GenPrivKey().PubKey(),
ed25519.GenPrivKey().PubKey(),
}
testDescription = staking.NewDescription("T", "E", "S", "T")
testCommissionMsg = staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec())
)
func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, powerAmt []int64) {
require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.")
for i := 0; i < len(addrs); i++ {
valTokens := sdk.TokensFromTendermintPower(powerAmt[i])
valCreateMsg := staking.NewMsgCreateValidator(
addrs[i], pubkeys[i], sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
testDescription, testCommissionMsg, sdk.OneInt(),
)
res := stakingHandler(ctx, valCreateMsg)
require.True(t, res.IsOK())
}
}

33
x/gov/types/codec.go Normal file
View File

@ -0,0 +1,33 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
var (
msgCdc = codec.New()
)
// RegisterCodec registers all the necessary types and interfaces for
// governance.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*Content)(nil), nil)
cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil)
cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil)
cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil)
cdc.RegisterConcrete(TextProposal{}, "cosmos-sdk/TextProposal", nil)
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "cosmos-sdk/SoftwareUpgradeProposal", nil)
}
// RegisterProposalTypeCodec registers an external proposal content type defined
// in another module for the internal msgCdc. This allows the MsgSubmitProposal
// to be correctly Amino encoded and decoded.
func RegisterProposalTypeCodec(o interface{}, name string) {
msgCdc.RegisterConcrete(o, name, nil)
}
func init() {
RegisterCodec(msgCdc)
}

53
x/gov/types/content.go Normal file
View File

@ -0,0 +1,53 @@
package types
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Constants pertaining to a Content object
const (
MaxDescriptionLength int = 5000
MaxTitleLength int = 140
)
// Content defines an interface that a proposal must implement. It contains
// information such as the title and description along with the type and routing
// information for the appropriate handler to process the proposal. Content can
// have additional fields, which will handled by a proposal's Handler.
type Content interface {
GetTitle() string
GetDescription() string
ProposalRoute() string
ProposalType() string
ValidateBasic() sdk.Error
String() string
}
// Handler defines a function that handles a proposal after it has passed the
// governance process.
type Handler func(ctx sdk.Context, content Content) sdk.Error
// ValidateAbstract validates a proposal's abstract contents returning an error
// if invalid.
func ValidateAbstract(codespace sdk.CodespaceType, c Content) sdk.Error {
title := c.GetTitle()
if len(strings.TrimSpace(title)) == 0 {
return ErrInvalidProposalContent(codespace, "proposal title cannot be blank")
}
if len(title) > MaxTitleLength {
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal title is longer than max length of %d", MaxTitleLength))
}
description := c.GetDescription()
if len(description) == 0 {
return ErrInvalidProposalContent(codespace, "proposal description cannot be blank")
}
if len(description) > MaxDescriptionLength {
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal description is longer than max length of %d", MaxDescriptionLength))
}
return nil
}

43
x/gov/types/deposit.go Normal file
View File

@ -0,0 +1,43 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Deposit
type Deposit struct {
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Amount sdk.Coins `json:"amount"` // Deposit amount
}
func (d Deposit) String() string {
return fmt.Sprintf("deposit by %s on Proposal %d is for the amount %s",
d.Depositor, d.ProposalID, d.Amount)
}
// Deposits is a collection of Deposit objects
type Deposits []Deposit
func (d Deposits) String() string {
if len(d) == 0 {
return "[]"
}
out := fmt.Sprintf("Deposits for Proposal %d:", d[0].ProposalID)
for _, dep := range d {
out += fmt.Sprintf("\n %s: %s", dep.Depositor, dep.Amount)
}
return out
}
// Equals returns whether two deposits are equal.
func (d Deposit) Equals(comp Deposit) bool {
return d.Depositor.Equals(comp.Depositor) && d.ProposalID == comp.ProposalID && d.Amount.IsEqual(comp.Amount)
}
// Empty returns whether a deposit is empty.
func (d Deposit) Empty() bool {
return d.Equals(Deposit{})
}

65
x/gov/types/errors.go Normal file
View File

@ -0,0 +1,65 @@
//nolint
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
DefaultCodespace sdk.CodespaceType = "gov"
CodeUnknownProposal sdk.CodeType = 1
CodeInactiveProposal sdk.CodeType = 2
CodeAlreadyActiveProposal sdk.CodeType = 3
CodeAlreadyFinishedProposal sdk.CodeType = 4
CodeAddressNotStaked sdk.CodeType = 5
CodeInvalidContent sdk.CodeType = 6
CodeInvalidProposalType sdk.CodeType = 7
CodeInvalidVote sdk.CodeType = 8
CodeInvalidGenesis sdk.CodeType = 9
CodeInvalidProposalStatus sdk.CodeType = 10
CodeProposalHandlerNotExists sdk.CodeType = 11
)
func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("unknown proposal with id %d", proposalID))
}
func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("inactive proposal with id %d", proposalID))
}
func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("proposal %d has been already active", proposalID))
}
func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("proposal %d has already passed its voting period", proposalID))
}
func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error {
return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("address %s is not staked and is thus ineligible to vote", address))
}
func ErrInvalidProposalContent(cs sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(cs, CodeInvalidContent, fmt.Sprintf("invalid proposal content: %s", msg))
}
func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("proposal type '%s' is not valid", proposalType))
}
func ErrInvalidVote(codespace sdk.CodespaceType, voteOption VoteOption) sdk.Error {
return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%v' is not a valid voting option", voteOption.String()))
}
func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidVote, msg)
}
func ErrNoProposalHandlerExists(codespace sdk.CodespaceType, content interface{}) sdk.Error {
return sdk.NewError(codespace, CodeProposalHandlerNotExists, fmt.Sprintf("'%T' does not have a corresponding handler", content))
}

View File

@ -1,4 +1,4 @@
package gov
package types
import (
"bytes"
@ -8,6 +8,23 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// ModuleKey is the name of the module
ModuleName = "gov"
// StoreKey is the store key string for gov
StoreKey = ModuleName
// RouterKey is the message route for gov
RouterKey = ModuleName
// QuerierRoute is the querier route for gov
QuerierRoute = ModuleName
// Parameter store default namestore
DefaultParamspace = ModuleName
)
// Key for getting a the next available proposalID from the store
var (
KeyDelimiter = []byte(":")

View File

@ -1,4 +1,4 @@
package gov
package types
import (
"fmt"
@ -11,30 +11,19 @@ const (
TypeMsgDeposit = "deposit"
TypeMsgVote = "vote"
TypeMsgSubmitProposal = "submit_proposal"
MaxDescriptionLength int = 5000
MaxTitleLength int = 140
)
var _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgDeposit{}, MsgVote{}
// MsgSubmitProposal
type MsgSubmitProposal struct {
Title string `json:"title"` // Title of the proposal
Description string `json:"description"` // Description of the proposal
ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
Content Content `json:"content"`
InitialDeposit sdk.Coins `json:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive
Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer
InitialDeposit sdk.Coins `json:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive.
}
func NewMsgSubmitProposal(title, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins) MsgSubmitProposal {
return MsgSubmitProposal{
Title: title,
Description: description,
ProposalType: proposalType,
Proposer: proposer,
InitialDeposit: initialDeposit,
}
func NewMsgSubmitProposal(content Content, initialDeposit sdk.Coins, proposer sdk.AccAddress) MsgSubmitProposal {
return MsgSubmitProposal{content, initialDeposit, proposer}
}
//nolint
@ -43,20 +32,14 @@ func (msg MsgSubmitProposal) Type() string { return TypeMsgSubmitProposal }
// Implements Msg.
func (msg MsgSubmitProposal) ValidateBasic() sdk.Error {
if len(msg.Title) == 0 {
return ErrInvalidTitle(DefaultCodespace, "No title present in proposal")
if msg.Content == nil {
return ErrInvalidProposalContent(DefaultCodespace, "missing content")
}
if len(msg.Title) > MaxTitleLength {
return ErrInvalidTitle(DefaultCodespace, fmt.Sprintf("Proposal title is longer than max length of %d", MaxTitleLength))
}
if len(msg.Description) == 0 {
return ErrInvalidDescription(DefaultCodespace, "No description present in proposal")
}
if len(msg.Description) > MaxDescriptionLength {
return ErrInvalidDescription(DefaultCodespace, fmt.Sprintf("Proposal description is longer than max length of %d", MaxDescriptionLength))
}
if !validProposalType(msg.ProposalType) {
return ErrInvalidProposalType(DefaultCodespace, msg.ProposalType)
if msg.Content.ProposalType() == ProposalTypeSoftwareUpgrade {
// Disable software upgrade proposals as they are currently equivalent
// to text proposals. Re-enable once a valid software upgrade proposal
// handler is implemented.
return ErrInvalidProposalType(DefaultCodespace, msg.Content.ProposalType())
}
if msg.Proposer.Empty() {
return sdk.ErrInvalidAddress(msg.Proposer.String())
@ -67,11 +50,18 @@ func (msg MsgSubmitProposal) ValidateBasic() sdk.Error {
if msg.InitialDeposit.IsAnyNegative() {
return sdk.ErrInvalidCoins(msg.InitialDeposit.String())
}
return nil
if !IsValidProposalType(msg.Content.ProposalType()) {
return ErrInvalidProposalType(DefaultCodespace, msg.Content.ProposalType())
}
return msg.Content.ValidateBasic()
}
func (msg MsgSubmitProposal) String() string {
return fmt.Sprintf("MsgSubmitProposal{%s, %s, %s, %v}", msg.Title, msg.Description, msg.ProposalType, msg.InitialDeposit)
return fmt.Sprintf(`Submit Proposal Message:
Content: %s
Initial Deposit: %s
`, msg.Content.String(), msg.InitialDeposit)
}
// Implements Msg.
@ -93,11 +83,7 @@ type MsgDeposit struct {
}
func NewMsgDeposit(depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit {
return MsgDeposit{
ProposalID: proposalID,
Depositor: depositor,
Amount: amount,
}
return MsgDeposit{proposalID, depositor, amount}
}
// Implements Msg.
@ -116,14 +102,16 @@ func (msg MsgDeposit) ValidateBasic() sdk.Error {
if msg.Amount.IsAnyNegative() {
return sdk.ErrInvalidCoins(msg.Amount.String())
}
if msg.ProposalID < 0 {
return ErrUnknownProposal(DefaultCodespace, msg.ProposalID)
}
return nil
}
func (msg MsgDeposit) String() string {
return fmt.Sprintf("MsgDeposit{%s=>%v: %v}", msg.Depositor, msg.ProposalID, msg.Amount)
return fmt.Sprintf(`Deposit Message:
Depositer: %s
Proposal ID: %d
Amount: %s
`, msg.Depositor, msg.ProposalID, msg.Amount)
}
// Implements Msg.
@ -145,11 +133,7 @@ type MsgVote struct {
}
func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgVote {
return MsgVote{
ProposalID: proposalID,
Voter: voter,
Option: option,
}
return MsgVote{proposalID, voter, option}
}
// Implements Msg.
@ -162,17 +146,18 @@ func (msg MsgVote) ValidateBasic() sdk.Error {
if msg.Voter.Empty() {
return sdk.ErrInvalidAddress(msg.Voter.String())
}
if msg.ProposalID < 0 {
return ErrUnknownProposal(DefaultCodespace, msg.ProposalID)
}
if !validVoteOption(msg.Option) {
if !ValidVoteOption(msg.Option) {
return ErrInvalidVote(DefaultCodespace, msg.Option)
}
return nil
}
func (msg MsgVote) String() string {
return fmt.Sprintf("MsgVote{%v - %s}", msg.ProposalID, msg.Option)
return fmt.Sprintf(`Vote Message:
Proposal ID: %d
Option: %s
`, msg.ProposalID, msg.Option)
}
// Implements Msg.

View File

@ -1,4 +1,4 @@
package gov
package types
import (
"strings"
@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/mock"
)
var (
@ -15,6 +14,10 @@ var (
coinsZero = sdk.NewCoins()
coinsPosNotAtoms = sdk.NewCoins(sdk.NewInt64Coin("foo", 10000))
coinsMulti = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000), sdk.NewInt64Coin("foo", 10000))
addrs = []sdk.AccAddress{
sdk.AccAddress("test1"),
sdk.AccAddress("test2"),
}
)
func init() {
@ -23,10 +26,9 @@ func init() {
// test ValidateBasic for MsgCreateValidator
func TestMsgSubmitProposal(t *testing.T) {
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.NewCoins())
tests := []struct {
title, description string
proposalType ProposalKind
proposalType string
proposerAddr sdk.AccAddress
initialDeposit sdk.Coins
expectPass bool
@ -34,9 +36,7 @@ func TestMsgSubmitProposal(t *testing.T) {
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsPos, true},
{"", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsPos, false},
{"Test Proposal", "", ProposalTypeText, addrs[0], coinsPos, false},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeParameterChange, addrs[0], coinsPos, true},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeSoftwareUpgrade, addrs[0], coinsPos, true},
{"Test Proposal", "the purpose of this proposal is to test", 0x05, addrs[0], coinsPos, false},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeSoftwareUpgrade, addrs[0], coinsPos, false},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, sdk.AccAddress{}, coinsPos, false},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsZero, true},
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsMulti, true},
@ -45,7 +45,12 @@ func TestMsgSubmitProposal(t *testing.T) {
}
for i, tc := range tests {
msg := NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit)
msg := NewMsgSubmitProposal(
ContentFromProposalType(tc.title, tc.description, tc.proposalType),
tc.initialDeposit,
tc.proposerAddr,
)
if tc.expectPass {
require.NoError(t, msg.ValidateBasic(), "test: %v", i)
} else {
@ -65,7 +70,6 @@ func TestMsgDepositGetSignBytes(t *testing.T) {
// test ValidateBasic for MsgDeposit
func TestMsgDeposit(t *testing.T) {
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.NewCoins())
tests := []struct {
proposalID uint64
depositorAddr sdk.AccAddress
@ -90,7 +94,6 @@ func TestMsgDeposit(t *testing.T) {
// test ValidateBasic for MsgDeposit
func TestMsgVote(t *testing.T) {
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.NewCoins())
tests := []struct {
proposalID uint64
voterAddr sdk.AccAddress

366
x/gov/types/proposal.go Normal file
View File

@ -0,0 +1,366 @@
package types
import (
"encoding/json"
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Proposal defines a struct used by the governance module to allow for voting
// on network changes.
type Proposal struct {
Content `json:"content"` // Proposal content interface
ProposalID uint64 `json:"id"` // ID of the proposal
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
func NewProposal(content Content, id uint64, submitTime, depositEndTime time.Time) Proposal {
return Proposal{
Content: content,
ProposalID: id,
Status: StatusDepositPeriod,
FinalTallyResult: EmptyTallyResult(),
TotalDeposit: sdk.NewCoins(),
SubmitTime: submitTime,
DepositEndTime: depositEndTime,
}
}
// nolint
func (p Proposal) String() string {
return fmt.Sprintf(`Proposal %d:
Title: %s
Type: %s
Status: %s
Submit Time: %s
Deposit End Time: %s
Total Deposit: %s
Voting Start Time: %s
Voting End Time: %s
Description: %s`,
p.ProposalID, p.GetTitle(), p.ProposalType(),
p.Status, p.SubmitTime, p.DepositEndTime,
p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
)
}
// Proposals is an array of proposal
type Proposals []Proposal
// nolint
func (p Proposals) String() string {
out := "ID - (Status) [Type] Title\n"
for _, prop := range p {
out += fmt.Sprintf("%d - (%s) [%s] %s\n",
prop.ProposalID, prop.Status,
prop.ProposalType(), prop.GetTitle())
}
return strings.TrimSpace(out)
}
type (
// ProposalQueue
ProposalQueue []uint64
// ProposalStatus is a type alias that represents a proposal status as a byte
ProposalStatus byte
)
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
StatusFailed ProposalStatus = 0x05
)
// ProposalStatusToString turns a string into a ProposalStatus
func ProposalStatusFromString(str string) (ProposalStatus, error) {
switch str {
case "DepositPeriod":
return StatusDepositPeriod, nil
case "VotingPeriod":
return StatusVotingPeriod, nil
case "Passed":
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "Failed":
return StatusFailed, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
}
}
// ValidProposalStatus returns true if the proposal status is valid and false
// otherwise.
func ValidProposalStatus(status ProposalStatus) bool {
if status == StatusDepositPeriod ||
status == StatusVotingPeriod ||
status == StatusPassed ||
status == StatusRejected ||
status == StatusFailed {
return true
}
return false
}
// Marshal needed for protobuf compatibility
func (status ProposalStatus) Marshal() ([]byte, error) {
return []byte{byte(status)}, nil
}
// Unmarshal needed for protobuf compatibility
func (status *ProposalStatus) Unmarshal(data []byte) error {
*status = ProposalStatus(data[0])
return nil
}
// Marshals to JSON using string
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalStatusFromString(s)
if err != nil {
return err
}
*status = bz2
return nil
}
// String implements the Stringer interface.
func (status ProposalStatus) String() string {
switch status {
case StatusDepositPeriod:
return "DepositPeriod"
case StatusVotingPeriod:
return "VotingPeriod"
case StatusPassed:
return "Passed"
case StatusRejected:
return "Rejected"
case StatusFailed:
return "Failed"
default:
return ""
}
}
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (status ProposalStatus) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(status.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
}
}
// Tally Results
type TallyResult struct {
Yes sdk.Int `json:"yes"`
Abstain sdk.Int `json:"abstain"`
No sdk.Int `json:"no"`
NoWithVeto sdk.Int `json:"no_with_veto"`
}
func NewTallyResult(yes, abstain, no, noWithVeto sdk.Int) TallyResult {
return TallyResult{
Yes: yes,
Abstain: abstain,
No: no,
NoWithVeto: noWithVeto,
}
}
func NewTallyResultFromMap(results map[VoteOption]sdk.Dec) TallyResult {
return TallyResult{
Yes: results[OptionYes].TruncateInt(),
Abstain: results[OptionAbstain].TruncateInt(),
No: results[OptionNo].TruncateInt(),
NoWithVeto: results[OptionNoWithVeto].TruncateInt(),
}
}
// EmptyTallyResult returns an empty TallyResult.
func EmptyTallyResult() TallyResult {
return TallyResult{
Yes: sdk.ZeroInt(),
Abstain: sdk.ZeroInt(),
No: sdk.ZeroInt(),
NoWithVeto: sdk.ZeroInt(),
}
}
// Equals returns if two proposals are equal.
func (tr TallyResult) Equals(comp TallyResult) bool {
return tr.Yes.Equal(comp.Yes) &&
tr.Abstain.Equal(comp.Abstain) &&
tr.No.Equal(comp.No) &&
tr.NoWithVeto.Equal(comp.NoWithVeto)
}
func (tr TallyResult) String() string {
return fmt.Sprintf(`Tally Result:
Yes: %s
Abstain: %s
No: %s
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
}
// Proposal types
const (
ProposalTypeText string = "Text"
ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
)
// Text Proposal
type TextProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewTextProposal(title, description string) Content {
return TextProposal{title, description}
}
// Implements Proposal Interface
var _ Content = TextProposal{}
// nolint
func (tp TextProposal) GetTitle() string { return tp.Title }
func (tp TextProposal) GetDescription() string { return tp.Description }
func (tp TextProposal) ProposalRoute() string { return RouterKey }
func (tp TextProposal) ProposalType() string { return ProposalTypeText }
func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) }
func (tp TextProposal) String() string {
return fmt.Sprintf(`Text Proposal:
Title: %s
Description: %s
`, tp.Title, tp.Description)
}
// Software Upgrade Proposals
// TODO: We have to add fields for SUP specific arguments e.g. commit hash,
// upgrade date, etc.
type SoftwareUpgradeProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewSoftwareUpgradeProposal(title, description string) Content {
return SoftwareUpgradeProposal{title, description}
}
// Implements Proposal Interface
var _ Content = SoftwareUpgradeProposal{}
// nolint
func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error {
return ValidateAbstract(DefaultCodespace, sup)
}
func (sup SoftwareUpgradeProposal) String() string {
return fmt.Sprintf(`Software Upgrade Proposal:
Title: %s
Description: %s
`, sup.Title, sup.Description)
}
var validProposalTypes = map[string]struct{}{
ProposalTypeText: struct{}{},
ProposalTypeSoftwareUpgrade: struct{}{},
}
// RegisterProposalType registers a proposal type. It will panic if the type is
// already registered.
func RegisterProposalType(ty string) {
if _, ok := validProposalTypes[ty]; ok {
panic(fmt.Sprintf("already registered proposal type: %s", ty))
}
validProposalTypes[ty] = struct{}{}
}
// ContentFromProposalType returns a Content object based on the proposal type.
func ContentFromProposalType(title, desc, ty string) Content {
switch ty {
case ProposalTypeText:
return NewTextProposal(title, desc)
case ProposalTypeSoftwareUpgrade:
return NewSoftwareUpgradeProposal(title, desc)
default:
return nil
}
}
// IsValidProposalType returns a boolean determining if the proposal type is
// valid.
//
// NOTE: Modules with their own proposal types must register them.
func IsValidProposalType(ty string) bool {
_, ok := validProposalTypes[ty]
return ok
}
// ProposalHandler implements the Handler interface for governance module-based
// proposals (ie. TextProposal and SoftwareUpgradeProposal). Since these are
// merely signaling mechanisms at the moment and do not affect state, it
// performs a no-op.
func ProposalHandler(_ sdk.Context, c Content) sdk.Error {
switch c.ProposalType() {
case ProposalTypeText, ProposalTypeSoftwareUpgrade:
// both proposal types do not change state so this performs a no-op
return nil
default:
errMsg := fmt.Sprintf("unrecognized gov proposal type: %s", c.ProposalType())
return sdk.ErrUnknownRequest(errMsg)
}
}

View File

@ -1,4 +1,4 @@
package gov
package types
import (
"fmt"
@ -7,22 +7,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestProposalKind_Format(t *testing.T) {
typeText, _ := ProposalTypeFromString("Text")
tests := []struct {
pt ProposalKind
sprintFArgs string
expectedStringOutput string
}{
{typeText, "%s", "Text"},
{typeText, "%v", "1"},
}
for _, tt := range tests {
got := fmt.Sprintf(tt.sprintFArgs, tt.pt)
require.Equal(t, tt.expectedStringOutput, got)
}
}
func TestProposalStatus_Format(t *testing.T) {
statusDepositPeriod, _ := ProposalStatusFromString("DepositPeriod")
tests := []struct {

View File

@ -1,4 +1,4 @@
package gov
package types
import (
"encoding/json"
@ -15,10 +15,10 @@ type Vote struct {
}
func (v Vote) String() string {
return fmt.Sprintf("Voter %s voted with option %s on proposal %d", v.Voter, v.Option, v.ProposalID)
return fmt.Sprintf("voter %s voted with option %s on proposal %d", v.Voter, v.Option, v.ProposalID)
}
// Votes is a collection of Vote
// Votes is a collection of Vote objects
type Votes []Vote
func (v Votes) String() string {
@ -29,56 +29,22 @@ func (v Votes) String() string {
return out
}
// Returns whether 2 votes are equal
// Equals returns whether two votes are equal.
func (v Vote) Equals(comp Vote) bool {
return v.Voter.Equals(comp.Voter) && v.ProposalID == comp.ProposalID && v.Option == comp.Option
return v.Voter.Equals(comp.Voter) &&
v.ProposalID == comp.ProposalID &&
v.Option == comp.Option
}
// Returns whether a vote is empty
// Empty returns whether a vote is empty.
func (v Vote) Empty() bool {
return v.Equals(Vote{})
}
// Deposit
type Deposit struct {
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Amount sdk.Coins `json:"amount"` // Deposit amount
}
func (d Deposit) String() string {
return fmt.Sprintf("Deposit by %s on Proposal %d is for the amount %s",
d.Depositor, d.ProposalID, d.Amount)
}
// Deposits is a collection of depoist
type Deposits []Deposit
func (d Deposits) String() string {
if len(d) == 0 {
return "[]"
}
out := fmt.Sprintf("Deposits for Proposal %d:", d[0].ProposalID)
for _, dep := range d {
out += fmt.Sprintf("\n %s: %s", dep.Depositor, dep.Amount)
}
return out
}
// Returns whether 2 deposits are equal
func (d Deposit) Equals(comp Deposit) bool {
return d.Depositor.Equals(comp.Depositor) && d.ProposalID == comp.ProposalID && d.Amount.IsEqual(comp.Amount)
}
// Returns whether a deposit is empty
func (d Deposit) Empty() bool {
return d.Equals(Deposit{})
}
// Type that represents VoteOption as a byte
// VoteOption defines a vote option
type VoteOption byte
//nolint
// Vote options
const (
OptionEmpty VoteOption = 0x00
OptionYes VoteOption = 0x01
@ -87,24 +53,29 @@ const (
OptionNoWithVeto VoteOption = 0x04
)
// String to proposalType byte. Returns ff if invalid.
// VoteOptionFromString returns a VoteOption from a string. It returns an error
// if the string is invalid.
func VoteOptionFromString(str string) (VoteOption, error) {
switch str {
case "Yes":
return OptionYes, nil
case "Abstain":
return OptionAbstain, nil
case "No":
return OptionNo, nil
case "NoWithVeto":
return OptionNoWithVeto, nil
default:
return VoteOption(0xff), fmt.Errorf("'%s' is not a valid vote option", str)
}
}
// Is defined VoteOption
func validVoteOption(option VoteOption) bool {
// ValidVoteOption returns true if the vote option is valid and false otherwise.
func ValidVoteOption(option VoteOption) bool {
if option == OptionYes ||
option == OptionAbstain ||
option == OptionNo ||
@ -114,23 +85,23 @@ func validVoteOption(option VoteOption) bool {
return false
}
// Marshal needed for protobuf compatibility
// Marshal needed for protobuf compatibility.
func (vo VoteOption) Marshal() ([]byte, error) {
return []byte{byte(vo)}, nil
}
// Unmarshal needed for protobuf compatibility
// Unmarshal needed for protobuf compatibility.
func (vo *VoteOption) Unmarshal(data []byte) error {
*vo = VoteOption(data[0])
return nil
}
// Marshals to JSON using string
// Marshals to JSON using string.
func (vo VoteOption) MarshalJSON() ([]byte, error) {
return json.Marshal(vo.String())
}
// Unmarshals from JSON assuming Bech32 encoding
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
func (vo *VoteOption) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
@ -142,11 +113,12 @@ func (vo *VoteOption) UnmarshalJSON(data []byte) error {
if err != nil {
return err
}
*vo = bz2
return nil
}
// Turns VoteOption byte to String
// String implements the Stringer interface.
func (vo VoteOption) String() string {
switch vo {
case OptionYes:
@ -162,7 +134,7 @@ func (vo VoteOption) String() string {
}
}
// For Printf / Sprintf, returns bech32 when using %s
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (vo VoteOption) Format(s fmt.State, verb rune) {
switch verb {

View File

@ -46,7 +46,7 @@ func setupTestInput() testInput {
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
ms.LoadLatestVersion()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
ak := auth.NewAccountKeeper(
cdc, authCapKey, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount,
)

View File

@ -56,7 +56,7 @@ func newTestInput(t *testing.T) testInput {
err := ms.LoadLatestVersion()
require.Nil(t, err)
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams)
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
feeCollectionKeeper := auth.NewFeeCollectionKeeper(cdc, keyFeeCollection)
accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount)
bankKeeper := bank.NewBaseKeeper(accountKeeper, paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)

View File

@ -65,7 +65,7 @@ func NewApp() *App {
TotalCoinsSupply: sdk.NewCoins(),
}
app.ParamsKeeper = params.NewKeeper(app.Cdc, app.KeyParams, app.TKeyParams)
app.ParamsKeeper = params.NewKeeper(app.Cdc, app.KeyParams, app.TKeyParams, params.DefaultCodespace)
// Define the accountKeeper
app.AccountKeeper = auth.NewAccountKeeper(

48
x/params/alias.go Normal file
View File

@ -0,0 +1,48 @@
// nolint
package params
import (
"github.com/cosmos/cosmos-sdk/x/params/subspace"
"github.com/cosmos/cosmos-sdk/x/params/types"
)
var (
DefaultCodespace = types.DefaultCodespace
)
const (
ModuleName = types.ModuleName
RouterKey = types.RouterKey
StoreKey = types.StoreKey
TStoreKey = types.TStoreKey
ProposalTypeChange = types.ProposalTypeChange
)
type (
Subspace = subspace.Subspace
ReadOnlySubspace = subspace.ReadOnlySubspace
ParamSet = subspace.ParamSet
ParamSetPairs = subspace.ParamSetPairs
KeyTable = subspace.KeyTable
ParameterChangeProposal = types.ParameterChangeProposal
ParamChange = types.ParamChange
)
var (
NewKeyTable = subspace.NewKeyTable
DefaultTestComponents = subspace.DefaultTestComponents
RegisterCodec = types.RegisterCodec
NewParamChange = types.NewParamChange
NewParameterChangeProposal = types.NewParameterChangeProposal
ErrUnknownSubspace = types.ErrUnknownSubspace
ErrSettingParameter = types.ErrSettingParameter
ErrEmptyChanges = types.ErrEmptyChanges
ErrEmptySubspace = types.ErrEmptySubspace
ErrEmptyKey = types.ErrEmptyKey
ErrEmptyValue = types.ErrEmptyValue
)

82
x/params/client/cli/tx.go Normal file
View File

@ -0,0 +1,82 @@
package cli
import (
"strings"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/params"
paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
)
// GetCmdSubmitProposal implements a command handler for submitting a parameter
// change proposal transaction.
func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "param-change [proposal-file]",
Args: cobra.ExactArgs(1),
Short: "Submit a parameter change proposal",
Long: strings.TrimSpace(`
Submit a parameter proposal along with an initial deposit. The proposal details
must be supplied via a JSON file.
IMPORTANT: Currently parameter changes are evaluated but not validated, so it is
very important that any "value" change is valid (ie. correct type and within bounds)
for its respective parameter, eg. "MaxValidators" should be an integer and not a decimal.
Proper vetting of a parameter change proposal should prevent this from happening
(no deposits should occur during the governance process), but it should be noted
regardless.
Example:
$ gaiacli tx gov submit-proposal param-change <path/to/proposal.json> --from=<key_or_address>
where proposal.json contains:
{
"title": "Staking Param Change",
"description": "Update max validators",
"changes": [
{
"subspace": "staking",
"key": "MaxValidators",
"value": "105"
}
],
"deposit": [
{
"denom": "stake",
"amount": "10000"
}
]
}
`),
RunE: func(cmd *cobra.Command, args []string) error {
txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
cliCtx := context.NewCLIContext().
WithCodec(cdc).
WithAccountDecoder(cdc)
proposal, err := paramscutils.ParseParamChangeProposalJSON(cdc, args[0])
if err != nil {
return err
}
from := cliCtx.GetFromAddress()
content := params.NewParameterChangeProposal(proposal.Title, proposal.Description, proposal.Changes)
msg := gov.NewMsgSubmitProposal(content, proposal.Deposit, from)
if err := msg.ValidateBasic(); err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}
return cmd
}

View File

@ -0,0 +1,48 @@
package rest
import (
"net/http"
"github.com/cosmos/cosmos-sdk/client/context"
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/gov"
govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
"github.com/cosmos/cosmos-sdk/x/params"
paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils"
)
// ProposalRESTHandler returns a ProposalRESTHandler that exposes the param
// change REST handler with a given sub-route.
func ProposalRESTHandler(cliCtx context.CLIContext, cdc *codec.Codec) govrest.ProposalRESTHandler {
return govrest.ProposalRESTHandler{
SubRoute: "param_change",
Handler: postProposalHandlerFn(cdc, cliCtx),
}
}
func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req paramscutils.ParamChangeProposalReq
if !rest.ReadRESTReq(w, r, cdc, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
content := params.NewParameterChangeProposal(req.Title, req.Description, req.Changes)
msg := gov.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}

View File

@ -0,0 +1,47 @@
package utils
import (
"io/ioutil"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/params"
)
// ParamChangeProposalJSON defines a ParameterChangeProposal with a deposit used
// to parse parameter change proposals from a JSON file.
type ParamChangeProposalJSON struct {
Title string `json:"title"`
Description string `json:"description"`
Changes []params.ParamChange `json:"changes"`
Deposit sdk.Coins `json:"deposit"`
}
// ParamChangeProposalReq defines a parameter change proposal request body.
type ParamChangeProposalReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Title string `json:"title"`
Description string `json:"description"`
Changes []params.ParamChange `json:"changes"`
Proposer sdk.AccAddress `json:"proposer"`
Deposit sdk.Coins `json:"deposit"`
}
// ParseParamChangeProposalJSON reads and parses a ParamChangeProposalJSON from
// file.
func ParseParamChangeProposalJSON(cdc *codec.Codec, proposalFile string) (ParamChangeProposalJSON, error) {
proposal := ParamChangeProposalJSON{}
contents, err := ioutil.ReadFile(proposalFile)
if err != nil {
return proposal, err
}
if err := cdc.UnmarshalJSON(contents, &proposal); err != nil {
return proposal, err
}
return proposal, nil
}

View File

@ -3,61 +3,58 @@ package params
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
const (
// StoreKey is the string key for the params store
StoreKey = subspace.StoreKey
// TStoreKey is the string key for the params transient store
TStoreKey = subspace.TStoreKey
"github.com/tendermint/tendermint/libs/log"
)
// Keeper of the global paramstore
type Keeper struct {
cdc *codec.Codec
key sdk.StoreKey
tkey sdk.StoreKey
spaces map[string]*Subspace
cdc *codec.Codec
key sdk.StoreKey
tkey sdk.StoreKey
codespace sdk.CodespaceType
spaces map[string]*Subspace
}
// NewKeeper constructs a params keeper
func NewKeeper(cdc *codec.Codec, key *sdk.KVStoreKey, tkey *sdk.TransientStoreKey) (k Keeper) {
func NewKeeper(cdc *codec.Codec, key *sdk.KVStoreKey, tkey *sdk.TransientStoreKey, codespace sdk.CodespaceType) (k Keeper) {
k = Keeper{
cdc: cdc,
key: key,
tkey: tkey,
spaces: make(map[string]*Subspace),
cdc: cdc,
key: key,
tkey: tkey,
codespace: codespace,
spaces: make(map[string]*Subspace),
}
return k
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/params")
}
// Allocate subspace used for keepers
func (k Keeper) Subspace(spacename string) Subspace {
_, ok := k.spaces[spacename]
func (k Keeper) Subspace(s string) Subspace {
_, ok := k.spaces[s]
if ok {
panic("subspace already occupied")
}
if spacename == "" {
if s == "" {
panic("cannot use empty string for subspace")
}
space := subspace.NewSubspace(k.cdc, k.key, k.tkey, spacename)
k.spaces[spacename] = &space
space := subspace.NewSubspace(k.cdc, k.key, k.tkey, s)
k.spaces[s] = &space
return space
}
// Get existing substore from keeper
func (k Keeper) GetSubspace(storename string) (Subspace, bool) {
space, ok := k.spaces[storename]
func (k Keeper) GetSubspace(s string) (Subspace, bool) {
space, ok := k.spaces[s]
if !ok {
return Subspace{}, false
}

View File

@ -70,7 +70,7 @@ func TestKeeper(t *testing.T) {
skey := sdk.NewKVStoreKey("test")
tkey := sdk.NewTransientStoreKey("transient_test")
ctx := defaultContext(skey, tkey)
keeper := NewKeeper(cdc, skey, tkey)
keeper := NewKeeper(cdc, skey, tkey, DefaultCodespace)
store := prefix.NewStore(ctx.KVStore(skey), []byte("test/"))
space := keeper.Subspace("test").WithKeyTable(table)
@ -141,7 +141,7 @@ func TestSubspace(t *testing.T) {
key := sdk.NewKVStoreKey("test")
tkey := sdk.NewTransientStoreKey("transient_test")
ctx := defaultContext(key, tkey)
keeper := NewKeeper(cdc, key, tkey)
keeper := NewKeeper(cdc, key, tkey, DefaultCodespace)
kvs := []struct {
key string

View File

@ -0,0 +1,49 @@
package params
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
func NewParamChangeProposalHandler(k Keeper) govtypes.Handler {
return func(ctx sdk.Context, content govtypes.Content) sdk.Error {
switch c := content.(type) {
case ParameterChangeProposal:
return handleParameterChangeProposal(ctx, k, c)
default:
errMsg := fmt.Sprintf("unrecognized param proposal content type: %T", c)
return sdk.ErrUnknownRequest(errMsg)
}
}
}
func handleParameterChangeProposal(ctx sdk.Context, k Keeper, p ParameterChangeProposal) sdk.Error {
for _, c := range p.Changes {
ss, ok := k.GetSubspace(c.Subspace)
if !ok {
return ErrUnknownSubspace(k.codespace, c.Subspace)
}
var err error
if len(c.Subkey) == 0 {
k.Logger(ctx).Info(
fmt.Sprintf("setting new parameter; key: %s, value: %s", c.Key, c.Value),
)
err = ss.SetRaw(ctx, []byte(c.Key), []byte(c.Value))
} else {
k.Logger(ctx).Info(
fmt.Sprintf("setting new parameter; key: %s, subkey: %s, value: %s", c.Key, c.Subspace, c.Value),
)
err = ss.SetRawWithSubkey(ctx, []byte(c.Key), []byte(c.Subkey), []byte(c.Value))
}
if err != nil {
return ErrSettingParameter(k.codespace, c.Key, c.Subkey, c.Value, err.Error())
}
}
return nil
}

View File

@ -0,0 +1,99 @@
package params_test
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
"github.com/cosmos/cosmos-sdk/x/params/types"
)
type testInput struct {
ctx sdk.Context
cdc *codec.Codec
keeper params.Keeper
}
var (
_ subspace.ParamSet = (*testParams)(nil)
keyMaxValidators = "MaxValidators"
testSubspace = "TestSubspace"
)
type testParams struct {
MaxValidators uint16 `json:"max_validators"` // maximum number of validators (max uint16 = 65535)
}
func (tp *testParams) ParamSetPairs() subspace.ParamSetPairs {
return subspace.ParamSetPairs{
{[]byte(keyMaxValidators), &tp.MaxValidators},
}
}
func testProposal(changes ...params.ParamChange) params.ParameterChangeProposal {
return params.NewParameterChangeProposal(
"Test",
"description",
changes,
)
}
func newTestInput(t *testing.T) testInput {
cdc := codec.New()
types.RegisterCodec(cdc)
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
keyParams := sdk.NewKVStoreKey("params")
tKeyParams := sdk.NewTransientStoreKey("transient_params")
cms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db)
err := cms.LoadLatestVersion()
require.Nil(t, err)
keeper := params.NewKeeper(cdc, keyParams, tKeyParams, params.DefaultCodespace)
ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger())
return testInput{ctx, cdc, keeper}
}
func TestProposalHandlerPassed(t *testing.T) {
input := newTestInput(t)
ss := input.keeper.Subspace(testSubspace).WithKeyTable(
params.NewKeyTable().RegisterParamSet(&testParams{}),
)
tp := testProposal(params.NewParamChange(testSubspace, keyMaxValidators, "", "1"))
hdlr := params.NewParamChangeProposalHandler(input.keeper)
require.NoError(t, hdlr(input.ctx, tp))
var param uint16
ss.Get(input.ctx, []byte(keyMaxValidators), &param)
require.Equal(t, param, uint16(1))
}
func TestProposalHandlerFailed(t *testing.T) {
input := newTestInput(t)
ss := input.keeper.Subspace(testSubspace).WithKeyTable(
params.NewKeyTable().RegisterParamSet(&testParams{}),
)
tp := testProposal(params.NewParamChange(testSubspace, keyMaxValidators, "", "invalidType"))
hdlr := params.NewParamChangeProposalHandler(input.keeper)
require.Error(t, hdlr(input.ctx, tp))
require.False(t, ss.Has(input.ctx, []byte(keyMaxValidators)))
}

View File

@ -1,26 +0,0 @@
package params
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
// re-export types from subspace
type (
Subspace = subspace.Subspace
ReadOnlySubspace = subspace.ReadOnlySubspace
ParamSet = subspace.ParamSet
ParamSetPairs = subspace.ParamSetPairs
KeyTable = subspace.KeyTable
)
// nolint - re-export functions from subspace
func NewKeyTable(keytypes ...interface{}) KeyTable {
return subspace.NewKeyTable(keytypes...)
}
func DefaultTestComponents(t *testing.T) (sdk.Context, Subspace, func() sdk.CommitID) {
return subspace.DefaultTestComponents(t)
}

View File

@ -1,6 +1,7 @@
package subspace
import (
"errors"
"reflect"
"github.com/cosmos/cosmos-sdk/codec"
@ -176,6 +177,30 @@ func (s Subspace) Set(ctx sdk.Context, key []byte, param interface{}) {
}
// SetRaw stores raw parameter bytes. It returns error if the stored parameter
// has a different type from the input. It also sets to the transient store to
// record change.
func (s Subspace) SetRaw(ctx sdk.Context, key []byte, param []byte) error {
attr, ok := s.table.m[string(key)]
if !ok {
panic("Parameter not registered")
}
ty := attr.ty
dest := reflect.New(ty).Interface()
err := s.cdc.UnmarshalJSON(param, dest)
if err != nil {
return err
}
store := s.kvStore(ctx)
store.Set(key, param)
tStore := s.transientStore(ctx)
tStore.Set(key, []byte{})
return nil
}
// SetWithSubkey set a parameter with a key and subkey
// Checks parameter type only over the key
func (s Subspace) SetWithSubkey(ctx sdk.Context, key []byte, subkey []byte, param interface{}) {
@ -195,6 +220,31 @@ func (s Subspace) SetWithSubkey(ctx sdk.Context, key []byte, subkey []byte, para
tstore.Set(newkey, []byte{})
}
// SetRawWithSubkey stores raw parameter bytes with a key and subkey. It checks
// the parameter type only over the key.
func (s Subspace) SetRawWithSubkey(ctx sdk.Context, key []byte, subkey []byte, param []byte) error {
concatkey := concatKeys(key, subkey)
attr, ok := s.table.m[string(concatkey)]
if !ok {
return errors.New("parameter not registered")
}
ty := attr.ty
dest := reflect.New(ty).Interface()
err := s.cdc.UnmarshalJSON(param, &dest)
if err != nil {
return err
}
store := s.kvStore(ctx)
store.Set(concatkey, param)
tStore := s.transientStore(ctx)
tStore.Set(concatkey, []byte{})
return nil
}
// Get to ParamSet
func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) {
for _, pair := range ps.ParamSetPairs() {

16
x/params/types/codec.go Normal file
View File

@ -0,0 +1,16 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
var msgCdc = codec.New()
func init() {
RegisterCodec(msgCdc)
}
// RegisterCodec registers all necessary param module types with a given codec.
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(ParameterChangeProposal{}, "cosmos-sdk/ParameterChangeProposal", nil)
}

46
x/params/types/errors.go Normal file
View File

@ -0,0 +1,46 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Param module codespace constants
const (
DefaultCodespace sdk.CodespaceType = "params"
CodeUnknownSubspace sdk.CodeType = 1
CodeSettingParameter sdk.CodeType = 2
CodeEmptyData sdk.CodeType = 3
)
// ErrUnknownSubspace returns an unknown subspace error.
func ErrUnknownSubspace(codespace sdk.CodespaceType, space string) sdk.Error {
return sdk.NewError(codespace, CodeUnknownSubspace, fmt.Sprintf("unknown subspace %s", space))
}
// ErrSettingParameter returns an error for failing to set a parameter.
func ErrSettingParameter(codespace sdk.CodespaceType, key, subkey, value, msg string) sdk.Error {
return sdk.NewError(codespace, CodeSettingParameter, fmt.Sprintf("error setting parameter %s on %s (%s): %s", value, key, subkey, msg))
}
// ErrEmptyChanges returns an error for empty parameter changes.
func ErrEmptyChanges(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeEmptyData, "submitted parameter changes are empty")
}
// ErrEmptySubspace returns an error for an empty subspace.
func ErrEmptySubspace(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeEmptyData, "parameter subspace is empty")
}
// ErrEmptyKey returns an error for when an empty key is given.
func ErrEmptyKey(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeEmptyData, "parameter key is empty")
}
// ErrEmptyValue returns an error for when an empty key is given.
func ErrEmptyValue(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeEmptyData, "parameter value is empty")
}

17
x/params/types/keys.go Normal file
View File

@ -0,0 +1,17 @@
package types
import "github.com/cosmos/cosmos-sdk/x/params/subspace"
const (
// ModuleKey defines the name of the module
ModuleName = "params"
// RouterKey defines the routing key for a ParameterChangeProposal
RouterKey = "params"
// StoreKey is the string key for the params store
StoreKey = subspace.StoreKey
// TStoreKey is the string key for the params transient store
TStoreKey = subspace.TStoreKey
)

121
x/params/types/proposal.go Normal file
View File

@ -0,0 +1,121 @@
package types
import (
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)
const (
// ProposalTypeChange defines the type for a ParameterChangeProposal
ProposalTypeChange = "ParameterChange"
)
// Assert ParameterChangeProposal implements govtypes.Content at compile-time
var _ govtypes.Content = ParameterChangeProposal{}
func init() {
govtypes.RegisterProposalType(ProposalTypeChange)
govtypes.RegisterProposalTypeCodec(ParameterChangeProposal{}, "cosmos-sdk/ParameterChangeProposal")
}
// ParameterChangeProposal defines a proposal which contains multiple parameter
// changes.
type ParameterChangeProposal struct {
Title string `json:"title"`
Description string `json:"description"`
Changes []ParamChange `json:"changes"`
}
func NewParameterChangeProposal(title, description string, changes []ParamChange) ParameterChangeProposal {
return ParameterChangeProposal{title, description, changes}
}
// GetTitle returns the title of a parameter change proposal.
func (pcp ParameterChangeProposal) GetTitle() string { return pcp.Title }
// GetDescription returns the description of a parameter change proposal.
func (pcp ParameterChangeProposal) GetDescription() string { return pcp.Description }
// GetDescription returns the routing key of a parameter change proposal.
func (pcp ParameterChangeProposal) ProposalRoute() string { return RouterKey }
// ProposalType returns the type of a parameter change proposal.
func (pcp ParameterChangeProposal) ProposalType() string { return ProposalTypeChange }
func (pcp ParameterChangeProposal) ValidateBasic() sdk.Error {
err := govtypes.ValidateAbstract(DefaultCodespace, pcp)
if err != nil {
return err
}
return ValidateChanges(pcp.Changes)
}
// String implements the Stringer interface.
func (pcp ParameterChangeProposal) String() string {
var b strings.Builder
b.WriteString(fmt.Sprintf(`Parameter Change Proposal:
Title: %s
Description: %s
Changes:
`, pcp.Title, pcp.Description))
for _, pc := range pcp.Changes {
b.WriteString(fmt.Sprintf(` Param Change:
Subspace: %s
Key: %s
Subkey: %X
Value: %X
`, pc.Subspace, pc.Key, pc.Subkey, pc.Value))
}
return b.String()
}
// ParamChange defines a parameter change.
type ParamChange struct {
Subspace string `json:"subspace"`
Key string `json:"key"`
Subkey string `json:"subkey,omitempty"`
Value string `json:"value"`
}
func NewParamChange(subspace, key, subkey, value string) ParamChange {
return ParamChange{subspace, key, subkey, value}
}
// String implements the Stringer interface.
func (pc ParamChange) String() string {
return fmt.Sprintf(`Param Change:
Subspace: %s
Key: %s
Subkey: %X
Value: %X
`, pc.Subspace, pc.Key, pc.Subkey, pc.Value)
}
// ValidateChange performs basic validation checks over a set of ParamChange. It
// returns an error if any ParamChange is invalid.
func ValidateChanges(changes []ParamChange) sdk.Error {
if len(changes) == 0 {
return ErrEmptyChanges(DefaultCodespace)
}
for _, pc := range changes {
if len(pc.Subspace) == 0 {
return ErrEmptySubspace(DefaultCodespace)
}
if len(pc.Key) == 0 {
return ErrEmptyKey(DefaultCodespace)
}
if len(pc.Value) == 0 {
return ErrEmptyValue(DefaultCodespace)
}
}
return nil
}

View File

@ -0,0 +1,31 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParameterChangeProposal(t *testing.T) {
pc1 := NewParamChange("sub", "foo", "", "baz")
pc2 := NewParamChange("sub", "bar", "cat", "dog")
pcp := NewParameterChangeProposal("test title", "test description", []ParamChange{pc1, pc2})
require.Equal(t, "test title", pcp.GetTitle())
require.Equal(t, "test description", pcp.GetDescription())
require.Equal(t, RouterKey, pcp.ProposalRoute())
require.Equal(t, ProposalTypeChange, pcp.ProposalType())
require.Nil(t, pcp.ValidateBasic())
pc3 := NewParamChange("", "bar", "cat", "dog")
pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc3})
require.Error(t, pcp.ValidateBasic())
pc4 := NewParamChange("sub", "", "cat", "dog")
pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc4})
require.Error(t, pcp.ValidateBasic())
pc5 := NewParamChange("sub", "foo", "cat", "")
pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc5})
require.Error(t, pcp.ValidateBasic())
}

View File

@ -68,7 +68,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s
require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout))
cdc := createTestCodec()
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams)
paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount)
ck := bank.NewBaseKeeper(accountKeeper, paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)

View File

@ -107,7 +107,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context
)
cdc := MakeTestCodec()
pk := params.NewKeeper(cdc, keyParams, tkeyParams)
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
accountKeeper := auth.NewAccountKeeper(
cdc, // amino codec