Merge PR #1168: Governance MVP
This commit is contained in:
parent
d08b916f01
commit
dc2c8f900b
|
@ -28,5 +28,9 @@ profile.out
|
|||
*.log
|
||||
vagrant
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Graphviz
|
||||
dependency-graph.png
|
||||
|
|
|
@ -14,6 +14,10 @@ FEATURES
|
|||
* [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag
|
||||
* [lcd] Queried TXs now include the tx hash to identify each tx
|
||||
* [mockapp] CompleteSetup() no longer takes a testing parameter
|
||||
* [governance] Implemented MVP
|
||||
* Supported proposal types: just binary (pass/fail) TextProposals for now
|
||||
* Proposals need deposits to be votable; deposits are burned if proposal fails
|
||||
* Delegators delegate votes to validator by default but can override (for their stake)
|
||||
|
||||
FIXES
|
||||
* \#1259 - fix bug where certain tests that could have a nil pointer in defer
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
tests "github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
|
||||
)
|
||||
|
@ -423,6 +424,96 @@ func TestBonding(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestSubmitProposal(t *testing.T) {
|
||||
name, password := "test", "1234567890"
|
||||
addr, seed := CreateAddr(t, "test", password, GetKB(t))
|
||||
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
|
||||
defer cleanup()
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx := doSubmitProposal(t, port, seed, name, password, addr)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
// check if tx was commited
|
||||
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
|
||||
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
|
||||
|
||||
var proposalID int64
|
||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID)
|
||||
|
||||
// query proposal
|
||||
proposal := getProposal(t, port, proposalID)
|
||||
assert.Equal(t, "Test", proposal.Title)
|
||||
}
|
||||
|
||||
func TestDeposit(t *testing.T) {
|
||||
name, password := "test", "1234567890"
|
||||
addr, seed := CreateAddr(t, "test", password, GetKB(t))
|
||||
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
|
||||
defer cleanup()
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx := doSubmitProposal(t, port, seed, name, password, addr)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
// check if tx was commited
|
||||
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
|
||||
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
|
||||
|
||||
var proposalID int64
|
||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID)
|
||||
|
||||
// query proposal
|
||||
proposal := getProposal(t, port, proposalID)
|
||||
assert.Equal(t, "Test", proposal.Title)
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx = doDeposit(t, port, seed, name, password, addr, proposalID)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
// query proposal
|
||||
proposal = getProposal(t, port, proposalID)
|
||||
assert.True(t, proposal.TotalDeposit.IsEqual(sdk.Coins{sdk.NewCoin("steak", 10)}))
|
||||
}
|
||||
|
||||
func TestVote(t *testing.T) {
|
||||
name, password := "test", "1234567890"
|
||||
addr, seed := CreateAddr(t, "test", password, GetKB(t))
|
||||
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
|
||||
defer cleanup()
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx := doSubmitProposal(t, port, seed, name, password, addr)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
// check if tx was commited
|
||||
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
|
||||
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
|
||||
|
||||
var proposalID int64
|
||||
cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID)
|
||||
|
||||
// query proposal
|
||||
proposal := getProposal(t, port, proposalID)
|
||||
assert.Equal(t, "Test", proposal.Title)
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx = doDeposit(t, port, seed, name, password, addr, proposalID)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
// query proposal
|
||||
proposal = getProposal(t, port, proposalID)
|
||||
assert.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal.Status)
|
||||
|
||||
// create SubmitProposal TX
|
||||
resultTx = doVote(t, port, seed, name, password, addr, proposalID)
|
||||
tests.WaitForHeight(resultTx.Height+1, port)
|
||||
|
||||
vote := getVote(t, port, proposalID, addr)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option)
|
||||
}
|
||||
|
||||
//_____________________________________________________________________________
|
||||
// get the account to get the sequence
|
||||
func getAccount(t *testing.T, port string, addr sdk.Address) auth.Account {
|
||||
|
@ -602,3 +693,129 @@ func getValidators(t *testing.T, port string) []stakerest.StakeValidatorOutput {
|
|||
require.Nil(t, err)
|
||||
return validators
|
||||
}
|
||||
|
||||
func getProposal(t *testing.T, port string, proposalID int64) gov.ProposalRest {
|
||||
res, body := Request(t, port, "GET", fmt.Sprintf("/gov/proposals/%d", proposalID), nil)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
var proposal gov.ProposalRest
|
||||
err := cdc.UnmarshalJSON([]byte(body), &proposal)
|
||||
require.Nil(t, err)
|
||||
return proposal
|
||||
}
|
||||
|
||||
func getVote(t *testing.T, port string, proposalID int64, voterAddr sdk.Address) gov.VoteRest {
|
||||
bechVoterAddr := sdk.MustBech32ifyAcc(voterAddr)
|
||||
res, body := Request(t, port, "GET", fmt.Sprintf("/gov/votes/%d/%s", proposalID, bechVoterAddr), nil)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
var vote gov.VoteRest
|
||||
err := cdc.UnmarshalJSON([]byte(body), &vote)
|
||||
require.Nil(t, err)
|
||||
return vote
|
||||
}
|
||||
|
||||
func doSubmitProposal(t *testing.T, port, seed, name, password string, proposerAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
|
||||
// get the account to get the sequence
|
||||
acc := getAccount(t, port, proposerAddr)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
bechProposerAddr := sdk.MustBech32ifyAcc(proposerAddr)
|
||||
|
||||
// submitproposal
|
||||
jsonStr := []byte(fmt.Sprintf(`{
|
||||
"title": "Test",
|
||||
"description": "test",
|
||||
"proposal_type": "Text",
|
||||
"proposer": "%s",
|
||||
"initial_deposit": [{ "denom": "steak", "amount": 5 }],
|
||||
"base_req": {
|
||||
"name": "%s",
|
||||
"password": "%s",
|
||||
"chain_id": "%s",
|
||||
"account_number": %d,
|
||||
"sequence": %d,
|
||||
"gas": 100000
|
||||
}
|
||||
}`, bechProposerAddr, name, password, chainID, accnum, sequence))
|
||||
res, body := Request(t, port, "POST", "/gov/submitproposal", jsonStr)
|
||||
fmt.Println(res)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
err := cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func doDeposit(t *testing.T, port, seed, name, password string, proposerAddr sdk.Address, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) {
|
||||
// get the account to get the sequence
|
||||
acc := getAccount(t, port, proposerAddr)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
bechProposerAddr := sdk.MustBech32ifyAcc(proposerAddr)
|
||||
|
||||
// deposit on proposal
|
||||
jsonStr := []byte(fmt.Sprintf(`{
|
||||
"depositer": "%s",
|
||||
"proposalID": %d,
|
||||
"amount": [{ "denom": "steak", "amount": 5 }],
|
||||
"base_req": {
|
||||
"name": "%s",
|
||||
"password": "%s",
|
||||
"chain_id": "%s",
|
||||
"account_number": %d,
|
||||
"sequence": %d,
|
||||
"gas": 100000
|
||||
}
|
||||
}`, bechProposerAddr, proposalID, name, password, chainID, accnum, sequence))
|
||||
res, body := Request(t, port, "POST", "/gov/deposit", jsonStr)
|
||||
fmt.Println(res)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
err := cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func doVote(t *testing.T, port, seed, name, password string, proposerAddr sdk.Address, proposalID int64) (resultTx ctypes.ResultBroadcastTxCommit) {
|
||||
// get the account to get the sequence
|
||||
acc := getAccount(t, port, proposerAddr)
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
bechProposerAddr := sdk.MustBech32ifyAcc(proposerAddr)
|
||||
|
||||
// vote on proposal
|
||||
jsonStr := []byte(fmt.Sprintf(`{
|
||||
"voter": "%s",
|
||||
"proposalID": %d,
|
||||
"option": "Yes",
|
||||
"base_req": {
|
||||
"name": "%s",
|
||||
"password": "%s",
|
||||
"chain_id": "%s",
|
||||
"account_number": %d,
|
||||
"sequence": %d,
|
||||
"gas": 100000
|
||||
}
|
||||
}`, bechProposerAddr, proposalID, name, password, chainID, accnum, sequence))
|
||||
res, body := Request(t, port, "POST", "/gov/vote", jsonStr)
|
||||
fmt.Println(res)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var results ctypes.ResultBroadcastTxCommit
|
||||
err := cdc.UnmarshalJSON([]byte(body), &results)
|
||||
require.Nil(t, err)
|
||||
|
||||
return results
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
|
||||
gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest"
|
||||
ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest"
|
||||
stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest"
|
||||
)
|
||||
|
@ -80,5 +81,6 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
|||
bank.RegisterRoutes(ctx, r, cdc, kb)
|
||||
ibc.RegisterRoutes(ctx, r, cdc, kb)
|
||||
stake.RegisterRoutes(ctx, r, cdc, kb)
|
||||
gov.RegisterRoutes(ctx, r, cdc, kb)
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
|
@ -41,6 +42,7 @@ type GaiaApp struct {
|
|||
keyIBC *sdk.KVStoreKey
|
||||
keyStake *sdk.KVStoreKey
|
||||
keySlashing *sdk.KVStoreKey
|
||||
keyGov *sdk.KVStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper auth.AccountMapper
|
||||
|
@ -49,6 +51,7 @@ type GaiaApp struct {
|
|||
ibcMapper ibc.Mapper
|
||||
stakeKeeper stake.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
govKeeper gov.Keeper
|
||||
}
|
||||
|
||||
func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
||||
|
@ -63,6 +66,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
keyIBC: sdk.NewKVStoreKey("ibc"),
|
||||
keyStake: sdk.NewKVStoreKey("stake"),
|
||||
keySlashing: sdk.NewKVStoreKey("slashing"),
|
||||
keyGov: sdk.NewKVStoreKey("gov"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
|
@ -77,20 +81,22 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp {
|
|||
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
|
||||
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace))
|
||||
app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace))
|
||||
|
||||
// register message routes
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(app.coinKeeper)).
|
||||
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)).
|
||||
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
|
||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper))
|
||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
|
||||
AddRoute("gov", gov.NewHandler(app.govKeeper))
|
||||
|
||||
// initialize BaseApp
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper))
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing)
|
||||
app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov)
|
||||
err := app.LoadLatestVersion(app.keyMain)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
|
@ -106,6 +112,7 @@ func MakeCodec() *wire.Codec {
|
|||
bank.RegisterWire(cdc)
|
||||
stake.RegisterWire(cdc)
|
||||
slashing.RegisterWire(cdc)
|
||||
gov.RegisterWire(cdc)
|
||||
auth.RegisterWire(cdc)
|
||||
sdk.RegisterWire(cdc)
|
||||
wire.RegisterCrypto(cdc)
|
||||
|
@ -125,8 +132,11 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab
|
|||
func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper)
|
||||
|
||||
tags, _ := gov.EndBlocker(ctx, app.govKeeper)
|
||||
|
||||
return abci.ResponseEndBlock{
|
||||
ValidatorUpdates: validatorUpdates,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +162,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
|
|||
// load the initial stake information
|
||||
stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData)
|
||||
|
||||
gov.InitGenesis(ctx, app.govKeeper, gov.DefaultGenesisState())
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
@ -147,6 +148,59 @@ func TestGaiaCLICreateValidator(t *testing.T) {
|
|||
assert.Equal(t, "1/1", validator.PoolShares.Amount.String())
|
||||
}
|
||||
|
||||
func TestGaiaCLISubmitProposal(t *testing.T) {
|
||||
|
||||
tests.ExecuteT(t, "gaiad unsafe_reset_all")
|
||||
pass := "1234567890"
|
||||
executeWrite(t, "gaiacli keys delete foo", pass)
|
||||
executeWrite(t, "gaiacli keys delete bar", pass)
|
||||
chainID := executeInit(t, "gaiad init -o --name=foo")
|
||||
executeWrite(t, "gaiacli keys add bar", pass)
|
||||
|
||||
// get a free port, also setup some common flags
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
flags := fmt.Sprintf("--node=%v --chain-id=%v", servAddr, chainID)
|
||||
|
||||
// start gaiad server
|
||||
proc := tests.GoExecuteT(t, fmt.Sprintf("gaiad start --rpc.laddr=%v", servAddr))
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForStart(port)
|
||||
|
||||
fooAddr, _ := executeGetAddrPK(t, "gaiacli keys show foo --output=json")
|
||||
fooCech, err := sdk.Bech32ifyAcc(fooAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov submitproposal %v --proposer=%v --deposit=5steak --type=Text --title=Test --description=test --name=foo", flags, fooCech), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
|
||||
proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags))
|
||||
assert.Equal(t, int64(1), proposal1.ProposalID)
|
||||
assert.Equal(t, gov.StatusToString(gov.StatusDepositPeriod), proposal1.Status)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov deposit %v --depositer=%v --deposit=10steak --proposalID=1 --name=foo", flags, fooCech), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", fooCech, flags))
|
||||
assert.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64())
|
||||
proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposalID=1 --output=json %v", flags))
|
||||
assert.Equal(t, int64(1), proposal1.ProposalID)
|
||||
assert.Equal(t, gov.StatusToString(gov.StatusVotingPeriod), proposal1.Status)
|
||||
|
||||
executeWrite(t, fmt.Sprintf("gaiacli gov vote %v --proposalID=1 --voter=%v --option=Yes --name=foo", flags, fooCech), pass)
|
||||
tests.WaitForNextHeightTM(port)
|
||||
|
||||
vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposalID=1 --voter=%v --output=json %v", fooCech, flags))
|
||||
assert.Equal(t, int64(1), vote.ProposalID)
|
||||
assert.Equal(t, gov.VoteOptionToString(gov.OptionYes), vote.Option)
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
// executors
|
||||
|
||||
|
@ -211,3 +265,21 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator {
|
|||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return validator
|
||||
}
|
||||
|
||||
func executeGetProposal(t *testing.T, cmdStr string) gov.ProposalRest {
|
||||
out := tests.ExecuteT(t, cmdStr)
|
||||
var proposal gov.ProposalRest
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &proposal)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return proposal
|
||||
}
|
||||
|
||||
func executeGetVote(t *testing.T, cmdStr string) gov.VoteRest {
|
||||
out := tests.ExecuteT(t, cmdStr)
|
||||
var vote gov.VoteRest
|
||||
cdc := app.MakeCodec()
|
||||
err := cdc.UnmarshalJSON([]byte(out), &vote)
|
||||
require.NoError(t, err, "out %v\n, err %v", out, err)
|
||||
return vote
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/version"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||
govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
|
||||
slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
|
||||
stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli"
|
||||
|
@ -101,6 +102,26 @@ func main() {
|
|||
stakeCmd,
|
||||
)
|
||||
|
||||
//Add stake commands
|
||||
govCmd := &cobra.Command{
|
||||
Use: "gov",
|
||||
Short: "Governance and voting subcommands",
|
||||
}
|
||||
govCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
govcmd.GetCmdQueryProposal("gov", cdc),
|
||||
govcmd.GetCmdQueryVote("gov", cdc),
|
||||
)...)
|
||||
govCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
govcmd.GetCmdSubmitProposal(cdc),
|
||||
govcmd.GetCmdDeposit(cdc),
|
||||
govcmd.GetCmdVote(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
govCmd,
|
||||
)
|
||||
|
||||
//Add auth and bank commands
|
||||
rootCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
|
|
|
@ -31,8 +31,7 @@ const (
|
|||
type Procedure struct {
|
||||
VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks
|
||||
MinDeposit int64 // Minimum deposit for a proposal to enter voting period.
|
||||
VoteTypes []VoteType // Vote types available to voters.
|
||||
ProposalTypes []ProposalType // Proposal types available to submitters.
|
||||
ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal}
|
||||
Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
|
||||
Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
|
||||
MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
|
||||
|
|
|
@ -183,8 +183,8 @@ vote on the proposal.
|
|||
```go
|
||||
type TxGovVote struct {
|
||||
ProposalID int64 // proposalID of the proposal
|
||||
Option string // option from OptionSet chosen by the voter
|
||||
Address crypto.address // Address of the validator voter wants to tie its vote to
|
||||
Option string // option chosen by the voter
|
||||
ValidatorAddress crypto.address // Address of the validator voter wants to tie its vote to
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -224,17 +224,15 @@ handled:
|
|||
throw
|
||||
|
||||
validator = load(CurrentValidators, txGovVote.Address)
|
||||
|
||||
if !proposal.InitProcedure.OptionSet.includes(txGovVote.Option) OR
|
||||
(validator == nil) then
|
||||
if (validator == nil) then
|
||||
|
||||
// Throws if
|
||||
// Option is not in Option Set of procedure that was active when vote opened OR if
|
||||
// Address is not the address of a current validator
|
||||
// ValidatorAddress is not the address of a current validator
|
||||
|
||||
throw
|
||||
|
||||
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.Address>)
|
||||
else
|
||||
option = load(Options, <txGovVote.ProposalID>:<sender>:<txGovVote.ValidatorAddress>)
|
||||
|
||||
if (option != nil)
|
||||
// sender has already voted with the Atoms bonded to Address
|
||||
|
|
|
@ -37,6 +37,7 @@ type Validator interface {
|
|||
GetOwner() Address // owner address to receive/return validators coins
|
||||
GetPubKey() crypto.PubKey // validation pubkey
|
||||
GetPower() Rat // validation power
|
||||
GetDelegatorShares() Rat // Total out standing delegator shares
|
||||
GetBondHeight() int64 // height in which the validator became active
|
||||
}
|
||||
|
||||
|
@ -76,9 +77,10 @@ type Delegation interface {
|
|||
|
||||
// properties for the set of all delegations for a particular
|
||||
type DelegationSet interface {
|
||||
GetValidatorSet() ValidatorSet // validator set for which delegation set is based upon
|
||||
|
||||
// iterate through all delegations from one delegator by validator-address,
|
||||
// execute func for each validator
|
||||
IterateDelegators(Context, delegator Address,
|
||||
IterateDelegations(ctx Context, delegator Address,
|
||||
fn func(index int64, delegation Delegation) (stop bool))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"os"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
|
@ -82,3 +83,24 @@ func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.Respo
|
|||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
|
||||
// Generate genesis accounts loaded with coins, and returns their addresses, pubkeys, and privkeys
|
||||
func CreateGenAccounts(numAccs int64, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.Address, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) {
|
||||
for i := int64(0); i < numAccs; i++ {
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
addr := pubKey.Address()
|
||||
|
||||
genAcc := &auth.BaseAccount{
|
||||
Address: addr,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
genAccs = append(genAccs, genAcc)
|
||||
privKeys = append(privKeys, privKey)
|
||||
pubKeys = append(pubKeys, pubKey)
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
flagProposalID = "proposalID"
|
||||
flagTitle = "title"
|
||||
flagDescription = "description"
|
||||
flagProposalType = "type"
|
||||
flagDeposit = "deposit"
|
||||
flagProposer = "proposer"
|
||||
flagDepositer = "depositer"
|
||||
flagVoter = "voter"
|
||||
flagOption = "option"
|
||||
)
|
||||
|
||||
// submit a proposal tx
|
||||
func GetCmdSubmitProposal(cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "submitproposal",
|
||||
Short: "Submit a proposal along with an initial deposit",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
title := viper.GetString(flagTitle)
|
||||
description := viper.GetString(flagDescription)
|
||||
strProposalType := viper.GetString(flagProposalType)
|
||||
initialDeposit := viper.GetString(flagDeposit)
|
||||
|
||||
// get the from address from the name flag
|
||||
from, err := sdk.GetAccAddressBech32(viper.GetString(flagProposer))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amount, err := sdk.ParseCoins(initialDeposit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalType, err := gov.StringToProposalType(strProposalType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgSubmitProposal(title, description, proposalType, from, amount)
|
||||
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Committed at block:%d. Hash:%s.Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagTitle, "", "title of proposal")
|
||||
cmd.Flags().String(flagDescription, "", "description of proposal")
|
||||
cmd.Flags().String(flagProposalType, "", "proposalType of proposal")
|
||||
cmd.Flags().String(flagDeposit, "", "deposit of proposal")
|
||||
cmd.Flags().String(flagProposer, "", "proposer of proposal")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// set a new Deposit transaction
|
||||
func GetCmdDeposit(cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "deposit",
|
||||
Short: "deposit tokens for activing proposal",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// get the from address from the name flag
|
||||
depositer, err := sdk.GetAccAddressBech32(viper.GetString(flagDepositer))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalID := viper.GetInt64(flagProposalID)
|
||||
|
||||
amount, err := sdk.ParseCoins(viper.GetString(flagDeposit))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgDeposit(depositer, proposalID, amount)
|
||||
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagProposalID, "", "proposalID of proposal depositing on")
|
||||
cmd.Flags().String(flagDepositer, "", "depositer of deposit")
|
||||
cmd.Flags().String(flagDeposit, "", "amount of deposit")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// set a new Vote transaction
|
||||
func GetCmdVote(cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vote",
|
||||
Short: "vote for an active proposal, options: Yes/No/NoWithVeto/Abstain",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
bechVoter := viper.GetString(flagVoter)
|
||||
voter, err := sdk.GetAccAddressBech32(bechVoter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proposalID := viper.GetInt64(flagProposalID)
|
||||
|
||||
option := viper.GetString(flagOption)
|
||||
|
||||
byteVoteOption, err := gov.StringToVoteOption(option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgVote(voter, proposalID, byteVoteOption)
|
||||
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", bechVoter, msg.ProposalID, gov.VoteOptionToString(msg.Option))
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on")
|
||||
cmd.Flags().String(flagVoter, "", "bech32 voter address")
|
||||
cmd.Flags().String(flagOption, "", "vote option {Yes, No, NoWithVeto, Abstain}")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Command to Get a Proposal Information
|
||||
func GetCmdQueryProposal(storeName string, cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "query-proposal",
|
||||
Short: "query proposal details",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
proposalID := viper.GetInt64(flagProposalID)
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName)
|
||||
if len(res) == 0 || err != nil {
|
||||
return errors.Errorf("proposalID [%d] is not existed", proposalID)
|
||||
}
|
||||
|
||||
var proposal gov.Proposal
|
||||
cdc.MustUnmarshalBinary(res, &proposal)
|
||||
proposalRest := gov.ProposalToRest(proposal)
|
||||
output, err := wire.MarshalJSONIndent(cdc, proposalRest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagProposalID, "", "proposalID of proposal being queried")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Command to Get a Proposal Information
|
||||
func GetCmdQueryVote(storeName string, cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "query-vote",
|
||||
Short: "query vote",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
proposalID := viper.GetInt64(flagProposalID)
|
||||
|
||||
voterAddr, err := sdk.GetAccAddressBech32(viper.GetString(flagVoter))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
res, err := ctx.QueryStore(gov.KeyVote(proposalID, voterAddr), storeName)
|
||||
if len(res) == 0 || err != nil {
|
||||
return errors.Errorf("proposalID [%d] does not exist", proposalID)
|
||||
}
|
||||
|
||||
var vote gov.Vote
|
||||
cdc.MustUnmarshalBinary(res, &vote)
|
||||
voteRest := gov.VoteToRest(vote)
|
||||
output, err := wire.MarshalJSONIndent(cdc, voteRest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagProposalID, "", "proposalID of proposal voting on")
|
||||
cmd.Flags().String(flagVoter, "", "bech32 voter address")
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
// REST Variable names
|
||||
// nolint
|
||||
const (
|
||||
ProposalRestID = "proposalID"
|
||||
RestVoter = "voterAddress"
|
||||
)
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
|
||||
r.HandleFunc("/gov/submitproposal", postProposalHandlerFn(cdc, kb, ctx)).Methods("POST")
|
||||
r.HandleFunc("/gov/deposit", depositHandlerFn(cdc, kb, ctx)).Methods("POST")
|
||||
r.HandleFunc("/gov/vote", voteHandlerFn(cdc, kb, ctx)).Methods("POST")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/proposals/{%s}", ProposalRestID), queryProposalHandlerFn("gov", cdc, kb, ctx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/gov/votes/{%s}/{%s}", ProposalRestID, RestVoter), queryVoteHandlerFn("gov", cdc, kb, ctx)).Methods("GET")
|
||||
}
|
||||
|
||||
type postProposalReq struct {
|
||||
BaseReq baseReq `json:"base_req"`
|
||||
Title string `json:"title"` // Title of the proposal
|
||||
Description string `json:"description"` // Description of the proposal
|
||||
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
||||
Proposer string `json:"proposer"` // Address of the proposer
|
||||
InitialDeposit sdk.Coins `json:"initial_deposit"` // Coins to add to the proposal's deposit
|
||||
}
|
||||
|
||||
type depositReq struct {
|
||||
BaseReq baseReq `json:"base_req"`
|
||||
ProposalID int64 `json:"proposalID"` // ID of the proposal
|
||||
Depositer string `json:"depositer"` // Address of the depositer
|
||||
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
||||
}
|
||||
|
||||
type voteReq struct {
|
||||
BaseReq baseReq `json:"base_req"`
|
||||
Voter string `json:"voter"` // address of the voter
|
||||
ProposalID int64 `json:"proposalID"` // proposalID of the proposal
|
||||
Option string `json:"option"` // option from OptionSet chosen by the voter
|
||||
}
|
||||
|
||||
func postProposalHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req postProposalReq
|
||||
err := buildReq(w, r, &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !req.BaseReq.baseReqValidate(w) {
|
||||
return
|
||||
}
|
||||
|
||||
proposer, err := sdk.GetAccAddressBech32(req.Proposer)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
proposalTypeByte, err := gov.StringToProposalType(req.ProposalType)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalTypeByte, proposer, req.InitialDeposit)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// sign
|
||||
signAndBuild(w, ctx, req.BaseReq, msg, cdc)
|
||||
}
|
||||
}
|
||||
|
||||
func depositHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req depositReq
|
||||
err := buildReq(w, r, &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !req.BaseReq.baseReqValidate(w) {
|
||||
return
|
||||
}
|
||||
|
||||
depositer, err := sdk.GetAccAddressBech32(req.Depositer)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgDeposit(depositer, req.ProposalID, req.Amount)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// sign
|
||||
signAndBuild(w, ctx, req.BaseReq, msg, cdc)
|
||||
}
|
||||
}
|
||||
|
||||
func voteHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req voteReq
|
||||
err := buildReq(w, r, &req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !req.BaseReq.baseReqValidate(w) {
|
||||
return
|
||||
}
|
||||
|
||||
voter, err := sdk.GetAccAddressBech32(req.Voter)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
voteOptionByte, err := gov.StringToVoteOption(req.Option)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := gov.NewMsgVote(voter, req.ProposalID, voteOptionByte)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// sign
|
||||
signAndBuild(w, ctx, req.BaseReq, msg, cdc)
|
||||
}
|
||||
}
|
||||
|
||||
func queryProposalHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[ProposalRestID]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err := errors.New("proposalId required but not specified")
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
|
||||
if err != nil {
|
||||
err := errors.Errorf("proposalID [%d] is not positive", proposalID)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName)
|
||||
if len(res) == 0 || err != nil {
|
||||
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var proposal gov.Proposal
|
||||
cdc.MustUnmarshalBinary(res, &proposal)
|
||||
proposalRest := gov.ProposalToRest(proposal)
|
||||
output, err := wire.MarshalJSONIndent(cdc, proposalRest)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
||||
|
||||
func queryVoteHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
strProposalID := vars[ProposalRestID]
|
||||
bechVoterAddr := vars[RestVoter]
|
||||
|
||||
if len(strProposalID) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err := errors.New("proposalId required but not specified")
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
proposalID, err := strconv.ParseInt(strProposalID, 10, 64)
|
||||
if err != nil {
|
||||
err := errors.Errorf("proposalID [%s] is not positive", proposalID)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
voterAddr, err := sdk.GetAccAddressBech32(bechVoterAddr)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
|
||||
key := []byte(gov.KeyVote(proposalID, voterAddr))
|
||||
res, err := ctx.QueryStore(key, storeName)
|
||||
if len(res) == 0 || err != nil {
|
||||
|
||||
res, err := ctx.QueryStore(gov.KeyProposal(proposalID), storeName)
|
||||
if len(res) == 0 || err != nil {
|
||||
err := errors.Errorf("proposalID [%d] does not exist", proposalID)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
err = errors.Errorf("voter [%s] did not vote on proposalID [%d]", bechVoterAddr, proposalID)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var vote gov.Vote
|
||||
cdc.MustUnmarshalBinary(res, &vote)
|
||||
voteRest := gov.VoteToRest(vote)
|
||||
output, err := wire.MarshalJSONIndent(cdc, voteRest)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type baseReq struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
ChainID string `json:"chain_id"`
|
||||
AccountNumber int64 `json:"account_number"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Gas int64 `json:"gas"`
|
||||
}
|
||||
|
||||
func buildReq(w http.ResponseWriter, r *http.Request, req interface{}) error {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusBadRequest, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (req baseReq) baseReqValidate(w http.ResponseWriter) bool {
|
||||
if len(req.Name) == 0 {
|
||||
writeErr(&w, http.StatusUnauthorized, "Name required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
if len(req.Password) == 0 {
|
||||
writeErr(&w, http.StatusUnauthorized, "Password required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
if len(req.ChainID) == 0 {
|
||||
writeErr(&w, http.StatusUnauthorized, "ChainID required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
if req.AccountNumber < 0 {
|
||||
writeErr(&w, http.StatusUnauthorized, "Account Number required but not specified")
|
||||
return false
|
||||
}
|
||||
|
||||
if req.Sequence < 0 {
|
||||
writeErr(&w, http.StatusUnauthorized, "Sequence required but not specified")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func writeErr(w *http.ResponseWriter, status int, msg string) {
|
||||
(*w).WriteHeader(status)
|
||||
err := errors.New(msg)
|
||||
(*w).Write([]byte(err.Error()))
|
||||
}
|
||||
|
||||
// TODO: Build this function out into a more generic base-request (probably should live in client/lcd)
|
||||
func signAndBuild(w http.ResponseWriter, ctx context.CoreContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) {
|
||||
ctx = ctx.WithAccountNumber(baseReq.AccountNumber)
|
||||
ctx = ctx.WithSequence(baseReq.Sequence)
|
||||
ctx = ctx.WithChainID(baseReq.ChainID)
|
||||
|
||||
// add gas to context
|
||||
ctx = ctx.WithGas(baseReq.Gas)
|
||||
|
||||
txBytes, err := ctx.SignAndBuild(baseReq.Name, baseReq.Password, []sdk.Msg{msg}, cdc)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusUnauthorized, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// send
|
||||
res, err := ctx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
output, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
writeErr(&w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Type that represents VoteOption as a byte
|
||||
type VoteOption = byte
|
||||
|
||||
//nolint
|
||||
const (
|
||||
OptionEmpty VoteOption = 0x00
|
||||
OptionYes VoteOption = 0x01
|
||||
OptionAbstain VoteOption = 0x02
|
||||
OptionNo VoteOption = 0x03
|
||||
OptionNoWithVeto VoteOption = 0x04
|
||||
)
|
||||
|
||||
// Vote
|
||||
type Vote struct {
|
||||
Voter sdk.Address `json:"voter"` // address of the voter
|
||||
ProposalID int64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
|
||||
}
|
||||
|
||||
// Deposit
|
||||
type Deposit struct {
|
||||
Depositer sdk.Address `json:"depositer"` // Address of the depositer
|
||||
Amount sdk.Coins `json:"amount"` // Deposit amount
|
||||
}
|
||||
|
||||
// ProposalTypeToString for pretty prints of ProposalType
|
||||
func VoteOptionToString(option VoteOption) string {
|
||||
switch option {
|
||||
case OptionYes:
|
||||
return "Yes"
|
||||
case OptionAbstain:
|
||||
return "Abstain"
|
||||
case OptionNo:
|
||||
return "No"
|
||||
case OptionNoWithVeto:
|
||||
return "NoWithVeto"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func validVoteOption(option VoteOption) bool {
|
||||
if option == OptionYes ||
|
||||
option == OptionAbstain ||
|
||||
option == OptionNo ||
|
||||
option == OptionNoWithVeto {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String to proposalType byte. Returns ff if invalid.
|
||||
func StringToVoteOption(str string) (VoteOption, sdk.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), ErrInvalidVote(DefaultCodespace, str)
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Rest Votes
|
||||
type VoteRest struct {
|
||||
Voter string `json:"voter"` // address of the voter
|
||||
ProposalID int64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Option string `json:"option"`
|
||||
}
|
||||
|
||||
// Turn any Vote to a ProposalRest
|
||||
func VoteToRest(vote Vote) VoteRest {
|
||||
bechAddr, _ := sdk.Bech32ifyAcc(vote.Voter)
|
||||
return VoteRest{
|
||||
Voter: bechAddr,
|
||||
ProposalID: vote.ProposalID,
|
||||
Option: VoteOptionToString(vote.Option),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
)
|
||||
|
||||
func TestTickExpiredDepositPeriod(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
govHandler := NewHandler(keeper)
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
|
||||
res := govHandler(ctx, newProposalMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
ctx = ctx.WithBlockHeight(10)
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
ctx = ctx.WithBlockHeight(250)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.True(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
}
|
||||
|
||||
func TestTickMultipleExpiredDepositPeriod(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
govHandler := NewHandler(keeper)
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
|
||||
res := govHandler(ctx, newProposalMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
ctx = ctx.WithBlockHeight(10)
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
newProposalMsg2 := NewMsgSubmitProposal("Test2", "test2", ProposalTypeText, addrs[1], sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
res = govHandler(ctx, newProposalMsg2)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
ctx = ctx.WithBlockHeight(205)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.True(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
ctx = ctx.WithBlockHeight(215)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.True(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
}
|
||||
|
||||
func TestTickPassedDepositPeriod(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
govHandler := NewHandler(keeper)
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopActiveProposalQueue(ctx, keeper))
|
||||
|
||||
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
|
||||
res := govHandler(ctx, newProposalMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
var proposalID int64
|
||||
keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID)
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
ctx = ctx.WithBlockHeight(10)
|
||||
EndBlocker(ctx, keeper)
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
|
||||
newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
res = govHandler(ctx, newDepositMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
assert.NotNil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.True(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
assert.NotNil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
assert.NotNil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopActiveProposalQueue(ctx, keeper))
|
||||
}
|
||||
|
||||
func TestTickPassedVotingPeriod(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 10)
|
||||
SortAddresses(addrs)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
govHandler := NewHandler(keeper)
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopInactiveProposalQueue(ctx, keeper))
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
assert.False(t, shouldPopActiveProposalQueue(ctx, keeper))
|
||||
|
||||
newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
|
||||
res := govHandler(ctx, newProposalMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
var proposalID int64
|
||||
keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID)
|
||||
|
||||
ctx = ctx.WithBlockHeight(10)
|
||||
newDepositMsg := NewMsgDeposit(addrs[1], proposalID, sdk.Coins{sdk.NewCoin("steak", 5)})
|
||||
res = govHandler(ctx, newDepositMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
|
||||
ctx = ctx.WithBlockHeight(215)
|
||||
assert.True(t, shouldPopActiveProposalQueue(ctx, keeper))
|
||||
depositsIterator := keeper.GetDeposits(ctx, proposalID)
|
||||
assert.True(t, depositsIterator.Valid())
|
||||
depositsIterator.Close()
|
||||
assert.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus())
|
||||
|
||||
EndBlocker(ctx, keeper)
|
||||
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
depositsIterator = keeper.GetDeposits(ctx, proposalID)
|
||||
assert.False(t, depositsIterator.Valid())
|
||||
depositsIterator.Close()
|
||||
assert.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus())
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//nolint
|
||||
package gov
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = 5
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
// Error constructors
|
||||
|
||||
func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal - %d", proposalID))
|
||||
}
|
||||
|
||||
func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal - %d", proposalID))
|
||||
}
|
||||
|
||||
func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("Proposal %d has been already active", proposalID))
|
||||
}
|
||||
|
||||
func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID int64) 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.Address) sdk.Error {
|
||||
bechAddr, _ := sdk.Bech32ifyAcc(address)
|
||||
return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("Address %s is not staked and is thus ineligible to vote", bechAddr))
|
||||
}
|
||||
|
||||
func ErrInvalidTitle(codespace sdk.CodespaceType, title string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidTitle, fmt.Sprintf("Proposal Title '%s' is not valid", title))
|
||||
}
|
||||
|
||||
func ErrInvalidDescription(codespace sdk.CodespaceType, description string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidDescription, fmt.Sprintf("Proposal Desciption '%s' is not valid", description))
|
||||
}
|
||||
|
||||
func ErrInvalidProposalType(codespace sdk.CodespaceType, strProposalType string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("Proposal Type '%s' is not valid", strProposalType))
|
||||
}
|
||||
|
||||
func ErrInvalidVote(codespace sdk.CodespaceType, strOption string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%s' is not a valid voting option", strOption))
|
||||
}
|
||||
|
||||
func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidVote, msg)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// GenesisState - all staking state that must be provided at genesis
|
||||
type GenesisState struct {
|
||||
StartingProposalID int64 `json:"starting_proposalID"`
|
||||
}
|
||||
|
||||
func NewGenesisState(startingProposalID int64) GenesisState {
|
||||
return GenesisState{
|
||||
StartingProposalID: startingProposalID,
|
||||
}
|
||||
}
|
||||
|
||||
// get raw genesis raw message for testing
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return GenesisState{
|
||||
StartingProposalID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// InitGenesis - store genesis parameters
|
||||
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
|
||||
k.setInitialProposalID(ctx, data.StartingProposalID)
|
||||
}
|
||||
|
||||
// WriteGenesis - output genesis parameters
|
||||
func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState {
|
||||
initalProposalID, _ := k.getNewProposalID(ctx)
|
||||
|
||||
return GenesisState{
|
||||
initalProposalID,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Handle all "gov" type messages.
|
||||
func NewHandler(keeper Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case MsgDeposit:
|
||||
return handleMsgDeposit(ctx, keeper, msg)
|
||||
case MsgSubmitProposal:
|
||||
return handleMsgSubmitProposal(ctx, keeper, msg)
|
||||
case MsgVote:
|
||||
return handleMsgVote(ctx, keeper, msg)
|
||||
default:
|
||||
errMsg := "Unrecognized gov msg type"
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result {
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, msg.Title, msg.Description, msg.ProposalType)
|
||||
|
||||
err, votingStarted := keeper.AddDeposit(ctx, proposal.GetProposalID(), msg.Proposer, msg.InitialDeposit)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(proposal.GetProposalID())
|
||||
|
||||
tags := sdk.NewTags(
|
||||
"action", []byte("submitProposal"),
|
||||
"proposer", []byte(msg.Proposer.String()),
|
||||
"proposalId", proposalIDBytes,
|
||||
)
|
||||
|
||||
if votingStarted {
|
||||
tags.AppendTag("votingPeriodStart", proposalIDBytes)
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Data: proposalIDBytes,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result {
|
||||
|
||||
err, votingStarted := keeper.AddDeposit(ctx, msg.ProposalID, msg.Depositer, msg.Amount)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(msg.ProposalID)
|
||||
|
||||
// TODO: Add tag for if voting period started
|
||||
tags := sdk.NewTags(
|
||||
"action", []byte("deposit"),
|
||||
"depositer", []byte(msg.Depositer.String()),
|
||||
"proposalId", proposalIDBytes,
|
||||
)
|
||||
|
||||
if votingStarted {
|
||||
tags.AppendTag("votingPeriodStart", proposalIDBytes)
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result {
|
||||
|
||||
err := keeper.AddVote(ctx, msg.ProposalID, msg.Voter, msg.Option)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(msg.ProposalID)
|
||||
|
||||
tags := sdk.NewTags(
|
||||
"action", []byte("vote"),
|
||||
"voter", []byte(msg.Voter.String()),
|
||||
"proposalId", proposalIDBytes,
|
||||
)
|
||||
return sdk.Result{
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
// Called every block, process inflation, update validator set
|
||||
func EndBlocker(ctx sdk.Context, keeper Keeper) (tags sdk.Tags, nonVotingVals []sdk.Address) {
|
||||
|
||||
tags = sdk.NewTags()
|
||||
|
||||
// Delete proposals that haven't met minDeposit
|
||||
for shouldPopInactiveProposalQueue(ctx, keeper) {
|
||||
inactiveProposal := keeper.InactiveProposalQueuePop(ctx)
|
||||
if inactiveProposal.GetStatus() == StatusDepositPeriod {
|
||||
proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(inactiveProposal.GetProposalID())
|
||||
keeper.DeleteProposal(ctx, inactiveProposal)
|
||||
tags.AppendTag("action", []byte("proposalDropped"))
|
||||
tags.AppendTag("proposalId", proposalIDBytes)
|
||||
}
|
||||
}
|
||||
|
||||
var passes bool
|
||||
|
||||
// Check if earliest Active Proposal ended voting period yet
|
||||
for shouldPopActiveProposalQueue(ctx, keeper) {
|
||||
activeProposal := keeper.ActiveProposalQueuePop(ctx)
|
||||
|
||||
if ctx.BlockHeight() >= activeProposal.GetVotingStartBlock()+keeper.GetVotingProcedure(ctx).VotingPeriod {
|
||||
passes, nonVotingVals = tally(ctx, keeper, activeProposal)
|
||||
proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID())
|
||||
if passes {
|
||||
keeper.RefundDeposits(ctx, activeProposal.GetProposalID())
|
||||
activeProposal.SetStatus(StatusPassed)
|
||||
tags.AppendTag("action", []byte("proposalPassed"))
|
||||
tags.AppendTag("proposalId", proposalIDBytes)
|
||||
} else {
|
||||
keeper.DeleteDeposits(ctx, activeProposal.GetProposalID())
|
||||
activeProposal.SetStatus(StatusRejected)
|
||||
tags.AppendTag("action", []byte("proposalRejected"))
|
||||
tags.AppendTag("proposalId", proposalIDBytes)
|
||||
}
|
||||
|
||||
keeper.SetProposal(ctx, activeProposal)
|
||||
}
|
||||
}
|
||||
|
||||
return tags, nonVotingVals
|
||||
}
|
||||
func shouldPopInactiveProposalQueue(ctx sdk.Context, keeper Keeper) bool {
|
||||
depositProcedure := keeper.GetDepositProcedure(ctx)
|
||||
peekProposal := keeper.InactiveProposalQueuePeek(ctx)
|
||||
|
||||
if peekProposal == nil {
|
||||
return false
|
||||
} else if peekProposal.GetStatus() != StatusDepositPeriod {
|
||||
return true
|
||||
} else if ctx.BlockHeight() >= peekProposal.GetSubmitBlock()+depositProcedure.MaxDepositPeriod {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func shouldPopActiveProposalQueue(ctx sdk.Context, keeper Keeper) bool {
|
||||
votingProcedure := keeper.GetVotingProcedure(ctx)
|
||||
peekProposal := keeper.ActiveProposalQueuePeek(ctx)
|
||||
|
||||
if peekProposal == nil {
|
||||
return false
|
||||
} else if ctx.BlockHeight() >= peekProposal.GetVotingStartBlock()+votingProcedure.VotingPeriod {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// Governance Keeper
|
||||
type Keeper struct {
|
||||
// The reference to the CoinKeeper to modify balances
|
||||
ck bank.Keeper
|
||||
|
||||
// The ValidatorSet to get information about validators
|
||||
vs sdk.ValidatorSet
|
||||
|
||||
// The reference to the DelegationSet to get information about delegators
|
||||
ds sdk.DelegationSet
|
||||
|
||||
// The (unexposed) keys used to access the stores from the Context.
|
||||
storeKey sdk.StoreKey
|
||||
|
||||
// The wire codec for binary encoding/decoding.
|
||||
cdc *wire.Codec
|
||||
|
||||
// Reserved codespace
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
// NewGovernanceMapper returns a mapper that uses go-wire to (binary) encode and decode gov types.
|
||||
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper {
|
||||
return Keeper{
|
||||
storeKey: key,
|
||||
ck: ck,
|
||||
ds: ds,
|
||||
vs: ds.GetValidatorSet(),
|
||||
cdc: cdc,
|
||||
codespace: codespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the go-wire codec.
|
||||
func (keeper Keeper) WireCodec() *wire.Codec {
|
||||
return keeper.cdc
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Proposals
|
||||
|
||||
// Creates a NewProposal
|
||||
func (keeper Keeper) NewTextProposal(ctx sdk.Context, title string, description string, proposalType byte) Proposal {
|
||||
proposalID, err := keeper.getNewProposalID(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var proposal Proposal = &TextProposal{
|
||||
ProposalID: proposalID,
|
||||
Title: title,
|
||||
Description: description,
|
||||
ProposalType: proposalType,
|
||||
Status: StatusDepositPeriod,
|
||||
TotalDeposit: sdk.Coins{},
|
||||
SubmitBlock: ctx.BlockHeight(),
|
||||
VotingStartBlock: -1, // TODO: Make Time
|
||||
}
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
keeper.InactiveProposalQueuePush(ctx, proposal)
|
||||
return proposal
|
||||
}
|
||||
|
||||
// Get Proposal from store by ProposalID
|
||||
func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) Proposal {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyProposal(proposalID))
|
||||
if bz == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var proposal Proposal
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &proposal)
|
||||
|
||||
return proposal
|
||||
}
|
||||
|
||||
// Implements sdk.AccountMapper.
|
||||
func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := keeper.cdc.MustMarshalBinary(proposal)
|
||||
store.Set(KeyProposal(proposal.GetProposalID()), bz)
|
||||
}
|
||||
|
||||
// Implements sdk.AccountMapper.
|
||||
func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
store.Delete(KeyProposal(proposal.GetProposalID()))
|
||||
}
|
||||
|
||||
func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID int64) sdk.Error {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyNextProposalID)
|
||||
if bz != nil {
|
||||
return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set")
|
||||
}
|
||||
bz = keeper.cdc.MustMarshalBinary(proposalID) // TODO: switch to MarshalBinaryBare when new go-amino gets added
|
||||
store.Set(KeyNextProposalID, bz)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID int64, err sdk.Error) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyNextProposalID)
|
||||
if bz == nil {
|
||||
return -1, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
|
||||
}
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &proposalID) // TODO: switch to UnmarshalBinaryBare when new go-amino gets added
|
||||
bz = keeper.cdc.MustMarshalBinary(proposalID + 1) // TODO: switch to MarshalBinaryBare when new go-amino gets added
|
||||
store.Set(KeyNextProposalID, bz)
|
||||
return proposalID, nil
|
||||
}
|
||||
|
||||
func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) {
|
||||
proposal.SetVotingStartBlock(ctx.BlockHeight())
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
keeper.ActiveProposalQueuePush(ctx, proposal)
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Procedures
|
||||
|
||||
// Gets procedure from store. TODO: move to global param store and allow for updating of this
|
||||
func (keeper Keeper) GetDepositProcedure(ctx sdk.Context) DepositProcedure {
|
||||
return DepositProcedure{
|
||||
MinDeposit: sdk.Coins{sdk.NewCoin("steak", 10)},
|
||||
MaxDepositPeriod: 200,
|
||||
}
|
||||
}
|
||||
|
||||
// Gets procedure from store. TODO: move to global param store and allow for updating of this
|
||||
func (keeper Keeper) GetVotingProcedure(ctx sdk.Context) VotingProcedure {
|
||||
return VotingProcedure{
|
||||
VotingPeriod: 200,
|
||||
}
|
||||
}
|
||||
|
||||
// Gets procedure from store. TODO: move to global param store and allow for updating of this
|
||||
func (keeper Keeper) GetTallyingProcedure(ctx sdk.Context) TallyingProcedure {
|
||||
return TallyingProcedure{
|
||||
Threshold: sdk.NewRat(1, 2),
|
||||
Veto: sdk.NewRat(1, 3),
|
||||
GovernancePenalty: sdk.NewRat(1, 100),
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Votes
|
||||
|
||||
// Adds a vote on a specific proposal
|
||||
func (keeper Keeper) AddVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Address, option VoteOption) sdk.Error {
|
||||
proposal := keeper.GetProposal(ctx, proposalID)
|
||||
if proposal == nil {
|
||||
return ErrUnknownProposal(keeper.codespace, proposalID)
|
||||
}
|
||||
if proposal.GetStatus() != StatusVotingPeriod {
|
||||
return ErrInactiveProposal(keeper.codespace, proposalID)
|
||||
}
|
||||
|
||||
if option != OptionYes && option != OptionAbstain && option != OptionNo && option != OptionNoWithVeto {
|
||||
return ErrInvalidVote(keeper.codespace, VoteOptionToString(option))
|
||||
}
|
||||
|
||||
vote := Vote{
|
||||
ProposalID: proposalID,
|
||||
Voter: voterAddr,
|
||||
Option: option,
|
||||
}
|
||||
keeper.setVote(ctx, proposalID, voterAddr, vote)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gets the vote of a specific voter on a specific proposal
|
||||
func (keeper Keeper) GetVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Address) (Vote, bool) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyVote(proposalID, voterAddr))
|
||||
if bz == nil {
|
||||
return Vote{}, false
|
||||
}
|
||||
var vote Vote
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &vote)
|
||||
return vote, true
|
||||
}
|
||||
|
||||
func (keeper Keeper) setVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Address, vote Vote) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := keeper.cdc.MustMarshalBinary(vote)
|
||||
store.Set(KeyVote(proposalID, voterAddr), bz)
|
||||
}
|
||||
|
||||
// Gets all the votes on a specific proposal
|
||||
func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID int64) sdk.Iterator {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID))
|
||||
}
|
||||
|
||||
func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID int64, voterAddr sdk.Address) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
store.Delete(KeyVote(proposalID, voterAddr))
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Deposits
|
||||
|
||||
// Gets the deposit of a specific depositer on a specific proposal
|
||||
func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.Address) (Deposit, bool) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyDeposit(proposalID, depositerAddr))
|
||||
if bz == nil {
|
||||
return Deposit{}, false
|
||||
}
|
||||
var deposit Deposit
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &deposit)
|
||||
return deposit, true
|
||||
}
|
||||
|
||||
func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.Address, deposit Deposit) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := keeper.cdc.MustMarshalBinary(deposit)
|
||||
store.Set(KeyDeposit(proposalID, depositerAddr), bz)
|
||||
}
|
||||
|
||||
// Adds or updates a deposit of a specific depositer on a specific proposal
|
||||
// Activates voting period when appropriate
|
||||
func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID int64, depositerAddr sdk.Address, depositAmount sdk.Coins) (sdk.Error, bool) {
|
||||
// Checks to see if proposal exists
|
||||
proposal := keeper.GetProposal(ctx, proposalID)
|
||||
if proposal == nil {
|
||||
return ErrUnknownProposal(keeper.codespace, proposalID), false
|
||||
}
|
||||
|
||||
// Check if proposal is still depositable
|
||||
if (proposal.GetStatus() != StatusDepositPeriod) && (proposal.GetStatus() != StatusVotingPeriod) {
|
||||
return ErrAlreadyFinishedProposal(keeper.codespace, proposalID), false
|
||||
}
|
||||
|
||||
// Subtract coins from depositers account
|
||||
_, _, err := keeper.ck.SubtractCoins(ctx, depositerAddr, depositAmount)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
// Update Proposal
|
||||
proposal.SetTotalDeposit(proposal.GetTotalDeposit().Plus(depositAmount))
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
// Check if deposit tipped proposal into voting period
|
||||
// Active voting period if so
|
||||
activatedVotingPeriod := false
|
||||
if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsGTE(keeper.GetDepositProcedure(ctx).MinDeposit) {
|
||||
keeper.activateVotingPeriod(ctx, proposal)
|
||||
activatedVotingPeriod = true
|
||||
}
|
||||
|
||||
// Add or update deposit object
|
||||
currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositerAddr)
|
||||
if !found {
|
||||
newDeposit := Deposit{depositerAddr, depositAmount}
|
||||
keeper.setDeposit(ctx, proposalID, depositerAddr, newDeposit)
|
||||
} else {
|
||||
currDeposit.Amount = currDeposit.Amount.Plus(depositAmount)
|
||||
keeper.setDeposit(ctx, proposalID, depositerAddr, currDeposit)
|
||||
}
|
||||
|
||||
return nil, activatedVotingPeriod
|
||||
}
|
||||
|
||||
// Gets all the deposits on a specific proposal
|
||||
func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID int64) sdk.Iterator {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID))
|
||||
}
|
||||
|
||||
// Returns and deletes all the deposits on a specific proposal
|
||||
func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID int64) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
depositsIterator := keeper.GetDeposits(ctx, proposalID)
|
||||
|
||||
for ; depositsIterator.Valid(); depositsIterator.Next() {
|
||||
deposit := &Deposit{}
|
||||
keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), deposit)
|
||||
|
||||
_, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount)
|
||||
if err != nil {
|
||||
panic("should not happen")
|
||||
}
|
||||
|
||||
store.Delete(depositsIterator.Key())
|
||||
}
|
||||
|
||||
depositsIterator.Close()
|
||||
}
|
||||
|
||||
// Deletes all the deposits on a specific proposal without refunding them
|
||||
func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID int64) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
depositsIterator := keeper.GetDeposits(ctx, proposalID)
|
||||
|
||||
for ; depositsIterator.Valid(); depositsIterator.Next() {
|
||||
store.Delete(depositsIterator.Key())
|
||||
}
|
||||
|
||||
depositsIterator.Close()
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// ProposalQueues
|
||||
|
||||
func (keeper Keeper) getActiveProposalQueue(ctx sdk.Context) ProposalQueue {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyActiveProposalQueue)
|
||||
if bz == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var proposalQueue ProposalQueue
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue)
|
||||
|
||||
return proposalQueue
|
||||
}
|
||||
|
||||
func (keeper Keeper) setActiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := keeper.cdc.MustMarshalBinary(proposalQueue)
|
||||
store.Set(KeyActiveProposalQueue, bz)
|
||||
}
|
||||
|
||||
// Return the Proposal at the front of the ProposalQueue
|
||||
func (keeper Keeper) ActiveProposalQueuePeek(ctx sdk.Context) Proposal {
|
||||
proposalQueue := keeper.getActiveProposalQueue(ctx)
|
||||
if len(proposalQueue) == 0 {
|
||||
return nil
|
||||
}
|
||||
return keeper.GetProposal(ctx, proposalQueue[0])
|
||||
}
|
||||
|
||||
// Remove and return a Proposal from the front of the ProposalQueue
|
||||
func (keeper Keeper) ActiveProposalQueuePop(ctx sdk.Context) Proposal {
|
||||
proposalQueue := keeper.getActiveProposalQueue(ctx)
|
||||
if len(proposalQueue) == 0 {
|
||||
return nil
|
||||
}
|
||||
frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:]
|
||||
keeper.setActiveProposalQueue(ctx, proposalQueue)
|
||||
return keeper.GetProposal(ctx, frontElement)
|
||||
}
|
||||
|
||||
// Add a proposalID to the back of the ProposalQueue
|
||||
func (keeper Keeper) ActiveProposalQueuePush(ctx sdk.Context, proposal Proposal) {
|
||||
proposalQueue := append(keeper.getActiveProposalQueue(ctx), proposal.GetProposalID())
|
||||
keeper.setActiveProposalQueue(ctx, proposalQueue)
|
||||
}
|
||||
|
||||
func (keeper Keeper) getInactiveProposalQueue(ctx sdk.Context) ProposalQueue {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := store.Get(KeyInactiveProposalQueue)
|
||||
if bz == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var proposalQueue ProposalQueue
|
||||
|
||||
keeper.cdc.MustUnmarshalBinary(bz, &proposalQueue)
|
||||
|
||||
return proposalQueue
|
||||
}
|
||||
|
||||
func (keeper Keeper) setInactiveProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) {
|
||||
store := ctx.KVStore(keeper.storeKey)
|
||||
bz := keeper.cdc.MustMarshalBinary(proposalQueue)
|
||||
store.Set(KeyInactiveProposalQueue, bz)
|
||||
}
|
||||
|
||||
// Return the Proposal at the front of the ProposalQueue
|
||||
func (keeper Keeper) InactiveProposalQueuePeek(ctx sdk.Context) Proposal {
|
||||
proposalQueue := keeper.getInactiveProposalQueue(ctx)
|
||||
if len(proposalQueue) == 0 {
|
||||
return nil
|
||||
}
|
||||
return keeper.GetProposal(ctx, proposalQueue[0])
|
||||
}
|
||||
|
||||
// Remove and return a Proposal from the front of the ProposalQueue
|
||||
func (keeper Keeper) InactiveProposalQueuePop(ctx sdk.Context) Proposal {
|
||||
proposalQueue := keeper.getInactiveProposalQueue(ctx)
|
||||
if len(proposalQueue) == 0 {
|
||||
return nil
|
||||
}
|
||||
frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:]
|
||||
keeper.setInactiveProposalQueue(ctx, proposalQueue)
|
||||
return keeper.GetProposal(ctx, frontElement)
|
||||
}
|
||||
|
||||
// Add a proposalID to the back of the ProposalQueue
|
||||
func (keeper Keeper) InactiveProposalQueuePush(ctx sdk.Context, proposal Proposal) {
|
||||
proposalQueue := append(keeper.getInactiveProposalQueue(ctx), proposal.GetProposalID())
|
||||
keeper.setInactiveProposalQueue(ctx, proposalQueue)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// TODO remove some of these prefixes once have working multistore
|
||||
|
||||
// Key for getting a the next available proposalID from the store
|
||||
var (
|
||||
KeyNextProposalID = []byte("newProposalID")
|
||||
KeyActiveProposalQueue = []byte("activeProposalQueue")
|
||||
KeyInactiveProposalQueue = []byte("inactiveProposalQueue")
|
||||
)
|
||||
|
||||
// Key for getting a specific proposal from the store
|
||||
func KeyProposal(proposalID int64) []byte {
|
||||
return []byte(fmt.Sprintf("proposals:%d", proposalID))
|
||||
}
|
||||
|
||||
// Key for getting a specific deposit from the store
|
||||
func KeyDeposit(proposalID int64, depositerAddr sdk.Address) []byte {
|
||||
return []byte(fmt.Sprintf("deposits:%d:%d", proposalID, depositerAddr))
|
||||
}
|
||||
|
||||
// Key for getting a specific vote from the store
|
||||
func KeyVote(proposalID int64, voterAddr sdk.Address) []byte {
|
||||
return []byte(fmt.Sprintf("votes:%d:%d", proposalID, voterAddr))
|
||||
}
|
||||
|
||||
// Key for getting all deposits on a proposal from the store
|
||||
func KeyDepositsSubspace(proposalID int64) []byte {
|
||||
return []byte(fmt.Sprintf("deposits:%d:", proposalID))
|
||||
}
|
||||
|
||||
// Key for getting all votes on a proposal from the store
|
||||
func KeyVotesSubspace(proposalID int64) []byte {
|
||||
return []byte(fmt.Sprintf("votes:%d:", proposalID))
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestGetSetProposal(t *testing.T) {
|
||||
mapp, keeper, _, _, _, _ := getMockApp(t, 0)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
gotProposal := keeper.GetProposal(ctx, proposalID)
|
||||
assert.True(t, ProposalEqual(proposal, gotProposal))
|
||||
}
|
||||
|
||||
func TestIncrementProposalNumber(t *testing.T) {
|
||||
mapp, keeper, _, _, _, _ := getMockApp(t, 0)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
|
||||
keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposal6 := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
|
||||
assert.Equal(t, int64(6), proposal6.GetProposalID())
|
||||
}
|
||||
|
||||
func TestActivateVotingPeriod(t *testing.T) {
|
||||
mapp, keeper, _, _, _, _ := getMockApp(t, 0)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
|
||||
assert.Equal(t, int64(-1), proposal.GetVotingStartBlock())
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
|
||||
keeper.activateVotingPeriod(ctx, proposal)
|
||||
|
||||
assert.Equal(t, proposal.GetVotingStartBlock(), ctx.BlockHeight())
|
||||
assert.Equal(t, proposal.GetProposalID(), keeper.ActiveProposalQueuePeek(ctx).GetProposalID())
|
||||
}
|
||||
|
||||
func TestDeposits(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2)
|
||||
SortAddresses(addrs)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
|
||||
fourSteak := sdk.Coins{sdk.NewCoin("steak", 4)}
|
||||
fiveSteak := sdk.Coins{sdk.NewCoin("steak", 5)}
|
||||
|
||||
addr0Initial := keeper.ck.GetCoins(ctx, addrs[0])
|
||||
addr1Initial := keeper.ck.GetCoins(ctx, addrs[1])
|
||||
|
||||
// assert.True(t, addr0Initial.IsEqual(sdk.Coins{sdk.NewCoin("steak", 42)}))
|
||||
assert.Equal(t, sdk.Coins{sdk.NewCoin("steak", 42)}, addr0Initial)
|
||||
|
||||
assert.True(t, proposal.GetTotalDeposit().IsEqual(sdk.Coins{}))
|
||||
|
||||
// Check no deposits at beginning
|
||||
deposit, found := keeper.GetDeposit(ctx, proposalID, addrs[1])
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, keeper.GetProposal(ctx, proposalID).GetVotingStartBlock(), int64(-1))
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
|
||||
// Check first deposit
|
||||
err, votingStarted := keeper.AddDeposit(ctx, proposalID, addrs[0], fourSteak)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, votingStarted)
|
||||
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, fourSteak, deposit.Amount)
|
||||
assert.Equal(t, addrs[0], deposit.Depositer)
|
||||
assert.Equal(t, fourSteak, keeper.GetProposal(ctx, proposalID).GetTotalDeposit())
|
||||
assert.Equal(t, addr0Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[0]))
|
||||
|
||||
// Check a second deposit from same address
|
||||
err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[0], fiveSteak)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, votingStarted)
|
||||
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[0])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount)
|
||||
assert.Equal(t, addrs[0], deposit.Depositer)
|
||||
assert.Equal(t, fourSteak.Plus(fiveSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit())
|
||||
assert.Equal(t, addr0Initial.Minus(fourSteak).Minus(fiveSteak), keeper.ck.GetCoins(ctx, addrs[0]))
|
||||
|
||||
// Check third deposit from a new address
|
||||
err, votingStarted = keeper.AddDeposit(ctx, proposalID, addrs[1], fourSteak)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, votingStarted)
|
||||
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, addrs[1], deposit.Depositer)
|
||||
assert.Equal(t, fourSteak, deposit.Amount)
|
||||
assert.Equal(t, fourSteak.Plus(fiveSteak).Plus(fourSteak), keeper.GetProposal(ctx, proposalID).GetTotalDeposit())
|
||||
assert.Equal(t, addr1Initial.Minus(fourSteak), keeper.ck.GetCoins(ctx, addrs[1]))
|
||||
|
||||
// Check that proposal moved to voting period
|
||||
assert.Equal(t, ctx.BlockHeight(), keeper.GetProposal(ctx, proposalID).GetVotingStartBlock())
|
||||
assert.NotNil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
assert.Equal(t, proposalID, keeper.ActiveProposalQueuePeek(ctx).GetProposalID())
|
||||
|
||||
// Test deposit iterator
|
||||
depositsIterator := keeper.GetDeposits(ctx, proposalID)
|
||||
assert.True(t, depositsIterator.Valid())
|
||||
keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit)
|
||||
assert.Equal(t, addrs[0], deposit.Depositer)
|
||||
assert.Equal(t, fourSteak.Plus(fiveSteak), deposit.Amount)
|
||||
depositsIterator.Next()
|
||||
keeper.cdc.MustUnmarshalBinary(depositsIterator.Value(), &deposit)
|
||||
assert.Equal(t, addrs[1], deposit.Depositer)
|
||||
assert.Equal(t, fourSteak, deposit.Amount)
|
||||
depositsIterator.Next()
|
||||
assert.False(t, depositsIterator.Valid())
|
||||
|
||||
// Test Refund Deposits
|
||||
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, fourSteak, deposit.Amount)
|
||||
keeper.RefundDeposits(ctx, proposalID)
|
||||
deposit, found = keeper.GetDeposit(ctx, proposalID, addrs[1])
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, addr0Initial, keeper.ck.GetCoins(ctx, addrs[0]))
|
||||
assert.Equal(t, addr1Initial, keeper.ck.GetCoins(ctx, addrs[1]))
|
||||
|
||||
}
|
||||
|
||||
func TestVotes(t *testing.T) {
|
||||
mapp, keeper, _, addrs, _, _ := getMockApp(t, 2)
|
||||
SortAddresses(addrs)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
// Test first vote
|
||||
keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
|
||||
vote, found := keeper.GetVote(ctx, proposalID, addrs[0])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, addrs[0], vote.Voter)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, OptionAbstain, vote.Option)
|
||||
|
||||
// Test change of vote
|
||||
keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
vote, found = keeper.GetVote(ctx, proposalID, addrs[0])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, addrs[0], vote.Voter)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, OptionYes, vote.Option)
|
||||
|
||||
// Test second vote
|
||||
keeper.AddVote(ctx, proposalID, addrs[1], OptionNoWithVeto)
|
||||
vote, found = keeper.GetVote(ctx, proposalID, addrs[1])
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, addrs[1], vote.Voter)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, OptionNoWithVeto, vote.Option)
|
||||
|
||||
// Test vote iterator
|
||||
votesIterator := keeper.GetVotes(ctx, proposalID)
|
||||
assert.True(t, votesIterator.Valid())
|
||||
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote)
|
||||
assert.True(t, votesIterator.Valid())
|
||||
assert.Equal(t, addrs[0], vote.Voter)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, OptionYes, vote.Option)
|
||||
votesIterator.Next()
|
||||
assert.True(t, votesIterator.Valid())
|
||||
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), &vote)
|
||||
assert.True(t, votesIterator.Valid())
|
||||
assert.Equal(t, addrs[1], vote.Voter)
|
||||
assert.Equal(t, proposalID, vote.ProposalID)
|
||||
assert.Equal(t, OptionNoWithVeto, vote.Option)
|
||||
votesIterator.Next()
|
||||
assert.False(t, votesIterator.Valid())
|
||||
}
|
||||
|
||||
func TestProposalQueues(t *testing.T) {
|
||||
mapp, keeper, _, _, _, _ := getMockApp(t, 0)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
mapp.InitChainer(ctx, abci.RequestInitChain{})
|
||||
|
||||
assert.Nil(t, keeper.InactiveProposalQueuePeek(ctx))
|
||||
assert.Nil(t, keeper.ActiveProposalQueuePeek(ctx))
|
||||
|
||||
// create test proposals
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposal2 := keeper.NewTextProposal(ctx, "Test2", "description", ProposalTypeText)
|
||||
proposal3 := keeper.NewTextProposal(ctx, "Test3", "description", ProposalTypeText)
|
||||
proposal4 := keeper.NewTextProposal(ctx, "Test4", "description", ProposalTypeText)
|
||||
|
||||
// test pushing to inactive proposal queue
|
||||
keeper.InactiveProposalQueuePush(ctx, proposal)
|
||||
keeper.InactiveProposalQueuePush(ctx, proposal2)
|
||||
keeper.InactiveProposalQueuePush(ctx, proposal3)
|
||||
keeper.InactiveProposalQueuePush(ctx, proposal4)
|
||||
|
||||
// test peeking and popping from inactive proposal queue
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID())
|
||||
assert.Equal(t, keeper.InactiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID())
|
||||
|
||||
// test pushing to active proposal queue
|
||||
keeper.ActiveProposalQueuePush(ctx, proposal)
|
||||
keeper.ActiveProposalQueuePush(ctx, proposal2)
|
||||
keeper.ActiveProposalQueuePush(ctx, proposal3)
|
||||
keeper.ActiveProposalQueuePush(ctx, proposal4)
|
||||
|
||||
// test peeking and popping from active proposal queue
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal2.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal2.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal3.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal3.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePeek(ctx).GetProposalID(), proposal4.GetProposalID())
|
||||
assert.Equal(t, keeper.ActiveProposalQueuePop(ctx).GetProposalID(), proposal4.GetProposalID())
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// name to idetify transaction types
|
||||
const MsgType = "gov"
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// MsgSubmitProposal
|
||||
type MsgSubmitProposal struct {
|
||||
Title string // Title of the proposal
|
||||
Description string // Description of the proposal
|
||||
ProposalType ProposalKind // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
||||
Proposer sdk.Address // Address of the proposer
|
||||
InitialDeposit sdk.Coins // Initial deposit paid by sender. Must be strictly positive.
|
||||
}
|
||||
|
||||
func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.Address, initialDeposit sdk.Coins) MsgSubmitProposal {
|
||||
return MsgSubmitProposal{
|
||||
Title: title,
|
||||
Description: description,
|
||||
ProposalType: proposalType,
|
||||
Proposer: proposer,
|
||||
InitialDeposit: initialDeposit,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSubmitProposal) Type() string { return MsgType }
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSubmitProposal) ValidateBasic() sdk.Error {
|
||||
if len(msg.Title) == 0 {
|
||||
return ErrInvalidTitle(DefaultCodespace, msg.Title) // TODO: Proper Error
|
||||
}
|
||||
if len(msg.Description) == 0 {
|
||||
return ErrInvalidDescription(DefaultCodespace, msg.Description) // TODO: Proper Error
|
||||
}
|
||||
if !validProposalType(msg.ProposalType) {
|
||||
return ErrInvalidProposalType(DefaultCodespace, ProposalTypeToString(msg.ProposalType))
|
||||
}
|
||||
if len(msg.Proposer) == 0 {
|
||||
return sdk.ErrInvalidAddress(msg.Proposer.String())
|
||||
}
|
||||
if !msg.InitialDeposit.IsValid() {
|
||||
return sdk.ErrInvalidCoins(msg.InitialDeposit.String())
|
||||
}
|
||||
if !msg.InitialDeposit.IsNotNegative() {
|
||||
return sdk.ErrInvalidCoins(msg.InitialDeposit.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg MsgSubmitProposal) String() string {
|
||||
return fmt.Sprintf("MsgSubmitProposal{%v, %v, %v, %v}", msg.Title, msg.Description, ProposalTypeToString(msg.ProposalType), msg.InitialDeposit)
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSubmitProposal) Get(key interface{}) (value interface{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSubmitProposal) GetSignBytes() []byte {
|
||||
b, err := msgCdc.MarshalJSON(struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
ProposalType string `json:"proposal_type"`
|
||||
Proposer string `json:"proposer"`
|
||||
InitialDeposit sdk.Coins `json:"deposit"`
|
||||
}{
|
||||
Title: msg.Title,
|
||||
Description: msg.Description,
|
||||
ProposalType: ProposalTypeToString(msg.ProposalType),
|
||||
Proposer: sdk.MustBech32ifyVal(msg.Proposer),
|
||||
InitialDeposit: msg.InitialDeposit,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgSubmitProposal) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Proposer}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// MsgDeposit
|
||||
type MsgDeposit struct {
|
||||
ProposalID int64 `json:"proposalID"` // ID of the proposal
|
||||
Depositer sdk.Address `json:"depositer"` // Address of the depositer
|
||||
Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit
|
||||
}
|
||||
|
||||
func NewMsgDeposit(depositer sdk.Address, proposalID int64, amount sdk.Coins) MsgDeposit {
|
||||
return MsgDeposit{
|
||||
ProposalID: proposalID,
|
||||
Depositer: depositer,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgDeposit) Type() string { return MsgType }
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgDeposit) ValidateBasic() sdk.Error {
|
||||
if len(msg.Depositer) == 0 {
|
||||
return sdk.ErrInvalidAddress(msg.Depositer.String())
|
||||
}
|
||||
if !msg.Amount.IsValid() {
|
||||
return sdk.ErrInvalidCoins(msg.Amount.String())
|
||||
}
|
||||
if !msg.Amount.IsNotNegative() {
|
||||
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{%v=>%v: %v}", msg.Depositer, msg.ProposalID, msg.Amount)
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgDeposit) Get(key interface{}) (value interface{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgDeposit) GetSignBytes() []byte {
|
||||
b, err := msgCdc.MarshalJSON(struct {
|
||||
ProposalID int64 `json:"proposalID"`
|
||||
Depositer string `json:"proposer"`
|
||||
Amount sdk.Coins `json:"deposit"`
|
||||
}{
|
||||
ProposalID: msg.ProposalID,
|
||||
Depositer: sdk.MustBech32ifyVal(msg.Depositer),
|
||||
Amount: msg.Amount,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgDeposit) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Depositer}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// MsgVote
|
||||
type MsgVote struct {
|
||||
ProposalID int64 // proposalID of the proposal
|
||||
Voter sdk.Address // address of the voter
|
||||
Option VoteOption // option from OptionSet chosen by the voter
|
||||
}
|
||||
|
||||
func NewMsgVote(voter sdk.Address, proposalID int64, option VoteOption) MsgVote {
|
||||
return MsgVote{
|
||||
ProposalID: proposalID,
|
||||
Voter: voter,
|
||||
Option: option,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgVote) Type() string { return MsgType }
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgVote) ValidateBasic() sdk.Error {
|
||||
if len(msg.Voter.Bytes()) == 0 {
|
||||
return sdk.ErrInvalidAddress(msg.Voter.String())
|
||||
}
|
||||
if msg.ProposalID < 0 {
|
||||
return ErrUnknownProposal(DefaultCodespace, msg.ProposalID)
|
||||
}
|
||||
if !validVoteOption(msg.Option) {
|
||||
return ErrInvalidVote(DefaultCodespace, VoteOptionToString(msg.Option))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg MsgVote) String() string {
|
||||
return fmt.Sprintf("MsgVote{%v - %v}", msg.ProposalID, msg.Option)
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgVote) Get(key interface{}) (value interface{}) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgVote) GetSignBytes() []byte {
|
||||
b, err := msgCdc.MarshalJSON(struct {
|
||||
ProposalID int64 `json:"proposalID"`
|
||||
Voter string `json:"voter"`
|
||||
Option string `json:"option"`
|
||||
}{
|
||||
ProposalID: msg.ProposalID,
|
||||
Voter: sdk.MustBech32ifyVal(msg.Voter),
|
||||
Option: VoteOptionToString(msg.Option),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Implements Msg.
|
||||
func (msg MsgVote) GetSigners() []sdk.Address {
|
||||
return []sdk.Address{msg.Voter}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
coinsPos = sdk.Coins{sdk.NewCoin("steak", 1000)}
|
||||
coinsZero = sdk.Coins{}
|
||||
coinsNeg = sdk.Coins{sdk.NewCoin("steak", -10000)}
|
||||
coinsPosNotAtoms = sdk.Coins{sdk.NewCoin("foo", 10000)}
|
||||
coinsMulti = sdk.Coins{sdk.NewCoin("foo", 10000), sdk.NewCoin("steak", 1000)}
|
||||
)
|
||||
|
||||
// test ValidateBasic for MsgCreateValidator
|
||||
func TestMsgSubmitProposal(t *testing.T) {
|
||||
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{})
|
||||
tests := []struct {
|
||||
title, description string
|
||||
proposalType byte
|
||||
proposerAddr sdk.Address
|
||||
initialDeposit sdk.Coins
|
||||
expectPass bool
|
||||
}{
|
||||
{"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", ProposalTypeText, sdk.Address{}, 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], coinsNeg, false},
|
||||
{"Test Proposal", "the purpose of this proposal is to test", ProposalTypeText, addrs[0], coinsMulti, true},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgSubmitProposal(tc.title, tc.description, tc.proposalType, tc.proposerAddr, tc.initialDeposit)
|
||||
if tc.expectPass {
|
||||
assert.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
} else {
|
||||
assert.NotNil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test ValidateBasic for MsgDeposit
|
||||
func TestMsgDeposit(t *testing.T) {
|
||||
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{})
|
||||
tests := []struct {
|
||||
proposalID int64
|
||||
depositerAddr sdk.Address
|
||||
depositAmount sdk.Coins
|
||||
expectPass bool
|
||||
}{
|
||||
{0, addrs[0], coinsPos, true},
|
||||
{-1, addrs[0], coinsPos, false},
|
||||
{1, sdk.Address{}, coinsPos, false},
|
||||
{1, addrs[0], coinsZero, true},
|
||||
{1, addrs[0], coinsNeg, false},
|
||||
{1, addrs[0], coinsMulti, true},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgDeposit(tc.depositerAddr, tc.proposalID, tc.depositAmount)
|
||||
if tc.expectPass {
|
||||
assert.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
} else {
|
||||
assert.NotNil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test ValidateBasic for MsgDeposit
|
||||
func TestMsgVote(t *testing.T) {
|
||||
_, addrs, _, _ := mock.CreateGenAccounts(1, sdk.Coins{})
|
||||
tests := []struct {
|
||||
proposalID int64
|
||||
voterAddr sdk.Address
|
||||
option VoteOption
|
||||
expectPass bool
|
||||
}{
|
||||
{0, addrs[0], OptionYes, true},
|
||||
{-1, addrs[0], OptionYes, false},
|
||||
{0, sdk.Address{}, OptionYes, false},
|
||||
{0, addrs[0], OptionNo, true},
|
||||
{0, addrs[0], OptionNoWithVeto, true},
|
||||
{0, addrs[0], OptionAbstain, true},
|
||||
{0, addrs[0], VoteOption(0x13), false},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgVote(tc.voterAddr, tc.proposalID, tc.option)
|
||||
if tc.expectPass {
|
||||
assert.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
} else {
|
||||
assert.NotNil(t, msg.ValidateBasic(), "test: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Procedure around Deposits for governance
|
||||
type DepositProcedure struct {
|
||||
MinDeposit sdk.Coins `json:"min_deposit"` // Minimum deposit for a proposal to enter voting period.
|
||||
MaxDepositPeriod int64 `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
|
||||
}
|
||||
|
||||
// Procedure around Tallying votes in governance
|
||||
type TallyingProcedure struct {
|
||||
Threshold sdk.Rat `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5
|
||||
Veto sdk.Rat `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
|
||||
GovernancePenalty sdk.Rat `json:"governance_penalty"` // Penalty if validator does not vote
|
||||
}
|
||||
|
||||
// Procedure around Voting in governance
|
||||
type VotingProcedure struct {
|
||||
VotingPeriod int64 `json:"voting_period"` // Length of the voting period.
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Type that represents Status as a byte
|
||||
type VoteStatus = byte
|
||||
|
||||
// Type that represents Proposal Type as a byte
|
||||
type ProposalKind = byte
|
||||
|
||||
//nolint
|
||||
const (
|
||||
StatusDepositPeriod VoteStatus = 0x01
|
||||
StatusVotingPeriod VoteStatus = 0x02
|
||||
StatusPassed VoteStatus = 0x03
|
||||
StatusRejected VoteStatus = 0x04
|
||||
|
||||
ProposalTypeText ProposalKind = 0x01
|
||||
ProposalTypeParameterChange ProposalKind = 0x02
|
||||
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
|
||||
)
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Proposal interface
|
||||
type Proposal interface {
|
||||
GetProposalID() int64
|
||||
SetProposalID(int64)
|
||||
|
||||
GetTitle() string
|
||||
SetTitle(string)
|
||||
|
||||
GetDescription() string
|
||||
SetDescription(string)
|
||||
|
||||
GetProposalType() ProposalKind
|
||||
SetProposalType(ProposalKind)
|
||||
|
||||
GetStatus() VoteStatus
|
||||
SetStatus(VoteStatus)
|
||||
|
||||
GetSubmitBlock() int64
|
||||
SetSubmitBlock(int64)
|
||||
|
||||
GetTotalDeposit() sdk.Coins
|
||||
SetTotalDeposit(sdk.Coins)
|
||||
|
||||
GetVotingStartBlock() int64
|
||||
SetVotingStartBlock(int64)
|
||||
}
|
||||
|
||||
// checks if two proposals are equal
|
||||
func ProposalEqual(proposalA Proposal, proposalB Proposal) bool {
|
||||
if proposalA.GetProposalID() != proposalB.GetProposalID() ||
|
||||
proposalA.GetTitle() != proposalB.GetTitle() ||
|
||||
proposalA.GetDescription() != proposalB.GetDescription() ||
|
||||
proposalA.GetProposalType() != proposalB.GetProposalType() ||
|
||||
proposalA.GetStatus() != proposalB.GetStatus() ||
|
||||
proposalA.GetSubmitBlock() != proposalB.GetSubmitBlock() ||
|
||||
!(proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit())) ||
|
||||
proposalA.GetVotingStartBlock() != proposalB.GetVotingStartBlock() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Text Proposals
|
||||
type TextProposal struct {
|
||||
ProposalID int64 `json:"proposal_id"` // ID of the proposal
|
||||
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}
|
||||
|
||||
Status VoteStatus `json:"string"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||
|
||||
SubmitBlock int64 `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included
|
||||
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||
|
||||
VotingStartBlock int64 `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||
}
|
||||
|
||||
// Implements Proposal Interface
|
||||
var _ Proposal = (*TextProposal)(nil)
|
||||
|
||||
// nolint
|
||||
func (tp TextProposal) GetProposalID() int64 { return tp.ProposalID }
|
||||
func (tp *TextProposal) SetProposalID(proposalID int64) { tp.ProposalID = proposalID }
|
||||
func (tp TextProposal) GetTitle() string { return tp.Title }
|
||||
func (tp *TextProposal) SetTitle(title string) { tp.Title = title }
|
||||
func (tp TextProposal) GetDescription() string { return tp.Description }
|
||||
func (tp *TextProposal) SetDescription(description string) { tp.Description = description }
|
||||
func (tp TextProposal) GetProposalType() ProposalKind { return tp.ProposalType }
|
||||
func (tp *TextProposal) SetProposalType(proposalType ProposalKind) { tp.ProposalType = proposalType }
|
||||
func (tp TextProposal) GetStatus() VoteStatus { return tp.Status }
|
||||
func (tp *TextProposal) SetStatus(status VoteStatus) { tp.Status = status }
|
||||
func (tp TextProposal) GetSubmitBlock() int64 { return tp.SubmitBlock }
|
||||
func (tp *TextProposal) SetSubmitBlock(submitBlock int64) { tp.SubmitBlock = submitBlock }
|
||||
func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit }
|
||||
func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit }
|
||||
func (tp TextProposal) GetVotingStartBlock() int64 { return tp.VotingStartBlock }
|
||||
func (tp *TextProposal) SetVotingStartBlock(votingStartBlock int64) {
|
||||
tp.VotingStartBlock = votingStartBlock
|
||||
}
|
||||
|
||||
// Current Active Proposals
|
||||
type ProposalQueue []int64
|
||||
|
||||
// ProposalTypeToString for pretty prints of ProposalType
|
||||
func ProposalTypeToString(proposalType ProposalKind) string {
|
||||
switch proposalType {
|
||||
case 0x00:
|
||||
return "Text"
|
||||
case 0x01:
|
||||
return "ParameterChange"
|
||||
case 0x02:
|
||||
return "SoftwareUpgrade"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func validProposalType(proposalType ProposalKind) bool {
|
||||
if proposalType == ProposalTypeText ||
|
||||
proposalType == ProposalTypeParameterChange ||
|
||||
proposalType == ProposalTypeSoftwareUpgrade {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String to proposalType byte. Returns ff if invalid.
|
||||
func StringToProposalType(str string) (ProposalKind, sdk.Error) {
|
||||
switch str {
|
||||
case "Text":
|
||||
return ProposalTypeText, nil
|
||||
case "ParameterChange":
|
||||
return ProposalTypeParameterChange, nil
|
||||
case "SoftwareUpgrade":
|
||||
return ProposalTypeSoftwareUpgrade, nil
|
||||
default:
|
||||
return ProposalKind(0xff), ErrInvalidProposalType(DefaultCodespace, str)
|
||||
}
|
||||
}
|
||||
|
||||
// StatusToString for pretty prints of Status
|
||||
func StatusToString(status VoteStatus) string {
|
||||
switch status {
|
||||
case StatusDepositPeriod:
|
||||
return "DepositPeriod"
|
||||
case StatusVotingPeriod:
|
||||
return "VotingPeriod"
|
||||
case StatusPassed:
|
||||
return "Passed"
|
||||
case StatusRejected:
|
||||
return "Rejected"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// StatusToString for pretty prints of Status
|
||||
func StringToStatus(status string) VoteStatus {
|
||||
switch status {
|
||||
case "DepositPeriod":
|
||||
return StatusDepositPeriod
|
||||
case "VotingPeriod":
|
||||
return StatusVotingPeriod
|
||||
case "Passed":
|
||||
return StatusPassed
|
||||
case "Rejected":
|
||||
return StatusRejected
|
||||
default:
|
||||
return VoteStatus(0xff)
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Rest Proposals
|
||||
type ProposalRest struct {
|
||||
ProposalID int64 `json:"proposal_id"` // ID of the proposal
|
||||
Title string `json:"title"` // Title of the proposal
|
||||
Description string `json:"description"` // Description of the proposal
|
||||
ProposalType string `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal}
|
||||
Status string `json:"string"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||
SubmitBlock int64 `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included
|
||||
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||
VotingStartBlock int64 `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||
}
|
||||
|
||||
// Turn any Proposal to a ProposalRest
|
||||
func ProposalToRest(proposal Proposal) ProposalRest {
|
||||
return ProposalRest{
|
||||
ProposalID: proposal.GetProposalID(),
|
||||
Title: proposal.GetTitle(),
|
||||
Description: proposal.GetDescription(),
|
||||
ProposalType: ProposalTypeToString(proposal.GetProposalType()),
|
||||
Status: StatusToString(proposal.GetStatus()),
|
||||
SubmitBlock: proposal.GetSubmitBlock(),
|
||||
TotalDeposit: proposal.GetTotalDeposit(),
|
||||
VotingStartBlock: proposal.GetVotingStartBlock(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// validatorGovInfo used for tallying
|
||||
type validatorGovInfo struct {
|
||||
Address sdk.Address // sdk.Address of the validator owner
|
||||
Power sdk.Rat // Power of a Validator
|
||||
DelegatorShares sdk.Rat // Total outstanding delegator shares
|
||||
Minus sdk.Rat // Minus of validator, used to compute validator's voting power
|
||||
Vote VoteOption // Vote of the validator
|
||||
}
|
||||
|
||||
func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, nonVoting []sdk.Address) {
|
||||
results := make(map[VoteOption]sdk.Rat)
|
||||
results[OptionYes] = sdk.ZeroRat()
|
||||
results[OptionAbstain] = sdk.ZeroRat()
|
||||
results[OptionNo] = sdk.ZeroRat()
|
||||
results[OptionNoWithVeto] = sdk.ZeroRat()
|
||||
|
||||
totalVotingPower := sdk.ZeroRat()
|
||||
currValidators := make(map[string]validatorGovInfo)
|
||||
|
||||
keeper.vs.IterateValidatorsBonded(ctx, func(index int64, validator sdk.Validator) (stop bool) {
|
||||
currValidators[validator.GetOwner().String()] = validatorGovInfo{
|
||||
Address: validator.GetOwner(),
|
||||
Power: validator.GetPower(),
|
||||
DelegatorShares: validator.GetDelegatorShares(),
|
||||
Minus: sdk.ZeroRat(),
|
||||
Vote: OptionEmpty,
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// iterate over all the votes
|
||||
votesIterator := keeper.GetVotes(ctx, proposal.GetProposalID())
|
||||
for ; votesIterator.Valid(); votesIterator.Next() {
|
||||
vote := &Vote{}
|
||||
keeper.cdc.MustUnmarshalBinary(votesIterator.Value(), vote)
|
||||
|
||||
// if validator, just record it in the map
|
||||
// if delegator tally voting power
|
||||
if val, ok := currValidators[vote.Voter.String()]; ok {
|
||||
val.Vote = vote.Option
|
||||
currValidators[vote.Voter.String()] = val
|
||||
} else {
|
||||
|
||||
keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) {
|
||||
val := currValidators[delegation.GetValidator().String()]
|
||||
val.Minus = val.Minus.Add(delegation.GetBondShares())
|
||||
currValidators[delegation.GetValidator().String()] = val
|
||||
|
||||
delegatorShare := delegation.GetBondShares().Quo(val.DelegatorShares)
|
||||
votingPower := val.Power.Mul(delegatorShare)
|
||||
|
||||
results[vote.Option] = results[vote.Option].Add(votingPower)
|
||||
totalVotingPower = totalVotingPower.Add(votingPower)
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
keeper.deleteVote(ctx, vote.ProposalID, vote.Voter)
|
||||
}
|
||||
votesIterator.Close()
|
||||
|
||||
// Iterate over the validators again to tally their voting power and see who didn't vote
|
||||
nonVoting = []sdk.Address{}
|
||||
for _, val := range currValidators {
|
||||
if val.Vote == OptionEmpty {
|
||||
nonVoting = append(nonVoting, val.Address)
|
||||
continue
|
||||
}
|
||||
sharesAfterMinus := val.DelegatorShares.Sub(val.Minus)
|
||||
percentAfterMinus := sharesAfterMinus.Quo(val.DelegatorShares)
|
||||
votingPower := val.Power.Mul(percentAfterMinus)
|
||||
|
||||
results[val.Vote] = results[val.Vote].Add(votingPower)
|
||||
totalVotingPower = totalVotingPower.Add(votingPower)
|
||||
}
|
||||
|
||||
tallyingProcedure := keeper.GetTallyingProcedure(ctx)
|
||||
|
||||
// If no one votes, proposal fails
|
||||
if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroRat()) {
|
||||
return false, nonVoting
|
||||
}
|
||||
// If more than 1/3 of voters veto, proposal fails
|
||||
if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) {
|
||||
return false, nonVoting
|
||||
}
|
||||
// If more than 1/2 of non-abstaining voters vote Yes, proposal passes
|
||||
if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) {
|
||||
return true, nonVoting
|
||||
}
|
||||
// If more than 1/2 of non-abstaining voters vote No, proposal fails
|
||||
return false, nonVoting
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
func TestTallyNoOneVotes(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidatorsAllYes(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
res := stakeHandler(ctx, val1CreateMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
res = stakeHandler(ctx, val2CreateMsg)
|
||||
assert.True(t, res.IsOK())
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.True(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidators51No(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidators51Yes(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.True(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidatorsVetoed(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.True(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidatorsAbstainFails(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionAbstain)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyOnlyValidatorsNonVoter(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
assert.Equal(t, 1, len(nonVoting))
|
||||
assert.Equal(t, addrs[0], nonVoting[0])
|
||||
}
|
||||
|
||||
func TestTallyDelgatorOverride(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 30))
|
||||
stakeHandler(ctx, delegator1Msg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyDelgatorInherit(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 30))
|
||||
stakeHandler(ctx, delegator1Msg)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.True(t, passes)
|
||||
assert.Equal(t, 0, len(nonVoting))
|
||||
}
|
||||
|
||||
func TestTallyDelgatorMultipleOverride(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 5), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 10))
|
||||
stakeHandler(ctx, delegator1Msg)
|
||||
delegator1Msg2 := stake.NewMsgDelegate(addrs[3], addrs[1], sdk.NewCoin("steak", 10))
|
||||
stakeHandler(ctx, delegator1Msg2)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
||||
|
||||
func TestTallyDelgatorMultipleInherit(t *testing.T) {
|
||||
mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10)
|
||||
mapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
ctx := mapp.BaseApp.NewContext(false, abci.Header{})
|
||||
stakeHandler := stake.NewHandler(sk)
|
||||
|
||||
dummyDescription := stake.NewDescription("T", "E", "S", "T")
|
||||
val1CreateMsg := stake.NewMsgCreateValidator(addrs[0], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 25), dummyDescription)
|
||||
stakeHandler(ctx, val1CreateMsg)
|
||||
val2CreateMsg := stake.NewMsgCreateValidator(addrs[1], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 6), dummyDescription)
|
||||
stakeHandler(ctx, val2CreateMsg)
|
||||
val3CreateMsg := stake.NewMsgCreateValidator(addrs[2], crypto.GenPrivKeyEd25519().PubKey(), sdk.NewCoin("steak", 7), dummyDescription)
|
||||
stakeHandler(ctx, val3CreateMsg)
|
||||
|
||||
delegator1Msg := stake.NewMsgDelegate(addrs[3], addrs[2], sdk.NewCoin("steak", 10))
|
||||
stakeHandler(ctx, delegator1Msg)
|
||||
delegator1Msg2 := stake.NewMsgDelegate(addrs[3], addrs[1], sdk.NewCoin("steak", 10))
|
||||
stakeHandler(ctx, delegator1Msg2)
|
||||
|
||||
proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText)
|
||||
proposalID := proposal.GetProposalID()
|
||||
proposal.SetStatus(StatusVotingPeriod)
|
||||
keeper.SetProposal(ctx, proposal)
|
||||
|
||||
err := keeper.AddVote(ctx, proposalID, addrs[0], OptionYes)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo)
|
||||
assert.Nil(t, err)
|
||||
|
||||
passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID))
|
||||
|
||||
assert.False(t, passes)
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||
)
|
||||
|
||||
// initialize the mock application for this module
|
||||
func getMockApp(t *testing.T, numGenAccs int64) (*mock.App, Keeper, stake.Keeper, []sdk.Address, []crypto.PubKey, []crypto.PrivKey) {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
stake.RegisterWire(mapp.Cdc)
|
||||
RegisterWire(mapp.Cdc)
|
||||
|
||||
keyStake := sdk.NewKVStoreKey("stake")
|
||||
keyGov := sdk.NewKVStoreKey("gov")
|
||||
|
||||
ck := bank.NewKeeper(mapp.AccountMapper)
|
||||
sk := stake.NewKeeper(mapp.Cdc, keyStake, ck, mapp.RegisterCodespace(stake.DefaultCodespace))
|
||||
keeper := NewKeeper(mapp.Cdc, keyGov, ck, sk, DefaultCodespace)
|
||||
mapp.Router().AddRoute("gov", NewHandler(keeper))
|
||||
|
||||
require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake, keyGov}))
|
||||
|
||||
mapp.SetEndBlocker(getEndBlocker(keeper))
|
||||
mapp.SetInitChainer(getInitChainer(mapp, keeper, sk))
|
||||
|
||||
genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{sdk.NewCoin("steak", 42)})
|
||||
mock.SetGenesis(mapp, genAccs)
|
||||
|
||||
return mapp, keeper, sk, addrs, pubKeys, privKeys
|
||||
}
|
||||
|
||||
// gov and stake endblocker
|
||||
func getEndBlocker(keeper Keeper) sdk.EndBlocker {
|
||||
return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
tags, _ := EndBlocker(ctx, keeper)
|
||||
return abci.ResponseEndBlock{
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gov and stake initchainer
|
||||
func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
mapp.InitChainer(ctx, req)
|
||||
stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState())
|
||||
InitGenesis(ctx, keeper, DefaultGenesisState())
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts Addresses
|
||||
func SortAddresses(addrs []sdk.Address) {
|
||||
var byteAddrs [][]byte
|
||||
for _, addr := range addrs {
|
||||
byteAddrs = append(byteAddrs, addr.Bytes())
|
||||
}
|
||||
SortByteArrays(byteAddrs)
|
||||
for i, byteAddr := range byteAddrs {
|
||||
addrs[i] = byteAddr
|
||||
}
|
||||
}
|
||||
|
||||
// implement `Interface` in sort package.
|
||||
type sortByteArrays [][]byte
|
||||
|
||||
func (b sortByteArrays) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b sortByteArrays) Less(i, j int) bool {
|
||||
// bytes package already implements Comparable for []byte.
|
||||
switch bytes.Compare(b[i], b[j]) {
|
||||
case -1:
|
||||
return true
|
||||
case 0, 1:
|
||||
return false
|
||||
default:
|
||||
log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (b sortByteArrays) Swap(i, j int) {
|
||||
b[j], b[i] = b[i], b[j]
|
||||
}
|
||||
|
||||
// Public
|
||||
func SortByteArrays(src [][]byte) [][]byte {
|
||||
sorted := sortByteArrays(src)
|
||||
sort.Sort(sorted)
|
||||
return sorted
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package gov
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
// Register concrete types on wire codec
|
||||
func RegisterWire(cdc *wire.Codec) {
|
||||
|
||||
cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/MsgSubmitProposal", nil)
|
||||
cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/MsgDeposit", nil)
|
||||
cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/MsgVote", nil)
|
||||
|
||||
cdc.RegisterInterface((*Proposal)(nil), nil)
|
||||
cdc.RegisterConcrete(&TextProposal{}, "gov/TextProposal", nil)
|
||||
}
|
||||
|
||||
var msgCdc = wire.NewCodec()
|
|
@ -618,6 +618,13 @@ func (k Keeper) setNewParams(ctx sdk.Context, params Params) {
|
|||
store.Set(ParamKey, b)
|
||||
}
|
||||
|
||||
// Public version of setNewParams
|
||||
func (k Keeper) SetNewParams(ctx sdk.Context, params Params) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
b := k.cdc.MustMarshalBinary(params)
|
||||
store.Set(ParamKey, b)
|
||||
}
|
||||
|
||||
func (k Keeper) setParams(ctx sdk.Context, params Params) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
exParams := k.getParams(store)
|
||||
|
@ -652,6 +659,13 @@ func (k Keeper) setPool(ctx sdk.Context, pool Pool) {
|
|||
store.Set(PoolKey, b)
|
||||
}
|
||||
|
||||
// Public version of setpool
|
||||
func (k Keeper) SetPool(ctx sdk.Context, pool Pool) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
b := k.cdc.MustMarshalBinary(pool)
|
||||
store.Set(PoolKey, b)
|
||||
}
|
||||
|
||||
//__________________________________________________________________________
|
||||
|
||||
// get the current in-block validator operation counter
|
||||
|
@ -777,8 +791,13 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Add
|
|||
return bond
|
||||
}
|
||||
|
||||
// Returns self as it is both a validatorset and delegationset
|
||||
func (k Keeper) GetValidatorSet() sdk.ValidatorSet {
|
||||
return k
|
||||
}
|
||||
|
||||
// iterate through the active validator set and perform the provided function
|
||||
func (k Keeper) IterateDelegators(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) {
|
||||
func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
key := GetDelegationsKey(delAddr, k.cdc)
|
||||
iterator := sdk.KVStorePrefixIterator(store, key)
|
||||
|
|
|
@ -256,6 +256,7 @@ func (v Validator) GetStatus() sdk.BondStatus { return v.Status() }
|
|||
func (v Validator) GetOwner() sdk.Address { return v.Owner }
|
||||
func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey }
|
||||
func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() }
|
||||
func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares }
|
||||
func (v Validator) GetBondHeight() int64 { return v.BondHeight }
|
||||
|
||||
//Human Friendly pretty printer
|
||||
|
|
Loading…
Reference in New Issue