Merge PR #4471: Migrate genesis cmd
This commit is contained in:
parent
4e86810a27
commit
00f753d684
|
@ -0,0 +1,3 @@
|
|||
#4409 Implement a command that migrates exported state from one version to the next.
|
||||
The `migrate` command currently supports migrating from v0.34 to v0.36 by implementing
|
||||
necessary types for both versions.
|
|
@ -5,8 +5,9 @@ import lib
|
|||
|
||||
def process_raw_genesis(genesis, parsed_args):
|
||||
# update genesis with breaking changes
|
||||
genesis['consensus_params']['block'] = genesis['consensus_params']['block_size']
|
||||
del genesis['consensus_params']['block_size']
|
||||
if 'block_size' in genesis['consensus_params']:
|
||||
genesis['consensus_params']['block'] = genesis['consensus_params']['block_size']
|
||||
del genesis['consensus_params']['block_size']
|
||||
|
||||
genesis['app_state']['crisis'] = {
|
||||
'constant_fee': {
|
|
@ -16,3 +16,11 @@ Light-clients enable users to interact with your application without having to d
|
|||
|
||||
- [Command-Line interface for SDK-based blockchain](./cli.md)
|
||||
- [Service provider doc](./service-providers.md)
|
||||
|
||||
## Genesis upgrade
|
||||
|
||||
If you need to upgrade your node you could export the genesis and migrate it to the new version through this script:
|
||||
|
||||
```bash
|
||||
<appbinary> migrate v0.36 genesis_0_34.json [--time "2019-04-22T17:00:11Z"] [--chain-id test] > ~/.gaiad/genesis.json
|
||||
```
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
extypes "github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil/legacy/v036"
|
||||
)
|
||||
|
||||
var migrationMap = extypes.MigrationMap{
|
||||
"v0.36": v036.Migrate,
|
||||
}
|
||||
|
||||
const (
|
||||
flagGenesisTime = "genesis-time"
|
||||
flagChainId = "chain-id"
|
||||
)
|
||||
|
||||
func MigrateGenesisCmd(_ *server.Context, cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate [target-version] [genesis-file]",
|
||||
Short: "Migrate genesis to a specified target version",
|
||||
Long: fmt.Sprintf(`Migrate the source genesis into the target version and print to STDOUT.
|
||||
|
||||
Example:
|
||||
$ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z
|
||||
`, version.ServerName),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
target := args[0]
|
||||
importGenesis := args[1]
|
||||
|
||||
genDoc, err := types.GenesisDocFromFile(importGenesis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var initialState extypes.AppMap
|
||||
cdc.MustUnmarshalJSON(genDoc.AppState, &initialState)
|
||||
|
||||
if migrationMap[target] == nil {
|
||||
return fmt.Errorf("unknown migration function version: %s", target)
|
||||
}
|
||||
|
||||
newGenState := migrationMap[target](initialState, cdc)
|
||||
genDoc.AppState = cdc.MustMarshalJSON(newGenState)
|
||||
|
||||
genesisTime := cmd.Flag(flagGenesisTime).Value.String()
|
||||
if genesisTime != "" {
|
||||
var t time.Time
|
||||
|
||||
err := t.UnmarshalText([]byte(genesisTime))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
genDoc.GenesisTime = t
|
||||
}
|
||||
|
||||
chainId := cmd.Flag(flagChainId).Value.String()
|
||||
if chainId != "" {
|
||||
genDoc.ChainID = chainId
|
||||
}
|
||||
|
||||
out, err := cdc.MarshalJSONIndent(genDoc, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagGenesisTime, "", "Override genesis_time with this flag")
|
||||
cmd.Flags().String(flagChainId, "", "Override chain_id with this flag")
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
)
|
||||
|
||||
func setupCmd(genesisTime string, chainId string) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "c",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Run: func(_ *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
c.Flags().String(flagGenesisTime, genesisTime, "")
|
||||
c.Flags().String(flagChainId, chainId, "")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func TestMigrateGenesis(t *testing.T) {
|
||||
home, cleanup := tests.NewTestCaseDir(t)
|
||||
viper.Set(cli.HomeFlag, home)
|
||||
viper.Set(client.FlagName, "moniker")
|
||||
logger := log.NewNopLogger()
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
require.Nil(t, err)
|
||||
ctx := server.NewContext(cfg, logger)
|
||||
cdc := makeCodec()
|
||||
|
||||
genesisPath := path.Join(home, "genesis.json")
|
||||
target := "v0.36"
|
||||
|
||||
defer cleanup()
|
||||
|
||||
// Reject if we dont' have the right parameters or genesis does not exists
|
||||
require.Error(t, MigrateGenesisCmd(ctx, cdc).RunE(nil, []string{target, genesisPath}))
|
||||
|
||||
// Noop migration with minimal genesis
|
||||
emptyGenesis := []byte(`{"chain_id":"test","app_state":{}}`)
|
||||
err = ioutil.WriteFile(genesisPath, emptyGenesis, 0644)
|
||||
require.Nil(t, err)
|
||||
cmd := setupCmd("", "test2")
|
||||
require.NoError(t, MigrateGenesisCmd(ctx, cdc).RunE(cmd, []string{target, genesisPath}))
|
||||
// Every migration function shuold tests its own module separately
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package v036
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
v034gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v034"
|
||||
v036gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v036"
|
||||
)
|
||||
|
||||
// Migrate migrates exported state from v0.34 to a v0.36 genesis state.
|
||||
func Migrate(appState genutil.AppMap, cdc *codec.Codec) genutil.AppMap {
|
||||
v034Codec := codec.New()
|
||||
codec.RegisterCrypto(v034Codec)
|
||||
v036Codec := codec.New()
|
||||
codec.RegisterCrypto(v036Codec)
|
||||
|
||||
if appState[v034gov.ModuleName] != nil {
|
||||
var govState v034gov.GenesisState
|
||||
v034gov.RegisterCodec(v034Codec)
|
||||
v034Codec.MustUnmarshalJSON(appState[v034gov.ModuleName], &govState)
|
||||
v036gov.RegisterCodec(v036Codec)
|
||||
delete(appState, v034gov.ModuleName) // Drop old key, in case it changed name
|
||||
appState[v036gov.ModuleName] = v036Codec.MustMarshalJSON(v036gov.MigrateGovernance(govState))
|
||||
}
|
||||
return appState
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package v036
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/go-amino"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
)
|
||||
|
||||
var basic034Gov = []byte(`
|
||||
{
|
||||
"starting_proposal_id": "2",
|
||||
"deposits": [
|
||||
{
|
||||
"proposal_id": "1",
|
||||
"deposit": {
|
||||
"depositor": "cosmos1grgelyng2v6v3t8z87wu3sxgt9m5s03xvslewd",
|
||||
"proposal_id": "1",
|
||||
"amount": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "512000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"votes" : [
|
||||
{
|
||||
"proposal_id": "1",
|
||||
"vote": {
|
||||
"voter": "cosmos1lktjhnzkpkz3ehrg8psvmwhafg56kfss5597tg",
|
||||
"proposal_id": "1",
|
||||
"option": "Yes"
|
||||
}
|
||||
}
|
||||
],
|
||||
"proposals": [
|
||||
{
|
||||
"proposal_content": {
|
||||
"type": "gov/TextProposal",
|
||||
"value": {
|
||||
"title": "test",
|
||||
"description": "test"
|
||||
}
|
||||
},
|
||||
"proposal_id": "1",
|
||||
"proposal_status": "Passed",
|
||||
"final_tally_result": {
|
||||
"yes": "1",
|
||||
"abstain": "0",
|
||||
"no": "0",
|
||||
"no_with_veto": "0"
|
||||
},
|
||||
"submit_time": "2019-05-03T21:08:25.443199036Z",
|
||||
"deposit_end_time": "2019-05-17T21:08:25.443199036Z",
|
||||
"total_deposit": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "512000000"
|
||||
}
|
||||
],
|
||||
"voting_start_time": "2019-05-04T16:02:33.24680295Z",
|
||||
"voting_end_time": "2019-05-18T16:02:33.24680295Z"
|
||||
}
|
||||
],
|
||||
"deposit_params": {
|
||||
"min_deposit": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "512000000"
|
||||
}
|
||||
],
|
||||
"max_deposit_period": "1209600000000000"
|
||||
},
|
||||
"voting_params": {
|
||||
"voting_period": "1209600000000000"
|
||||
},
|
||||
"tally_params": {
|
||||
"quorum": "0.400000000000000000",
|
||||
"threshold": "0.500000000000000000",
|
||||
"veto": "0.334000000000000000"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
func TestDummyGenesis(t *testing.T) {
|
||||
genesisDummy := genutil.AppMap{
|
||||
"foo": {},
|
||||
"bar": []byte(`{"custom": "module"}`),
|
||||
}
|
||||
cdc := amino.NewCodec()
|
||||
migratedDummy := Migrate(genesisDummy, cdc)
|
||||
|
||||
// We should not touch custom modules in the map
|
||||
require.Equal(t, genesisDummy["foo"], migratedDummy["foo"])
|
||||
require.Equal(t, genesisDummy["bar"], migratedDummy["bar"])
|
||||
}
|
||||
|
||||
func TestGovGenesis(t *testing.T) {
|
||||
genesis := genutil.AppMap{
|
||||
"gov": basic034Gov,
|
||||
}
|
||||
cdc := amino.NewCodec()
|
||||
|
||||
require.NotPanics(t, func() { Migrate(genesis, cdc) })
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package genutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
type (
|
||||
// AppMap map modules names with their json raw representation
|
||||
AppMap map[string]json.RawMessage
|
||||
// MigrationCallback converts a genesis map from the previous version to the targeted one
|
||||
MigrationCallback func(AppMap, *codec.Codec) AppMap
|
||||
// MigrationMap defines a mapping from a version to a MigrationCallback
|
||||
MigrationMap map[string]MigrationCallback
|
||||
)
|
|
@ -0,0 +1,513 @@
|
|||
package v034
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Keys
|
||||
const (
|
||||
// ModuleName is the name of the module
|
||||
ModuleName = "gov"
|
||||
|
||||
// StoreKey is the store key string for gov
|
||||
StoreKey = ModuleName
|
||||
|
||||
// RouterKey is the message route for gov
|
||||
RouterKey = ModuleName
|
||||
|
||||
// QuerierRoute is the querier route for gov
|
||||
QuerierRoute = ModuleName
|
||||
|
||||
// DefaultParamspace default name for parameter store
|
||||
DefaultParamspace = ModuleName
|
||||
)
|
||||
|
||||
type GenesisState struct {
|
||||
StartingProposalID uint64 `json:"starting_proposal_id"`
|
||||
Deposits []DepositWithMetadata `json:"deposits"`
|
||||
Votes []VoteWithMetadata `json:"votes"`
|
||||
Proposals []Proposal `json:"proposals"`
|
||||
DepositParams DepositParams `json:"deposit_params"`
|
||||
VotingParams VotingParams `json:"voting_params"`
|
||||
TallyParams TallyParams `json:"tally_params"`
|
||||
}
|
||||
|
||||
type DepositWithMetadata struct {
|
||||
ProposalID uint64 `json:"proposal_id"`
|
||||
Deposit Deposit `json:"deposit"`
|
||||
}
|
||||
|
||||
type VoteWithMetadata struct {
|
||||
ProposalID uint64 `json:"proposal_id"`
|
||||
Vote Vote `json:"vote"`
|
||||
}
|
||||
|
||||
type Deposit struct {
|
||||
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
|
||||
Amount sdk.Coins `json:"amount"` // Deposit amount
|
||||
}
|
||||
|
||||
type Deposits []Deposit
|
||||
|
||||
type Vote struct {
|
||||
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
||||
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
|
||||
}
|
||||
|
||||
type Votes []Vote
|
||||
|
||||
// Param around deposits for governance
|
||||
type DepositParams struct {
|
||||
MinDeposit sdk.Coins `json:"min_deposit,omitempty"` // Minimum deposit for a proposal to enter voting period.
|
||||
MaxDepositPeriod time.Duration `json:"max_deposit_period,omitempty"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
|
||||
}
|
||||
|
||||
type TallyParams struct {
|
||||
Quorum sdk.Dec `json:"quorum,omitempty"` // Minimum percentage of total stake needed to vote for a result to be considered valid
|
||||
Threshold sdk.Dec `json:"threshold,omitempty"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
|
||||
Veto sdk.Dec `json:"veto,omitempty"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
|
||||
}
|
||||
|
||||
type VotingParams struct {
|
||||
VotingPeriod time.Duration `json:"voting_period,omitempty"` // Length of the voting period.
|
||||
}
|
||||
|
||||
type TallyResult struct {
|
||||
Yes sdk.Int `json:"yes"`
|
||||
Abstain sdk.Int `json:"abstain"`
|
||||
No sdk.Int `json:"no"`
|
||||
NoWithVeto sdk.Int `json:"no_with_veto"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ProposalStatus
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ProposalStatus is a type alias that represents a proposal status as a byte
|
||||
type ProposalStatus byte
|
||||
|
||||
//nolint
|
||||
const (
|
||||
StatusNil ProposalStatus = 0x00
|
||||
StatusDepositPeriod ProposalStatus = 0x01
|
||||
StatusVotingPeriod ProposalStatus = 0x02
|
||||
StatusPassed ProposalStatus = 0x03
|
||||
StatusRejected ProposalStatus = 0x04
|
||||
StatusFailed ProposalStatus = 0x05
|
||||
)
|
||||
|
||||
// ProposalStatusToString turns a string into a ProposalStatus
|
||||
func ProposalStatusFromString(str string) (ProposalStatus, error) {
|
||||
switch str {
|
||||
case "DepositPeriod":
|
||||
return StatusDepositPeriod, nil
|
||||
|
||||
case "VotingPeriod":
|
||||
return StatusVotingPeriod, nil
|
||||
|
||||
case "Passed":
|
||||
return StatusPassed, nil
|
||||
|
||||
case "Rejected":
|
||||
return StatusRejected, nil
|
||||
|
||||
case "Failed":
|
||||
return StatusFailed, nil
|
||||
|
||||
case "":
|
||||
return StatusNil, nil
|
||||
|
||||
default:
|
||||
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal needed for protobuf compatibility
|
||||
func (status ProposalStatus) Marshal() ([]byte, error) {
|
||||
return []byte{byte(status)}, nil
|
||||
}
|
||||
|
||||
// Unmarshal needed for protobuf compatibility
|
||||
func (status *ProposalStatus) Unmarshal(data []byte) error {
|
||||
*status = ProposalStatus(data[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshals to JSON using string
|
||||
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(status.String())
|
||||
}
|
||||
|
||||
// Unmarshals from JSON assuming Bech32 encoding
|
||||
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz2, err := ProposalStatusFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*status = bz2
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (status ProposalStatus) String() string {
|
||||
switch status {
|
||||
case StatusDepositPeriod:
|
||||
return "DepositPeriod"
|
||||
|
||||
case StatusVotingPeriod:
|
||||
return "VotingPeriod"
|
||||
|
||||
case StatusPassed:
|
||||
return "Passed"
|
||||
|
||||
case StatusRejected:
|
||||
return "Rejected"
|
||||
|
||||
case StatusFailed:
|
||||
return "Failed"
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
// nolint: errcheck
|
||||
func (status ProposalStatus) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
s.Write([]byte(status.String()))
|
||||
default:
|
||||
// TODO: Do this conversion more directly
|
||||
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// VoteOption
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type VoteOption byte
|
||||
|
||||
// Vote options
|
||||
const (
|
||||
OptionEmpty VoteOption = 0x00
|
||||
OptionYes VoteOption = 0x01
|
||||
OptionAbstain VoteOption = 0x02
|
||||
OptionNo VoteOption = 0x03
|
||||
OptionNoWithVeto VoteOption = 0x04
|
||||
)
|
||||
|
||||
// VoteOptionFromString returns a VoteOption from a string. It returns an error
|
||||
// if the string is invalid.
|
||||
func VoteOptionFromString(str string) (VoteOption, error) {
|
||||
switch str {
|
||||
case "Yes":
|
||||
return OptionYes, nil
|
||||
|
||||
case "Abstain":
|
||||
return OptionAbstain, nil
|
||||
|
||||
case "No":
|
||||
return OptionNo, nil
|
||||
|
||||
case "NoWithVeto":
|
||||
return OptionNoWithVeto, nil
|
||||
|
||||
default:
|
||||
return VoteOption(0xff), fmt.Errorf("'%s' is not a valid vote option", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal needed for protobuf compatibility.
|
||||
func (vo VoteOption) Marshal() ([]byte, error) {
|
||||
return []byte{byte(vo)}, nil
|
||||
}
|
||||
|
||||
// Unmarshal needed for protobuf compatibility.
|
||||
func (vo *VoteOption) Unmarshal(data []byte) error {
|
||||
*vo = VoteOption(data[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshals to JSON using string.
|
||||
func (vo VoteOption) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(vo.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
|
||||
func (vo *VoteOption) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz2, err := VoteOptionFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*vo = bz2
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (vo VoteOption) String() string {
|
||||
switch vo {
|
||||
case OptionYes:
|
||||
return "Yes"
|
||||
case OptionAbstain:
|
||||
return "Abstain"
|
||||
case OptionNo:
|
||||
return "No"
|
||||
case OptionNoWithVeto:
|
||||
return "NoWithVeto"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
// nolint: errcheck
|
||||
func (vo VoteOption) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
s.Write([]byte(vo.String()))
|
||||
default:
|
||||
s.Write([]byte(fmt.Sprintf("%v", byte(vo))))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Tally
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Equals returns if two proposals are equal.
|
||||
func (tr TallyResult) Equals(comp TallyResult) bool {
|
||||
return tr.Yes.Equal(comp.Yes) &&
|
||||
tr.Abstain.Equal(comp.Abstain) &&
|
||||
tr.No.Equal(comp.No) &&
|
||||
tr.NoWithVeto.Equal(comp.NoWithVeto)
|
||||
}
|
||||
|
||||
func (tr TallyResult) String() string {
|
||||
return fmt.Sprintf(`Tally Result:
|
||||
Yes: %s
|
||||
Abstain: %s
|
||||
No: %s
|
||||
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Proposal
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type Proposal struct {
|
||||
ProposalContent `json:"proposal_content"` // Proposal content interface
|
||||
|
||||
ProposalID uint64 `json:"proposal_id"` // ID of the proposal
|
||||
|
||||
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
|
||||
|
||||
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
|
||||
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
|
||||
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||
|
||||
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (p Proposal) String() string {
|
||||
return fmt.Sprintf(`Proposal %d:
|
||||
Title: %s
|
||||
Type: %s
|
||||
Status: %s
|
||||
Submit Time: %s
|
||||
Deposit End Time: %s
|
||||
Total Deposit: %s
|
||||
Voting Start Time: %s
|
||||
Voting End Time: %s
|
||||
Description: %s`,
|
||||
p.ProposalID, p.GetTitle(), p.ProposalType(),
|
||||
p.Status, p.SubmitTime, p.DepositEndTime,
|
||||
p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
|
||||
)
|
||||
}
|
||||
|
||||
// ProposalContent is an interface that has title, description, and proposaltype
|
||||
// that the governance module can use to identify them and generate human readable messages
|
||||
// ProposalContent can have additional fields, which will handled by ProposalHandlers
|
||||
// via type assertion, e.g. parameter change amount in ParameterChangeProposal
|
||||
type ProposalContent interface {
|
||||
GetTitle() string
|
||||
GetDescription() string
|
||||
ProposalType() ProposalKind
|
||||
}
|
||||
|
||||
// Proposals is an array of proposal
|
||||
type Proposals []Proposal
|
||||
|
||||
// nolint
|
||||
func (p Proposals) String() string {
|
||||
out := "ID - (Status) [Type] Title\n"
|
||||
for _, prop := range p {
|
||||
out += fmt.Sprintf("%d - (%s) [%s] %s\n",
|
||||
prop.ProposalID, prop.Status,
|
||||
prop.ProposalType(), prop.GetTitle())
|
||||
}
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// Text Proposals
|
||||
type TextProposal struct {
|
||||
Title string `json:"title"` // Title of the proposal
|
||||
Description string `json:"description"` // Description of the proposal
|
||||
}
|
||||
|
||||
func NewTextProposal(title, description string) TextProposal {
|
||||
return TextProposal{
|
||||
Title: title,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Proposal Interface
|
||||
var _ ProposalContent = TextProposal{}
|
||||
|
||||
// nolint
|
||||
func (tp TextProposal) GetTitle() string { return tp.Title }
|
||||
func (tp TextProposal) GetDescription() string { return tp.Description }
|
||||
func (tp TextProposal) ProposalType() ProposalKind { return ProposalTypeText }
|
||||
|
||||
// Software Upgrade Proposals
|
||||
type SoftwareUpgradeProposal struct {
|
||||
TextProposal
|
||||
}
|
||||
|
||||
func NewSoftwareUpgradeProposal(title, description string) SoftwareUpgradeProposal {
|
||||
return SoftwareUpgradeProposal{
|
||||
TextProposal: NewTextProposal(title, description),
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Proposal Interface
|
||||
var _ ProposalContent = SoftwareUpgradeProposal{}
|
||||
|
||||
// nolint
|
||||
func (sup SoftwareUpgradeProposal) ProposalType() ProposalKind { return ProposalTypeSoftwareUpgrade }
|
||||
|
||||
// ProposalQueue
|
||||
type ProposalQueue []uint64
|
||||
|
||||
// ProposalKind
|
||||
|
||||
// Type that represents Proposal Type as a byte
|
||||
type ProposalKind byte
|
||||
|
||||
//nolint
|
||||
const (
|
||||
ProposalTypeNil ProposalKind = 0x00
|
||||
ProposalTypeText ProposalKind = 0x01
|
||||
ProposalTypeParameterChange ProposalKind = 0x02
|
||||
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
|
||||
)
|
||||
|
||||
// String to proposalType byte. Returns 0xff if invalid.
|
||||
func ProposalTypeFromString(str string) (ProposalKind, error) {
|
||||
switch str {
|
||||
case "Text":
|
||||
return ProposalTypeText, nil
|
||||
case "ParameterChange":
|
||||
return ProposalTypeParameterChange, nil
|
||||
case "SoftwareUpgrade":
|
||||
return ProposalTypeSoftwareUpgrade, nil
|
||||
default:
|
||||
return ProposalKind(0xff), fmt.Errorf("'%s' is not a valid proposal type", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal needed for protobuf compatibility
|
||||
func (pt ProposalKind) Marshal() ([]byte, error) {
|
||||
return []byte{byte(pt)}, nil
|
||||
}
|
||||
|
||||
// Unmarshal needed for protobuf compatibility
|
||||
func (pt *ProposalKind) Unmarshal(data []byte) error {
|
||||
*pt = ProposalKind(data[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshals to JSON using string
|
||||
func (pt ProposalKind) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(pt.String())
|
||||
}
|
||||
|
||||
// Unmarshals from JSON assuming Bech32 encoding
|
||||
func (pt *ProposalKind) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz2, err := ProposalTypeFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*pt = bz2
|
||||
return nil
|
||||
}
|
||||
|
||||
// Turns VoteOption byte to String
|
||||
func (pt ProposalKind) String() string {
|
||||
switch pt {
|
||||
case ProposalTypeText:
|
||||
return "Text"
|
||||
case ProposalTypeParameterChange:
|
||||
return "ParameterChange"
|
||||
case ProposalTypeSoftwareUpgrade:
|
||||
return "SoftwareUpgrade"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// For Printf / Sprintf, returns bech32 when using %s
|
||||
// nolint: errcheck
|
||||
func (pt ProposalKind) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
s.Write([]byte(pt.String()))
|
||||
default:
|
||||
// TODO: Do this conversion more directly
|
||||
s.Write([]byte(fmt.Sprintf("%v", byte(pt))))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterInterface((*ProposalContent)(nil), nil)
|
||||
cdc.RegisterConcrete(TextProposal{}, "gov/TextProposal", nil)
|
||||
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "gov/SoftwareUpgradeProposal", nil)
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package v036
|
||||
|
||||
import (
|
||||
v034gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v034"
|
||||
)
|
||||
|
||||
func MigrateGovernance(initialState v034gov.GenesisState) GenesisState {
|
||||
targetGov := GenesisState{
|
||||
StartingProposalID: initialState.StartingProposalID,
|
||||
DepositParams: DepositParams{
|
||||
MinDeposit: initialState.DepositParams.MinDeposit,
|
||||
MaxDepositPeriod: initialState.DepositParams.MaxDepositPeriod,
|
||||
},
|
||||
TallyParams: TallyParams{
|
||||
Quorum: initialState.TallyParams.Quorum,
|
||||
Threshold: initialState.TallyParams.Threshold,
|
||||
Veto: initialState.TallyParams.Veto,
|
||||
},
|
||||
VotingParams: VotingParams{
|
||||
VotingPeriod: initialState.VotingParams.VotingPeriod,
|
||||
},
|
||||
}
|
||||
|
||||
var deposits Deposits
|
||||
for _, p := range initialState.Deposits {
|
||||
deposits = append(deposits, Deposit{
|
||||
ProposalID: p.Deposit.ProposalID,
|
||||
Amount: p.Deposit.Amount,
|
||||
Depositor: p.Deposit.Depositor,
|
||||
})
|
||||
}
|
||||
|
||||
targetGov.Deposits = deposits
|
||||
|
||||
var votes Votes
|
||||
for _, p := range initialState.Votes {
|
||||
votes = append(votes, Vote{
|
||||
ProposalID: p.Vote.ProposalID,
|
||||
Option: VoteOption(p.Vote.Option),
|
||||
Voter: p.Vote.Voter,
|
||||
})
|
||||
}
|
||||
|
||||
targetGov.Votes = votes
|
||||
|
||||
var proposals Proposals
|
||||
for _, p := range initialState.Proposals {
|
||||
proposal := Proposal{
|
||||
Content: migrateContent(p.ProposalContent),
|
||||
ProposalID: p.ProposalID,
|
||||
Status: ProposalStatus(p.Status),
|
||||
FinalTallyResult: TallyResult(p.FinalTallyResult),
|
||||
SubmitTime: p.SubmitTime,
|
||||
DepositEndTime: p.DepositEndTime,
|
||||
TotalDeposit: p.TotalDeposit,
|
||||
VotingStartTime: p.VotingStartTime,
|
||||
VotingEndTime: p.VotingEndTime,
|
||||
}
|
||||
|
||||
proposals = append(proposals, proposal)
|
||||
}
|
||||
|
||||
targetGov.Proposals = proposals
|
||||
return targetGov
|
||||
}
|
||||
|
||||
func migrateContent(proposalContent v034gov.ProposalContent) (content Content) {
|
||||
switch proposalContent.ProposalType() {
|
||||
case v034gov.ProposalTypeText:
|
||||
return NewTextProposal(proposalContent.GetTitle(), proposalContent.GetDescription())
|
||||
case v034gov.ProposalTypeSoftwareUpgrade:
|
||||
return NewSoftwareUpgradeProposal(proposalContent.GetTitle(), proposalContent.GetDescription())
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,454 @@
|
|||
package v036
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Keys
|
||||
const (
|
||||
// ModuleName is the name of the module
|
||||
ModuleName = "gov"
|
||||
|
||||
// StoreKey is the store key string for gov
|
||||
StoreKey = ModuleName
|
||||
|
||||
// RouterKey is the message route for gov
|
||||
RouterKey = ModuleName
|
||||
|
||||
// QuerierRoute is the querier route for gov
|
||||
QuerierRoute = ModuleName
|
||||
|
||||
// DefaultParamspace default name for parameter store
|
||||
DefaultParamspace = ModuleName
|
||||
)
|
||||
|
||||
// GenesisState represents v0.34.x genesis state for the governance module.
|
||||
type GenesisState struct {
|
||||
StartingProposalID uint64 `json:"starting_proposal_id"`
|
||||
Deposits Deposits `json:"deposits"`
|
||||
Votes Votes `json:"votes"`
|
||||
Proposals []Proposal `json:"proposals"`
|
||||
DepositParams DepositParams `json:"deposit_params"`
|
||||
VotingParams VotingParams `json:"voting_params"`
|
||||
TallyParams TallyParams `json:"tally_params"`
|
||||
}
|
||||
|
||||
type Deposit struct {
|
||||
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
|
||||
Amount sdk.Coins `json:"amount"` // Deposit amount
|
||||
}
|
||||
|
||||
type Deposits []Deposit
|
||||
|
||||
type Vote struct {
|
||||
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
|
||||
Voter sdk.AccAddress `json:"voter"` // address of the voter
|
||||
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
|
||||
}
|
||||
|
||||
type Votes []Vote
|
||||
|
||||
// Param around deposits for governance
|
||||
type DepositParams struct {
|
||||
MinDeposit sdk.Coins `json:"min_deposit,omitempty"` // Minimum deposit for a proposal to enter voting period.
|
||||
MaxDepositPeriod time.Duration `json:"max_deposit_period,omitempty"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
|
||||
}
|
||||
|
||||
type Content interface {
|
||||
GetTitle() string
|
||||
GetDescription() string
|
||||
ProposalRoute() string
|
||||
ProposalType() string
|
||||
ValidateBasic() sdk.Error
|
||||
String() string
|
||||
}
|
||||
|
||||
type Proposal struct {
|
||||
Content `json:"content"` // Proposal content interface
|
||||
|
||||
ProposalID uint64 `json:"id"` // ID of the proposal
|
||||
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
|
||||
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
|
||||
|
||||
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
|
||||
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
|
||||
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
|
||||
|
||||
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
|
||||
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
|
||||
}
|
||||
|
||||
type Proposals []Proposal
|
||||
type ProposalQueue []uint64
|
||||
|
||||
type TallyParams struct {
|
||||
Quorum sdk.Dec `json:"quorum,omitempty"` // Minimum percentage of total stake needed to vote for a result to be considered valid
|
||||
Threshold sdk.Dec `json:"threshold,omitempty"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
|
||||
Veto sdk.Dec `json:"veto,omitempty"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ProposalStatus
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// ProposalStatus is a type alias that represents a proposal status as a byte
|
||||
type ProposalStatus byte
|
||||
|
||||
//nolint
|
||||
const (
|
||||
StatusNil ProposalStatus = 0x00
|
||||
StatusDepositPeriod ProposalStatus = 0x01
|
||||
StatusVotingPeriod ProposalStatus = 0x02
|
||||
StatusPassed ProposalStatus = 0x03
|
||||
StatusRejected ProposalStatus = 0x04
|
||||
StatusFailed ProposalStatus = 0x05
|
||||
)
|
||||
|
||||
// ProposalStatusToString turns a string into a ProposalStatus
|
||||
func ProposalStatusFromString(str string) (ProposalStatus, error) {
|
||||
switch str {
|
||||
case "DepositPeriod":
|
||||
return StatusDepositPeriod, nil
|
||||
|
||||
case "VotingPeriod":
|
||||
return StatusVotingPeriod, nil
|
||||
|
||||
case "Passed":
|
||||
return StatusPassed, nil
|
||||
|
||||
case "Rejected":
|
||||
return StatusRejected, nil
|
||||
|
||||
case "Failed":
|
||||
return StatusFailed, nil
|
||||
|
||||
case "":
|
||||
return StatusNil, nil
|
||||
|
||||
default:
|
||||
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal needed for protobuf compatibility
|
||||
func (status ProposalStatus) Marshal() ([]byte, error) {
|
||||
return []byte{byte(status)}, nil
|
||||
}
|
||||
|
||||
// Unmarshal needed for protobuf compatibility
|
||||
func (status *ProposalStatus) Unmarshal(data []byte) error {
|
||||
*status = ProposalStatus(data[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshals to JSON using string
|
||||
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(status.String())
|
||||
}
|
||||
|
||||
// Unmarshals from JSON assuming Bech32 encoding
|
||||
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz2, err := ProposalStatusFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*status = bz2
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (status ProposalStatus) String() string {
|
||||
switch status {
|
||||
case StatusDepositPeriod:
|
||||
return "DepositPeriod"
|
||||
|
||||
case StatusVotingPeriod:
|
||||
return "VotingPeriod"
|
||||
|
||||
case StatusPassed:
|
||||
return "Passed"
|
||||
|
||||
case StatusRejected:
|
||||
return "Rejected"
|
||||
|
||||
case StatusFailed:
|
||||
return "Failed"
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
// nolint: errcheck
|
||||
func (status ProposalStatus) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
s.Write([]byte(status.String()))
|
||||
default:
|
||||
// TODO: Do this conversion more directly
|
||||
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
|
||||
}
|
||||
}
|
||||
|
||||
type VotingParams struct {
|
||||
VotingPeriod time.Duration `json:"voting_period,omitempty"` // Length of the voting period.
|
||||
}
|
||||
|
||||
type TallyResult struct {
|
||||
Yes sdk.Int `json:"yes"`
|
||||
Abstain sdk.Int `json:"abstain"`
|
||||
No sdk.Int `json:"no"`
|
||||
NoWithVeto sdk.Int `json:"no_with_veto"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// VoteOption
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type VoteOption byte
|
||||
|
||||
// Vote options
|
||||
const (
|
||||
OptionEmpty VoteOption = 0x00
|
||||
OptionYes VoteOption = 0x01
|
||||
OptionAbstain VoteOption = 0x02
|
||||
OptionNo VoteOption = 0x03
|
||||
OptionNoWithVeto VoteOption = 0x04
|
||||
)
|
||||
|
||||
// VoteOptionFromString returns a VoteOption from a string. It returns an error
|
||||
// if the string is invalid.
|
||||
func VoteOptionFromString(str string) (VoteOption, error) {
|
||||
switch str {
|
||||
case "Yes":
|
||||
return OptionYes, nil
|
||||
|
||||
case "Abstain":
|
||||
return OptionAbstain, nil
|
||||
|
||||
case "No":
|
||||
return OptionNo, nil
|
||||
|
||||
case "NoWithVeto":
|
||||
return OptionNoWithVeto, nil
|
||||
|
||||
default:
|
||||
return VoteOption(0xff), fmt.Errorf("'%s' is not a valid vote option", str)
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal needed for protobuf compatibility.
|
||||
func (vo VoteOption) Marshal() ([]byte, error) {
|
||||
return []byte{byte(vo)}, nil
|
||||
}
|
||||
|
||||
// Unmarshal needed for protobuf compatibility.
|
||||
func (vo *VoteOption) Unmarshal(data []byte) error {
|
||||
*vo = VoteOption(data[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshals to JSON using string.
|
||||
func (vo VoteOption) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(vo.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
|
||||
func (vo *VoteOption) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bz2, err := VoteOptionFromString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*vo = bz2
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (vo VoteOption) String() string {
|
||||
switch vo {
|
||||
case OptionYes:
|
||||
return "Yes"
|
||||
case OptionAbstain:
|
||||
return "Abstain"
|
||||
case OptionNo:
|
||||
return "No"
|
||||
case OptionNoWithVeto:
|
||||
return "NoWithVeto"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
// nolint: errcheck
|
||||
func (vo VoteOption) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
s.Write([]byte(vo.String()))
|
||||
default:
|
||||
s.Write([]byte(fmt.Sprintf("%v", byte(vo))))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Tally
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Equals returns if two proposals are equal.
|
||||
func (tr TallyResult) Equals(comp TallyResult) bool {
|
||||
return tr.Yes.Equal(comp.Yes) &&
|
||||
tr.Abstain.Equal(comp.Abstain) &&
|
||||
tr.No.Equal(comp.No) &&
|
||||
tr.NoWithVeto.Equal(comp.NoWithVeto)
|
||||
}
|
||||
|
||||
func (tr TallyResult) String() string {
|
||||
return fmt.Sprintf(`Tally Result:
|
||||
Yes: %s
|
||||
Abstain: %s
|
||||
No: %s
|
||||
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Proposal
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Proposal types
|
||||
const (
|
||||
ProposalTypeText string = "Text"
|
||||
ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
|
||||
)
|
||||
|
||||
// Text Proposal
|
||||
type TextProposal struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func NewTextProposal(title, description string) Content {
|
||||
return TextProposal{title, description}
|
||||
}
|
||||
|
||||
// Implements Proposal Interface
|
||||
var _ Content = TextProposal{}
|
||||
|
||||
// nolint
|
||||
func (tp TextProposal) GetTitle() string { return tp.Title }
|
||||
func (tp TextProposal) GetDescription() string { return tp.Description }
|
||||
func (tp TextProposal) ProposalRoute() string { return RouterKey }
|
||||
func (tp TextProposal) ProposalType() string { return ProposalTypeText }
|
||||
func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) }
|
||||
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = "gov"
|
||||
|
||||
CodeInvalidContent sdk.CodeType = 6
|
||||
CodeInvalidProposalType sdk.CodeType = 7
|
||||
)
|
||||
|
||||
func ErrInvalidProposalContent(cs sdk.CodespaceType, msg string) sdk.Error {
|
||||
return sdk.NewError(cs, CodeInvalidContent, fmt.Sprintf("invalid proposal content: %s", msg))
|
||||
}
|
||||
|
||||
func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("proposal type '%s' is not valid", proposalType))
|
||||
}
|
||||
|
||||
// ValidateAbstract validates a proposal's abstract contents returning an error
|
||||
// if invalid.
|
||||
func ValidateAbstract(codespace sdk.CodespaceType, c Content) sdk.Error {
|
||||
title := c.GetTitle()
|
||||
if len(strings.TrimSpace(title)) == 0 {
|
||||
return ErrInvalidProposalContent(codespace, "proposal title cannot be blank")
|
||||
}
|
||||
if len(title) > MaxTitleLength {
|
||||
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal title is longer than max length of %d", MaxTitleLength))
|
||||
}
|
||||
|
||||
description := c.GetDescription()
|
||||
if len(description) == 0 {
|
||||
return ErrInvalidProposalContent(codespace, "proposal description cannot be blank")
|
||||
}
|
||||
if len(description) > MaxDescriptionLength {
|
||||
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal description is longer than max length of %d", MaxDescriptionLength))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constants pertaining to a Content object
|
||||
const (
|
||||
MaxDescriptionLength int = 5000
|
||||
MaxTitleLength int = 140
|
||||
)
|
||||
|
||||
func (tp TextProposal) String() string {
|
||||
return fmt.Sprintf(`Text Proposal:
|
||||
Title: %s
|
||||
Description: %s
|
||||
`, tp.Title, tp.Description)
|
||||
}
|
||||
|
||||
// Software Upgrade Proposals
|
||||
// TODO: We have to add fields for SUP specific arguments e.g. commit hash,
|
||||
// upgrade date, etc.
|
||||
type SoftwareUpgradeProposal struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func NewSoftwareUpgradeProposal(title, description string) Content {
|
||||
return SoftwareUpgradeProposal{title, description}
|
||||
}
|
||||
|
||||
// Implements Proposal Interface
|
||||
var _ Content = SoftwareUpgradeProposal{}
|
||||
|
||||
// nolint
|
||||
func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
|
||||
func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
|
||||
func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
|
||||
func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
|
||||
func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error {
|
||||
return ValidateAbstract(DefaultCodespace, sup)
|
||||
}
|
||||
|
||||
func (sup SoftwareUpgradeProposal) String() string {
|
||||
return fmt.Sprintf(`Software Upgrade Proposal:
|
||||
Title: %s
|
||||
Description: %s
|
||||
`, sup.Title, sup.Description)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterInterface((*Content)(nil), nil)
|
||||
cdc.RegisterConcrete(TextProposal{}, "cosmos-sdk/TextProposal", nil)
|
||||
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "cosmos-sdk/SoftwareUpgradeProposal", nil)
|
||||
}
|
Loading…
Reference in New Issue