diff --git a/client/context/helpers.go b/client/context/helpers.go index 562bde9b4ca7..e94ddf509f12 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -113,7 +113,7 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w ChainID: chainID, Sequences: []int64{sequence}, Msg: msg, - Fee: sdk.NewStdFee(10000, sdk.Coin{}), // TODO run simulate to estimate gas? + Fee: sdk.NewStdFee(100000, sdk.Coin{}), // TODO run simulate to estimate gas? } keybase, err := keys.GetKeyBase() diff --git a/client/lcd/root.go b/client/lcd/root.go index a7be5079bf38..41f66d57507f 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -22,6 +22,7 @@ import ( auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" + gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" ) const ( @@ -83,5 +84,7 @@ func createHandler(cdc *wire.Codec) http.Handler { auth.RegisterRoutes(ctx, r, cdc, "acc") bank.RegisterRoutes(ctx, r, cdc, kb) ibc.RegisterRoutes(ctx, r, cdc, kb) + gov.RegisterRoutes(ctx, r, cdc, kb) + return r } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 5ff532bffab2..f9bd561ae731 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -14,6 +14,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/stake" ) @@ -38,12 +39,14 @@ type GaiaApp struct { keyAccount *sdk.KVStoreKey keyIBC *sdk.KVStoreKey keyStake *sdk.KVStoreKey + keyGov *sdk.KVStoreKey // Manage getting and setting accounts accountMapper sdk.AccountMapper coinKeeper bank.Keeper ibcMapper ibc.Mapper stakeKeeper stake.Keeper + govKeeper gov.Keeper } func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { @@ -57,6 +60,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { keyAccount: sdk.NewKVStoreKey("acc"), keyIBC: sdk.NewKVStoreKey("ibc"), keyStake: sdk.NewKVStoreKey("stake"), + keyGov: sdk.NewKVStoreKey("gov"), } // define the accountMapper @@ -70,17 +74,20 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.coinKeeper = bank.NewKeeper(app.accountMapper) 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.govKeeper = gov.NewKeeper(app.keyGov, app.coinKeeper, app.stakeKeeper) // 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("stake", stake.NewHandler(app.stakeKeeper)). + AddRoute("gov", gov.NewHandler(app.govKeeper)) // initialize BaseApp app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(gov.NewBeginBlocker(app.govKeeper)) app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keyGov) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, stake.FeeHandler)) err := app.LoadLatestVersion(app.keyMain) if err != nil { @@ -99,6 +106,7 @@ func MakeCodec() *wire.Codec { auth.RegisterWire(cdc) sdk.RegisterWire(cdc) wire.RegisterCrypto(cdc) + gov.RegisterWire(cdc) return cdc } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 8de2e3acc2da..0721c622eb83 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -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" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" @@ -48,6 +49,7 @@ func main() { stakecmd.GetCmdQueryCandidate("stake", cdc), stakecmd.GetCmdQueryCandidates("stake", cdc), stakecmd.GetCmdQueryDelegatorBond("stake", cdc), + govcmd.GetProposalCmd("gov", cdc), //stakecmd.GetCmdQueryDelegatorBonds("stake", cdc), )...) rootCmd.AddCommand( @@ -59,6 +61,9 @@ func main() { stakecmd.GetCmdEditCandidacy(cdc), stakecmd.GetCmdDelegate(cdc), stakecmd.GetCmdUnbond(cdc), + govcmd.SubmitProposalCmd(cdc), + govcmd.DepositCmd(cdc), + govcmd.VoteCmd(cdc), )...) // add proxy, version and key info diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go new file mode 100644 index 000000000000..bd628f533aac --- /dev/null +++ b/x/gov/client/cli/tx.go @@ -0,0 +1,180 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "encoding/hex" + "github.com/pkg/errors" + "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" + "strconv" +) + +const ( + flagTitle = "title" + flagDescription = "description" + flagProposalType = "type" + flagInitialDeposit = "deposit" + flagproposer = "proposer" +) + +// submit a proposal tx +func SubmitProposalCmd(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) + proposalType := viper.GetString(flagProposalType) + initialDeposit := viper.GetString(flagInitialDeposit) + + // get the from address from the name flag + from, err := sdk.GetAddress(viper.GetString(flagproposer)) + if err != nil { + return err + } + + amount, err := sdk.ParseCoins(initialDeposit) + if err != nil { + return err + } + + // create the message + msg := gov.NewMsgSubmitProposal(title, description, proposalType, from, amount) + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, 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(flagInitialDeposit, "", "deposit of proposal") + cmd.Flags().String(flagproposer, "", "proposer of proposal") + + return cmd +} + +// set a new Deposit transaction +func DepositCmd(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "deposit [depositer] [proposalID] [amount]", + Short: "deposit your token [steak] for activing proposalI", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + // get the from address from the name flag + depositer, err := sdk.GetAddress(args[0]) + if err != nil { + return err + } + + proposalID, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + + amount, err := sdk.ParseCoins(args[2]) + if err != nil { + return err + } + + // create the message + msg := gov.NewMsgDeposit(proposalID, depositer, amount) + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + if err != nil { + return err + } + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + return cmd +} + +// set a new Vote transaction +func VoteCmd(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "vote [voter] [proposalID] [option]", + Short: "vote for current actived proposal,option:Yes/NO/NoWithVeto/Abstain", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + voter, err := sdk.GetAddress(args[0]) + if err != nil { + return err + } + + proposalID, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + + option := args[2] + // create the message + msg := gov.NewMsgVote(voter, proposalID, option) + + fmt.Printf("Vote[Voter:%s,ProposalID:%d,Option:%s]", hex.EncodeToString(msg.Voter), msg.ProposalID, msg.Option) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) + if err != nil { + return err + } + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + return cmd +} + +func GetProposalCmd(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "proposal [proposalID]", + Short: "query proposal details", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + proposalID, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + ctx := context.NewCoreContextFromViper() + + key, _ := cdc.MarshalBinary(proposalID) + res, err := ctx.Query(key, storeName) + if len(res) == 0 || err != nil { + return errors.Errorf("proposalID [%d] is not existed", proposalID) + } + + proposal := new(gov.Proposal) + cdc.MustUnmarshalBinary(res, proposal) + output, err := wire.MarshalJSONIndent(cdc, proposal) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + return nil + }, + } + return cmd +} diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go new file mode 100644 index 000000000000..d4cbd642dbed --- /dev/null +++ b/x/gov/client/rest/rest.go @@ -0,0 +1,145 @@ +package rest + +import ( + "encoding/hex" + "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/tendermint/go-crypto/keys" + "net/http" + "github.com/pkg/errors" + "strconv" +) + +// 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/proposal", 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("/gov/{proposalId}/proposal", queryProposalHandlerFn(gov.MsgType,cdc, kb, ctx)).Methods("GET") +} + +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.Validate(w) { + return + } + + bz, err := hex.DecodeString(req.Proposer) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + proposer := sdk.Address(bz) + // create the message + msg := gov.NewMsgSubmitProposal(req.Title, req.Description, req.ProposalType, proposer, req.InitialDeposit) + + // 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.Validate(w) { + return + } + + bz, err := hex.DecodeString(req.Depositer) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + Depositer := sdk.Address(bz) + // create the message + msg := gov.NewMsgDeposit(req.ProposalID, Depositer, req.Amount) + + // 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.Validate(w) { + return + } + + bz, err := hex.DecodeString(req.Voter) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + voter := sdk.Address(bz) + // create the message + msg := gov.NewMsgVote(voter, req.ProposalID, req.Option) + // 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) + proposalId := vars["proposalId"] + + if len(proposalId) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("proposalId required but not specified") + w.Write([]byte(err.Error())) + return + } + + id, err := strconv.ParseInt(proposalId, 10, 64) + if err != nil { + err := errors.Errorf("proposalID [%s] is not positive", proposalId) + w.Write([]byte(err.Error())) + return + } + + ctx := context.NewCoreContextFromViper() + + key, _ := cdc.MarshalBinary(id) + res, err := ctx.Query(key, storeName) + if len(res) == 0 || err != nil { + err := errors.Errorf("proposalID [%d] is not existed", proposalId) + w.Write([]byte(err.Error())) + return + } + + proposal := new(gov.Proposal) + cdc.MustUnmarshalBinary(res, proposal) + output, err := wire.MarshalJSONIndent(cdc, proposal) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go new file mode 100644 index 000000000000..83851a5462c8 --- /dev/null +++ b/x/gov/client/rest/util.go @@ -0,0 +1,184 @@ +package rest + +import ( + "encoding/json" + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/pkg/errors" + "net/http" + "io/ioutil" +) + +//type request interface { +// Validate(w http.ResponseWriter) bool +//} + +type baseReq struct { + Name string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + Sequence int64 `json:"sequence"` +} + +type postProposalReq struct { + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType string `json:"proposalType"` // 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 + BaseReq baseReq `json:"base_req"` +} + +type depositReq struct { + ProposalID int64 `json:"proposal_id"` // 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 + BaseReq baseReq `json:"base_req"` +} + +type voteReq struct { + Voter string `json:"voter"` // address of the voter + ProposalID int64 `json:"proposal_iD"` // proposalID of the proposal + Option string `json:"option"` // option from OptionSet chosen by the voter + BaseReq baseReq `json:"base_req"` +} + +func buildReq(w http.ResponseWriter, r *http.Request,req interface{}) error{ + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return err + } + err = json.Unmarshal(body, &req) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return err + } + return nil +} + +func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { + if len(req.Name) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("Name required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.Password) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("Password required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.ChainID) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("ChainID required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if req.Sequence < 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("Sequence required but not specified") + w.Write([]byte(err.Error())) + return false + } + return true +} + +func (req postProposalReq) Validate(w http.ResponseWriter) bool { + if len(req.Title) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("Title required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.ProposalType) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("ProposalType required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.Proposer) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("Proposer required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.InitialDeposit) == 0 { + w.WriteHeader(http.StatusUnauthorized) + err := errors.Errorf("InitialDeposit required but not specified") + w.Write([]byte(err.Error())) + return false + } + return req.BaseReq.baseReqValidate(w) +} + +func (req depositReq) Validate(w http.ResponseWriter) bool { + if len(req.Depositer) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("Depositer required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.Amount) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("Amount required but not specified") + w.Write([]byte(err.Error())) + return false + } + return req.BaseReq.baseReqValidate(w) +} + +func (req voteReq) Validate(w http.ResponseWriter) bool { + if len(req.Voter) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("Voter required but not specified") + w.Write([]byte(err.Error())) + return false + } + + if len(req.Option) == 0 { + w.WriteHeader(http.StatusBadRequest) + err := errors.Errorf("Option required but not specified") + w.Write([]byte(err.Error())) + return false + } + return req.BaseReq.baseReqValidate(w) +} + +func signAndBuild(w http.ResponseWriter, ctx context.CoreContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { + ctx = ctx.WithSequence(baseReq.Sequence) + txBytes, err := ctx.SignAndBuild(baseReq.Name, baseReq.Password, msg, cdc) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // send + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) +} diff --git a/x/gov/commands/tx.go b/x/gov/commands/tx.go deleted file mode 100644 index 7c12ef77589a..000000000000 --- a/x/gov/commands/tx.go +++ /dev/null @@ -1,152 +0,0 @@ -package commands - -// import ( -// "fmt" - -// "github.com/pkg/errors" -// "github.com/spf13/cobra" -// "github.com/spf13/viper" - -// "github.com/cosmos/cosmos-sdk/client" -// "github.com/cosmos/cosmos-sdk/client/builder" -// "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" -// sdk "github.com/cosmos/cosmos-sdk/types" -// "github.com/cosmos/cosmos-sdk/wire" -// "github.com/cosmos/cosmos-sdk/x/gov" -// ) - -// const ( -// flagTitle = "title" -// flagDescription = "description" -// flagProposalType = "type" -// flagInitialDeposit = "deposit" -// ) - -// func SubmitProposalCmd(cdc *wire.Codec) *cobra.Command { -// cmdr := commander{cdc} -// cmd := &cobra.Command{ -// Use: "bond", -// Short: "Bond to a validator", -// RunE: cmdr.bondTxCmd, -// } -// cmd.Flags().String(flagStake, "", "Amount of coins to stake") -// cmd.Flags().String(flagValidator, "", "Validator address to stake") -// return cmd -// } - -// // submit a proposal tx -// func SubmitProposalCmd(cdc *wire.Codec) *cobra.Command { -// return &cobra.Command{ -// Use: "submitproposal [title] [description] [proposaltype] [initialdeposit]", -// Short: "Submit a proposal along with an initial deposit", -// RunE: func(cmd *cobra.Command, args []string) error { -// if len(args) != 1 || len(args[0]) == 0 { -// return errors.New("You must provide an answer") -// } - -// // get the from address from the name flag -// from, err := builder.GetFromAddress() -// if err != nil { -// return err -// } - -// // create the message -// msg := gov.NewSubmitProposalMsg(arg[0], arg[1], arg[2], from, arg[3]) -// chainID := viper.GetString(client.FlagChainID) -// sequence := int64(viper.GetInt(client.FlagSequence)) - -// signMsg := sdk.StdSignMsg{ -// ChainID: chainID, -// Sequences: []int64{sequence}, -// Msg: msg, -// } - -// // build and sign the transaction, then broadcast to Tendermint -// res, err := builder.SignBuildBroadcast(signMsg, cdc) -// if err != nil { -// return err -// } - -// fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) -// return nil -// }, -// } -// } - -// // set a new cool trend transaction -// func DepositCmd(cdc *wire.Codec) *cobra.Command { -// return &cobra.Command{ -// Use: "setcool [answer]", -// Short: "You're so cool, tell us what is cool!", -// RunE: func(cmd *cobra.Command, args []string) error { -// if len(args) != 1 || len(args[0]) == 0 { -// return errors.New("You must provide an answer") -// } - -// // get the from address from the name flag -// from, err := builder.GetFromAddress() -// if err != nil { -// return err -// } - -// // create the message -// msg := cool.NewSetTrendMsg(from, args[0]) -// chainID := viper.GetString(client.FlagChainID) -// sequence := int64(viper.GetInt(client.FlagSequence)) - -// signMsg := sdk.StdSignMsg{ -// ChainID: chainID, -// Sequences: []int64{sequence}, -// Msg: msg, -// } - -// // build and sign the transaction, then broadcast to Tendermint -// res, err := builder.SignBuildBroadcast(signMsg, cdc) -// if err != nil { -// return err -// } - -// fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) -// return nil -// }, -// } -// } - -// // set a new cool trend transaction -// func VoteCmd(cdc *wire.Codec) *cobra.Command { -// return &cobra.Command{ -// Use: "setcool [answer]", -// Short: "You're so cool, tell us what is cool!", -// RunE: func(cmd *cobra.Command, args []string) error { -// if len(args) != 1 || len(args[0]) == 0 { -// return errors.New("You must provide an answer") -// } - -// // get the from address from the name flag -// from, err := builder.GetFromAddress() -// if err != nil { -// return err -// } - -// // create the message -// msg := cool.NewSetTrendMsg(from, args[0]) -// chainID := viper.GetString(client.FlagChainID) -// sequence := int64(viper.GetInt(client.FlagSequence)) - -// signMsg := sdk.StdSignMsg{ -// ChainID: chainID, -// Sequences: []int64{sequence}, -// Msg: msg, -// } - -// // build and sign the transaction, then broadcast to Tendermint -// res, err := builder.SignBuildBroadcast(signMsg, cdc) -// if err != nil { -// return err -// } - -// fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) -// return nil -// }, -// } -// } diff --git a/x/gov/errors.go b/x/gov/errors.go index 212a627760a6..a82de887a5c5 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -4,57 +4,74 @@ package gov import ( "strconv" + "encoding/hex" sdk "github.com/cosmos/cosmos-sdk/types" ) const ( // TODO TODO TODO TODO TODO TODO - // Gov errors reserve 200 ~ 299. - CodeUnknownProposal sdk.CodeType = 201 - CodeInactiveProposal sdk.CodeType = 202 - CodeAlreadyActiveProposal sdk.CodeType = 203 - CodeAddressChangedDelegation sdk.CodeType = 204 - CodeAddressNotStaked sdk.CodeType = 205 - CodeInvalidTitle sdk.CodeType = 206 - CodeInvalidDescription sdk.CodeType = 207 - CodeInvalidProposalType sdk.CodeType = 208 - CodeInvalidVote sdk.CodeType = 209 + + DefaultCodespace sdk.CodespaceType = 4 + + // Gov errors reserve 401 ~ 499. + CodeUnknownProposal sdk.CodeType = 401 + CodeInactiveProposal sdk.CodeType = 402 + CodeAlreadyActiveProposal sdk.CodeType = 403 + CodeAddressChangedDelegation sdk.CodeType = 404 + CodeAddressNotStaked sdk.CodeType = 405 + CodeInvalidTitle sdk.CodeType = 406 + CodeInvalidDescription sdk.CodeType = 407 + CodeInvalidProposalType sdk.CodeType = 408 + CodeInvalidVote sdk.CodeType = 409 + CodeAddressAlreadyVote sdk.CodeType = 410 + CodeVotingPeriodOver sdk.CodeType = 411 + CodeDepositPeriodOver sdk.CodeType = 412 ) //---------------------------------------- // Error constructors func ErrUnknownProposal(proposalID int64) sdk.Error { - return sdk.NewError(CodeUnknownProposal, "Unknown proposal - "+strconv.FormatInt(proposalID, 10)) + return sdk.NewError(DefaultCodespace, CodeUnknownProposal, "Unknown proposal - "+strconv.FormatInt(proposalID, 10)) } func ErrInactiveProposal(proposalID int64) sdk.Error { - return sdk.NewError(CodeInactiveProposal, "Unknown proposal - "+strconv.FormatInt(proposalID, 10)) + return sdk.NewError(DefaultCodespace, CodeInactiveProposal, "Unknown proposal - "+strconv.FormatInt(proposalID, 10)) } func ErrAlreadyActiveProposal(proposalID int64) sdk.Error { - return sdk.NewError(CodeAlreadyActiveProposal, "Proposal "+strconv.FormatInt(proposalID, 10)+" already active") + return sdk.NewError(DefaultCodespace, CodeAlreadyActiveProposal, "Proposal "+strconv.FormatInt(proposalID, 10)+" already active") } func ErrAddressChangedDelegation(address sdk.Address) sdk.Error { - return sdk.NewError(CodeAddressChangedDelegation, "Address "+string(address)+" has redelegated since vote began and is thus ineligible to vote") + return sdk.NewError(DefaultCodespace, CodeAddressChangedDelegation, "Address "+hex.EncodeToString(address)+" has redelegated since vote began and is thus ineligible to vote") } func ErrAddressNotStaked(address sdk.Address) sdk.Error { - return sdk.NewError(CodeAddressNotStaked, "Address "+string(address)+" is not staked and is thus ineligible to vote") + return sdk.NewError(DefaultCodespace, CodeAddressNotStaked, "Address "+hex.EncodeToString(address)+" is not staked and is thus ineligible to vote") } func ErrInvalidTitle(title string) sdk.Error { - return sdk.NewError(CodeInvalidTitle, "Proposal Title '"+title+"' is not valid") + return sdk.NewError(DefaultCodespace, CodeInvalidTitle, "Proposal Title '"+title+"' is not valid") } func ErrInvalidDescription(description string) sdk.Error { - return sdk.NewError(CodeInvalidDescription, "Proposal Desciption '"+description+"' is not valid") + return sdk.NewError(DefaultCodespace, CodeInvalidDescription, "Proposal Desciption '"+description+"' is not valid") } func ErrInvalidProposalType(proposalType string) sdk.Error { - return sdk.NewError(CodeInvalidProposalType, "Proposal Type '"+proposalType+"' is not valid") + return sdk.NewError(DefaultCodespace, CodeInvalidProposalType, "Proposal Type '"+proposalType+"' is not valid") } func ErrInvalidVote(voteOption string) sdk.Error { - return sdk.NewError(CodeInvalidVote, "'"+voteOption+"' is not a valid voting option") + return sdk.NewError(DefaultCodespace, CodeInvalidVote, "'"+voteOption+"' is not a valid voting option") +} + +func ErrAlreadyVote(address sdk.Address) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeAddressAlreadyVote, "Address "+hex.EncodeToString(address)+" has already voted") +} +func ErrVotingPeriodOver(proposalID int64) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeVotingPeriodOver, "Proposal "+strconv.FormatInt(proposalID, 10)+"'s votingPeriod has been expired") +} +func ErrDepositPeriodOver(proposalID int64) sdk.Error { + return sdk.NewError(DefaultCodespace, CodeDepositPeriodOver, "Proposal "+strconv.FormatInt(proposalID, 10)+"'s depositPeriod has been expired") } diff --git a/x/gov/handler.go b/x/gov/handler.go index 94e9d8cf1210..df501515ad7f 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -1,9 +1,8 @@ package gov import ( - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" + "reflect" ) // Handle all "gov" type messages. @@ -26,7 +25,7 @@ func NewHandler(keeper Keeper) sdk.Handler { // Handle MsgSubmitProposal. func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result { - _, err := keeper.ck.SubtractCoins(ctx, msg.Proposer, msg.InitialDeposit) + _, _, err := keeper.ck.SubtractCoins(ctx, msg.Proposer, msg.InitialDeposit) if err != nil { return err.Result() } @@ -35,23 +34,14 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos if !keeper.GetActiveProcedure().validProposalType(msg.ProposalType) { return ErrInvalidProposalType(msg.ProposalType).Result() } - - return sdk.Result{} } - initDeposit := Deposit{ - Depositer: msg.Proposer, - Amount: msg.InitialDeposit, - } - - keeper.NewProposal(ctx, msg.Title, msg.Description, msg.ProposalType, initDeposit) - if !keeper.GetActiveProcedure().validProposalType(msg.ProposalType) { return ErrInvalidProposalType(msg.ProposalType).Result() } if ctx.IsCheckTx() { - return sdk.Result{} // TODO + return sdk.Result{} } initialDeposit := Deposit{ @@ -67,7 +57,7 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos TotalDeposit: initialDeposit.Amount, Deposits: []Deposit{initialDeposit}, SubmitBlock: ctx.BlockHeight(), - VotingStartBlock: -1, // TODO: Make Time + VotingStartBlock: -1, TotalVotingPower: 0, Procedure: *(keeper.GetActiveProcedure()), // TODO: Get cloned active Procedure from params kvstore YesVotes: 0, @@ -76,24 +66,28 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos AbstainVotes: 0, } + keeper.getDepositQueue(ctx).Push(proposal.ProposalID) + if proposal.TotalDeposit.IsGTE(proposal.Procedure.MinDeposit) { keeper.activateVotingPeriod(ctx, &proposal) } keeper.SetProposal(ctx, proposal) - return sdk.Result{} // TODO + return sdk.Result{ + Data: []byte{byte(proposal.ProposalID)}, + } } // Handle MsgDeposit. func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result { - _, err := keeper.ck.SubtractCoins(ctx, msg.Depositer, msg.Amount) + _, _, err := keeper.ck.SubtractCoins(ctx, msg.Depositer, msg.Amount) if err != nil { return err.Result() } - proposal := keeper.GetProposal(ctx, msg.ProposalID) + proposal := keeper.GetProposal(ctx,msg.ProposalID) if proposal == nil { return ErrUnknownProposal(msg.ProposalID).Result() @@ -103,8 +97,12 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result return ErrAlreadyActiveProposal(msg.ProposalID).Result() } + if proposal.isDepositPeriodOver(ctx.BlockHeight()) { + return ErrDepositPeriodOver(proposal.ProposalID).Result() + } + if ctx.IsCheckTx() { - return sdk.Result{} // TODO + return sdk.Result{} } deposit := Deposit{ @@ -121,78 +119,84 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result keeper.SetProposal(ctx, *proposal) - return sdk.Result{} // TODO + return sdk.Result{} } // Handle SendMsg. func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { - proposal := keeper.GetProposal(ctx, msg.ProposalID) + proposal := keeper.GetProposal(ctx,msg.ProposalID) if proposal == nil { return ErrUnknownProposal(msg.ProposalID).Result() } - if !proposal.isActive() || ctx.BlockHeight() > proposal.VotingStartBlock+proposal.Procedure.VotingPeriod { + if !proposal.isActive(){ return ErrInactiveProposal(msg.ProposalID).Result() } + curProposal := keeper.getProposalQueue(ctx).Peek() + if curProposal == nil || proposal.ProposalID != curProposal.ProposalID { + return ErrInactiveProposal(msg.ProposalID).Result()//TODO + } + + err := proposal.isExpired(ctx.BlockHeight()) + if err != nil { + return err.Result() + } + validatorGovInfo := proposal.getValidatorGovInfo(msg.Voter) // Need to finalize interface to staking mapper for delegatedTo. Makes assumption from here on out. - delegatedTo := keeper.sm.LoadDelegatorCandidates(ctx, msg.Voter) // TODO: Finalize with staking store + cantVote, delegatedTo := keeper.sm.LoadValidDelegatorCandidates(ctx, msg.Voter, proposal.VotingStartBlock) if validatorGovInfo == nil && len(delegatedTo) == 0 { - return ErrAddressNotStaked(msg.Voter).Result() // TODO: Return proper Error - } - - if proposal.VotingStartBlock <= keeper.sm.getLastDelationChangeBlock(msg.Voter) { // TODO: Get last block in which voter bonded or unbonded - return ErrAddressChangedDelegation(msg.Voter).Result() // TODO: Return proper Error + return ErrAddressNotStaked(msg.Voter).Result() } - if ctx.IsCheckTx() { - return sdk.Result{} // TODO + if cantVote && validatorGovInfo == nil { + return ErrAddressChangedDelegation(msg.Voter).Result() } existingVote := proposal.getVote(msg.Voter) - if existingVote == nil { - proposal.VoteList = append(proposal.VoteList, Vote{Voter: msg.Voter, ProposalID: msg.ProposalID, Option: msg.Option}) - - if validatorGovInfo != nil { - voteWeight := validatorGovInfo.InitVotingPower - validatorGovInfo.Minus - proposal.updateTally(msg.Option, voteWeight) - validatorGovInfo.LastVoteWeight = voteWeight - } + if existingVote != nil { + return ErrAlreadyVote(msg.Voter).Result() + } - for index, delegation := range delegatedTo { - proposal.updateTally(msg.Option, delegation.amount) - delegatedValidatorGovInfo := proposal.getValidatorGovInfo(delegation.validator) - delegatedValidatorGovInfo.Minus += delegation.amount + if ctx.IsCheckTx() { + return sdk.Result{} + } - delegatedValidatorVote := proposal.getVote(delegation.validator) + var voteWeight int64 = 0 - if delegatedValidatorVote != nil { - proposal.updateTally(delegatedValidatorVote.Option, -delegation.amount) - } - } + // TODO can vote repeatedly + //if voter is validator + if validatorGovInfo != nil { + voteWeight = validatorGovInfo.InitVotingPower - validatorGovInfo.Minus + proposal.updateTally(msg.Option, voteWeight) + validatorGovInfo.LastVoteWeight = voteWeight + } - } else { - if validatorGovInfo != nil { - proposal.updateTally(existingVote.Option, -(validatorGovInfo.LastVoteWeight)) - voteWeight := validatorGovInfo.InitVotingPower - validatorGovInfo.Minus - proposal.updateTally(msg.Option, voteWeight) - validatorGovInfo.LastVoteWeight = voteWeight + //if voter is delegator + for _, delegation := range delegatedTo { + if delegation.ChangeAfterVote { + continue } - - for index, delegation := range delegatedTo { - proposal.updateTally(existingVote.Option, -delegation.amount) - proposal.updateTally(msg.Option, delegation.amount) + weight := delegation.Amount + proposal.updateTally(msg.Option, weight) + delegatedValidatorGovInfo := proposal.getValidatorGovInfo(delegation.Validator) + delegatedValidatorGovInfo.Minus += delegation.Amount + + delegatedValidatorVote := proposal.getVote(delegation.Validator) + if delegatedValidatorVote != nil { + proposal.updateTally(delegatedValidatorVote.Option, -delegation.Amount) + delegatedValidatorVote.Weight -= weight } - - existingVote.Option = msg.Option + voteWeight += weight } - + //save vote record + proposal.VoteList = append(proposal.VoteList, NewVote(msg.Voter, msg.ProposalID, msg.Option, voteWeight)) + ctx.Logger().Info("Execute Proposal Vote", "Vote", msg, "Proposal", proposal) keeper.SetProposal(ctx, *proposal) - return sdk.Result{} // TODO } diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 27e07b373eb4..f38f87d3b7e7 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -8,27 +8,35 @@ import ( stake "github.com/cosmos/cosmos-sdk/x/stake" ) +var ( + NewProposalIDKey = []byte{0x00} // + ProposalQueueKey = []byte{0x01} // + DepositQueueKey = []byte{0x02} // + ProposalTypes = []string{"TextProposal"} +) + type Keeper struct { // The reference to the CoinKeeper to modify balances - ck bank.CoinKeeper + ck bank.Keeper // The reference to the StakeMapper to get information about stakers sm stake.Keeper // The (unexposed) keys used to access the stores from the Context. - proposalStoreKey sdk.StoreKey + storeKey sdk.StoreKey // The wire codec for binary encoding/decoding. cdc *wire.Codec } // NewGovernanceMapper returns a mapper that uses go-wire to (binary) encode and decode gov types. -func NewKeeper(key sdk.StoreKey, ck bank.CoinKeeper, sk stake.Keeper) Keeper { +func NewKeeper(key sdk.StoreKey, ck bank.Keeper, sk stake.Keeper) Keeper { cdc := wire.NewCodec() return Keeper{ - proposalStoreKey: key, - ck: ck, - cdc: cdc, + storeKey: key, + ck: ck, + cdc: cdc, + sm: sk, } } @@ -37,8 +45,22 @@ func (keeper Keeper) WireCodec() *wire.Codec { return keeper.cdc } +// Implements sdk.AccountMapper. +func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { + store := ctx.KVStore(keeper.storeKey) + + bz, err := keeper.cdc.MarshalBinary(proposal) + if err != nil { + panic(err) + } + + key, _ := keeper.cdc.MarshalBinary(proposal.ProposalID) + + store.Set(key, bz) +} + func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) *Proposal { - store := ctx.KVStore(keeper.proposalStoreKey) + store := ctx.KVStore(keeper.storeKey) key, _ := keeper.cdc.MarshalBinary(proposalID) bz := store.Get(key) if bz == nil { @@ -54,28 +76,15 @@ func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID int64) *Proposal { return proposal } -// Implements sdk.AccountMapper. -func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { - store := ctx.KVStore(keeper.proposalStoreKey) - - bz, err := keeper.cdc.MarshalBinary(proposal) - if err != nil { - panic(err) - } - - key, _ := keeper.cdc.MarshalBinary(proposal.ProposalID) - - store.Set(key, bz) -} - func (keeper Keeper) getNewProposalID(ctx sdk.Context) int64 { - store := ctx.KVStore(keeper.proposalStoreKey) - bz := store.Get([]byte("newProposalID")) + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(NewProposalIDKey) + + proposalID := new(int64) if bz == nil { - return 0 + bz, _ = keeper.cdc.MarshalBinary(int64(0)) } - proposalID := new(int64) err := keeper.cdc.UnmarshalBinary(bz, proposalID) // TODO: switch to UnmarshalBinaryBare when new go-amino gets added if err != nil { panic("should not happen") @@ -86,74 +95,50 @@ func (keeper Keeper) getNewProposalID(ctx sdk.Context) int64 { panic("should not happen") } - store.Set([]byte("newProposalID"), bz) + store.Set(NewProposalIDKey, bz) return *proposalID } -func (keeper Keeper) getProposalQueue(ctx sdk.Context) ProposalQueue { - store := ctx.KVStore(keeper.proposalStoreKey) - bz := store.Get([]byte("proposalQueue")) - if bz == nil { - return nil - } - - proposalQueue := &ProposalQueue{} - err := keeper.cdc.UnmarshalBinary(bz, proposalQueue) // TODO: switch to UnmarshalBinaryBare when new go-amino gets added - if err != nil { - panic(err) - } - - return *proposalQueue +func (keeper Keeper) getProposalQueue(ctx sdk.Context) *Queue{ + store := ctx.KVStore(keeper.storeKey) + queue := GetQueue(keeper,store,ProposalQueueKey) + return queue } -func (keeper Keeper) setProposalQueue(ctx sdk.Context, proposalQueue ProposalQueue) { - store := ctx.KVStore(keeper.proposalStoreKey) - - bz, err := keeper.cdc.MarshalBinary(proposalQueue) // TODO: switch to MarshalBinaryBare when new go-amino gets added - if err != nil { - panic(err) - } - - store.Set([]byte("proposalQueue"), bz) +func (keeper Keeper) getDepositQueue(ctx sdk.Context) *Queue{ + store := ctx.KVStore(keeper.storeKey) + queue := GetQueue(keeper,store, DepositQueueKey) + return queue } -func (keeper Keeper) ProposalQueuePeek(ctx sdk.Context) *Proposal { - proposalQueue := keeper.getProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil - } - return keeper.GetProposal(ctx, proposalQueue[0]) -} - -func (keeper Keeper) ProposalQueuePop(ctx sdk.Context) *Proposal { - proposalQueue := keeper.getProposalQueue(ctx) - if len(proposalQueue) == 0 { - return nil +func (keeper Keeper) popExpiredProposal(ctx sdk.Context) (list []*Proposal){ + depositQueue := keeper.getDepositQueue(ctx) + for _,proposalID := range depositQueue.value { + proposal := keeper.GetProposal(ctx,proposalID) + if proposal.isDepositPeriodOver(ctx.BlockHeight()){ + list = append(list,proposal) + }else { + break + } } - frontElement, proposalQueue := proposalQueue[0], proposalQueue[1:] - keeper.setProposalQueue(ctx, proposalQueue) - return keeper.GetProposal(ctx, frontElement) -} - -func (keeper Keeper) ProposalQueuePush(ctx sdk.Context, proposal Proposal) { - store := ctx.KVStore(keeper.proposalStoreKey) - proposalQueue := append(keeper.getProposalQueue(ctx), proposal.ProposalID) - bz, err := keeper.cdc.MarshalBinary(proposalQueue) - if err != nil { - panic(err) + for _,proposal := range list{ + if !depositQueue.Remove(proposal.ProposalID) { + panic("should not happen") + } } - store.Set([]byte("proposalQueue"), bz) + return list } func (keeper Keeper) GetActiveProcedure() *Procedure { // TODO: move to param store and allow for updating of this return &Procedure{ VotingPeriod: 200, - MinDeposit: sdk.Coins{{"atom", 10}}, - ProposalTypes: []string{"TextProposal"}, + MinDeposit: sdk.Coins{{stake.StakingToken, 2}}, + ProposalTypes: ProposalTypes, Threshold: sdk.NewRat(1, 2), Veto: sdk.NewRat(1, 3), + FastPassThreshold: sdk.NewRat(2, 3), MaxDepositPeriod: 200, GovernancePenalty: sdk.NewRat(1, 100), } @@ -162,19 +147,16 @@ func (keeper Keeper) GetActiveProcedure() *Procedure { // TODO: move to param st func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal *Proposal) { proposal.VotingStartBlock = ctx.BlockHeight() - // TODO: Can we get this directly from stakeState - // stakeState := k.sm.loadGlobalState() - // proposal.TotalVotingPower = stakeState.TotalSupply - - proposal.TotalVotingPower = 0 + pool := keeper.sm.GetPool(ctx) + proposal.TotalVotingPower = pool.BondedPool - validatorList := keeper.sm.GetValidators(ctx, 100) // TODO: Finalize with staking module + validatorList := keeper.sm.GetValidators(ctx) + for _, validator := range validatorList { - for index, validator := range validatorList { validatorGovInfo := ValidatorGovInfo{ ProposalID: proposal.ProposalID, - ValidatorAddr: validator.Address, // TODO: Finalize with staking module - InitVotingPower: validator.VotingPower, // TODO: Finalize with staking module + ValidatorAddr: validator.Address, + InitVotingPower: validator.Power.Evaluate(), Minus: 0, LastVoteWeight: -1, } @@ -182,9 +164,6 @@ func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal *Proposal) { proposal.ValidatorGovInfos = append(proposal.ValidatorGovInfos, validatorGovInfo) } - keeper.ProposalQueuePush(ctx, *proposal) -} - -func (keeper Keeper) NewProposal(ctx, title string, description string, proposalType string, initDeposit Deposit) (Proposal, sdk.Error) { // TODO: move to param store and allow for updating of this - + keeper.getDepositQueue(ctx).Remove(proposal.ProposalID) + keeper.getProposalQueue(ctx).Push(proposal.ProposalID) } diff --git a/x/gov/msgs.go b/x/gov/msgs.go index 52b0724812e2..56d099c32994 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -7,8 +7,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// name to idetify transaction types +const MsgType = "gov" + //----------------------------------------------------------- -// MsgSubmitProposal +// MsgSubmitProposal. type MsgSubmitProposal struct { Title string // Title of the proposal @@ -29,19 +32,19 @@ func NewMsgSubmitProposal(title string, description string, proposalType string, } // Implements Msg. -func (msg MsgSubmitProposal) Type() string { return "gov" } +func (msg MsgSubmitProposal) Type() string { return MsgType } // Implements Msg. func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { if len(msg.Title) == 0 { - return ErrInvalidTitle(msg.Title) // TODO: Proper Error + return ErrInvalidTitle(msg.Title) } if len(msg.Description) == 0 { - return ErrInvalidDescription(msg.Description) // TODO: Proper Error + return ErrInvalidDescription(msg.Description) } if len(msg.ProposalType) == 0 { - return ErrInvalidProposalType(msg.ProposalType) // TODO: Proper Error + return ErrInvalidProposalType(msg.ProposalType) } if len(msg.Proposer) == 0 { return sdk.ErrInvalidAddress(msg.Proposer.String()) @@ -96,7 +99,7 @@ func NewMsgDeposit(proposalID int64, depositer sdk.Address, amount sdk.Coins) Ms } // Implements Msg. -func (msg MsgDeposit) Type() string { return "gov" } +func (msg MsgDeposit) Type() string { return MsgType } // Implements Msg. func (msg MsgDeposit) ValidateBasic() sdk.Error { @@ -153,7 +156,7 @@ func NewMsgVote(voter sdk.Address, proposalID int64, option string) MsgVote { } // Implements Msg. -func (msg MsgVote) Type() string { return "gov" } +func (msg MsgVote) Type() string { return MsgType } // Implements Msg. func (msg MsgVote) ValidateBasic() sdk.Error { @@ -161,7 +164,7 @@ func (msg MsgVote) ValidateBasic() sdk.Error { if len(msg.Voter) == 0 { return sdk.ErrInvalidAddress(msg.Voter.String()) } - if msg.Option != "Yes" || msg.Option != "No" || msg.Option != "NoWithVeto" || msg.Option != "Abstain" { + if msg.Option != YesOption && msg.Option != NoOption && msg.Option != NoWithVetoOption && msg.Option != AbstainOption { return ErrInvalidVote(msg.Option) } return nil diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index d479b875212a..1212dcad5c8b 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -3,37 +3,43 @@ package gov import ( "testing" - "github.com/stretchr/testify/assert" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tendermint/tmlibs/log" + "os" ) func defaultProcedure() Procedure { return Procedure{ - VotingPeriod: 200, - MinDeposit: sdk.Coins{{"atom", 10}}, - ProposalTypes: []string{"TextProposal"}, - Threshold: sdk.NewRat(1, 2), - Veto: sdk.NewRat(1, 3), - MaxDepositPeriod: 200, - GovernancePenalty: 12, + VotingPeriod: 200, + MinDeposit: sdk.Coins{{"atom", 10}}, + ProposalTypes: []string{"TextProposal"}, + Threshold: sdk.NewRat(1, 2), + Veto: sdk.NewRat(1, 3), + MaxDepositPeriod: 200, + //GovernancePenalty: 12, } } -func TestNewSubmitProposalMsg(t *testing.T) {} - -func TestSubmitProposaslType(t *testing.T) { - // Construct a SubmitProposalMsg - - addr1 := sdk.Address([]byte("input")) - coins := sdk.Coins{{"atom", 5}} - - NewSubmitProposalMsg("Test Proposal", "the purpose of this proposal is to test", "TextProposal", addr1, coins) - - // TODO some failures for bad result - assert.Equal(t, msg.Type(), "gov") +func TestNewSubmitProposalMsg(t *testing.T) { + addr, _ := sdk.GetAddress("AF816B3A3CD3739F9D3C900E352A07FD78131B6B") + msg := NewMsgVote(addr, 1, "Yes") + msg.ValidateBasic() + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger.Info("msg", "Proposal", msg) } +//func TestSubmitProposaslType(t *testing.T) { +// // Construct a SubmitProposalMsg +// +// addr1 := sdk.Address([]byte("input")) +// coins := sdk.Coins{{"atom", 5}} +// +// NewSubmitProposalMsg("Test Proposal", "the purpose of this proposal is to test", "TextProposal", addr1, coins) +// +// // TODO some failures for bad result +// assert.Equal(t, msg.Type(), "gov") +//} + // func TestInputValidation(t *testing.T) { // addr1 := sdk.Address([]byte{1, 2}) // addr2 := sdk.Address([]byte{7, 8}) diff --git a/x/gov/queue.go b/x/gov/queue.go new file mode 100644 index 000000000000..ec1161d1bd9e --- /dev/null +++ b/x/gov/queue.go @@ -0,0 +1,110 @@ +package gov + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type Queue struct { + value []int64 + key []byte + store sdk.KVStore + keeper Keeper +} + +func GetQueue(keeper Keeper, store sdk.KVStore, key []byte) *Queue { + queueName, _ := keeper.cdc.MarshalBinary(key) + bz := store.Get(queueName) + + var data []int64 + if len(bz) != 0 { + err := keeper.cdc.UnmarshalBinary(bz, &data) + if err != nil { + panic(err) + } + } + + return &Queue{ + data, key, store, keeper, + } +} + +func (q *Queue) Push(data int64) { + q.value = append(q.value, data) + bz, err := q.keeper.cdc.MarshalBinary(q.value) + if err != nil { + panic(err) + } + key, _ := q.keeper.cdc.MarshalBinary(q.key) + q.store.Set(key, bz) +} + +func (q *Queue) Peek() (*Proposal) { + if len(q.value) == 0 { + return nil + } + return q.get(q.value[0]) +} + +func (q *Queue) Pop() (*Proposal) { + var newQ = []int64{} + var element int64 + if len(q.value) == 0 { + return nil + }else if len(q.value) == 1 { + element = q.value[0] + }else { + element, newQ = q.value[0], q.value[1:] + } + bz, err := q.keeper.cdc.MarshalBinary(newQ) + if err != nil { + panic(err) + } + key, _ := q.keeper.cdc.MarshalBinary(q.key) + q.store.Set(key, bz) + return q.get(element) +} + +func (q *Queue) Remove(value int64) (removed bool){ + for index, ele := range q.value { + if ele == value { + data := q.value[:index] + q.value = append(data, q.value[index+1:]...) + removed = true + break + } + } + bz, err := q.keeper.cdc.MarshalBinary(q.value) + if err != nil { + panic(err) + } + key, err := q.keeper.cdc.MarshalBinary(q.key) + if err != nil { + panic(err) + } + q.store.Set(key, bz) + + return removed +} + +func (q *Queue) GetAll() (list []*Proposal) { + for _,id := range q.value { + list = append(list,q.get(id)) + } + return list +} + +func (q *Queue) get(proposalID int64) *Proposal { + key, _ := q.keeper.cdc.MarshalBinary(proposalID) + bz := q.store.Get(key) + if bz == nil { + return nil + } + + proposal := &Proposal{} + err := q.keeper.cdc.UnmarshalBinary(bz, proposal) + if err != nil { + panic(err) + } + + return proposal +} diff --git a/x/gov/tick.go b/x/gov/tick.go index 183f35d8f9b9..c50d56f85fc4 100644 --- a/x/gov/tick.go +++ b/x/gov/tick.go @@ -1,61 +1,88 @@ package gov -/* - import ( + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" ) -func NewBeginBlocker(gm governanceMapper) sdk.BeginBlocker { +func NewBeginBlocker(keeper Keeper) sdk.BeginBlocker { return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - proposal := gm.ProposalQueuePeek(ctx) + checkProposal(ctx,keeper) + + proposal := keeper.getProposalQueue(ctx).Peek() if proposal == nil { - return abci.ResponseBeginBlock{} // TODO + return abci.ResponseBeginBlock{} } // Don't want to do urgent for now + passV := types.NewRat(proposal.YesVotes, proposal.TotalVotingPower) + //Urgent proposal accepted + if passV.GT(proposal.Procedure.FastPassThreshold) || passV.Equal(proposal.Procedure.FastPassThreshold) { - // // Urgent proposal accepted - // if proposal.Votes.YesVotes/proposal.InitTotalVotingPower >= 2/3 { - // gm.PopProposalQueue(ctx) - // refund(ctx, gm, proposalID, proposal) - // return checkProposal() - // } + ctx.Logger().Info("execute Proposal", "Proposal", proposal.ProposalID) - // Proposal reached the end of the voting period - if ctx.BlockHeight() == proposal.VotingStartBlock+proposal.Procedure.VotingPeriod { - gm.ProposalQueuePop(ctx) - - // Slash validators if not voted - for _, validatorGovInfo := range proposal.ValidatorGovInfos { - if validatorOption.LastVoteWeight < 0 { - // TODO: SLASH MWAHAHAHAHAHA - } + prop := keeper.getProposalQueue(ctx).Pop() + if prop != nil && prop.ProposalID == proposal.ProposalID{ + refund(ctx, proposal, keeper) } + //TODO proposal.execute - //Proposal was accepted - nonAbstainTotal := proposal.Votes.YesVotes + proposal.Votes.NoVotes + proposal.Votes.NoWithVetoVotes - if proposal.YesVotes/nonAbstainTotal > 0.5 && proposal.NoWithVetoVotes/nonAbstainTotal < 1/3 { // TODO: Deal with decimals + return abci.ResponseBeginBlock{} + } - // TODO: Act upon accepting of proposal + // Proposal reached the end of the voting period + if ctx.BlockHeight() >= proposal.VotingStartBlock+proposal.Procedure.VotingPeriod { + prop := keeper.getProposalQueue(ctx).Pop() + if prop != nil && prop.ProposalID == proposal.ProposalID{ + refund(ctx, proposal, keeper) + } - // Refund deposits - for _, deposit := range proposal.Deposits { - gm.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) - if err != nil { - panic("should not happen") - } - } + //Slash validators if not voted + slash(ctx, proposal.ValidatorGovInfos) - // check next proposal recursively - checkProposal() + //Proposal was accepted + nonAbstainTotal := proposal.YesVotes + proposal.NoVotes + proposal.NoWithVetoVotes + if nonAbstainTotal <= 0 { + return abci.ResponseBeginBlock{} + } + yRat := types.NewRat(proposal.YesVotes, nonAbstainTotal) + vetoRat := types.NewRat(proposal.NoWithVetoVotes, nonAbstainTotal) + if yRat.GT(proposal.Procedure.Threshold) && vetoRat.LT(proposal.Procedure.Veto) { + ctx.Logger().Info("Execute proposal", "proposal", proposal) + // TODO proposal.execute } - - // TODO: Prune proposal } return abci.ResponseBeginBlock{} } } -*/ +// refund Deposit +func refund(ctx sdk.Context, proposal *Proposal, keeper Keeper) { + for _, deposit := range proposal.Deposits { + ctx.Logger().Info("Execute Refund", "Depositer", deposit.Depositer, "Amount", deposit.Amount) + _, _, err := keeper.ck.AddCoins(ctx, deposit.Depositer, deposit.Amount) + if err != nil { + panic("should not happen") + } + } +} + +// Slash validators if not voted +func slash(ctx sdk.Context, validators []ValidatorGovInfo) { + ctx.Logger().Info("Begin to Execute Slash") + for _, validatorGovInfo := range validators { + if validatorGovInfo.LastVoteWeight < 0 { + // TODO: SLASH MWAHAHAHAHAHA + ctx.Logger().Info("Execute Slash", "validator", validatorGovInfo.ValidatorAddr) + } + } +} + +//check Deposit timeout +func checkProposal(ctx sdk.Context,keeper Keeper){ + proposals := keeper.popExpiredProposal(ctx) + for _,proposal := range proposals { + refund(ctx, proposal, keeper) + } +} diff --git a/x/gov/types.go b/x/gov/types.go index 945df80a6e7f..76355f23b3f3 100644 --- a/x/gov/types.go +++ b/x/gov/types.go @@ -2,14 +2,27 @@ package gov import ( "bytes" - sdk "github.com/cosmos/cosmos-sdk/types" ) +const ( + YesOption = "Yes" + NoOption = "No" + NoWithVetoOption = "NoWithVeto" + AbstainOption = "Abstain" +) + type Vote struct { Voter sdk.Address `json:"voter"` // address of the voter ProposalID int64 `json:"proposal_id"` // proposalID of the proposal Option string `json:"option"` // option from OptionSet chosen by the voter + Weight int64 `json:"weight"` // weight of the Vote +} + +func NewVote(voter sdk.Address, proposalID int64, option string, weight int64) Vote { + return Vote{ + voter, proposalID, option, weight, + } } //----------------------------------------------------------- @@ -38,19 +51,19 @@ type Proposal struct { AbstainVotes int64 `json:"abstain_votes"` // Weight of Abstain Votes } -func (proposal Proposal) getValidatorGovInfo(validatorAddr sdk.Address) *ValidatorGovInfo { - for _, validatorGovInfo := range proposal.ValidatorGovInfos { +func (proposal *Proposal) getValidatorGovInfo(validatorAddr sdk.Address) *ValidatorGovInfo { + for i, validatorGovInfo := range proposal.ValidatorGovInfos { if bytes.Equal(validatorGovInfo.ValidatorAddr, validatorAddr) { - return &validatorGovInfo + return &proposal.ValidatorGovInfos[i] } } return nil } -func (proposal Proposal) getVote(voterAddr sdk.Address) *Vote { - for _, vote := range proposal.VoteList { +func (proposal *Proposal) getVote(voterAddr sdk.Address) *Vote { + for i, vote := range proposal.VoteList { if bytes.Equal(vote.Voter, voterAddr) { - return &vote + return &proposal.VoteList[i] } } return nil @@ -60,15 +73,32 @@ func (proposal Proposal) isActive() bool { return proposal.VotingStartBlock >= 0 } -func (proposal Proposal) updateTally(option string, amount int64) { +func (proposal Proposal) isVotingPeriodOver(height int64) bool { + return height > proposal.VotingStartBlock + proposal.Procedure.VotingPeriod +} + +func (proposal Proposal) isDepositPeriodOver(height int64) bool { + return height > proposal.SubmitBlock + proposal.Procedure.MaxDepositPeriod +} + +func (proposal Proposal) isExpired(height int64) sdk.Error { + if height > proposal.SubmitBlock + proposal.Procedure.MaxDepositPeriod { + return ErrDepositPeriodOver(proposal.ProposalID) + } else if height > proposal.VotingStartBlock + proposal.Procedure.VotingPeriod { + return ErrVotingPeriodOver(proposal.ProposalID) + } + return nil +} + +func (proposal *Proposal) updateTally(option string, amount int64) { switch option { - case "Yes": + case YesOption: proposal.YesVotes += amount - case "No": + case NoOption: proposal.NoVotes += amount - case "NoWithVeto": + case NoWithVetoOption: proposal.NoWithVetoVotes += amount - case "Abstain": + case AbstainOption: proposal.AbstainVotes += amount } } @@ -80,6 +110,7 @@ type Procedure struct { ProposalTypes []string `json:"proposal_type"` // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} 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 + FastPassThreshold sdk.Rat `json:"fast_pass_threshold"`// Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 MaxDepositPeriod int64 `json:"max_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty sdk.Rat `json:"governance_penalty"` // Penalty if validator does not vote } @@ -100,11 +131,9 @@ type Deposit struct { } type ValidatorGovInfo struct { - ProposalID int64 // Id of the Proposal this validator - ValidatorAddr sdk.Address // Address of the validator - InitVotingPower int64 // Voting power of validator when proposal enters voting period - Minus int64 // Minus of validator, used to compute validator's voting power - LastVoteWeight int64 // Weight of the last vote by validator at time of casting, -1 if hasn't voted yet + ProposalID int64 `json:"proposal_iD"` // Id of the Proposal this validator + ValidatorAddr sdk.Address `json:"validator_addr"` // Address of the validator + InitVotingPower int64 `json:"init_voting_power"` // Voting power of validator when proposal enters voting period + Minus int64 `json:"minus"` // Minus of validator, used to compute validator's voting power + LastVoteWeight int64 `json:"last_vote_weight"` // Weight of the last vote by validator at time of casting, -1 if hasn't voted yet } - -type ProposalQueue []int64 diff --git a/x/gov/wire.go b/x/gov/wire.go index e38ae450f6ea..9f3c77d078ee 100644 --- a/x/gov/wire.go +++ b/x/gov/wire.go @@ -5,11 +5,8 @@ import ( ) func RegisterWire(cdc *wire.Codec) { - // TODO: bring this back ... - /* - // TODO include option to always include prefix bytes. - cdc.RegisterConcrete(SubmitProposalMsg{}, "cosmos-sdk/SubmitProposalMsg", nil) - cdc.RegisterConcrete(DepositMsg{}, "cosmos-sdk/DepositMsg", nil) - cdc.RegisterConcrete(VoteMsg{}, "cosmos-sdk/VoteMsg", nil) - */ + cdc.RegisterConcrete(MsgSubmitProposal{}, "cosmos-sdk/SubmitProposalMsg", nil) + cdc.RegisterConcrete(MsgDeposit{}, "cosmos-sdk/DepositMsg", nil) + cdc.RegisterConcrete(MsgVote{}, "cosmos-sdk/VoteMsg", nil) + } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 605f675db0ee..88c3094c3e2f 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -493,3 +493,30 @@ func (k Keeper) setPool(ctx sdk.Context, p Pool) { store.Set(PoolKey, b) k.pool = Pool{} //clear the cache } + +func (k Keeper) LoadValidDelegatorCandidates(ctx sdk.Context, delegator sdk.Address, Height int64) (bool, []DelegatorCandidate) { + var cantVote bool = true + delegatorBonds := k.GetDelegatorBonds(ctx, delegator, int16(k.GetParams(ctx).MaxValidators)) + delegations := make([]DelegatorCandidate, len(delegatorBonds)) + if len(delegatorBonds) != 0 { + for i, bond := range delegatorBonds { + var changeAfterVote bool + if bond.Height > Height { + changeAfterVote = true + } + cantVote = cantVote && changeAfterVote + candidate, _ := k.GetCandidate(ctx, bond.CandidateAddr) + pool := k.GetPool(ctx) + globalPoolShares := candidate.delegatorShareExRate().Mul(bond.Shares) + amount := pool.bondedShareExRate().Mul(globalPoolShares).Evaluate() + d := DelegatorCandidate{ + Amount: amount, + Validator: bond.CandidateAddr, + Height: bond.Height, + ChangeAfterVote: changeAfterVote, + } + delegations[i] = d + } + } + return cantVote, delegations +} diff --git a/x/stake/types.go b/x/stake/types.go index 741783fa1d6d..9adcbde8d4f9 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -187,3 +187,10 @@ type DelegatorBond struct { Shares sdk.Rat `json:"shares"` Height int64 `json:"height"` // Last height bond updated } + +type DelegatorCandidate struct { + Validator sdk.Address `json:"candidate_addr"` + Amount int64 `json:"amount"` // Last height bond updated + Height int64 `json:"height"` // Last height bond updated + ChangeAfterVote bool +}