diff --git a/CHANGELOG.md b/CHANGELOG.md index 482d03561..ee0b550d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.10.2 + +*January 17th, 2019* + +- [iris] The proposer must deposit 30% of the mindeposit when submitting the proposal + + ## 0.10.1 *January 17th, 2019* diff --git a/app/app.go b/app/app.go index ad2054197..3d078b9d3 100644 --- a/app/app.go +++ b/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/irisnet/irishub/app/protocol" "github.com/irisnet/irishub/app/v0" + "github.com/irisnet/irishub/app/v1" "github.com/irisnet/irishub/codec" "github.com/irisnet/irishub/modules/auth" sdk "github.com/irisnet/irishub/types" @@ -67,7 +68,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio } engine.Add(v0.NewProtocolV0(0, logger, protocolKeeper, sdk.InvariantLevel)) - // engine.Add(v1.NewProtocolV1(1, ...)) + engine.Add(v1.NewProtocolV1(1, logger, protocolKeeper, sdk.InvariantLevel)) // engine.Add(v2.NewProtocolV1(2, ...)) loaded, current := engine.LoadCurrentProtocol(app.GetKVStore(protocol.KeyMain)) @@ -81,7 +82,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio // latest version of codec func MakeLatestCodec() *codec.Codec { - var cdc = v0.MakeCodec() // replace with latest protocol version + var cdc = v1.MakeCodec() // replace with latest protocol version return cdc } diff --git a/app/protocol/engine.go b/app/protocol/engine.go index de1ee2c0d..0b757085d 100644 --- a/app/protocol/engine.go +++ b/app/protocol/engine.go @@ -3,6 +3,7 @@ package protocol import ( sdk "github.com/irisnet/irishub/types" "fmt" + "github.com/irisnet/irishub/modules/params" ) type ProtocolEngine struct { @@ -37,6 +38,7 @@ func (pe *ProtocolEngine) LoadCurrentProtocol(kvStore sdk.KVStore) (bool, uint64 func (pe *ProtocolEngine) Activate(version uint64) bool { p, flag := pe.protocols[version] if flag == true { + params.ParamSetMapping = make(map[string]params.ParamSet) p.Load() p.Init() pe.current = version diff --git a/app/v1/export.go b/app/v1/export.go new file mode 100644 index 000000000..eb8017f7f --- /dev/null +++ b/app/v1/export.go @@ -0,0 +1,191 @@ +package v1 + +import ( + "encoding/json" + "fmt" + + "github.com/irisnet/irishub/app/protocol" + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/auth" + distr "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/app/v1/gov" + "github.com/irisnet/irishub/modules/guardian" + "github.com/irisnet/irishub/modules/mint" + "github.com/irisnet/irishub/modules/service" + "github.com/irisnet/irishub/modules/slashing" + stake "github.com/irisnet/irishub/modules/stake" + "github.com/irisnet/irishub/modules/upgrade" + sdk "github.com/irisnet/irishub/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// export the state of iris for a genesis file +func (p *ProtocolV1) ExportAppStateAndValidators(ctx sdk.Context, forZeroHeight bool) ( + appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + + if forZeroHeight { + p.prepForZeroHeightGenesis(ctx) + } + + // iterate to get the accounts + accounts := []GenesisAccount{} + appendAccount := func(acc auth.Account) (stop bool) { + account := NewGenesisAccountI(acc) + accounts = append(accounts, account) + return false + } + p.accountMapper.IterateAccounts(ctx, appendAccount) + fileAccounts := []GenesisFileAccount{} + for _, acc := range accounts { + if acc.Coins == nil { + continue + } + var coinsString []string + for _, coin := range acc.Coins { + coinsString = append(coinsString, coin.String()) + } + fileAccounts = append(fileAccounts, + GenesisFileAccount{ + Address: acc.Address, + Coins: coinsString, + Sequence: acc.Sequence, + AccountNumber: acc.AccountNumber, + }) + } + + genState := NewGenesisFileState( + fileAccounts, + auth.ExportGenesis(ctx, p.feeKeeper), + stake.ExportGenesis(ctx, p.StakeKeeper), + mint.ExportGenesis(ctx, p.mintKeeper), + distr.ExportGenesis(ctx, p.distrKeeper), + gov.ExportGenesis(ctx, p.govKeeper), + upgrade.ExportGenesis(ctx), + service.ExportGenesis(ctx, p.serviceKeeper), + guardian.ExportGenesis(ctx, p.guardianKeeper), + slashing.ExportGenesis(ctx, p.slashingKeeper), + ) + appState, err = codec.MarshalJSONIndent(p.cdc, genState) + if err != nil { + return nil, nil, err + } + sdk.MustSortJSON(appState) + validators = stake.WriteValidators(ctx, p.StakeKeeper) + return appState, validators, nil +} + +// prepare for fresh start at zero height +func (p *ProtocolV1) prepForZeroHeightGenesis(ctx sdk.Context) { + + /* Handle fee distribution state. */ + + // withdraw all delegator & validator rewards + vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { + _, _, err := p.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) + if err != nil { + panic(err) + } + return false + } + p.distrKeeper.IterateValidatorDistInfos(ctx, vdiIter) + + ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { + _, err := p.distrKeeper.WithdrawDelegationReward( + ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) + if err != nil { + panic(err) + } + return false + } + p.distrKeeper.IterateDelegationDistInfos(ctx, ddiIter) + + // set distribution info withdrawal heights to 0 + p.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, delInfo distr.DelegationDistInfo) (stop bool) { + delInfo.DelPoolWithdrawalHeight = 0 + p.distrKeeper.SetDelegationDistInfo(ctx, delInfo) + return false + }) + p.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { + valInfo.FeePoolWithdrawalHeight = 0 + valInfo.DelAccum.UpdateHeight = 0 + p.distrKeeper.SetValidatorDistInfo(ctx, valInfo) + return false + }) + + // assert that the fee pool is empty + feePool := p.distrKeeper.GetFeePool(ctx) + if !feePool.TotalValAccum.Accum.IsZero() { + panic("unexpected leftover validator accum") + } + bondDenom := p.StakeKeeper.BondDenom() + if !feePool.ValPool.AmountOf(bondDenom).IsZero() { + panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", + feePool.ValPool.AmountOf(bondDenom).String())) + } + + // reset fee pool height, save fee pool + feePool.TotalValAccum = distr.NewTotalAccum(0) + p.distrKeeper.SetFeePool(ctx, feePool) + + /* Handle stake state. */ + + // iterate through redelegations, reset creation height + p.StakeKeeper.IterateRedelegations(ctx, func(_ int64, red stake.Redelegation) (stop bool) { + red.CreationHeight = 0 + p.StakeKeeper.SetRedelegation(ctx, red) + return false + }) + + // iterate through unbonding delegations, reset creation height + p.StakeKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) (stop bool) { + ubd.CreationHeight = 0 + p.StakeKeeper.SetUnbondingDelegation(ctx, ubd) + return false + }) + // Iterate through validators by power descending, reset bond and unbonding heights + store := ctx.KVStore(protocol.KeyStake) + iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsKey) + defer iter.Close() + counter := int16(0) + var valConsAddrs []sdk.ConsAddress + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[1:]) + validator, found := p.StakeKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + validator.BondHeight = 0 + validator.UnbondingHeight = 0 + valConsAddrs = append(valConsAddrs, validator.ConsAddress()) + p.StakeKeeper.SetValidator(ctx, validator) + counter++ + } + + /* Handle slashing state. */ + + // remove all existing slashing periods and recreate one for each validator + p.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) + for _, valConsAddr := range valConsAddrs { + sp := slashing.ValidatorSlashingPeriod{ + ValidatorAddr: valConsAddr, + StartHeight: 0, + EndHeight: 0, + SlashedSoFar: sdk.ZeroDec(), + } + p.slashingKeeper.SetValidatorSlashingPeriod(ctx, sp) + } + + // reset start height on signing infos + p.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + p.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }) + + /* Handle gov state. */ + + gov.PrepForZeroHeightGenesis(ctx, p.govKeeper) + + /* Handle service state. */ + service.PrepForZeroHeightGenesis(ctx, p.serviceKeeper) +} diff --git a/app/v1/genesis.go b/app/v1/genesis.go new file mode 100644 index 000000000..e9e8e7827 --- /dev/null +++ b/app/v1/genesis.go @@ -0,0 +1,415 @@ +package v1 + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/auth" + distr "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/app/v1/gov" + "github.com/irisnet/irishub/modules/guardian" + "github.com/irisnet/irishub/modules/mint" + "github.com/irisnet/irishub/modules/service" + "github.com/irisnet/irishub/modules/slashing" + "github.com/irisnet/irishub/modules/stake" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" + "github.com/irisnet/irishub/modules/upgrade" + "github.com/irisnet/irishub/types" + sdk "github.com/irisnet/irishub/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// State to Unmarshal +type GenesisState struct { + Accounts []GenesisAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` + StakeData stake.GenesisState `json:"stake"` + MintData mint.GenesisState `json:"mint"` + DistrData distr.GenesisState `json:"distr"` + GovData gov.GenesisState `json:"gov"` + UpgradeData upgrade.GenesisState `json:"upgrade"` + SlashingData slashing.GenesisState `json:"slashing"` + ServiceData service.GenesisState `json:"service"` + GuardianData guardian.GenesisState `json:"guardian"` + GenTxs []json.RawMessage `json:"gentxs"` +} + +func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, upgradeData upgrade.GenesisState, serviceData service.GenesisState, + guardianData guardian.GenesisState, slashingData slashing.GenesisState) GenesisState { + + return GenesisState{ + Accounts: accounts, + AuthData: authData, + StakeData: stakeData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + UpgradeData: upgradeData, + ServiceData: serviceData, + GuardianData: guardianData, + SlashingData: slashingData, + } +} + +// GenesisAccount doesn't need pubkey or sequence +type GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Sequence uint64 `json:"sequence_number"` + AccountNumber uint64 `json:"account_number"` +} + +func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { + return GenesisAccount{ + Address: acc.Address, + Coins: acc.Coins, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, + } +} + +func NewGenesisAccountI(acc auth.Account) GenesisAccount { + return GenesisAccount{ + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + AccountNumber: acc.GetAccountNumber(), + Sequence: acc.GetSequence(), + } +} + +// convert GenesisAccount to auth.BaseAccount +func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { + return &auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + AccountNumber: ga.AccountNumber, + Sequence: ga.Sequence, + } +} + +// Create the core parameters for genesis initialization for iris +// note that the pubkey input is this machines pubkey +func IrisAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + genesisState GenesisFileState, err error) { + if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { + return genesisState, err + } + + // if there are no gen txs to be processed, return the default empty state + if len(appGenTxs) == 0 { + return genesisState, errors.New("there must be at least one genesis tx") + } + + stakeData := genesisState.StakeData + for i, genTx := range appGenTxs { + var tx auth.StdTx + if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { + return genesisState, err + } + msgs := tx.GetMsgs() + if len(msgs) != 1 { + return genesisState, errors.New( + "must provide genesis StdTx with exactly 1 CreateValidator message") + } + if _, ok := msgs[0].(stake.MsgCreateValidator); !ok { + return genesisState, fmt.Errorf( + "Genesis transaction %v does not contain a MsgCreateValidator", i) + } + } + + genesisState.StakeData = stakeData + genesisState.GenTxs = appGenTxs + genesisState.UpgradeData = genesisState.UpgradeData + return genesisState, nil +} + +// IrisValidateGenesisState ensures that the genesis state obeys the expected invariants +// TODO: No validators are both bonded and jailed (#2088) +// TODO: Error if there is a duplicate validator (#1708) +// TODO: Ensure all state machine parameters are in genesis (#1704) +func IrisValidateGenesisState(genesisState GenesisState) (err error) { + err = validateGenesisStateAccounts(genesisState.Accounts) + if err != nil { + return + } + // skip stakeData validation as genesis is created from txs + if len(genesisState.GenTxs) > 0 { + return nil + } + return stake.ValidateGenesis(genesisState.StakeData) +} + +// Ensures that there are no duplicate accounts in the genesis state, +func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { + addrMap := make(map[string]bool, len(accs)) + for i := 0; i < len(accs); i++ { + acc := accs[i] + strAddr := string(acc.Address) + if _, ok := addrMap[strAddr]; ok { + return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address) + } + addrMap[strAddr] = true + } + return +} + +// IrisAppGenState but with JSON +func IrisAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( + appState json.RawMessage, err error) { + + // create the final app state + genesisState, err := IrisAppGenState(cdc, genDoc, appGenTxs) + if err != nil { + return nil, err + } + appState, err = codec.MarshalJSONIndent(cdc, genesisState) + return +} + +// CollectStdTxs processes and validates application's genesis StdTxs and returns +// the list of appGenTxs, and persistent peers required to generate genesis.json. +func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tmtypes.GenesisDoc) ( + appGenTxs []auth.StdTx, persistentPeers string, err error) { + + var fos []os.FileInfo + fos, err = ioutil.ReadDir(genTxsDir) + if err != nil { + return appGenTxs, persistentPeers, err + } + + // prepare a map of all accounts in genesis state to then validate + // against the validators addresses + var appFileState GenesisFileState + if err := cdc.UnmarshalJSON(genDoc.AppState, &appFileState); err != nil { + return appGenTxs, persistentPeers, err + } + appState := convertToGenesisState(appFileState) + addrMap := make(map[string]GenesisAccount, len(appState.Accounts)) + for i := 0; i < len(appState.Accounts); i++ { + acc := appState.Accounts[i] + strAddr := acc.Address.String() + addrMap[strAddr] = acc + } + + // addresses and IPs (and port) validator server info + var addressesIPs []string + + for _, fo := range fos { + filename := filepath.Join(genTxsDir, fo.Name()) + if !fo.IsDir() && (filepath.Ext(filename) != ".json") { + continue + } + + // get the genStdTx + var jsonRawTx []byte + if jsonRawTx, err = ioutil.ReadFile(filename); err != nil { + return appGenTxs, persistentPeers, err + } + var genStdTx auth.StdTx + if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil { + return appGenTxs, persistentPeers, err + } + appGenTxs = append(appGenTxs, genStdTx) + + // the memo flag is used to store + // the ip and node-id, for example this may be: + // "528fd3df22b31f4969b05652bfe8f0fe921321d5@192.168.2.37:26656" + nodeAddrIP := genStdTx.GetMemo() + if len(nodeAddrIP) == 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "couldn't find node's address and IP in %s", fo.Name()) + } + + // genesis transactions must be single-message + msgs := genStdTx.GetMsgs() + if len(msgs) != 1 { + + return appGenTxs, persistentPeers, errors.New( + "each genesis transaction must provide a single genesis message") + } + + msg := msgs[0].(stake.MsgCreateValidator) + // validate delegator and validator addresses and funds against the accounts in the state + delAddr := msg.DelegatorAddr.String() + valAddr := sdk.AccAddress(msg.ValidatorAddr).String() + + delAcc, delOk := addrMap[delAddr] + _, valOk := addrMap[valAddr] + + accsNotInGenesis := []string{} + if !delOk { + accsNotInGenesis = append(accsNotInGenesis, delAddr) + } + if !valOk { + accsNotInGenesis = append(accsNotInGenesis, valAddr) + } + if len(accsNotInGenesis) != 0 { + return appGenTxs, persistentPeers, fmt.Errorf( + "account(s) %v not in genesis.json: %+v", strings.Join(accsNotInGenesis, " "), addrMap) + } + + if delAcc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) { + return appGenTxs, persistentPeers, fmt.Errorf( + "insufficient fund for delegation %v: %v < %v", + delAcc.Address, delAcc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount) + } + + // exclude itself from persistent peers + if msg.Description.Moniker != moniker { + addressesIPs = append(addressesIPs, nodeAddrIP) + } + } + + sort.Strings(addressesIPs) + persistentPeers = strings.Join(addressesIPs, ",") + + return appGenTxs, persistentPeers, nil +} + +// normalize stake token to mini-unit +func normalizeNativeToken(coins []string) sdk.Coins { + var accountCoins sdk.Coins + nativeCoin := sdk.NewInt64Coin(stakeTypes.StakeDenom, 0) + for _, coin := range coins { + coinName, err := types.GetCoinName(coin) + if err != nil { + panic(fmt.Sprintf("fatal error: failed pick out demon from coin: %s", coin)) + } + if coinName == sdk.NativeTokenName { + normalizeNativeToken, err := sdk.IRIS.ConvertToMinCoin(coin) + if err != nil { + panic(fmt.Sprintf("fatal error in converting %s to %s", coin, stakeTypes.StakeDenom)) + } + nativeCoin = nativeCoin.Plus(normalizeNativeToken) + } else { + // not native token + denom, amount, err := types.GetCoin(coin) + if err != nil { + panic(fmt.Sprintf("fatal error: genesis file contains invalid coin: %s", coin)) + } + + amt, ok := sdk.NewIntFromString(amount) + if !ok { + panic(fmt.Sprintf("non-native coin(%s) amount should be integer ", coin)) + } + denom = strings.ToLower(denom) + accountCoins = append(accountCoins, sdk.NewCoin(denom, amt)) + } + } + accountCoins = append(accountCoins, nativeCoin) + if accountCoins.IsZero() { + panic("invalid genesis file, found account without any token") + } + return accountCoins +} + +func convertToGenesisState(genesisFileState GenesisFileState) GenesisState { + var genesisAccounts []GenesisAccount + for _, gacc := range genesisFileState.Accounts { + acc := GenesisAccount{ + Address: gacc.Address, + Coins: normalizeNativeToken(gacc.Coins), + AccountNumber: gacc.AccountNumber, + Sequence: gacc.Sequence, + } + genesisAccounts = append(genesisAccounts, acc) + } + return GenesisState{ + Accounts: genesisAccounts, + AuthData: genesisFileState.AuthData, + StakeData: genesisFileState.StakeData, + MintData: genesisFileState.MintData, + DistrData: genesisFileState.DistrData, + GovData: genesisFileState.GovData, + UpgradeData: genesisFileState.UpgradeData, + SlashingData: genesisFileState.SlashingData, + ServiceData: genesisFileState.ServiceData, + GuardianData: genesisFileState.GuardianData, + GenTxs: genesisFileState.GenTxs, + } +} + +type GenesisFileState struct { + Accounts []GenesisFileAccount `json:"accounts"` + AuthData auth.GenesisState `json:"auth"` + StakeData stake.GenesisState `json:"stake"` + MintData mint.GenesisState `json:"mint"` + DistrData distr.GenesisState `json:"distr"` + GovData gov.GenesisState `json:"gov"` + UpgradeData upgrade.GenesisState `json:"upgrade"` + SlashingData slashing.GenesisState `json:"slashing"` + ServiceData service.GenesisState `json:"service"` + GuardianData guardian.GenesisState `json:"guardian"` + GenTxs []json.RawMessage `json:"gentxs"` +} + +type GenesisFileAccount struct { + Address sdk.AccAddress `json:"address"` + Coins []string `json:"coins"` + Sequence uint64 `json:"sequence_number"` + AccountNumber uint64 `json:"account_number"` +} + +func NewGenesisFileAccount(acc *auth.BaseAccount) GenesisFileAccount { + var coins []string + for _, coin := range acc.Coins { + coins = append(coins, coin.String()) + } + return GenesisFileAccount{ + Address: acc.Address, + Coins: coins, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, + } +} + +func NewGenesisFileState(accounts []GenesisFileAccount, authData auth.GenesisState, stakeData stake.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, upgradeData upgrade.GenesisState, serviceData service.GenesisState, + guardianData guardian.GenesisState, slashingData slashing.GenesisState) GenesisFileState { + + return GenesisFileState{ + Accounts: accounts, + AuthData: authData, + StakeData: stakeData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + UpgradeData: upgradeData, + ServiceData: serviceData, + GuardianData: guardianData, + SlashingData: slashingData, + } +} + +// NewDefaultGenesisState generates the default state for iris. +func NewDefaultGenesisFileState() GenesisFileState { + return GenesisFileState{ + Accounts: nil, + AuthData: auth.DefaultGenesisState(), + StakeData: stake.DefaultGenesisState(), + MintData: mint.DefaultGenesisState(), + DistrData: distr.DefaultGenesisState(), + GovData: gov.DefaultGenesisState(), + UpgradeData: upgrade.DefaultGenesisState(), + ServiceData: service.DefaultGenesisState(), + GuardianData: guardian.DefaultGenesisState(), + SlashingData: slashing.DefaultGenesisState(), + GenTxs: nil, + } +} + +func NewDefaultGenesisFileAccount(addr sdk.AccAddress) GenesisFileAccount { + accAuth := auth.NewBaseAccountWithAddress(addr) + accAuth.Coins = []sdk.Coin{ + sdk.FreeToken4Acc, + } + return NewGenesisFileAccount(&accAuth) +} diff --git a/app/v1/gov/codec.go b/app/v1/gov/codec.go new file mode 100644 index 000000000..290a46fcd --- /dev/null +++ b/app/v1/gov/codec.go @@ -0,0 +1,24 @@ +package gov + +import ( + "github.com/irisnet/irishub/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgSubmitProposal{}, "irishub/gov/MsgSubmitProposal", nil) + cdc.RegisterConcrete(MsgSubmitTxTaxUsageProposal{}, "irishub/gov/MsgSubmitTxTaxUsageProposal", nil) + cdc.RegisterConcrete(MsgSubmitSoftwareUpgradeProposal{}, "irishub/gov/MsgSubmitSoftwareUpgradeProposal", nil) + cdc.RegisterConcrete(MsgDeposit{}, "irishub/gov/MsgDeposit", nil) + cdc.RegisterConcrete(MsgVote{}, "irishub/gov/MsgVote", nil) + + cdc.RegisterInterface((*Proposal)(nil), nil) + cdc.RegisterConcrete(&TextProposal{}, "irishub/gov/TextProposal", nil) + cdc.RegisterConcrete(&ParameterProposal{}, "irishub/gov/ParameterProposal", nil) + cdc.RegisterConcrete(&SoftwareUpgradeProposal{}, "irishub/gov/SoftwareUpgradeProposal", nil) + cdc.RegisterConcrete(&SystemHaltProposal{}, "irishub/gov/SystemHaltProposal", nil) + cdc.RegisterConcrete(&TaxUsageProposal{}, "irishub/gov/TaxUsageProposal", nil) + cdc.RegisterConcrete(&Vote{}, "irishub/gov/Vote", nil) +} + +var msgCdc = codec.New() diff --git a/app/v1/gov/depositsvotes.go b/app/v1/gov/depositsvotes.go new file mode 100644 index 000000000..e0014a427 --- /dev/null +++ b/app/v1/gov/depositsvotes.go @@ -0,0 +1,143 @@ +package gov + +import ( + "encoding/json" + "fmt" + + sdk "github.com/irisnet/irishub/types" + "github.com/pkg/errors" +) + +// Vote +type Vote struct { + Voter sdk.AccAddress `json:"voter"` // address of the voter + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter +} + +// Returns whether 2 votes are equal +func (voteA Vote) Equals(voteB Vote) bool { + return voteA.Voter.Equals(voteB.Voter) && voteA.ProposalID == voteB.ProposalID && voteA.Option == voteB.Option +} + +// Returns whether a vote is empty +func (voteA Vote) Empty() bool { + voteB := Vote{} + return voteA.Equals(voteB) +} + +// Deposit +type Deposit struct { + Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor + ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal + Amount sdk.Coins `json:"amount"` // Deposit amount +} + +// Returns whether 2 deposits are equal +func (depositA Deposit) Equals(depositB Deposit) bool { + return depositA.Depositor.Equals(depositB.Depositor) && depositA.ProposalID == depositB.ProposalID && depositA.Amount.IsEqual(depositB.Amount) +} + +// Returns whether a deposit is empty +func (depositA Deposit) Empty() bool { + depositB := Deposit{} + return depositA.Equals(depositB) +} + +// Type that represents VoteOption as a byte +type VoteOption byte + +//nolint +const ( + OptionEmpty VoteOption = 0x00 + OptionYes VoteOption = 0x01 + OptionAbstain VoteOption = 0x02 + OptionNo VoteOption = 0x03 + OptionNoWithVeto VoteOption = 0x04 +) + +// String to proposalType byte. Returns ff if 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), errors.Errorf("'%s' is not a valid vote option", str) + } +} + +// Is defined VoteOption +func ValidVoteOption(option VoteOption) bool { + if option == OptionYes || + option == OptionAbstain || + option == OptionNo || + option == OptionNoWithVeto { + return true + } + return false +} + +// 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()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (vo *VoteOption) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + bz2, err := VoteOptionFromString(s) + if err != nil { + return err + } + *vo = bz2 + return nil +} + +// Turns VoteOption byte to String +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 "" + } +} + +// For Printf / Sprintf, returns bech32 when using %s +// nolint: errcheck +func (vo VoteOption) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(fmt.Sprintf("%s", vo.String()))) + default: + s.Write([]byte(fmt.Sprintf("%v", byte(vo)))) + } +} diff --git a/app/v1/gov/errors.go b/app/v1/gov/errors.go new file mode 100644 index 000000000..cf21f3d6b --- /dev/null +++ b/app/v1/gov/errors.go @@ -0,0 +1,155 @@ +//nolint +package gov + +import ( + "fmt" + + sdk "github.com/irisnet/irishub/types" +) + +const ( + DefaultCodespace sdk.CodespaceType = "gov" + + CodeUnknownProposal sdk.CodeType = 1 + CodeInactiveProposal sdk.CodeType = 2 + CodeAlreadyActiveProposal sdk.CodeType = 3 + CodeAlreadyFinishedProposal sdk.CodeType = 4 + CodeAddressNotStaked sdk.CodeType = 5 + CodeInvalidTitle sdk.CodeType = 6 + CodeInvalidDescription sdk.CodeType = 7 + CodeInvalidProposalType sdk.CodeType = 8 + CodeInvalidVote sdk.CodeType = 9 + CodeInvalidGenesis sdk.CodeType = 10 + CodeInvalidProposalStatus sdk.CodeType = 11 + CodeInvalidParam sdk.CodeType = 12 + CodeInvalidParamOp sdk.CodeType = 13 + CodeSwitchPeriodInProcess sdk.CodeType = 14 + CodeInvalidPercent sdk.CodeType = 15 + CodeInvalidUsageType sdk.CodeType = 16 + CodeInvalidInput sdk.CodeType = 17 + CodeInvalidVersion sdk.CodeType = 18 + CodeInvalidSwitchHeight sdk.CodeType = 19 + CodeNotEnoughInitialDeposit sdk.CodeType = 20 + CodeDepositDeleted sdk.CodeType = 21 + CodeVoteNotExisted sdk.CodeType = 22 + CodeDepositNotExisted sdk.CodeType = 23 + CodeNotInDepositPeriod sdk.CodeType = 24 + CodeAlreadyVote sdk.CodeType = 25 + CodeOnlyValidatorVote sdk.CodeType = 26 + CodeMoreThanMaxProposal sdk.CodeType = 27 + CodeEmptyParam sdk.CodeType = 29 +) + +//---------------------------------------- +// Error constructors + +func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal with id %d", proposalID)) +} + +func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal with id %d", proposalID)) +} + +func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeAlreadyActiveProposal, fmt.Sprintf("Proposal %d has been already active", proposalID)) +} + +func ErrAlreadyFinishedProposal(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeAlreadyFinishedProposal, fmt.Sprintf("Proposal %d has already passed its voting period", proposalID)) +} + +func ErrAddressNotStaked(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeAddressNotStaked, fmt.Sprintf("Address %s is not staked and is thus ineligible to vote", address)) +} + +func ErrInvalidTitle(codespace sdk.CodespaceType, title string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidTitle, fmt.Sprintf("Proposal Title '%s' is not valid", title)) +} + +func ErrInvalidDescription(codespace sdk.CodespaceType, description string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDescription, fmt.Sprintf("Proposal Desciption '%s' is not valid", description)) +} + +func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType ProposalKind) sdk.Error { + return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("Proposal Type '%s' is not valid", proposalType)) +} + +func ErrInvalidVote(codespace sdk.CodespaceType, voteOption VoteOption) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVote, fmt.Sprintf("'%v' is not a valid voting option", voteOption)) +} + +func ErrInvalidGenesis(codespace sdk.CodespaceType, msg string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVote, msg) +} + +//////////////////// iris begin /////////////////////////// +func ErrInvalidParam(codespace sdk.CodespaceType, str string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidParam, fmt.Sprintf("%s Params don't support the ParameterChange.", str)) +} + +func ErrEmptyParam(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeEmptyParam, fmt.Sprintf("Params can't be empty")) +} + +func ErrInvalidParamOp(codespace sdk.CodespaceType, opStr string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidParamOp, fmt.Sprintf("Op '%s' is not valid", opStr)) +} +func ErrSwitchPeriodInProcess(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeSwitchPeriodInProcess, fmt.Sprintf("Software Upgrade Switch Period is in process.")) +} + +func ErrInvalidPercent(codespace sdk.CodespaceType, percent sdk.Dec) sdk.Error { + return sdk.NewError(codespace, CodeInvalidPercent, fmt.Sprintf("invalid percent [%s], must be greater than 0 and less than or equal to 1", percent.String())) +} + +func ErrInvalidUsageType(codespace sdk.CodespaceType, usageType UsageType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidUsageType, fmt.Sprintf("Usage Type '%s' is not valid", usageType)) +} + +func ErrNotTrustee(codespace sdk.CodespaceType, trustee sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, fmt.Sprintf("[%s] is not a trustee address", trustee)) +} + +func ErrNotProfiler(codespace sdk.CodespaceType, profiler sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, fmt.Sprintf("[%s] is not a profiler address", profiler)) +} + +func ErrCodeInvalidVersion(codespace sdk.CodespaceType, version uint64) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVersion, fmt.Sprintf("Version [%v] in SoftwareUpgradeProposal isn't valid", version)) +} +func ErrCodeInvalidSwitchHeight(codespace sdk.CodespaceType, blockHeight uint64, switchHeight uint64) sdk.Error { + return sdk.NewError(codespace, CodeInvalidVersion, fmt.Sprintf("Protocol switchHeight [%v] in SoftwareUpgradeProposal isn't large than current block height [%v]", switchHeight, blockHeight)) +} + +func ErrCodeDepositDeleted(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeDepositDeleted, fmt.Sprintf("The deposit records of proposal [%d] have been deleted.", proposalID)) +} + +func ErrCodeVoteNotExisted(codespace sdk.CodespaceType, address sdk.AccAddress, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeVoteNotExisted, fmt.Sprintf("Address %s hasn't voted for the proposal [%d]", address, proposalID)) +} + +func ErrCodeDepositNotExisted(codespace sdk.CodespaceType, address sdk.AccAddress, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeDepositNotExisted, fmt.Sprintf("Address %s hasn't deposited on the proposal [%d]", address, proposalID)) +} + +func ErrNotInDepositPeriod(codespace sdk.CodespaceType, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeNotInDepositPeriod, fmt.Sprintf("Proposal %d isn't in deposit period", proposalID)) +} + +func ErrAlreadyVote(codespace sdk.CodespaceType, address sdk.AccAddress, proposalID uint64) sdk.Error { + return sdk.NewError(codespace, CodeAlreadyVote, fmt.Sprintf("Address %s has voted for the proposal [%d]", address, proposalID)) +} + +func ErrOnlyValidatorVote(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error { + return sdk.NewError(codespace, CodeOnlyValidatorVote, fmt.Sprintf("Address %s isn't a validator, so can't vote.", address)) +} + +func ErrMoreThanMaxProposal(codespace sdk.CodespaceType, num uint64, proposalLevel string) sdk.Error { + return sdk.NewError(codespace, CodeMoreThanMaxProposal, fmt.Sprintf("The num of %s proposal can't be more than the maximum %v.", proposalLevel, num)) +} + +func ErrNotEnoughInitialDeposit(codespace sdk.CodespaceType, initialDeposit sdk.Coins, minDeposit sdk.Coins) sdk.Error { + return sdk.NewError(codespace, CodeNotEnoughInitialDeposit, fmt.Sprintf("Initial Deposit [%s] is less than minInitialDeposit [%s]", initialDeposit.String(), minDeposit.String())) +} diff --git a/app/v1/gov/execute.go b/app/v1/gov/execute.go new file mode 100644 index 000000000..5c80cfeac --- /dev/null +++ b/app/v1/gov/execute.go @@ -0,0 +1,95 @@ +package gov + +import ( + "fmt" + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/params" +) + +func Execute(ctx sdk.Context, gk Keeper, p Proposal) (err error) { + switch p.GetProposalType() { + case ProposalTypeParameterChange: + return ParameterProposalExecute(ctx, gk, p.(*ParameterProposal)) + case ProposalTypeSystemHalt: + return SystemHaltProposalExecute(ctx, gk) + case ProposalTypeTxTaxUsage: + return TaxUsageProposalExecute(ctx, gk, p.(*TaxUsageProposal)) + case ProposalTypeSoftwareUpgrade: + return SoftwareUpgradeProposalExecute(ctx, gk, p.(*SoftwareUpgradeProposal)) + } + return nil +} + +func TaxUsageProposalExecute(ctx sdk.Context, gk Keeper, p *TaxUsageProposal) (err error) { + logger := ctx.Logger().With("module", "gov") + burn := false + if p.TaxUsage.Usage == UsageTypeBurn { + burn = true + } else { + _, found := gk.guardianKeeper.GetTrustee(ctx, p.TaxUsage.DestAddress) + if !found { + logger.Error("Execute TaxUsageProposal Failure", "info", + fmt.Sprintf("the destination address [%s] is not a trustee now", p.TaxUsage.DestAddress)) + return + } + } + gk.dk.AllocateFeeTax(ctx, p.TaxUsage.DestAddress, p.TaxUsage.Percent, burn) + return +} + +func ParameterProposalExecute(ctx sdk.Context, gk Keeper, pp *ParameterProposal) (err error) { + logger := ctx.Logger().With("module", "gov") + logger.Info("Execute ParameterProposal begin", "info", fmt.Sprintf("current height:%d", ctx.BlockHeight())) + for _, param := range pp.Params { + paramSet := params.ParamSetMapping[param.Subspace] + value, _ := paramSet.Validate(param.Key, param.Value) + subspace, bool := gk.paramsKeeper.GetSubspace(param.Subspace) + if bool { + subspace.Set(ctx, []byte(param.Key), value) + } + + logger.Info("Execute ParameterProposal begin", "info", fmt.Sprintf("%s = %s", param.Key, param.Value)) + } + + return +} + +func SoftwareUpgradeProposalExecute(ctx sdk.Context, gk Keeper, sp *SoftwareUpgradeProposal) error { + logger := ctx.Logger().With("module", "x/gov") + + if _, ok := gk.protocolKeeper.GetUpgradeConfig(ctx); ok { + logger.Info("Execute SoftwareProposal Failure", "info", + fmt.Sprintf("Software Upgrade Switch Period is in process. current height:%d", ctx.BlockHeight())) + return nil + } + if !gk.protocolKeeper.IsValidVersion(ctx, sp.ProtocolDefinition.Version) { + logger.Info("Execute SoftwareProposal Failure", "info", + fmt.Sprintf("version [%v] in SoftwareUpgradeProposal isn't valid ", sp.ProposalID)) + return nil + } + if uint64(ctx.BlockHeight())+1 >= sp.ProtocolDefinition.Height { + logger.Info("Execute SoftwareProposal Failure", "info", + fmt.Sprintf("switch height must be more than blockHeight + 1")) + return nil + } + + gk.protocolKeeper.SetUpgradeConfig(ctx, sdk.NewUpgradeConfig(sp.ProposalID, sp.ProtocolDefinition)) + + logger.Info("Execute SoftwareProposal Success", "info", + fmt.Sprintf("current height:%d", ctx.BlockHeight())) + + return nil +} + +func SystemHaltProposalExecute(ctx sdk.Context, gk Keeper) error { + logger := ctx.Logger().With("module", "x/gov") + + if gk.GetSystemHaltHeight(ctx) == -1 { + gk.SetSystemHaltHeight(ctx, ctx.BlockHeight()+gk.GetSystemHaltPeriod(ctx)) + logger.Info("Execute SystemHaltProposal begin", "info", fmt.Sprintf("SystemHalt height:%d", gk.GetSystemHaltHeight(ctx))) + } else { + logger.Info("SystemHalt Period is in process.", "info", fmt.Sprintf("SystemHalt height:%d", gk.GetSystemHaltHeight(ctx))) + + } + return nil +} diff --git a/app/v1/gov/genesis.go b/app/v1/gov/genesis.go new file mode 100644 index 000000000..080bb70a4 --- /dev/null +++ b/app/v1/gov/genesis.go @@ -0,0 +1,82 @@ +package gov + +import ( + sdk "github.com/irisnet/irishub/types" + +) + +const StartingProposalID = 1 + +// GenesisState - all gov state that must be provided at genesis +type GenesisState struct { + Params GovParams `json:"params"` // inflation params +} + +func NewGenesisState(systemHaltPeriod int64, params GovParams) GenesisState { + return GenesisState{ + Params:params, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + } +} + +// InitGenesis - store genesis parameters +func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + err := ValidateGenesis(data) + if err != nil { + // TODO: Handle this with #870 + panic(err) + } + + err = k.setInitialProposalID(ctx, StartingProposalID) + if err != nil { + // TODO: Handle this with #870 + panic(err) + } + + k.SetSystemHaltHeight(ctx, -1) + k.SetParamSet(ctx,data.Params) +} + +// ExportGenesis - output genesis parameters +func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { + + return GenesisState{ + Params:k.GetParamSet(ctx), + } +} + +func ValidateGenesis(data GenesisState) error { + err := validateParams(data.Params) + if err != nil { + return err + } + return nil +} + +// get raw genesis raw message for testing +func DefaultGenesisStateForCliTest() GenesisState { + + return GenesisState{ + Params:DefaultParams(), + } +} + +func PrepForZeroHeightGenesis(ctx sdk.Context, k Keeper) { + proposals := k.GetProposalsFiltered(ctx, nil, nil, StatusDepositPeriod, 0) + for _, proposal := range proposals { + proposalID := proposal.GetProposalID() + k.RefundDeposits(ctx, proposalID) + } + + proposals = k.GetProposalsFiltered(ctx, nil, nil, StatusVotingPeriod, 0) + for _, proposal := range proposals { + proposalID := proposal.GetProposalID() + k.RefundDeposits(ctx, proposalID) + } +} diff --git a/app/v1/gov/govparams_util.go b/app/v1/gov/govparams_util.go new file mode 100644 index 000000000..29f095121 --- /dev/null +++ b/app/v1/gov/govparams_util.go @@ -0,0 +1,149 @@ +package gov + +import ( + sdk "github.com/irisnet/irishub/types" +) + +//----------------------------------------------------------- +// ProposalLevel + +// Type that represents Proposal Level as a byte +type ProposalLevel byte + +//nolint +const ( + ProposalLevelNil ProposalLevel = 0x00 + ProposalLevelCritical ProposalLevel = 0x01 + ProposalLevelImportant ProposalLevel = 0x02 + //////////////////// iris begin ///////////////////////////// + ProposalLevelNormal ProposalLevel = 0x03 + //////////////////// iris end ///////////////////////////// +) + +func (p ProposalLevel) string() string { + switch p { + case ProposalLevelCritical: + return "critical" + case ProposalLevelImportant: + return "important" + case ProposalLevelNormal: + return "normal" + default: + return " " + } +} + +func GetProposalLevel(p Proposal) ProposalLevel { + return GetProposalLevelByProposalKind(p.GetProposalType()) +} + +func GetProposalLevelByProposalKind(p ProposalKind) ProposalLevel { + switch p { + case ProposalTypeTxTaxUsage: + return ProposalLevelNormal + case ProposalTypeParameterChange: + return ProposalLevelImportant + case ProposalTypeSystemHalt: + return ProposalLevelCritical + case ProposalTypeSoftwareUpgrade: + return ProposalLevelCritical + default: + return ProposalLevelNil + } +} + +// Returns the current Deposit Procedure from the global param store +func (Keeper Keeper) GetDepositProcedure(ctx sdk.Context, p Proposal) DepositProcedure { + params := Keeper.GetParamSet(ctx) + switch GetProposalLevel(p) { + case ProposalLevelCritical: + return DepositProcedure{ + MinDeposit: params.CriticalMinDeposit, + MaxDepositPeriod: params.CriticalDepositPeriod, + } + case ProposalLevelImportant: + return DepositProcedure{ + MinDeposit: params.ImportantMinDeposit, + MaxDepositPeriod: params.ImportantDepositPeriod, + } + case ProposalLevelNormal: + return DepositProcedure{ + MinDeposit: params.NormalMinDeposit, + MaxDepositPeriod: params.NormalDepositPeriod, + } + default: + panic("There is no level for this proposal which type is " + p.GetProposalType().String()) + } +} + +// Returns the current Voting Procedure from the global param store +func (Keeper Keeper) GetVotingProcedure(ctx sdk.Context, p Proposal) VotingProcedure { + params := Keeper.GetParamSet(ctx) + switch GetProposalLevel(p) { + case ProposalLevelCritical: + return VotingProcedure{ + VotingPeriod: params.CriticalVotingPeriod, + } + case ProposalLevelImportant: + return VotingProcedure{ + VotingPeriod: params.ImportantVotingPeriod, + } + case ProposalLevelNormal: + return VotingProcedure{ + VotingPeriod: params.NormalVotingPeriod, + } + default: + panic("There is no level for this proposal which type is " + p.GetProposalType().String()) + } +} + +func (Keeper Keeper) GetMaxNumByProposalLevel(ctx sdk.Context, pl ProposalLevel) uint64 { + params := Keeper.GetParamSet(ctx) + switch pl { + case ProposalLevelCritical: + return params.CriticalMaxNum + + case ProposalLevelImportant: + return params.ImportantMaxNum + + case ProposalLevelNormal: + return params.NormalMaxNum + default: + panic("There is no level for this proposal which type is " + pl.string()) + } +} + +// Returns the current Tallying Procedure from the global param store +func (Keeper Keeper) GetTallyingProcedure(ctx sdk.Context, p Proposal) TallyingProcedure { + params := Keeper.GetParamSet(ctx) + switch GetProposalLevel(p) { + case ProposalLevelCritical: + return TallyingProcedure{ + Threshold: params.CriticalThreshold, + Veto: params.CriticalVeto, + Participation: params.CriticalParticipation, + Penalty: params.CriticalPenalty, + } + case ProposalLevelImportant: + return TallyingProcedure{ + Threshold: params.ImportantThreshold, + Veto: params.ImportantVeto, + Participation: params.ImportantParticipation, + Penalty: params.ImportantPenalty, + } + case ProposalLevelNormal: + return TallyingProcedure{ + Threshold: params.NormalThreshold, + Veto: params.NormalVeto, + Participation: params.NormalParticipation, + Penalty: params.NormalPenalty, + } + default: + panic("There is no level for this proposal which type is " + p.GetProposalType().String()) + } +} + +func (keeper Keeper) GetSystemHaltPeriod(ctx sdk.Context) (SystemHaltPeriod int64) { + keeper.paramSpace.Get(ctx, KeySystemHaltPeriod, &SystemHaltPeriod) + return +} diff --git a/app/v1/gov/handler.go b/app/v1/gov/handler.go new file mode 100644 index 000000000..e9a4985dc --- /dev/null +++ b/app/v1/gov/handler.go @@ -0,0 +1,304 @@ +package gov + +import ( + "fmt" + + "encoding/json" + "strconv" + + "github.com/irisnet/irishub/modules/gov/tags" + sdk "github.com/irisnet/irishub/types" + + tmstate "github.com/tendermint/tendermint/state" +) + +// Handle all "gov" type messages. +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgDeposit: + return handleMsgDeposit(ctx, keeper, msg) + case MsgSubmitProposal: + return handleMsgSubmitProposal(ctx, keeper, msg) + case MsgSubmitTxTaxUsageProposal: + return handleMsgSubmitTxTaxUsageProposal(ctx, keeper, msg) + case MsgSubmitSoftwareUpgradeProposal: + return handleMsgSubmitSoftwareUpgradeProposal(ctx, keeper, msg) + case MsgVote: + return handleMsgVote(ctx, keeper, msg) + default: + errMsg := "Unrecognized gov msg type" + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitProposal) sdk.Result { + + proposalLevel := GetProposalLevelByProposalKind(msg.ProposalType) + if num, ok := keeper.HasReachedTheMaxProposalNum(ctx, proposalLevel); ok { + return ErrMoreThanMaxProposal(keeper.codespace, num, proposalLevel.string()).Result() + } + //////////////////// iris begin /////////////////////////// + if msg.ProposalType == ProposalTypeSystemHalt { + _, found := keeper.guardianKeeper.GetProfiler(ctx, msg.Proposer) + if !found { + return ErrNotProfiler(keeper.codespace, msg.Proposer).Result() + } + } + proposal := keeper.NewProposal(ctx, msg.Title, msg.Description, msg.ProposalType, msg.Params) + + //////////////////// iris end ///////////////////////////// + + err, votingStarted := keeper.AddInitialDeposit(ctx, proposal, msg.Proposer, msg.InitialDeposit) + if err != nil { + return err.Result() + } + //////////////////// iris begin /////////////////////////// + proposalIDBytes := []byte(strconv.FormatUint(proposal.GetProposalID(), 10)) + + var paramBytes []byte + if msg.ProposalType == ProposalTypeParameterChange { + paramBytes, _ = json.Marshal(proposal.(*ParameterProposal).Params) + } + //////////////////// iris end ///////////////////////////// + resTags := sdk.NewTags( + tags.Proposer, []byte(msg.Proposer.String()), + tags.ProposalID, proposalIDBytes, + //////////////////// iris begin /////////////////////////// + tags.Param, paramBytes, + //////////////////// iris end ///////////////////////////// + ) + + if votingStarted { + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + } + + keeper.AddProposalNum(ctx, proposal) + return sdk.Result{ + Data: proposalIDBytes, + Tags: resTags, + } +} + +func handleMsgSubmitTxTaxUsageProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitTxTaxUsageProposal) sdk.Result { + proposalLevel := GetProposalLevelByProposalKind(msg.ProposalType) + if num, ok := keeper.HasReachedTheMaxProposalNum(ctx, proposalLevel); ok { + return ErrMoreThanMaxProposal(keeper.codespace, num, proposalLevel.string()).Result() + } + + if msg.Usage != UsageTypeBurn { + _, found := keeper.guardianKeeper.GetTrustee(ctx, msg.DestAddress) + if !found { + return ErrNotTrustee(keeper.codespace, msg.DestAddress).Result() + } + } + + proposal := keeper.NewUsageProposal(ctx, msg) + + err, votingStarted := keeper.AddInitialDeposit(ctx, proposal, msg.Proposer, msg.InitialDeposit) + if err != nil { + return err.Result() + } + proposalIDBytes := []byte(strconv.FormatUint(proposal.GetProposalID(), 10)) + + resTags := sdk.NewTags( + tags.Proposer, []byte(msg.Proposer.String()), + tags.ProposalID, proposalIDBytes, + tags.Usage, []byte(msg.Usage.String()), + tags.Percent, []byte(msg.Percent.String()), + ) + + if msg.Usage != UsageTypeBurn { + resTags = resTags.AppendTag(tags.DestAddress, []byte(msg.DestAddress.String())) + } + + if votingStarted { + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + } + + keeper.AddProposalNum(ctx, proposal) + return sdk.Result{ + Data: proposalIDBytes, + Tags: resTags, + } +} + +func handleMsgSubmitSoftwareUpgradeProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitSoftwareUpgradeProposal) sdk.Result { + proposalLevel := GetProposalLevelByProposalKind(msg.ProposalType) + if num, ok := keeper.HasReachedTheMaxProposalNum(ctx, proposalLevel); ok { + return ErrMoreThanMaxProposal(keeper.codespace, num, proposalLevel.string()).Result() + } + + if !keeper.protocolKeeper.IsValidVersion(ctx, msg.Version) { + return ErrCodeInvalidVersion(keeper.codespace, msg.Version).Result() + } + + if uint64(ctx.BlockHeight()) > msg.SwitchHeight { + return ErrCodeInvalidSwitchHeight(keeper.codespace, uint64(ctx.BlockHeight()), msg.SwitchHeight).Result() + } + _, found := keeper.guardianKeeper.GetProfiler(ctx, msg.Proposer) + if !found { + return ErrNotProfiler(keeper.codespace, msg.Proposer).Result() + } + + if _, ok := keeper.protocolKeeper.GetUpgradeConfig(ctx); ok { + return ErrSwitchPeriodInProcess(keeper.codespace).Result() + } + + proposal := keeper.NewSoftwareUpgradeProposal(ctx, msg) + + err, votingStarted := keeper.AddInitialDeposit(ctx, proposal, msg.Proposer, msg.InitialDeposit) + if err != nil { + return err.Result() + } + proposalIDBytes := []byte(strconv.FormatUint(proposal.GetProposalID(), 10)) + + resTags := sdk.NewTags( + tags.Proposer, []byte(msg.Proposer.String()), + tags.ProposalID, proposalIDBytes, + ) + + if votingStarted { + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + } + + keeper.AddProposalNum(ctx, proposal) + return sdk.Result{ + Data: proposalIDBytes, + Tags: resTags, + } +} + +func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) sdk.Result { + + err, votingStarted := keeper.AddDeposit(ctx, msg.ProposalID, msg.Depositor, msg.Amount) + if err != nil { + return err.Result() + } + + //////////////////// iris begin /////////////////////////// + proposalIDBytes := []byte(strconv.FormatUint(msg.ProposalID, 10)) + //////////////////// iris end ///////////////////////////// + + // TODO: Add tag for if voting period started + resTags := sdk.NewTags( + tags.Depositor, []byte(msg.Depositor.String()), + tags.ProposalID, proposalIDBytes, + ) + + if votingStarted { + resTags = resTags.AppendTag(tags.VotingPeriodStart, proposalIDBytes) + } + + return sdk.Result{ + Tags: resTags, + } +} + +func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) sdk.Result { + + err := keeper.AddVote(ctx, msg.ProposalID, msg.Voter, msg.Option) + if err != nil { + return err.Result() + } + + //////////////////// iris begin /////////////////////////// + proposalIDBytes := []byte(strconv.FormatUint(msg.ProposalID, 10)) + //////////////////// iris end ///////////////////////////// + + resTags := sdk.NewTags( + tags.Voter, []byte(msg.Voter.String()), + tags.ProposalID, proposalIDBytes, + ) + return sdk.Result{ + Tags: resTags, + } +} + +// Called every block, process inflation, update validator set +func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { + + logger := ctx.Logger().With("module", "gov") + + resTags = sdk.NewTags() + + if ctx.BlockHeight() == keeper.GetSystemHaltHeight(ctx) { + resTags = resTags.AppendTag(tmstate.HaltTagKey, []byte(tmstate.HaltTagValue)) + logger.Info(fmt.Sprintf("SystemHalt Start!!!")) + } + + inactiveIterator := keeper.InactiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + defer inactiveIterator.Close() + for ; inactiveIterator.Valid(); inactiveIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(inactiveIterator.Value(), &proposalID) + inactiveProposal := keeper.GetProposal(ctx, proposalID) + keeper.SubProposalNum(ctx, inactiveProposal) + keeper.DeleteDeposits(ctx, proposalID) + keeper.DeleteProposal(ctx, proposalID) + + resTags = resTags.AppendTag(tags.Action, tags.ActionProposalDropped) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) + + keeper.RemoveFromInactiveProposalQueue(ctx, inactiveProposal.GetDepositEndTime(), inactiveProposal.GetProposalID()) + logger.Info( + fmt.Sprintf("proposal %d (%s) didn't meet minimum deposit of %s (had only %s); deleted", + inactiveProposal.GetProposalID(), + inactiveProposal.GetTitle(), + keeper.GetDepositProcedure(ctx, inactiveProposal).MinDeposit, + inactiveProposal.GetTotalDeposit(), + ), + ) + } + + activeIterator := keeper.ActiveProposalQueueIterator(ctx, ctx.BlockHeader().Time) + defer activeIterator.Close() + for ; activeIterator.Valid(); activeIterator.Next() { + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(activeIterator.Value(), &proposalID) + activeProposal := keeper.GetProposal(ctx, proposalID) + result, tallyResults, votingVals := tally(ctx, keeper, activeProposal) + + var action []byte + if result == PASS { + keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) + activeProposal.SetStatus(StatusPassed) + action = tags.ActionProposalPassed + Execute(ctx, keeper, activeProposal) + } else if result == REJECT { + keeper.RefundDeposits(ctx, activeProposal.GetProposalID()) + activeProposal.SetStatus(StatusRejected) + action = tags.ActionProposalRejected + } else if result == REJECTVETO { + keeper.DeleteDeposits(ctx, activeProposal.GetProposalID()) + activeProposal.SetStatus(StatusRejected) + action = tags.ActionProposalRejected + } + keeper.RemoveFromActiveProposalQueue(ctx, activeProposal.GetVotingEndTime(), activeProposal.GetProposalID()) + activeProposal.SetTallyResult(tallyResults) + keeper.SetProposal(ctx, activeProposal) + logger.Info(fmt.Sprintf("proposal %d (%s) tallied; result: %v", + activeProposal.GetProposalID(), activeProposal.GetTitle(), result)) + + resTags = resTags.AppendTag(tags.Action, action) + resTags = resTags.AppendTag(tags.ProposalID, []byte(string(proposalID))) + + for _, valAddr := range keeper.GetValidatorSet(ctx, proposalID) { + if _, ok := votingVals[valAddr.String()]; !ok { + val := keeper.ds.GetValidatorSet().Validator(ctx, valAddr) + if val != nil && val.GetStatus() == sdk.Bonded { + keeper.ds.GetValidatorSet().Slash(ctx, + val.GetConsAddr(), + ctx.BlockHeight(), + val.GetPower().RoundInt64(), + keeper.GetTallyingProcedure(ctx, activeProposal).Penalty) + } + } + } + + keeper.SubProposalNum(ctx, activeProposal) + keeper.DeleteValidatorSet(ctx, activeProposal.GetProposalID()) + } + return resTags +} diff --git a/app/v1/gov/keeper.go b/app/v1/gov/keeper.go new file mode 100644 index 000000000..33c44fe69 --- /dev/null +++ b/app/v1/gov/keeper.go @@ -0,0 +1,778 @@ +package gov + +import ( + "time" + + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/bank" + "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/modules/guardian" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" + sdk "github.com/irisnet/irishub/types" + + "github.com/irisnet/irishub/modules/params" + "github.com/tendermint/tendermint/crypto" +) + +// nolint +var ( + DepositedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("govDepositedCoins"))) + BurnRate = sdk.NewDecWithPrec(2, 1) + MinDepositRate = sdk.NewDecWithPrec(3,1) +) + +// Governance ProtocolKeeper +type Keeper struct { + // The (unexposed) keys used to access the stores from the Context. + storeKey sdk.StoreKey + + // The codec codec for binary encoding/decoding. + cdc *codec.Codec + + // The reference to the Param ProtocolKeeper to get and set Global Params + paramSpace params.Subspace + paramsKeeper params.Keeper + + protocolKeeper sdk.ProtocolKeeper + + // The reference to the CoinKeeper to modify balances + ck bank.Keeper + + dk distribution.Keeper + + guardianKeeper guardian.Keeper + + // The ValidatorSet to get information about validators + vs sdk.ValidatorSet + + // The reference to the DelegationSet to get information about delegators + ds sdk.DelegationSet + + // Reserved codespace + codespace sdk.CodespaceType +} + +// NewProtocolKeeper returns a governance keeper. It handles: +// - submitting governance proposals +// - depositing funds into proposals, and activating upon sufficient funds being deposited +// - users voting on proposals, with weight proportional to stake in the system +// - and tallying the result of the vote. +func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, paramSpace params.Subspace, paramsKeeper params.Keeper, protocolKeeper sdk.ProtocolKeeper, ck bank.Keeper, dk distribution.Keeper, guardianKeeper guardian.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { + return Keeper{ + key, + cdc, + paramSpace.WithTypeTable(ParamTypeTable()), + paramsKeeper, + protocolKeeper, + ck, + dk, + guardianKeeper, + ds.GetValidatorSet(), + ds, + codespace, + } +} + +// ===================================================== +// Proposals + +//////////////////// iris begin /////////////////////////// +func (keeper Keeper) NewProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind, param Params) Proposal { + switch proposalType { + case ProposalTypeParameterChange: + return keeper.NewParametersProposal(ctx, title, description, proposalType, param) + case ProposalTypeSystemHalt: + return keeper.NewSystemHaltProposal(ctx, title, description, proposalType) + } + return nil +} + +//////////////////// iris end ///////////////////////////// + +// ===================================================== +// Proposals + +// Creates a NewProposal +//////////////////// iris begin /////////////////////////// +func (keeper Keeper) NewParametersProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind, params Params) Proposal { + proposalID, err := keeper.getNewProposalID(ctx) + if err != nil { + return nil + } + var textProposal = TextProposal{ + ProposalID: proposalID, + Title: title, + Description: description, + ProposalType: proposalType, + Status: StatusDepositPeriod, + TallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: ctx.BlockHeader().Time, + } + + var proposal Proposal = &ParameterProposal{ + textProposal, + params, + } + + depositPeriod := keeper.GetDepositProcedure(ctx, proposal).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + return proposal +} + +func (keeper Keeper) NewSystemHaltProposal(ctx sdk.Context, title string, description string, proposalType ProposalKind) Proposal { + proposalID, err := keeper.getNewProposalID(ctx) + if err != nil { + return nil + } + var textProposal = TextProposal{ + ProposalID: proposalID, + Title: title, + Description: description, + ProposalType: proposalType, + Status: StatusDepositPeriod, + TallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: time.Now(), + } + var proposal Proposal = &SystemHaltProposal{ + textProposal, + } + + depositPeriod := keeper.GetDepositProcedure(ctx, proposal).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + return proposal +} + +func (keeper Keeper) NewUsageProposal(ctx sdk.Context, msg MsgSubmitTxTaxUsageProposal) Proposal { + proposalID, err := keeper.getNewProposalID(ctx) + if err != nil { + return nil + } + var textProposal = TextProposal{ + ProposalID: proposalID, + Title: msg.Title, + Description: msg.Description, + ProposalType: msg.ProposalType, + Status: StatusDepositPeriod, + TallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: ctx.BlockHeader().Time, + } + var proposal Proposal = &TaxUsageProposal{ + textProposal, + TaxUsage{ + msg.Usage, + msg.DestAddress, + msg.Percent}, + } + keeper.saveProposal(ctx, proposal) + return proposal +} + +func (keeper Keeper) NewSoftwareUpgradeProposal(ctx sdk.Context, msg MsgSubmitSoftwareUpgradeProposal) Proposal { + proposalID, err := keeper.getNewProposalID(ctx) + if err != nil { + return nil + } + var textProposal = TextProposal{ + ProposalID: proposalID, + Title: msg.Title, + Description: msg.Description, + ProposalType: msg.ProposalType, + Status: StatusDepositPeriod, + TallyResult: EmptyTallyResult(), + TotalDeposit: sdk.Coins{}, + SubmitTime: ctx.BlockHeader().Time, + } + var proposal Proposal = &SoftwareUpgradeProposal{ + textProposal, + sdk.ProtocolDefinition{ + msg.Version, + msg.Software, + msg.SwitchHeight,}, + } + keeper.saveProposal(ctx, proposal) + return proposal +} + +func (keeper Keeper) saveProposal(ctx sdk.Context, proposal Proposal) { + depositPeriod := keeper.GetDepositProcedure(ctx, proposal).MaxDepositPeriod + proposal.SetDepositEndTime(proposal.GetSubmitTime().Add(depositPeriod)) + keeper.SetProposal(ctx, proposal) + keeper.InsertInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) +} + +// Get Proposal from store by ProposalID +func (keeper Keeper) GetProposal(ctx sdk.Context, proposalID uint64) Proposal { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyProposal(proposalID)) + if bz == nil { + return nil + } + + var proposal Proposal + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposal) + + return proposal +} + +// Implements sdk.AccountKeeper. +func (keeper Keeper) SetProposal(ctx sdk.Context, proposal Proposal) { + + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposal) + store.Set(KeyProposal(proposal.GetProposalID()), bz) +} + +// Implements sdk.AccountKeeper. +func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + proposal := keeper.GetProposal(ctx, proposalID) + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposalID) + keeper.RemoveFromActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposalID) + store.Delete(KeyProposal(proposalID)) +} + +// Get Proposal from store by ProposalID +func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositorAddr sdk.AccAddress, status ProposalStatus, numLatest uint64) []Proposal { + + maxProposalID, err := keeper.peekCurrentProposalID(ctx) + if err != nil { + return nil + } + + matchingProposals := []Proposal{} + + if numLatest == 0 || maxProposalID < numLatest { + numLatest = maxProposalID + } + + for proposalID := maxProposalID - numLatest; proposalID < maxProposalID; proposalID++ { + if voterAddr != nil && len(voterAddr) != 0 { + _, found := keeper.GetVote(ctx, proposalID, voterAddr) + if !found { + continue + } + } + + if depositorAddr != nil && len(depositorAddr) != 0 { + _, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) + if !found { + continue + } + } + + proposal := keeper.GetProposal(ctx, proposalID) + if proposal == nil { + continue + } + + if ValidProposalStatus(status) { + if proposal.GetStatus() != status { + continue + } + } + + matchingProposals = append(matchingProposals, proposal) + } + return matchingProposals +} + +func (keeper Keeper) setInitialProposalID(ctx sdk.Context, proposalID uint64) sdk.Error { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyNextProposalID) + if bz != nil { + return ErrInvalidGenesis(keeper.codespace, "Initial ProposalID already set") + } + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyNextProposalID, bz) + return nil +} + +// Get the last used proposal ID +func (keeper Keeper) GetLastProposalID(ctx sdk.Context) (proposalID uint64) { + proposalID, err := keeper.peekCurrentProposalID(ctx) + if err != nil { + return 0 + } + proposalID-- + return +} + +// Gets the next available ProposalID and increments it +func (keeper Keeper) getNewProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyNextProposalID) + if bz == nil { + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + } + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + bz = keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID + 1) + store.Set(KeyNextProposalID, bz) + return proposalID, nil +} + +// Peeks the next available ProposalID without incrementing it +func (keeper Keeper) peekCurrentProposalID(ctx sdk.Context) (proposalID uint64, err sdk.Error) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyNextProposalID) + if bz == nil { + return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") + } + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + return proposalID, nil +} + +func (keeper Keeper) activateVotingPeriod(ctx sdk.Context, proposal Proposal) { + proposal.SetVotingStartTime(ctx.BlockHeader().Time) + votingPeriod := keeper.GetVotingProcedure(ctx, proposal).VotingPeriod + proposal.SetVotingEndTime(proposal.GetVotingStartTime().Add(votingPeriod)) + proposal.SetStatus(StatusVotingPeriod) + keeper.SetProposal(ctx, proposal) + + keeper.RemoveFromInactiveProposalQueue(ctx, proposal.GetDepositEndTime(), proposal.GetProposalID()) + keeper.InsertActiveProposalQueue(ctx, proposal.GetVotingEndTime(), proposal.GetProposalID()) + keeper.SetValidatorSet(ctx, proposal.GetProposalID()) +} + +// ===================================================== +// Votes + +// Adds a vote on a specific proposal +func (keeper Keeper) AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, option VoteOption) sdk.Error { + proposal := keeper.GetProposal(ctx, proposalID) + if proposal == nil { + return ErrUnknownProposal(keeper.codespace, proposalID) + } + if proposal.GetStatus() != StatusVotingPeriod { + return ErrInactiveProposal(keeper.codespace, proposalID) + } + + if keeper.vs.Validator(ctx, sdk.ValAddress(voterAddr)) == nil { + return ErrOnlyValidatorVote(keeper.codespace, voterAddr) + } + + if _, ok := keeper.GetVote(ctx, proposalID, voterAddr); ok { + return ErrAlreadyVote(keeper.codespace, voterAddr, proposalID) + } + + if !ValidVoteOption(option) { + return ErrInvalidVote(keeper.codespace, option) + } + + vote := Vote{ + ProposalID: proposalID, + Voter: voterAddr, + Option: option, + } + keeper.setVote(ctx, proposalID, voterAddr, vote) + + return nil +} + +// Gets the vote of a specific voter on a specific proposal +func (keeper Keeper) GetVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) (Vote, bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyVote(proposalID, voterAddr)) + if bz == nil { + return Vote{}, false + } + var vote Vote + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &vote) + return vote, true +} + +func (keeper Keeper) setVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, vote Vote) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(vote) + store.Set(KeyVote(proposalID, voterAddr), bz) +} + +// Gets all the votes on a specific proposal +func (keeper Keeper) GetVotes(ctx sdk.Context, proposalID uint64) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return sdk.KVStorePrefixIterator(store, KeyVotesSubspace(proposalID)) +} + +func (keeper Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyVote(proposalID, voterAddr)) +} + +// ===================================================== +// Deposits + +// Gets the deposit of a specific depositor on a specific proposal +func (keeper Keeper) GetDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) (Deposit, bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyDeposit(proposalID, depositorAddr)) + if bz == nil { + return Deposit{}, false + } + var deposit Deposit + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &deposit) + return deposit, true +} + +func (keeper Keeper) setDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, deposit Deposit) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(deposit) + store.Set(KeyDeposit(proposalID, depositorAddr), bz) +} + +func (keeper Keeper) AddInitialDeposit(ctx sdk.Context, proposal Proposal, depositorAddr sdk.AccAddress, initialDeposit sdk.Coins) (sdk.Error, bool) { + + minDepositInt := sdk.NewDecFromInt(keeper.GetDepositProcedure(ctx, proposal).MinDeposit.AmountOf(stakeTypes.StakeDenom)).Mul(MinDepositRate).RoundInt() + minInitialDeposit := sdk.Coins{sdk.NewCoin(stakeTypes.StakeDenom,minDepositInt)} + if !initialDeposit.IsAllGTE(minInitialDeposit) { + return ErrNotEnoughInitialDeposit(DefaultCodespace,initialDeposit,minInitialDeposit), false + } + + return keeper.AddDeposit(ctx,proposal.GetProposalID(),depositorAddr, initialDeposit) +} + +// Adds or updates a deposit of a specific depositor on a specific proposal +// Activates voting period when appropriate +func (keeper Keeper) AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (sdk.Error, bool) { + // Checks to see if proposal exists + proposal := keeper.GetProposal(ctx, proposalID) + if proposal == nil { + return ErrUnknownProposal(keeper.codespace, proposalID), false + } + + // Check if proposal is still depositable + if proposal.GetStatus() != StatusDepositPeriod { + return ErrNotInDepositPeriod(keeper.codespace, proposalID), false + } + + // Send coins from depositor's account to DepositedCoinsAccAddr account + _, err := keeper.ck.SendCoins(ctx, depositorAddr, DepositedCoinsAccAddr, depositAmount) + if err != nil { + return err, false + } + + // Update Proposal + proposal.SetTotalDeposit(proposal.GetTotalDeposit().Plus(depositAmount)) + keeper.SetProposal(ctx, proposal) + + // Check if deposit tipped proposal into voting period + // Active voting period if so + activatedVotingPeriod := false + if proposal.GetStatus() == StatusDepositPeriod && proposal.GetTotalDeposit().IsAllGTE(keeper.GetDepositProcedure(ctx, proposal).MinDeposit) { + keeper.activateVotingPeriod(ctx, proposal) + activatedVotingPeriod = true + } + + // Add or update deposit object + currDeposit, found := keeper.GetDeposit(ctx, proposalID, depositorAddr) + if !found { + newDeposit := Deposit{depositorAddr, proposalID, depositAmount} + keeper.setDeposit(ctx, proposalID, depositorAddr, newDeposit) + } else { + currDeposit.Amount = currDeposit.Amount.Plus(depositAmount) + keeper.setDeposit(ctx, proposalID, depositorAddr, currDeposit) + } + + return nil, activatedVotingPeriod +} + +// Gets all the deposits on a specific proposal +func (keeper Keeper) GetDeposits(ctx sdk.Context, proposalID uint64) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return sdk.KVStorePrefixIterator(store, KeyDepositsSubspace(proposalID)) +} + +func (keeper Keeper) RefundDepositsWithoutFee(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + + for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) + + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount) + if err != nil { + panic("should not happen") + } + + store.Delete(depositsIterator.Key()) + } + + depositsIterator.Close() +} + +// Returns and deletes all the deposits on a specific proposal +func (keeper Keeper) RefundDeposits(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + defer depositsIterator.Close() + depositSum := sdk.Coins{} + deposits := []*Deposit{} + for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) + deposits = append(deposits, deposit) + depositSum = depositSum.Plus(deposit.Amount) + store.Delete(depositsIterator.Key()) + } + + proposal := keeper.GetProposal(ctx, proposalID) + BurnAmountDec := sdk.NewDecFromInt(keeper.GetDepositProcedure(ctx, proposal).MinDeposit.AmountOf(stakeTypes.StakeDenom)).Mul(BurnRate) + DepositSumInt := depositSum.AmountOf(stakeTypes.StakeDenom) + rate := BurnAmountDec.Quo(sdk.NewDecFromInt(DepositSumInt)) + RefundSumInt := sdk.NewInt(0) + for _, deposit := range deposits { + AmountDec := sdk.NewDecFromInt(deposit.Amount.AmountOf(stakeTypes.StakeDenom)) + RefundAmountInt := AmountDec.Sub(AmountDec.Mul(rate)).RoundInt() + RefundSumInt = RefundSumInt.Add(RefundAmountInt) + deposit.Amount = sdk.Coins{sdk.NewCoin(stakeTypes.StakeDenom, RefundAmountInt)} + + _, err := keeper.ck.SendCoins(ctx, DepositedCoinsAccAddr, deposit.Depositor, deposit.Amount) + if err != nil { + panic(err) + } + } + + _, err := keeper.ck.BurnCoinsFromAddr(ctx, DepositedCoinsAccAddr, sdk.Coins{sdk.NewCoin(stakeTypes.StakeDenom, DepositSumInt.Sub(RefundSumInt))}) + if err != nil { + panic(err) + } + +} + +// Deletes all the deposits on a specific proposal without refunding them +func (keeper Keeper) DeleteDeposits(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + depositsIterator := keeper.GetDeposits(ctx, proposalID) + + for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := &Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), deposit) + + _, err := keeper.ck.BurnCoinsFromAddr(ctx, DepositedCoinsAccAddr, deposit.Amount) + if err != nil { + panic(err) + } + + store.Delete(depositsIterator.Key()) + } + + depositsIterator.Close() +} + +// ===================================================== +// ProposalQueues + +// Returns an iterator for all the proposals in the Active Queue that expire by endTime +func (keeper Keeper) ActiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return store.Iterator(PrefixActiveProposalQueue, sdk.PrefixEndBytes(PrefixActiveProposalQueueTime(endTime))) +} + +// Inserts a ProposalID into the active proposal queue at endTime +func (keeper Keeper) InsertActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyActiveProposalQueueProposal(endTime, proposalID), bz) +} + +// removes a proposalID from the Active Proposal Queue +func (keeper Keeper) RemoveFromActiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyActiveProposalQueueProposal(endTime, proposalID)) +} + +// Returns an iterator for all the proposals in the Inactive Queue that expire by endTime +func (keeper Keeper) InactiveProposalQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { + store := ctx.KVStore(keeper.storeKey) + return store.Iterator(PrefixInactiveProposalQueue, sdk.PrefixEndBytes(PrefixInactiveProposalQueueTime(endTime))) +} + +// Inserts a ProposalID into the inactive proposal queue at endTime +func (keeper Keeper) InsertInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyInactiveProposalQueueProposal(endTime, proposalID), bz) +} + +// removes a proposalID from the Inactive Proposal Queue +func (keeper Keeper) RemoveFromInactiveProposalQueue(ctx sdk.Context, endTime time.Time, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyInactiveProposalQueueProposal(endTime, proposalID)) +} + +func (keeper Keeper) GetSystemHaltHeight(ctx sdk.Context) int64 { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeySystemHaltHeight) + if bz == nil { + return -1 + } + var height int64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &height) + + return height +} + +func (keeper Keeper) SetSystemHaltHeight(ctx sdk.Context, height int64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(height) + store.Set(KeySystemHaltHeight, bz) +} + +func (keeper Keeper) GetCriticalProposalID(ctx sdk.Context) (uint64, bool) { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyCriticalProposal) + if bz == nil { + return 0, false + } + var proposalID uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &proposalID) + return proposalID, true +} + +func (keeper Keeper) SetCriticalProposalID(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(proposalID) + store.Set(KeyCriticalProposal, bz) +} + +func (keeper Keeper) GetCriticalProposalNum(ctx sdk.Context) uint64 { + if _, ok := keeper.GetCriticalProposalID(ctx); ok { + return 1 + } + return 0 +} + +func (keeper Keeper) AddCriticalProposalNum(ctx sdk.Context, proposalID uint64) { + keeper.SetCriticalProposalID(ctx, proposalID) +} + +func (keeper Keeper) SubCriticalProposalNum(ctx sdk.Context) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyCriticalProposal) +} + +func (keeper Keeper) GetImportantProposalNum(ctx sdk.Context) uint64 { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyImportantProposalNum) + if bz == nil { + keeper.SetImportantProposalNum(ctx, 0) + return 0 + } + var num uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &num) + return num +} + +func (keeper Keeper) SetImportantProposalNum(ctx sdk.Context, num uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(num) + store.Set(KeyImportantProposalNum, bz) +} + +func (keeper Keeper) AddImportantProposalNum(ctx sdk.Context) { + keeper.SetImportantProposalNum(ctx, keeper.GetImportantProposalNum(ctx)+1) +} + +func (keeper Keeper) SubImportantProposalNum(ctx sdk.Context) { + keeper.SetImportantProposalNum(ctx, keeper.GetImportantProposalNum(ctx)-1) +} + +func (keeper Keeper) GetNormalProposalNum(ctx sdk.Context) uint64 { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyNormalProposalNum) + if bz == nil { + keeper.SetImportantProposalNum(ctx, 0) + return 0 + } + var num uint64 + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &num) + return num +} + +func (keeper Keeper) SetNormalProposalNum(ctx sdk.Context, num uint64) { + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(num) + store.Set(KeyNormalProposalNum, bz) +} + +func (keeper Keeper) AddNormalProposalNum(ctx sdk.Context) { + keeper.SetNormalProposalNum(ctx, keeper.GetNormalProposalNum(ctx)+1) +} + +func (keeper Keeper) SubNormalProposalNum(ctx sdk.Context) { + keeper.SetNormalProposalNum(ctx, keeper.GetNormalProposalNum(ctx)-1) +} + +func (keeper Keeper) HasReachedTheMaxProposalNum(ctx sdk.Context, pl ProposalLevel) (uint64, bool) { + maxNum := keeper.GetMaxNumByProposalLevel(ctx, pl) + switch pl { + case ProposalLevelCritical: + return keeper.GetCriticalProposalNum(ctx), keeper.GetCriticalProposalNum(ctx) == maxNum + case ProposalLevelImportant: + return keeper.GetImportantProposalNum(ctx), keeper.GetImportantProposalNum(ctx) == maxNum + case ProposalLevelNormal: + return keeper.GetNormalProposalNum(ctx), keeper.GetNormalProposalNum(ctx) == maxNum + default: + panic("There is no level for this proposal") + } +} + +func (keeper Keeper) AddProposalNum(ctx sdk.Context, p Proposal) { + switch GetProposalLevel(p) { + case ProposalLevelCritical: + keeper.AddCriticalProposalNum(ctx, p.GetProposalID()) + case ProposalLevelImportant: + keeper.AddImportantProposalNum(ctx) + case ProposalLevelNormal: + keeper.AddNormalProposalNum(ctx) + default: + panic("There is no level for this proposal which type is " + p.GetProposalType().String()) + } +} + +func (keeper Keeper) SubProposalNum(ctx sdk.Context, p Proposal) { + switch GetProposalLevel(p) { + case ProposalLevelCritical: + keeper.SubCriticalProposalNum(ctx) + case ProposalLevelImportant: + keeper.SubImportantProposalNum(ctx) + case ProposalLevelNormal: + keeper.SubNormalProposalNum(ctx) + default: + panic("There is no level for this proposal which type is " + p.GetProposalType().String()) + } +} + +func (keeper Keeper) SetValidatorSet(ctx sdk.Context, proposalID uint64) { + + valAddrs := []sdk.ValAddress{} + keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { + valAddrs = append(valAddrs, validator.GetOperator()) + return false + }) + store := ctx.KVStore(keeper.storeKey) + bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(valAddrs) + store.Set(KeyValidatorSet(proposalID), bz) +} + +func (keeper Keeper) GetValidatorSet(ctx sdk.Context, proposalID uint64) []sdk.ValAddress { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeyValidatorSet(proposalID)) + if bz == nil { + return []sdk.ValAddress{} + } + valAddrs := []sdk.ValAddress{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &valAddrs) + return valAddrs +} + +func (keeper Keeper) DeleteValidatorSet(ctx sdk.Context, proposalID uint64) { + store := ctx.KVStore(keeper.storeKey) + store.Delete(KeyValidatorSet(proposalID)) +} diff --git a/app/v1/gov/keeper_keys.go b/app/v1/gov/keeper_keys.go new file mode 100644 index 000000000..2a8c6bfb8 --- /dev/null +++ b/app/v1/gov/keeper_keys.go @@ -0,0 +1,92 @@ +package gov + +import ( + "bytes" + "fmt" + "time" + + sdk "github.com/irisnet/irishub/types" +) + +// TODO remove some of these prefixes once have working multistore + +// Key for getting a the next available proposalID from the store +var ( + KeyDelimiter = []byte(":") + + KeyNextProposalID = []byte("newProposalID") + PrefixActiveProposalQueue = []byte("activeProposalQueue") + PrefixInactiveProposalQueue = []byte("inactiveProposalQueue") +) + +// Key for getting a specific proposal from the store +func KeyProposal(proposalID uint64) []byte { + return []byte(fmt.Sprintf("proposals:%d", proposalID)) +} + +// Key for getting a specific deposit from the store +func KeyDeposit(proposalID uint64, depositorAddr sdk.AccAddress) []byte { + return []byte(fmt.Sprintf("deposits:%d:%d", proposalID, depositorAddr)) +} + +// Key for getting a specific vote from the store +func KeyVote(proposalID uint64, voterAddr sdk.AccAddress) []byte { + return []byte(fmt.Sprintf("votes:%d:%d", proposalID, voterAddr)) +} + +// Key for getting all deposits on a proposal from the store +func KeyDepositsSubspace(proposalID uint64) []byte { + return []byte(fmt.Sprintf("deposits:%d:", proposalID)) +} + +// Key for getting all votes on a proposal from the store +func KeyVotesSubspace(proposalID uint64) []byte { + return []byte(fmt.Sprintf("votes:%d:", proposalID)) +} + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixActiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyActiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixActiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func PrefixInactiveProposalQueueTime(endTime time.Time) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + }, KeyDelimiter) +} + +// Returns the key for a proposalID in the activeProposalQueue +func KeyInactiveProposalQueueProposal(endTime time.Time, proposalID uint64) []byte { + return bytes.Join([][]byte{ + PrefixInactiveProposalQueue, + sdk.FormatTimeBytes(endTime), + sdk.Uint64ToBigEndian(proposalID), + }, KeyDelimiter) +} + +// Key for getting a the next available proposalID from the store +var ( + KeySystemHaltHeight = []byte("SystemHaltHeight") + KeyCriticalProposal = []byte("CriticalProposal") + KeyImportantProposalNum = []byte("ImportantProposalNum") + KeyNormalProposalNum = []byte("NormalProposalNum") + PrefixValidatorSet = []byte("vs") +) + +func KeyValidatorSet(proposalID uint64) []byte { + return bytes.Join([][]byte{PrefixValidatorSet, sdk.Uint64ToBigEndian(proposalID)}, KeyDelimiter) +} diff --git a/app/v1/gov/msgs.go b/app/v1/gov/msgs.go new file mode 100644 index 000000000..32077331d --- /dev/null +++ b/app/v1/gov/msgs.go @@ -0,0 +1,300 @@ +package gov + +import ( + "fmt" + + "github.com/irisnet/irishub/modules/params" + sdk "github.com/irisnet/irishub/types" +) + +// name to idetify transaction types +const MsgRoute = "gov" + +var _, _, _, _ sdk.Msg = MsgSubmitProposal{}, MsgSubmitTxTaxUsageProposal{}, MsgDeposit{}, MsgVote{} + +//----------------------------------------------------------- +// MsgSubmitProposal +type MsgSubmitProposal struct { + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Proposer sdk.AccAddress `json:"proposer"` // Address of the proposer + InitialDeposit sdk.Coins `json:"initial_deposit"` // Initial deposit paid by sender. Must be strictly positive. + //////////////////// iris begin /////////////////////////// + Params Params + //////////////////// iris end ///////////////////////////// +} + +func NewMsgSubmitProposal(title string, description string, proposalType ProposalKind, proposer sdk.AccAddress, initialDeposit sdk.Coins, params Params) MsgSubmitProposal { + return MsgSubmitProposal{ + Title: title, + Description: description, + ProposalType: proposalType, + Proposer: proposer, + InitialDeposit: initialDeposit, + //////////////////// iris begin /////////////////////////// + Params: params, + //////////////////// iris end ///////////////////////////// + } +} + +//nolint +func (msg MsgSubmitProposal) Route() string { return MsgRoute } +func (msg MsgSubmitProposal) Type() string { return "submit_proposal" } + +// Implements Msg. +func (msg MsgSubmitProposal) ValidateBasic() sdk.Error { + if len(msg.Title) == 0 { + return ErrInvalidTitle(DefaultCodespace, msg.Title) // TODO: Proper Error + } + if len(msg.Description) == 0 { + return ErrInvalidDescription(DefaultCodespace, msg.Description) // TODO: Proper Error + } + if !ValidProposalType(msg.ProposalType) { + return ErrInvalidProposalType(DefaultCodespace, msg.ProposalType) + } + if len(msg.Proposer) == 0 { + return sdk.ErrInvalidAddress(msg.Proposer.String()) + } + if !msg.InitialDeposit.IsValid() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + if !msg.InitialDeposit.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.InitialDeposit.String()) + } + //////////////////// iris begin /////////////////////////// + if msg.ProposalType == ProposalTypeParameterChange { + + if len(msg.Params) == 0 { + return ErrEmptyParam(DefaultCodespace) + } + + for _, param := range msg.Params { + if p, ok := params.ParamSetMapping[param.Subspace]; ok { + if _, err := p.Validate(param.Key, param.Value); err != nil { + return err + } + } else { + return ErrInvalidParam(DefaultCodespace, param.Subspace) + } + } + + } + //////////////////// iris end ///////////////////////////// + return nil +} + +func (msg MsgSubmitProposal) String() string { + return fmt.Sprintf("MsgSubmitProposal{%s, %s, %s, %v}", msg.Title, msg.Description, msg.ProposalType, msg.InitialDeposit) +} + +// Implements Msg. +func (msg MsgSubmitProposal) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgSubmitProposal) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Proposer} +} + +type MsgSubmitSoftwareUpgradeProposal struct { + MsgSubmitProposal + Version uint64 `json:"version"` + Software string `json:"software"` + SwitchHeight uint64 `json:"switch_height"` +} + +func NewMsgSubmitSoftwareUpgradeProposal(msgSubmitProposal MsgSubmitProposal, version uint64, software string, switchHeight uint64) MsgSubmitSoftwareUpgradeProposal { + return MsgSubmitSoftwareUpgradeProposal{ + MsgSubmitProposal: msgSubmitProposal, + Version: version, + Software: software, + SwitchHeight: switchHeight, + } +} + +func (msg MsgSubmitSoftwareUpgradeProposal) ValidateBasic() sdk.Error { + err := msg.MsgSubmitProposal.ValidateBasic() + if err != nil { + return err + } + return nil +} + +func (msg MsgSubmitSoftwareUpgradeProposal) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +type MsgSubmitTxTaxUsageProposal struct { + MsgSubmitProposal + Usage UsageType `json:"usage"` + DestAddress sdk.AccAddress `json:"dest_address"` + Percent sdk.Dec `json:"percent"` +} + +func NewMsgSubmitTaxUsageProposal(msgSubmitProposal MsgSubmitProposal, usage UsageType, destAddress sdk.AccAddress, percent sdk.Dec) MsgSubmitTxTaxUsageProposal { + return MsgSubmitTxTaxUsageProposal{ + MsgSubmitProposal: msgSubmitProposal, + Usage: usage, + DestAddress: destAddress, + Percent: percent, + } +} + +func (msg MsgSubmitTxTaxUsageProposal) ValidateBasic() sdk.Error { + err := msg.MsgSubmitProposal.ValidateBasic() + if err != nil { + return err + } + if !ValidUsageType(msg.Usage) { + return ErrInvalidUsageType(DefaultCodespace, msg.Usage) + } + if msg.Usage != UsageTypeBurn && len(msg.DestAddress) == 0 { + return sdk.ErrInvalidAddress(msg.DestAddress.String()) + } + if msg.Percent.LTE(sdk.NewDec(0)) || msg.Percent.GT(sdk.NewDec(1)) { + return ErrInvalidPercent(DefaultCodespace, msg.Percent) + } + return nil +} + +func (msg MsgSubmitTxTaxUsageProposal) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +//----------------------------------------------------------- +// MsgDeposit +type MsgDeposit struct { + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit +} + +func NewMsgDeposit(depositor sdk.AccAddress, proposalID uint64, amount sdk.Coins) MsgDeposit { + return MsgDeposit{ + ProposalID: proposalID, + Depositor: depositor, + Amount: amount, + } +} + +// Implements Msg. +// nolint +func (msg MsgDeposit) Route() string { return MsgRoute } +func (msg MsgDeposit) Type() string { return "deposit" } + +// Implements Msg. +func (msg MsgDeposit) ValidateBasic() sdk.Error { + if len(msg.Depositor) == 0 { + return sdk.ErrInvalidAddress(msg.Depositor.String()) + } + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if !msg.Amount.IsNotNegative() { + return sdk.ErrInvalidCoins(msg.Amount.String()) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + return nil +} + +func (msg MsgDeposit) String() string { + return fmt.Sprintf("MsgDeposit{%s=>%v: %v}", msg.Depositor, msg.ProposalID, msg.Amount) +} + +// Implements Msg. +func (msg MsgDeposit) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgDeposit) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgDeposit) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Depositor} +} + +//----------------------------------------------------------- +// MsgVote +type MsgVote struct { + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter +} + +func NewMsgVote(voter sdk.AccAddress, proposalID uint64, option VoteOption) MsgVote { + return MsgVote{ + ProposalID: proposalID, + Voter: voter, + Option: option, + } +} + +// Implements Msg. +// nolint +func (msg MsgVote) Route() string { return MsgRoute } +func (msg MsgVote) Type() string { return "vote" } + +// Implements Msg. +func (msg MsgVote) ValidateBasic() sdk.Error { + if len(msg.Voter.Bytes()) == 0 { + return sdk.ErrInvalidAddress(msg.Voter.String()) + } + if msg.ProposalID < 0 { + return ErrUnknownProposal(DefaultCodespace, msg.ProposalID) + } + if !ValidVoteOption(msg.Option) { + return ErrInvalidVote(DefaultCodespace, msg.Option) + } + return nil +} + +func (msg MsgVote) String() string { + return fmt.Sprintf("MsgVote{%v - %s}", msg.ProposalID, msg.Option) +} + +// Implements Msg. +func (msg MsgVote) Get(key interface{}) (value interface{}) { + return nil +} + +// Implements Msg. +func (msg MsgVote) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// Implements Msg. +func (msg MsgVote) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Voter} +} diff --git a/app/v1/gov/params.go b/app/v1/gov/params.go new file mode 100644 index 000000000..9ef0d34f8 --- /dev/null +++ b/app/v1/gov/params.go @@ -0,0 +1,430 @@ +package gov + +import ( + "fmt" + + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/params" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" + sdk "github.com/irisnet/irishub/types" + "strconv" + "time" +) + +const ( + CRITICAL_DEPOSIT = 4000 + IMPORTANT_DEPOSIT = 2000 + NORMAL_DEPOSIT = 1000 + CRITICAL = "Critical" + IMPORTANT = "Important" + NORMAL = "Normal" + LOWER_BOUND_AMOUNT = 10 + UPPER_BOUND_AMOUNT = 10000 + STABLE_CRITIACAL_NUM = 1 + DEFAULT_IMPORTANT_NUM = 2 + DEFAULT_NORMAL_NUM = 1 + MIN_IMPORTANT_NUM = 1 + MIN_NORMAL_NUM = 1 +) + +var _ params.ParamSet = (*GovParams)(nil) + +// default paramspace for params keeper +const ( + DefaultParamSpace = "gov" +) + +//Parameter store key +var ( + KeyCriticalDepositPeriod = []byte(CRITICAL + "DepositPeriod") + KeyCriticalMinDeposit = []byte(CRITICAL + "MinDeposit") + KeyCriticalVotingPeriod = []byte(CRITICAL + "VotingPeriod") + KeyCriticalMaxNum = []byte(CRITICAL + "MaxNum") + KeyCriticalThreshold = []byte(CRITICAL + "Threshold") + KeyCriticalVeto = []byte(CRITICAL + "Veto") + KeyCriticalParticipation = []byte(CRITICAL + "Participation") + KeyCriticalPenalty = []byte(CRITICAL + "Penalty") + + KeyImportantDepositPeriod = []byte(IMPORTANT + "DepositPeriod") + KeyImportantMinDeposit = []byte(IMPORTANT + "MinDeposit") + KeyImportantVotingPeriod = []byte(IMPORTANT + "VotingPeriod") + KeyImportantMaxNum = []byte(IMPORTANT + "MaxNum") + KeyImportantThreshold = []byte(IMPORTANT + "Threshold") + KeyImportantVeto = []byte(IMPORTANT + "Veto") + KeyImportantParticipation = []byte(IMPORTANT + "Participation") + KeyImportantPenalty = []byte(IMPORTANT + "Penalty") + + KeyNormalDepositPeriod = []byte(NORMAL + "DepositPeriod") + KeyNormalMinDeposit = []byte(NORMAL + "MinDeposit") + KeyNormalVotingPeriod = []byte(NORMAL + "VotingPeriod") + KeyNormalMaxNum = []byte(NORMAL + "MaxNum") + KeyNormalThreshold = []byte(NORMAL + "Threshold") + KeyNormalVeto = []byte(NORMAL + "Veto") + KeyNormalParticipation = []byte(NORMAL + "Participation") + KeyNormalPenalty = []byte(NORMAL + "Penalty") + + KeySystemHaltPeriod = []byte("SystemHaltPeriod") +) + +// ParamTable for mint module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable().RegisterParamSet(&GovParams{}) +} + +// mint parameters +type GovParams struct { + CriticalDepositPeriod time.Duration `json:"critical_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + CriticalMinDeposit sdk.Coins `json:"critical_min_deposit"` // Minimum deposit for a critical proposal to enter voting period. + CriticalVotingPeriod time.Duration `json:"critical_voting_period"` // Length of the critical voting period. + CriticalMaxNum uint64 `json:"critical_max_num"` + CriticalThreshold sdk.Dec `json:"critical_threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + CriticalVeto sdk.Dec `json:"critical_veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + CriticalParticipation sdk.Dec `json:"critical_participation"` // + CriticalPenalty sdk.Dec `json:"critical_penalty"` // Penalty if validator does not vote + + ImportantDepositPeriod time.Duration `json:"important_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + ImportantMinDeposit sdk.Coins `json:"important_min_deposit"` // Minimum deposit for a important proposal to enter voting period. + ImportantVotingPeriod time.Duration `json:"important_voting_period"` // Length of the important voting period. + ImportantMaxNum uint64 `json:"important_max_num"` + ImportantThreshold sdk.Dec `json:"important_threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + ImportantVeto sdk.Dec `json:"important_veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + ImportantParticipation sdk.Dec `json:"important_participation"` // + ImportantPenalty sdk.Dec `json:"important_penalty"` // Penalty if validator does not vote + + NormalDepositPeriod time.Duration `json:"normal_deposit_period"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + NormalMinDeposit sdk.Coins `json:"normal_min_deposit"` // Minimum deposit for a normal proposal to enter voting period. + NormalVotingPeriod time.Duration `json:"normal_voting_period"` // Length of the normal voting period. + NormalMaxNum uint64 `json:"normal_max_num"` + NormalThreshold sdk.Dec `json:"normal_threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + NormalVeto sdk.Dec `json:"normal_veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + NormalParticipation sdk.Dec `json:"normal_participation"` // + NormalPenalty sdk.Dec `json:"normal_penalty"` // Penalty if validator does not vote + + SystemHaltPeriod int64 `json:"system_halt_period"` +} + +// Implements params.ParamStruct +func (p *GovParams) GetParamSpace() string { + return DefaultParamSpace +} + +func (p *GovParams) KeyValuePairs() params.KeyValuePairs { + return params.KeyValuePairs{ + {KeyCriticalDepositPeriod, &p.CriticalDepositPeriod}, + {KeyCriticalMinDeposit, &p.CriticalMinDeposit}, + {KeyCriticalVotingPeriod, &p.CriticalVotingPeriod}, + {KeyCriticalMaxNum, &p.CriticalMaxNum}, + {KeyCriticalThreshold, &p.CriticalThreshold}, + {KeyCriticalVeto, &p.CriticalVeto}, + {KeyCriticalParticipation, &p.CriticalParticipation}, + {KeyCriticalPenalty, &p.CriticalPenalty}, + + {KeyImportantDepositPeriod, &p.ImportantDepositPeriod}, + {KeyImportantMinDeposit, &p.ImportantMinDeposit}, + {KeyImportantVotingPeriod, &p.ImportantVotingPeriod}, + {KeyImportantMaxNum, &p.ImportantMaxNum}, + {KeyImportantThreshold, &p.ImportantThreshold}, + {KeyImportantVeto, &p.ImportantVeto}, + {KeyImportantParticipation, &p.ImportantParticipation}, + {KeyImportantPenalty, &p.ImportantPenalty}, + + {KeyNormalDepositPeriod, &p.NormalDepositPeriod}, + {KeyNormalMinDeposit, &p.NormalMinDeposit}, + {KeyNormalVotingPeriod, &p.NormalVotingPeriod}, + {KeyNormalMaxNum, &p.NormalMaxNum}, + {KeyNormalThreshold, &p.NormalThreshold}, + {KeyNormalVeto, &p.NormalVeto}, + {KeyNormalParticipation, &p.NormalParticipation}, + {KeyNormalPenalty, &p.NormalPenalty}, + + {KeySystemHaltPeriod, &p.SystemHaltPeriod}, + } +} + +func (p *GovParams) Validate(key string, value string) (interface{}, sdk.Error) { + return nil, nil +} + +func (p *GovParams) StringFromBytes(cdc *codec.Codec, key string, bytes []byte) (string, error) { + switch key { + case string(KeyCriticalDepositPeriod): + err := cdc.UnmarshalJSON(bytes, &p.CriticalDepositPeriod) + return p.CriticalDepositPeriod.String(), err + case string(KeyCriticalMinDeposit): + err := cdc.UnmarshalJSON(bytes, &p.CriticalMinDeposit) + return p.CriticalMinDeposit.String(), err + case string(KeyCriticalVotingPeriod): + err := cdc.UnmarshalJSON(bytes, &p.CriticalVotingPeriod) + return p.CriticalDepositPeriod.String(), err + case string(KeyCriticalMaxNum): + err := cdc.UnmarshalJSON(bytes, &p.CriticalMaxNum) + return strconv.FormatUint(p.CriticalMaxNum, 10), err + case string(KeyCriticalThreshold): + err := cdc.UnmarshalJSON(bytes, &p.CriticalThreshold) + return p.CriticalThreshold.String(), err + case string(KeyCriticalVeto): + err := cdc.UnmarshalJSON(bytes, &p.CriticalVeto) + return p.CriticalThreshold.String(), err + case string(KeyCriticalParticipation): + err := cdc.UnmarshalJSON(bytes, &p.CriticalParticipation) + return p.CriticalParticipation.String(), err + case string(KeyCriticalPenalty): + err := cdc.UnmarshalJSON(bytes, &p.CriticalPenalty) + return p.CriticalPenalty.String(), err + + case string(KeyImportantDepositPeriod): + err := cdc.UnmarshalJSON(bytes, &p.ImportantDepositPeriod) + return p.ImportantDepositPeriod.String(), err + case string(KeyImportantMinDeposit): + err := cdc.UnmarshalJSON(bytes, &p.ImportantMinDeposit) + return p.ImportantMinDeposit.String(), err + case string(KeyImportantVotingPeriod): + err := cdc.UnmarshalJSON(bytes, &p.ImportantVotingPeriod) + return p.ImportantDepositPeriod.String(), err + case string(KeyImportantMaxNum): + err := cdc.UnmarshalJSON(bytes, &p.ImportantMaxNum) + return strconv.FormatUint(p.ImportantMaxNum, 10), err + case string(KeyImportantThreshold): + err := cdc.UnmarshalJSON(bytes, &p.ImportantThreshold) + return p.ImportantThreshold.String(), err + case string(KeyImportantVeto): + err := cdc.UnmarshalJSON(bytes, &p.ImportantVeto) + return p.ImportantThreshold.String(), err + case string(KeyImportantParticipation): + err := cdc.UnmarshalJSON(bytes, &p.ImportantParticipation) + return p.ImportantParticipation.String(), err + case string(KeyImportantPenalty): + err := cdc.UnmarshalJSON(bytes, &p.ImportantPenalty) + return p.ImportantPenalty.String(), err + + case string(KeyNormalDepositPeriod): + err := cdc.UnmarshalJSON(bytes, &p.NormalDepositPeriod) + return p.NormalDepositPeriod.String(), err + case string(KeyNormalMinDeposit): + err := cdc.UnmarshalJSON(bytes, &p.NormalMinDeposit) + return p.NormalMinDeposit.String(), err + case string(KeyNormalVotingPeriod): + err := cdc.UnmarshalJSON(bytes, &p.NormalVotingPeriod) + return p.NormalDepositPeriod.String(), err + case string(KeyNormalMaxNum): + err := cdc.UnmarshalJSON(bytes, &p.NormalMaxNum) + return strconv.FormatUint(p.NormalMaxNum, 10), err + case string(KeyNormalThreshold): + err := cdc.UnmarshalJSON(bytes, &p.NormalThreshold) + return p.NormalThreshold.String(), err + case string(KeyNormalVeto): + err := cdc.UnmarshalJSON(bytes, &p.NormalVeto) + return p.NormalThreshold.String(), err + case string(KeyNormalParticipation): + err := cdc.UnmarshalJSON(bytes, &p.NormalParticipation) + return p.NormalParticipation.String(), err + case string(KeyNormalPenalty): + err := cdc.UnmarshalJSON(bytes, &p.NormalPenalty) + return p.NormalPenalty.String(), err + + case string(KeySystemHaltPeriod): + err := cdc.UnmarshalJSON(bytes, &p.SystemHaltPeriod) + return strconv.FormatInt(p.SystemHaltPeriod, 10), err + default: + return "", fmt.Errorf("%s is not existed", key) + } +} + +// default minting module parameters +func DefaultParams() GovParams { + var criticalMinDeposit, _ = sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", CRITICAL_DEPOSIT, stakeTypes.StakeTokenName)) + var importantMinDeposit, _ = sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", IMPORTANT_DEPOSIT, stakeTypes.StakeTokenName)) + var normalMinDeposit, _ = sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", NORMAL_DEPOSIT, stakeTypes.StakeTokenName)) + + return GovParams{ + CriticalDepositPeriod: time.Duration(sdk.Day), + CriticalMinDeposit: sdk.Coins{criticalMinDeposit}, + CriticalVotingPeriod: time.Duration(sdk.ThreeDays), + CriticalMaxNum: STABLE_CRITIACAL_NUM, + CriticalThreshold: sdk.NewDecWithPrec(834, 3), + CriticalVeto: sdk.NewDecWithPrec(334, 3), + CriticalParticipation: sdk.NewDecWithPrec(8572, 4), + CriticalPenalty: sdk.NewDecWithPrec(9, 4), + + ImportantDepositPeriod: time.Duration(sdk.Day), + ImportantMinDeposit: sdk.Coins{importantMinDeposit}, + ImportantVotingPeriod: time.Duration(sdk.SixtyHours), + ImportantMaxNum: DEFAULT_IMPORTANT_NUM, + ImportantThreshold: sdk.NewDecWithPrec(8, 1), + ImportantVeto: sdk.NewDecWithPrec(334, 3), + ImportantParticipation: sdk.NewDecWithPrec(834, 3), + ImportantPenalty: sdk.NewDecWithPrec(7, 4), + + NormalDepositPeriod: time.Duration(sdk.Day), + NormalMinDeposit: sdk.Coins{normalMinDeposit}, + NormalVotingPeriod: time.Duration(sdk.TwoDays), + NormalMaxNum: DEFAULT_NORMAL_NUM, + NormalThreshold: sdk.NewDecWithPrec(667, 3), + NormalVeto: sdk.NewDecWithPrec(334, 3), + NormalParticipation: sdk.NewDecWithPrec(75, 2), + NormalPenalty: sdk.NewDecWithPrec(5, 4), + SystemHaltPeriod: 20000, + } +} + +func validateParams(p GovParams) sdk.Error { + if err := validateDepositProcedure(DepositProcedure{ + MaxDepositPeriod: p.CriticalDepositPeriod, + MinDeposit: p.CriticalMinDeposit, + }, CRITICAL); err != nil { + return err + } + + if err := validatorVotingProcedure(VotingProcedure{ + VotingPeriod: p.CriticalVotingPeriod, + }, CRITICAL); err != nil { + return err + } + + if err := validateTallyingProcedure(TallyingProcedure{ + Threshold: p.CriticalThreshold, + Veto: p.CriticalVeto, + Participation: p.CriticalParticipation, + Penalty: p.CriticalPenalty, + }, CRITICAL); err != nil { + return err + } + + if err := validateDepositProcedure(DepositProcedure{ + MaxDepositPeriod: p.ImportantDepositPeriod, + MinDeposit: p.ImportantMinDeposit, + }, IMPORTANT); err != nil { + return err + } + + if err := validatorVotingProcedure(VotingProcedure{ + VotingPeriod: p.ImportantVotingPeriod, + }, IMPORTANT); err != nil { + return err + } + + if err := validateTallyingProcedure(TallyingProcedure{ + Threshold: p.ImportantThreshold, + Veto: p.ImportantVeto, + Participation: p.ImportantParticipation, + Penalty: p.ImportantPenalty, + }, IMPORTANT); err != nil { + return err + } + + if err := validateDepositProcedure(DepositProcedure{ + MaxDepositPeriod: p.NormalDepositPeriod, + MinDeposit: p.NormalMinDeposit, + }, NORMAL); err != nil { + return err + } + + if err := validatorVotingProcedure(VotingProcedure{ + VotingPeriod: p.NormalVotingPeriod, + }, NORMAL); err != nil { + return err + } + + if err := validateTallyingProcedure(TallyingProcedure{ + Threshold: p.NormalThreshold, + Veto: p.NormalVeto, + Participation: p.NormalParticipation, + Penalty: p.NormalPenalty, + }, NORMAL); err != nil { + return err + } + + if err := validateMaxNum(p); err != nil { + return err + } + + if p.SystemHaltPeriod < 0 || p.SystemHaltPeriod > 50000 { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidSystemHaltPeriod, fmt.Sprintf("SystemHaltPeriod should be between [0, 50000]")) + } + + return nil +} + +//______________________________________________________________________ + +// get inflation params from the global param store +func (k Keeper) GetParamSet(ctx sdk.Context) GovParams { + var params GovParams + k.paramSpace.GetParamSet(ctx, ¶ms) + return params +} + +// set inflation params from the global param store +func (k Keeper) SetParamSet(ctx sdk.Context, params GovParams) { + k.paramSpace.SetParamSet(ctx, ¶ms) +} + +type DepositProcedure struct { + MinDeposit sdk.Coins + MaxDepositPeriod time.Duration +} + +type VotingProcedure struct { + VotingPeriod time.Duration `json:"critical_voting_period"` // Length of the critical voting period. +} + +type TallyingProcedure struct { + Threshold sdk.Dec `json:"threshold"` // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto sdk.Dec `json:"veto"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Participation sdk.Dec `json:"participation"` // + Penalty sdk.Dec `json:"penalty"` // Penalty if validator does not vote +} + +func validateDepositProcedure(dp DepositProcedure, level string) sdk.Error { + if dp.MinDeposit[0].Denom != stakeTypes.StakeDenom { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMinDepositDenom, fmt.Sprintf(level+"MinDeposit should be %s!", stakeTypes.StakeDenom)) + } + + LowerBound, _ := sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", LOWER_BOUND_AMOUNT, stakeTypes.StakeTokenName)) + UpperBound, _ := sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", UPPER_BOUND_AMOUNT, stakeTypes.StakeTokenName)) + + if dp.MinDeposit[0].Amount.LT(LowerBound.Amount) || dp.MinDeposit[0].Amount.GT(UpperBound.Amount) { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMinDepositAmount, fmt.Sprintf(level+"MinDepositAmount"+dp.MinDeposit[0].String()+" should be larger than 10iris and less than 10000iris")) + } + + if dp.MaxDepositPeriod < sdk.TwentySeconds || dp.MaxDepositPeriod > sdk.ThreeDays { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidDepositPeriod, fmt.Sprintf(level+"MaxDepositPeriod (%s) should be between 20s and %ds", dp.MaxDepositPeriod.String(), sdk.ThreeDays.String())) + } + return nil +} + +func validatorVotingProcedure(vp VotingProcedure, level string) sdk.Error { + if vp.VotingPeriod < sdk.TwentySeconds || vp.VotingPeriod > sdk.ThreeDays { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidVotingPeriod, fmt.Sprintf(level+"VotingPeriod (%s) should be between 20s and %ds", vp.VotingPeriod.String(), sdk.ThreeDays.String())) + } + return nil +} + +func validateTallyingProcedure(tp TallyingProcedure, level string) sdk.Error { + if tp.Threshold.LTE(sdk.ZeroDec()) || tp.Threshold.GTE(sdk.NewDec(1)) { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidThreshold, fmt.Sprintf("Invalid "+level+" Threshold ( "+tp.Threshold.String()+" ) should be between 0 and 1")) + } + if tp.Participation.LTE(sdk.ZeroDec()) || tp.Participation.GTE(sdk.NewDec(1)) { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidParticipation, fmt.Sprintf("Invalid "+level+" participation ( "+tp.Participation.String()+" ) should be between 0 and 1")) + } + if tp.Veto.LTE(sdk.ZeroDec()) || tp.Veto.GTE(sdk.NewDec(1)) { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidVeto, fmt.Sprintf("Invalid "+level+" Veto ( "+tp.Veto.String()+" ) should be between 0 and 1")) + } + if tp.Penalty.LTE(sdk.ZeroDec()) || tp.Penalty.GTE(sdk.NewDec(1)) { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidGovernancePenalty, fmt.Sprintf("Invalid "+level+" GovernancePenalty ( "+tp.Penalty.String()+" ) should be between 0 and 1")) + } + return nil +} + +func validateMaxNum(gp GovParams) sdk.Error { + if gp.CriticalMaxNum != STABLE_CRITIACAL_NUM { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMaxProposalNum, fmt.Sprintf("The num of Max"+CRITICAL+"Proposal [%v] can only be %v.", gp.CriticalMaxNum, STABLE_CRITIACAL_NUM)) + } + if gp.ImportantMaxNum < MIN_IMPORTANT_NUM { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMaxProposalNum, fmt.Sprintf("The num of Max"+IMPORTANT+"Proposal [%v] should be no less than %v.", gp.CriticalMaxNum, MIN_IMPORTANT_NUM)) + } + if gp.NormalMaxNum < MIN_NORMAL_NUM { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMaxProposalNum, fmt.Sprintf("The num of Max"+NORMAL+"Proposal [%v] should be no less than %v.", gp.NormalMaxNum, MIN_NORMAL_NUM)) + } + return nil +} diff --git a/app/v1/gov/proposal_halt.go b/app/v1/gov/proposal_halt.go new file mode 100644 index 000000000..55afd27f9 --- /dev/null +++ b/app/v1/gov/proposal_halt.go @@ -0,0 +1,7 @@ +package gov + +var _ Proposal = (*SystemHaltProposal)(nil) + +type SystemHaltProposal struct { + TextProposal +} diff --git a/app/v1/gov/proposal_params.go b/app/v1/gov/proposal_params.go new file mode 100644 index 000000000..b2e1d3e3e --- /dev/null +++ b/app/v1/gov/proposal_params.go @@ -0,0 +1,22 @@ +package gov + +const ( + Insert string = "insert" + Update string = "update" +) + +type Param struct { + Subspace string `json:"subspace"` + Key string `json:"key"` + Value string `json:"value"` +} + +type Params []Param + +// Implements Proposal Interface +var _ Proposal = (*ParameterProposal)(nil) + +type ParameterProposal struct { + TextProposal + Params Params `json:"params"` +} diff --git a/app/v1/gov/proposal_taxusage.go b/app/v1/gov/proposal_taxusage.go new file mode 100644 index 000000000..b73c89318 --- /dev/null +++ b/app/v1/gov/proposal_taxusage.go @@ -0,0 +1,116 @@ +package gov + +import ( + "encoding/json" + "fmt" + "github.com/pkg/errors" + sdk "github.com/irisnet/irishub/types" +) + +type UsageType byte + +const ( + UsageTypeBurn UsageType = 0x01 + UsageTypeDistribute UsageType = 0x02 + UsageTypeGrant UsageType = 0x03 +) + +// String to UsageType byte. Returns ff if invalid. +func UsageTypeFromString(str string) (UsageType, error) { + switch str { + case "Burn": + return UsageTypeBurn, nil + case "Distribute": + return UsageTypeDistribute, nil + case "Grant": + return UsageTypeGrant, nil + default: + return UsageType(0xff), errors.Errorf("'%s' is not a valid usage type", str) + } +} + +// is defined UsageType? +func ValidUsageType(ut UsageType) bool { + if ut == UsageTypeBurn || + ut == UsageTypeDistribute || + ut == UsageTypeGrant { + return true + } + return false +} + +// Marshal needed for protobuf compatibility +func (ut UsageType) Marshal() ([]byte, error) { + return []byte{byte(ut)}, nil +} + +// Unmarshal needed for protobuf compatibility +func (ut *UsageType) Unmarshal(data []byte) error { + *ut = UsageType(data[0]) + return nil +} + +// Marshals to JSON using string +func (ut UsageType) MarshalJSON() ([]byte, error) { + return json.Marshal(ut.String()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (ut *UsageType) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + bz2, err := UsageTypeFromString(s) + if err != nil { + return err + } + *ut = bz2 + return nil +} + +// Turns VoteOption byte to String +func (ut UsageType) String() string { + switch ut { + case UsageTypeBurn: + return "Burn" + case UsageTypeDistribute: + return "Distribute" + case UsageTypeGrant: + return "Grant" + default: + return "" + } +} + +// For Printf / Sprintf, returns bech32 when using %s +// nolint: errcheck +func (ut UsageType) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(ut.String())) + default: + s.Write([]byte(fmt.Sprintf("%v", byte(ut)))) + } +} + +// Implements Proposal Interface +var _ Proposal = (*ParameterProposal)(nil) + +type TaxUsage struct { + Usage UsageType `json:"usage"` + DestAddress sdk.AccAddress `json:"dest_address"` + Percent sdk.Dec `json:"percent"` +} + +type TaxUsageProposal struct { + TextProposal + TaxUsage TaxUsage `json:"tax_usage"` +} + +func (tp TaxUsageProposal) GetTaxUsage() TaxUsage { return tp.TaxUsage } +func (tp *TaxUsageProposal) SetTaxUsage(taxUsage TaxUsage) { + tp.TaxUsage = taxUsage +} \ No newline at end of file diff --git a/app/v1/gov/proposal_upgrade.go b/app/v1/gov/proposal_upgrade.go new file mode 100644 index 000000000..574e021b6 --- /dev/null +++ b/app/v1/gov/proposal_upgrade.go @@ -0,0 +1,15 @@ +package gov + +import sdk "github.com/irisnet/irishub/types" + +var _ Proposal = (*SoftwareUpgradeProposal)(nil) + +type SoftwareUpgradeProposal struct { + TextProposal + ProtocolDefinition sdk.ProtocolDefinition `json:"protocol_definition"` +} + +func (sp SoftwareUpgradeProposal) GetProtocolDefinition() sdk.ProtocolDefinition { return sp.ProtocolDefinition } +func (sp *SoftwareUpgradeProposal) SetProtocolDefinition(upgrade sdk.ProtocolDefinition) { + sp.ProtocolDefinition = upgrade +} diff --git a/app/v1/gov/proposals.go b/app/v1/gov/proposals.go new file mode 100644 index 000000000..d5322f2ec --- /dev/null +++ b/app/v1/gov/proposals.go @@ -0,0 +1,364 @@ +package gov + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/pkg/errors" + sdk "github.com/irisnet/irishub/types" +) + +//----------------------------------------------------------- +// Proposal interface +type Proposal interface { + GetProposalID() uint64 + SetProposalID(uint64) + + GetTitle() string + SetTitle(string) + + GetDescription() string + SetDescription(string) + + GetProposalType() ProposalKind + SetProposalType(ProposalKind) + + GetStatus() ProposalStatus + SetStatus(ProposalStatus) + + GetTallyResult() TallyResult + SetTallyResult(TallyResult) + + GetSubmitTime() time.Time + SetSubmitTime(time.Time) + + GetDepositEndTime() time.Time + SetDepositEndTime(time.Time) + + GetTotalDeposit() sdk.Coins + SetTotalDeposit(sdk.Coins) + + GetVotingStartTime() time.Time + SetVotingStartTime(time.Time) + + GetVotingEndTime() time.Time + SetVotingEndTime(time.Time) + + GetProtocolDefinition() sdk.ProtocolDefinition + SetProtocolDefinition(sdk.ProtocolDefinition) + + GetTaxUsage() TaxUsage + SetTaxUsage(TaxUsage) +} + +// checks if two proposals are equal +func ProposalEqual(proposalA Proposal, proposalB Proposal) bool { + if proposalA.GetProposalID() == proposalB.GetProposalID() && + proposalA.GetTitle() == proposalB.GetTitle() && + proposalA.GetDescription() == proposalB.GetDescription() && + proposalA.GetProposalType() == proposalB.GetProposalType() && + proposalA.GetStatus() == proposalB.GetStatus() && + proposalA.GetTallyResult().Equals(proposalB.GetTallyResult()) && + proposalA.GetSubmitTime().Equal(proposalB.GetSubmitTime()) && + proposalA.GetDepositEndTime().Equal(proposalB.GetDepositEndTime()) && + proposalA.GetTotalDeposit().IsEqual(proposalB.GetTotalDeposit()) && + proposalA.GetVotingStartTime().Equal(proposalB.GetVotingStartTime()) && + proposalA.GetVotingEndTime().Equal(proposalB.GetVotingEndTime()) { + return true + } + return false +} + +//----------------------------------------------------------- +// Text Proposals +type TextProposal struct { + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + + Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} + TallyResult TallyResult `json:"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 +} + +// Implements Proposal Interface +var _ Proposal = (*TextProposal)(nil) + +// nolint +func (tp TextProposal) GetProposalID() uint64 { return tp.ProposalID } +func (tp *TextProposal) SetProposalID(proposalID uint64) { tp.ProposalID = proposalID } +func (tp TextProposal) GetTitle() string { return tp.Title } +func (tp *TextProposal) SetTitle(title string) { tp.Title = title } +func (tp TextProposal) GetDescription() string { return tp.Description } +func (tp *TextProposal) SetDescription(description string) { tp.Description = description } +func (tp TextProposal) GetProposalType() ProposalKind { return tp.ProposalType } +func (tp *TextProposal) SetProposalType(proposalType ProposalKind) { tp.ProposalType = proposalType } +func (tp TextProposal) GetStatus() ProposalStatus { return tp.Status } +func (tp *TextProposal) SetStatus(status ProposalStatus) { tp.Status = status } +func (tp TextProposal) GetTallyResult() TallyResult { return tp.TallyResult } +func (tp *TextProposal) SetTallyResult(tallyResult TallyResult) { tp.TallyResult = tallyResult } +func (tp TextProposal) GetSubmitTime() time.Time { return tp.SubmitTime } +func (tp *TextProposal) SetSubmitTime(submitTime time.Time) { tp.SubmitTime = submitTime } +func (tp TextProposal) GetDepositEndTime() time.Time { return tp.DepositEndTime } +func (tp *TextProposal) SetDepositEndTime(depositEndTime time.Time) { + tp.DepositEndTime = depositEndTime +} +func (tp TextProposal) GetTotalDeposit() sdk.Coins { return tp.TotalDeposit } +func (tp *TextProposal) SetTotalDeposit(totalDeposit sdk.Coins) { tp.TotalDeposit = totalDeposit } +func (tp TextProposal) GetVotingStartTime() time.Time { return tp.VotingStartTime } +func (tp *TextProposal) SetVotingStartTime(votingStartTime time.Time) { + tp.VotingStartTime = votingStartTime +} +func (tp TextProposal) GetVotingEndTime() time.Time { return tp.VotingEndTime } +func (tp *TextProposal) SetVotingEndTime(votingEndTime time.Time) { + tp.VotingEndTime = votingEndTime +} +func (tp TextProposal) GetProtocolDefinition() sdk.ProtocolDefinition { return sdk.ProtocolDefinition{} } +func (tp *TextProposal) SetProtocolDefinition(sdk.ProtocolDefinition) {} +func (tp TextProposal) GetTaxUsage() TaxUsage { return TaxUsage{} } +func (tp *TextProposal) SetTaxUsage(taxUsage TaxUsage) {} + +//----------------------------------------------------------- +// ProposalQueue +type ProposalQueue []uint64 + +//----------------------------------------------------------- +// ProposalKind + +// Type that represents Proposal Type as a byte +type ProposalKind byte + +//nolint +const ( + ProposalTypeNil ProposalKind = 0x00 + ProposalTypeParameterChange ProposalKind = 0x01 + ProposalTypeSoftwareUpgrade ProposalKind = 0x02 + ProposalTypeSystemHalt ProposalKind = 0x03 + ProposalTypeTxTaxUsage ProposalKind = 0x04 +) + +// String to proposalType byte. Returns ff if invalid. +func ProposalTypeFromString(str string) (ProposalKind, error) { + switch str { + case "ParameterChange": + return ProposalTypeParameterChange, nil + case "SoftwareUpgrade": + return ProposalTypeSoftwareUpgrade, nil + case "SystemHalt": + return ProposalTypeSystemHalt, nil + case "TxTaxUsage": + return ProposalTypeTxTaxUsage, nil + default: + return ProposalKind(0xff), errors.Errorf("'%s' is not a valid proposal type", str) + } +} + +// is defined ProposalType? +func ValidProposalType(pt ProposalKind) bool { + if pt == ProposalTypeParameterChange || + pt == ProposalTypeSoftwareUpgrade || + pt == ProposalTypeSystemHalt || + pt == ProposalTypeTxTaxUsage { + return true + } + return false +} + +// Marshal needed for protobuf compatibility +func (pt ProposalKind) Marshal() ([]byte, error) { + return []byte{byte(pt)}, nil +} + +// Unmarshal needed for protobuf compatibility +func (pt *ProposalKind) Unmarshal(data []byte) error { + *pt = ProposalKind(data[0]) + return nil +} + +// Marshals to JSON using string +func (pt ProposalKind) MarshalJSON() ([]byte, error) { + return json.Marshal(pt.String()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (pt *ProposalKind) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + 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 ProposalTypeParameterChange: + return "ParameterChange" + case ProposalTypeSoftwareUpgrade: + return "SoftwareUpgrade" + case ProposalTypeSystemHalt: + return "SystemHalt" + case ProposalTypeTxTaxUsage: + return "TxTaxUsage" + default: + return "" + } +} + +// For Printf / Sprintf, returns bech32 when using %s +// nolint: errcheck +func (pt ProposalKind) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(pt.String())) + default: + // TODO: Do this conversion more directly + s.Write([]byte(fmt.Sprintf("%v", byte(pt)))) + } +} + +//----------------------------------------------------------- +// ProposalStatus + +// Type that represents Proposal Status as a byte +type ProposalStatus byte + +//nolint +const ( + StatusNil ProposalStatus = 0x00 + StatusDepositPeriod ProposalStatus = 0x01 + StatusVotingPeriod ProposalStatus = 0x02 + StatusPassed ProposalStatus = 0x03 + StatusRejected ProposalStatus = 0x04 +) + +// ProposalStatusToString turns a string into a ProposalStatus +func ProposalStatusFromString(str string) (ProposalStatus, error) { + switch str { + case "DepositPeriod": + return StatusDepositPeriod, nil + case "VotingPeriod": + return StatusVotingPeriod, nil + case "Passed": + return StatusPassed, nil + case "Rejected": + return StatusRejected, nil + case "": + return StatusNil, nil + default: + return ProposalStatus(0xff), errors.Errorf("'%s' is not a valid proposal status", str) + } +} + +// is defined ProposalType? +func ValidProposalStatus(status ProposalStatus) bool { + if status == StatusDepositPeriod || + status == StatusVotingPeriod || + status == StatusPassed || + status == StatusRejected { + return true + } + return false +} + +// Marshal needed for protobuf compatibility +func (status ProposalStatus) Marshal() ([]byte, error) { + return []byte{byte(status)}, nil +} + +// Unmarshal needed for protobuf compatibility +func (status *ProposalStatus) Unmarshal(data []byte) error { + *status = ProposalStatus(data[0]) + return nil +} + +// Marshals to JSON using string +func (status ProposalStatus) MarshalJSON() ([]byte, error) { + return json.Marshal(status.String()) +} + +// Unmarshals from JSON assuming Bech32 encoding +func (status *ProposalStatus) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + bz2, err := ProposalStatusFromString(s) + if err != nil { + return err + } + *status = bz2 + return nil +} + +// Turns VoteStatus byte to String +func (status ProposalStatus) String() string { + switch status { + case StatusDepositPeriod: + return "DepositPeriod" + case StatusVotingPeriod: + return "VotingPeriod" + case StatusPassed: + return "Passed" + case StatusRejected: + return "Rejected" + default: + return "" + } +} + +// For Printf / Sprintf, returns bech32 when using %s +// nolint: errcheck +func (status ProposalStatus) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(status.String())) + default: + // TODO: Do this conversion more directly + s.Write([]byte(fmt.Sprintf("%v", byte(status)))) + } +} + +//----------------------------------------------------------- +// Tally Results +type TallyResult struct { + Yes sdk.Dec `json:"yes"` + Abstain sdk.Dec `json:"abstain"` + No sdk.Dec `json:"no"` + NoWithVeto sdk.Dec `json:"no_with_veto"` +} + +// checks if two proposals are equal +func EmptyTallyResult() TallyResult { + return TallyResult{ + Yes: sdk.ZeroDec(), + Abstain: sdk.ZeroDec(), + No: sdk.ZeroDec(), + NoWithVeto: sdk.ZeroDec(), + } +} + +// checks if two proposals are equal +func (resultA TallyResult) Equals(resultB TallyResult) bool { + return resultA.Yes.Equal(resultB.Yes) && + resultA.Abstain.Equal(resultB.Abstain) && + resultA.No.Equal(resultB.No) && + resultA.NoWithVeto.Equal(resultB.NoWithVeto) +} diff --git a/app/v1/gov/queryable.go b/app/v1/gov/queryable.go new file mode 100644 index 000000000..8e76c6d93 --- /dev/null +++ b/app/v1/gov/queryable.go @@ -0,0 +1,334 @@ +package gov + +import ( + "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" + abci "github.com/tendermint/tendermint/abci/types" + "time" +) + +// query endpoints supported by the governance Querier +const ( + QueryProposals = "proposals" + QueryProposal = "proposal" + QueryDeposits = "deposits" + QueryDeposit = "deposit" + QueryVotes = "votes" + QueryVote = "vote" + QueryTally = "tally" +) + +func NewQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case QueryProposals: + return queryProposals(ctx, path[1:], req, keeper) + case QueryProposal: + return queryProposal(ctx, path[1:], req, keeper) + case QueryDeposits: + return queryDeposits(ctx, path[1:], req, keeper) + case QueryDeposit: + return queryDeposit(ctx, path[1:], req, keeper) + case QueryVotes: + return queryVotes(ctx, path[1:], req, keeper) + case QueryVote: + return queryVote(ctx, path[1:], req, keeper) + case QueryTally: + return queryTally(ctx, path[1:], req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown gov query endpoint") + } + } +} + +type ProposalOutput struct { + ProposalID uint64 `json:"proposal_id"` // ID of the proposal + Title string `json:"title"` // Title of the proposal + Description string `json:"description"` // Description of the proposal + ProposalType ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + + Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} + TallyResult TallyResult `json:"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 + Params Params `json:"param"` + TaxUsage TaxUsage `json:"tax_usage"` + Upgrade sdk.ProtocolDefinition `json:"protocol_definition"` +} + +type ProposalOutputs []ProposalOutput + +func ConvertProposalToProposalOutput(proposal Proposal) ProposalOutput { + + proposalOutput := ProposalOutput{ + ProposalID: proposal.GetProposalID(), + Title: proposal.GetTitle(), + Description: proposal.GetDescription(), + ProposalType: proposal.GetProposalType(), + + Status: proposal.GetStatus(), + TallyResult: proposal.GetTallyResult(), + + SubmitTime: proposal.GetSubmitTime(), + DepositEndTime: proposal.GetDepositEndTime(), + TotalDeposit: proposal.GetTotalDeposit(), + + VotingStartTime: proposal.GetVotingStartTime(), + VotingEndTime: proposal.GetVotingEndTime(), + Params: Params{}, + TaxUsage: proposal.GetTaxUsage(), + Upgrade: proposal.GetProtocolDefinition(), + } + + if proposal.GetProposalType() == ProposalTypeParameterChange { + proposalOutput.Params = proposal.(*ParameterProposal).Params + } + return proposalOutput +} + +func ConvertProposalsToProposalOutputs(proposals []Proposal) ProposalOutputs { + + var proposalOutputs ProposalOutputs + for _, proposal := range proposals { + proposalOutputs = append(proposalOutputs, ConvertProposalToProposalOutput(proposal)) + } + return proposalOutputs +} + +// Params for query 'custom/gov/proposal' +type QueryProposalParams struct { + ProposalID uint64 +} + +// nolint: unparam +func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryProposalParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, params.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + } + + proposalOutput := ConvertProposalToProposalOutput(proposal) + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposalOutput) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/deposit' +type QueryDepositParams struct { + ProposalID uint64 + Depositor sdk.AccAddress +} + +// nolint: unparam +func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryDepositParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, params.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + } + + if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { + return nil, ErrCodeDepositDeleted(DefaultCodespace, params.ProposalID) + } + + deposit, bool := keeper.GetDeposit(ctx, params.ProposalID, params.Depositor) + if !bool { + return nil, ErrCodeDepositNotExisted(DefaultCodespace, params.Depositor, params.ProposalID) + } + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposit) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/vote' +type QueryVoteParams struct { + ProposalID uint64 + Voter sdk.AccAddress +} + +// nolint: unparam +func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryVoteParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, params.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + } + + vote, bool := keeper.GetVote(ctx, params.ProposalID, params.Voter) + if !bool { + return nil, ErrCodeVoteNotExisted(DefaultCodespace, params.Voter, params.ProposalID) + } + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, vote) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/deposits' +type QueryDepositsParams struct { + ProposalID uint64 +} + +// nolint: unparam +func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryDepositsParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, params.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + } + + if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { + return nil, ErrCodeDepositDeleted(DefaultCodespace, params.ProposalID) + } + + var deposits []Deposit + depositsIterator := keeper.GetDeposits(ctx, params.ProposalID) + defer depositsIterator.Close() + for ; depositsIterator.Valid(); depositsIterator.Next() { + deposit := Deposit{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(depositsIterator.Value(), &deposit) + deposits = append(deposits, deposit) + } + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposits) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/votes' +type QueryVotesParams struct { + ProposalID uint64 +} + +// nolint: unparam +func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryVotesParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, params.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + } + + var votes []Vote + votesIterator := keeper.GetVotes(ctx, params.ProposalID) + defer votesIterator.Close() + for ; votesIterator.Valid(); votesIterator.Next() { + vote := Vote{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), &vote) + votes = append(votes, vote) + } + + if len(votes) == 0 { + return nil, nil + } + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, votes) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/proposals' +type QueryProposalsParams struct { + Voter sdk.AccAddress + Depositor sdk.AccAddress + ProposalStatus ProposalStatus + Limit uint64 +} + +// nolint: unparam +func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + var params QueryProposalsParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositor, params.ProposalStatus, params.Limit) + + proposalOutputs := ConvertProposalsToProposalOutputs(proposals) + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposalOutputs) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} + +// Params for query 'custom/gov/tally' +type QueryTallyParams struct { + ProposalID uint64 +} + +// nolint: unparam +func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { + // TODO: Dependant on #1914 + + var param QueryTallyParams + err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶m) + if err2 != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) + } + + proposal := keeper.GetProposal(ctx, param.ProposalID) + if proposal == nil { + return nil, ErrUnknownProposal(DefaultCodespace, param.ProposalID) + } + + var tallyResult TallyResult + + if proposal.GetStatus() == StatusDepositPeriod { + tallyResult = EmptyTallyResult() + } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { + tallyResult = proposal.GetTallyResult() + } else { + _, tallyResult, _ = tally(ctx, keeper, proposal) + } + + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) + if err2 != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + } + return bz, nil +} diff --git a/app/v1/gov/simulation/invariants.go b/app/v1/gov/simulation/invariants.go new file mode 100644 index 000000000..9beb6d1a8 --- /dev/null +++ b/app/v1/gov/simulation/invariants.go @@ -0,0 +1,15 @@ +package simulation + +import ( + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/mock/simulation" +) + +// AllInvariants tests all governance invariants +func AllInvariants() simulation.Invariant { + return func(ctx sdk.Context) error { + // TODO Add some invariants! + // Checking proposal queues, no passed-but-unexecuted proposals, etc. + return nil + } +} diff --git a/app/v1/gov/simulation/msgs.go b/app/v1/gov/simulation/msgs.go new file mode 100644 index 000000000..c831f7cb8 --- /dev/null +++ b/app/v1/gov/simulation/msgs.go @@ -0,0 +1,234 @@ +package simulation + +import ( + "fmt" + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" + "github.com/irisnet/irishub/modules/gov" + "math" + "math/rand" + "time" + + "github.com/irisnet/irishub/modules/mock/baseapp" + "github.com/irisnet/irishub/modules/mock/simulation" + +) + +const ( + denom = "iris-atto" +) + +// SimulateSubmittingVotingAndSlashingForProposal simulates creating a msg Submit Proposal +// voting on the proposal, and subsequently slashing the proposal. It is implemented using +// future operations. +// TODO: Vote more intelligently, so we can actually do some checks regarding votes passing or failing +// TODO: Actually check that validator slashings happened +func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { + handler := gov.NewHandler(k) + // The states are: + // column 1: All validators vote + // column 2: 90% vote + // column 3: 75% vote + // column 4: 40% vote + // column 5: 15% vote + // column 6: noone votes + // All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily, + // feel free to change. + numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{ + {20, 10, 0, 0, 0, 0}, + {55, 50, 20, 10, 0, 0}, + {25, 25, 30, 25, 30, 15}, + {0, 15, 30, 25, 30, 30}, + {0, 0, 20, 30, 30, 30}, + {0, 0, 0, 10, 10, 25}, + }) + statePercentageArray := []float64{1, .9, .75, .4, .15, 0} + curNumVotesState := 1 + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { + // 1) submit proposal now + sender := simulation.RandomAcc(r, accs) + msg, err := simulationCreateMsgSubmitProposal(r, sender) + if err != nil { + return "", nil, err + } + action, ok := simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + // don't schedule votes if proposal failed + if !ok { + return action, nil, nil + } + proposalID := k.GetLastProposalID(ctx) + // 2) Schedule operations for votes + // 2.1) first pick a number of people to vote. + curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) + numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState])) + // 2.2) select who votes and when + whoVotes := r.Perm(len(accs)) + // didntVote := whoVotes[numVotes:] + whoVotes = whoVotes[:numVotes] + // TODO: can't find GetVotingProcedure method, use customized votingPeriod + //votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod + votingPeriod := time.Duration(60) * time.Second + fops := make([]simulation.FutureOperation, numVotes+1) + for i := 0; i < numVotes; i++ { + whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) + fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, accs[whoVotes[i]], int64(proposalID))} + } + // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) + // TODO: Find a way to check if a validator was slashed other than just checking their balance a block + // before and after. + + return action, fops, nil + } +} + +// SimulateMsgSubmitProposal simulates a msg Submit Proposal +// Note: Currently doesn't ensure that the proposal txt is in JSON form +func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { + handler := gov.NewHandler(k) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { + sender := simulation.RandomAcc(r, accs) + msg, err := simulationCreateMsgSubmitProposal(r, sender) + if err != nil { + return "", nil, err + } + action, _ = simulateHandleMsgSubmitProposal(msg, sk, handler, ctx, event) + return action, nil, nil + } +} + +func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, handler sdk.Handler, ctx sdk.Context, event func(string)) (action string, ok bool) { + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + ok = result.IsOK() + if ok { + // Update pool to keep invariants + pool := sk.GetPool(ctx) + pool.BankKeeper.DecreaseLoosenToken(ctx, msg.InitialDeposit) + sk.SetPool(ctx, pool) + write() + } + event(fmt.Sprintf("gov/MsgSubmitProposal/%v", ok)) + action = fmt.Sprintf("TestMsgSubmitProposal: ok %v, msg %s", ok, msg.GetSignBytes()) + return +} + +func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) { + deposit := randomDeposit(r) + param := gov.Param{ + Key: "test", + Value: "value", + Subspace: "insert", + } + msg = gov.NewMsgSubmitProposal( + simulation.RandStringOfLength(r, 5), + simulation.RandStringOfLength(r, 5), + gov.ProposalTypeSystemHalt, + sender.Address, + deposit, + gov.Params{param}, + ) + if msg.ValidateBasic() != nil { + err = fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + return +} + +// SimulateMsgDeposit +func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + acc := simulation.RandomAcc(r, accs) + proposalID, ok := randomProposalID(r, k, ctx) + if !ok { + return "no-operation", nil, nil + } + deposit := randomDeposit(r) + msg := gov.NewMsgDeposit(acc.Address, uint64(proposalID), deposit) + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + ctx, write := ctx.CacheContext() + result := gov.NewHandler(k)(ctx, msg) + if result.IsOK() { + // Update pool to keep invariants + pool := sk.GetPool(ctx) + pool.BankKeeper.DecreaseLoosenToken(ctx, deposit) + sk.SetPool(ctx, pool) + write() + } + event(fmt.Sprintf("gov/MsgDeposit/%v", result.IsOK())) + action = fmt.Sprintf("TestMsgDeposit: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// SimulateMsgVote +// nolint: unparam +func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { + return operationSimulateMsgVote(k, sk, simulation.Account{}, -1) +} + +// nolint: unparam +func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, acc simulation.Account, proposalID int64) simulation.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + if acc.Equals(simulation.Account{}) { + acc = simulation.RandomAcc(r, accs) + } + + var ok bool + + if proposalID < 0 { + proposalID, ok = randomProposalID(r, k, ctx) + if !ok { + return "no-operation", nil, nil + } + } + option := randomVotingOption(r) + + msg := gov.NewMsgVote(acc.Address, uint64(proposalID), option) + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + ctx, write := ctx.CacheContext() + result := gov.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + + event(fmt.Sprintf("gov/MsgVote/%v", result.IsOK())) + action = fmt.Sprintf("TestMsgVote: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// Pick a random deposit +func randomDeposit(r *rand.Rand) sdk.Coins { + // TODO Choose based on account balance and min deposit + amount := int64(r.Intn(20)) + 1 + return sdk.Coins{sdk.NewInt64Coin(denom, amount)} +} + +// Pick a random proposal ID +func randomProposalID(r *rand.Rand, k gov.Keeper, ctx sdk.Context) (proposalID int64, ok bool) { + lastProposalID := k.GetLastProposalID(ctx) + if lastProposalID < 1 { + return 0, false + } + proposalID = int64(r.Intn(int(lastProposalID))) + return proposalID, true +} + +// Pick a random voting option +func randomVotingOption(r *rand.Rand) gov.VoteOption { + switch r.Intn(4) { + case 0: + return gov.OptionYes + case 1: + return gov.OptionAbstain + case 2: + return gov.OptionNo + case 3: + return gov.OptionNoWithVeto + } + panic("should not happen") +} diff --git a/app/v1/gov/simulation/sim_test.go b/app/v1/gov/simulation/sim_test.go new file mode 100644 index 000000000..c58058af2 --- /dev/null +++ b/app/v1/gov/simulation/sim_test.go @@ -0,0 +1,118 @@ +package simulation + +import ( + "encoding/json" + "math/rand" + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/bank" + "github.com/irisnet/irishub/modules/stake" + "github.com/irisnet/irishub/modules/gov" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" + distr "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/modules/guardian" +) + +// TestGovWithRandomMessages +func TestGovWithRandomMessages(t *testing.T) { + mapp := mock.NewApp() + + bank.RegisterCodec(mapp.Cdc) + gov.RegisterCodec(mapp.Cdc) + + bankKeeper := mapp.BankKeeper + stakeKey := mapp.KeyStake + stakeTKey := mapp.TkeyStake + paramKey := mapp.KeyParams + govKey := sdk.NewKVStoreKey("gov") + distrKey := sdk.NewKVStoreKey("distr") + guardianKey := sdk.NewKVStoreKey("guardian") + + paramKeeper := mapp.ParamsKeeper + stakeKeeper := stake.NewKeeper( + mapp.Cdc, stakeKey, + stakeTKey, bankKeeper, + paramKeeper.Subspace(stake.DefaultParamspace), + stake.DefaultCodespace, + ) + distrKeeper := distr.NewKeeper( + mapp.Cdc, + distrKey, + mapp.ParamsKeeper.Subspace(distr.DefaultParamspace), + mapp.BankKeeper, &stakeKeeper, mapp.FeeKeeper, + distr.DefaultCodespace, + ) + guardianKeeper := guardian.NewKeeper( + mapp.Cdc, + guardianKey, + guardian.DefaultCodespace, + ) + govKeeper := gov.NewKeeper( + govKey, + mapp.Cdc, + mapp.ParamsKeeper.Subspace(gov.DefaultParamSpace), + paramKeeper, + sdk.NewProtocolKeeper(mapp.KeyMain), + bankKeeper, + distrKeeper, + guardianKeeper, + stakeKeeper, + gov.DefaultCodespace, + ) + + mapp.Router().AddRoute("gov", []*sdk.KVStoreKey{govKey, mapp.KeyAccount, stakeKey, paramKey}, gov.NewHandler(govKeeper)) + mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + gov.EndBlocker(ctx, govKeeper) + return abci.ResponseEndBlock{} + }) + + err := mapp.CompleteSetup(govKey) + if err != nil { + panic(err) + } + + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + return json.RawMessage("{}") + } + + setup := func(r *rand.Rand, accs []simulation.Account) { + ctx := mapp.NewContext(false, abci.Header{}) + stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) + + gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState()) + } + + // Test with unscheduled votes + simulation.Simulate( + t, mapp.BaseApp, appStateFn, + []simulation.WeightedOperation{ + {2, SimulateMsgSubmitProposal(govKeeper, stakeKeeper)}, + {3, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + {20, SimulateMsgVote(govKeeper, stakeKeeper)}, + }, []simulation.RandSetup{ + setup, + }, []simulation.Invariant{ + //AllInvariants(), + }, 10, 100, + false, + ) + + // Test with scheduled votes + simulation.Simulate( + t, mapp.BaseApp, appStateFn, + []simulation.WeightedOperation{ + {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, + {5, SimulateMsgDeposit(govKeeper, stakeKeeper)}, + }, []simulation.RandSetup{ + setup, + }, []simulation.Invariant{ + AllInvariants(), + }, 10, 100, + false, + ) +} diff --git a/app/v1/gov/store_key.go b/app/v1/gov/store_key.go new file mode 100644 index 000000000..40337ba07 --- /dev/null +++ b/app/v1/gov/store_key.go @@ -0,0 +1,5 @@ +package gov + +const ( + Prefix = "gov/" +) diff --git a/app/v1/gov/tags/tags.go b/app/v1/gov/tags/tags.go new file mode 100644 index 000000000..a19bcee95 --- /dev/null +++ b/app/v1/gov/tags/tags.go @@ -0,0 +1,28 @@ +// nolint +package tags + +import ( + sdk "github.com/irisnet/irishub/types" +) + +var ( + ActionSubmitProposal = []byte("submit-proposal") + ActionDeposit = []byte("deposit") + ActionVote = []byte("vote") + ActionProposalDropped = []byte("proposal-dropped") + ActionProposalPassed = []byte("proposal-passed") + ActionProposalRejected = []byte("proposal-rejected") + + Action = sdk.TagAction + Proposer = "proposer" + ProposalID = "proposal-id" + VotingPeriodStart = "voting-period-start" + Depositor = "depositor" + Voter = "voter" + //////////////////// iris begin /////////////////////////// + Param = "param" + Usage = "usage" + Percent = "percent" + DestAddress = "dest-address" + //////////////////// iris end ///////////////////////////// +) diff --git a/app/v1/gov/tally.go b/app/v1/gov/tally.go new file mode 100644 index 000000000..71374014a --- /dev/null +++ b/app/v1/gov/tally.go @@ -0,0 +1,94 @@ +package gov + +import ( + sdk "github.com/irisnet/irishub/types" +) + +type ProposalResult string + +const ( + PASS ProposalResult = "pass" + REJECT ProposalResult = "reject" + REJECTVETO ProposalResult = "reject-veto" +) + +// validatorGovInfo used for tallying +type validatorGovInfo struct { + Address sdk.ValAddress // address of the validator operator + Power sdk.Dec // Power of a Validator + Vote VoteOption // Vote of the validator +} + +func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (result ProposalResult, tallyResults TallyResult, votingVals map[string]bool) { + results := make(map[VoteOption]sdk.Dec) + results[OptionYes] = sdk.ZeroDec() + results[OptionAbstain] = sdk.ZeroDec() + results[OptionNo] = sdk.ZeroDec() + results[OptionNoWithVeto] = sdk.ZeroDec() + + totalVotingPower := sdk.ZeroDec() + systemVotingPower := sdk.ZeroDec() + currValidators := make(map[string]validatorGovInfo) + votingVals = make(map[string]bool) + keeper.vs.IterateBondedValidatorsByPower(ctx, func(index int64, validator sdk.Validator) (stop bool) { + currValidators[validator.GetOperator().String()] = validatorGovInfo{ + Address: validator.GetOperator(), + Power: validator.GetPower(), + Vote: OptionEmpty, + } + systemVotingPower = systemVotingPower.Add(validator.GetPower()) + return false + }) + // iterate over all the votes + votesIterator := keeper.GetVotes(ctx, proposal.GetProposalID()) + defer votesIterator.Close() + for ; votesIterator.Valid(); votesIterator.Next() { + vote := &Vote{} + keeper.cdc.MustUnmarshalBinaryLengthPrefixed(votesIterator.Value(), vote) + + // if validator, just record it in the map + valAddrStr := sdk.ValAddress(vote.Voter).String() + if val, ok := currValidators[valAddrStr]; ok { + val.Vote = vote.Option + results[val.Vote] = results[val.Vote].Add(val.Power) + totalVotingPower = totalVotingPower.Add(val.Power) + votingVals[valAddrStr] = true + } + } + + //////////////////// iris begin /////////////////////////// + tallyingProcedure := keeper.GetTallyingProcedure(ctx, proposal) + //////////////////// iris end ///////////////////////////// + + tallyResults = TallyResult{ + Yes: results[OptionYes], + Abstain: results[OptionAbstain], + No: results[OptionNo], + NoWithVeto: results[OptionNoWithVeto], + } + + + // If no one votes, proposal fails + if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) { + return REJECT, tallyResults, votingVals + } + + //if more than 1/3 of voters abstain, proposal fails + if tallyingProcedure.Participation.GT(totalVotingPower.Quo(systemVotingPower)) { + return REJECT, tallyResults, votingVals + } + + + // If more than 1/3 of voters veto, proposal fails + if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { + return REJECTVETO, tallyResults, votingVals + } + + // If more than 1/2 of non-abstaining voters vote Yes, proposal passes + if results[OptionYes].Quo(totalVotingPower).GT(tallyingProcedure.Threshold) { + return PASS, tallyResults, votingVals + } + // If more than 1/2 of non-abstaining voters vote No, proposal fails + + return REJECT, tallyResults, votingVals +} diff --git a/app/v1/gov/test_common.go b/app/v1/gov/test_common.go new file mode 100644 index 000000000..d276b7338 --- /dev/null +++ b/app/v1/gov/test_common.go @@ -0,0 +1,141 @@ +package gov + +import ( + "bytes" + "github.com/stretchr/testify/require" + "log" + "sort" + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + "fmt" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/modules/guardian" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/params" + "github.com/irisnet/irishub/modules/stake" + sdk "github.com/irisnet/irishub/types" +) + +// initialize the mock application for this module +func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.AccAddress, []crypto.PubKey, []crypto.PrivKey) { + mapp := mock.NewApp() + + stake.RegisterCodec(mapp.Cdc) + RegisterCodec(mapp.Cdc) + + keyGov := sdk.NewKVStoreKey("gov") + keyDistr := sdk.NewKVStoreKey("distr") + + paramsKeeper := params.NewKeeper( + mapp.Cdc, + sdk.NewKVStoreKey("params"), + sdk.NewTransientStoreKey("transient_params"), + ) + feeKeeper := auth.NewFeeKeeper( + mapp.Cdc, + sdk.NewKVStoreKey("fee"), + paramsKeeper.Subspace(auth.DefaultParamSpace), + ) + + ck := bank.NewBaseKeeper(mapp.AccountKeeper) + sk := stake.NewKeeper( + mapp.Cdc, + mapp.KeyStake, mapp.TkeyStake, + mapp.BankKeeper, mapp.ParamsKeeper.Subspace(stake.DefaultParamspace), + stake.DefaultCodespace) + dk := distribution.NewKeeper(mapp.Cdc, keyDistr, paramsKeeper.Subspace(distribution.DefaultParamspace), ck, sk, feeKeeper, DefaultCodespace) + guardianKeeper := guardian.NewKeeper(mapp.Cdc, sdk.NewKVStoreKey("guardian"), guardian.DefaultCodespace) + gk := NewKeeper(keyGov, mapp.Cdc, paramsKeeper.Subspace(DefaultParamSpace), paramsKeeper, sdk.NewProtocolKeeper(sdk.NewKVStoreKey("main")), ck, dk, guardianKeeper, sk, DefaultCodespace) + + mapp.Router().AddRoute("gov", []*sdk.KVStoreKey{keyGov}, NewHandler(gk)) + + mapp.SetEndBlocker(getEndBlocker(gk)) + mapp.SetInitChainer(getInitChainer(mapp, gk, sk)) + + require.NoError(t, mapp.CompleteSetup(keyGov)) + + coin, _ := sdk.IRIS.ConvertToMinCoin(fmt.Sprintf("%d%s", 1042, sdk.NativeTokenName)) + genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{coin}) + + mock.SetGenesis(mapp, genAccs) + + return mapp, gk, sk, addrs, pubKeys, privKeys +} + +// gov and stake endblocker +func getEndBlocker(keeper Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + tags := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + Tags: tags, + } + } +} + +// gov and stake initchainer +func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + + stakeGenesis := stake.DefaultGenesisState() + + validators, err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) + if err != nil { + panic(err) + } + InitGenesis(ctx, keeper, GenesisState{ + Params: DefaultParams(), + }) + return abci.ResponseInitChain{ + Validators: validators, + } + } +} + +// Sorts Addresses +func SortAddresses(addrs []sdk.AccAddress) { + var byteAddrs [][]byte + for _, addr := range addrs { + byteAddrs = append(byteAddrs, addr.Bytes()) + } + SortByteArrays(byteAddrs) + for i, byteAddr := range byteAddrs { + addrs[i] = byteAddr + } +} + +// implement `Interface` in sort package. +type sortByteArrays [][]byte + +func (b sortByteArrays) Len() int { + return len(b) +} + +func (b sortByteArrays) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i], b[j]) { + case -1: + return true + case 0, 1: + return false + default: + log.Panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + return false + } +} + +func (b sortByteArrays) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + +// Public +func SortByteArrays(src [][]byte) [][]byte { + sorted := sortByteArrays(src) + sort.Sort(sorted) + return sorted +} diff --git a/app/v1/invariants.go b/app/v1/invariants.go new file mode 100644 index 000000000..83e7466ae --- /dev/null +++ b/app/v1/invariants.go @@ -0,0 +1,43 @@ +package v1 + +import ( + "fmt" + + banksim "github.com/irisnet/irishub/modules/bank/simulation" + distrsim "github.com/irisnet/irishub/modules/distribution/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" + stakesim "github.com/irisnet/irishub/modules/stake/simulation" + sdk "github.com/irisnet/irishub/types" +) + +func (p *ProtocolV1) runtimeInvariants() []simulation.Invariant { + return []simulation.Invariant{ + banksim.NonnegativeBalanceInvariant(p.accountMapper), + + distrsim.ValAccumInvariants(p.distrKeeper, p.StakeKeeper), + distrsim.DelAccumInvariants(p.distrKeeper, p.StakeKeeper), + distrsim.CanWithdrawInvariant(p.distrKeeper, p.StakeKeeper), + + stakesim.SupplyInvariants(p.bankKeeper, p.StakeKeeper, + p.feeKeeper, p.distrKeeper, p.accountMapper), + stakesim.NonNegativePowerInvariant(p.StakeKeeper), + stakesim.PositiveDelegationInvariant(p.StakeKeeper), + stakesim.DelegatorSharesInvariant(p.StakeKeeper), + } +} + +func (p *ProtocolV1) assertRuntimeInvariants(ctx sdk.Context) { + if p.invariantLevel != sdk.InvariantError && p.invariantLevel != sdk.InvariantPanic { + return + } + invariants := p.runtimeInvariants() + for _, inv := range invariants { + if err := inv(ctx); err != nil { + if p.invariantLevel == sdk.InvariantPanic { + panic(fmt.Errorf("invariant broken: %s", err)) + } else { + p.logger.Error(fmt.Sprintf("Invariant broken: height %d, reason %s", ctx.BlockHeight(), err.Error())) + } + } + } +} diff --git a/app/v1/protocol_v1.go b/app/v1/protocol_v1.go new file mode 100644 index 000000000..c1e69d402 --- /dev/null +++ b/app/v1/protocol_v1.go @@ -0,0 +1,441 @@ +package v1 + +import ( + "fmt" + "sort" + "github.com/irisnet/irishub/app/protocol" + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + distr "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/app/v1/gov" + "github.com/irisnet/irishub/modules/guardian" + "github.com/irisnet/irishub/modules/mint" + "github.com/irisnet/irishub/modules/params" + "github.com/irisnet/irishub/modules/service" + "github.com/irisnet/irishub/modules/slashing" + "github.com/irisnet/irishub/modules/stake" + "github.com/irisnet/irishub/modules/upgrade" + "github.com/irisnet/irishub/modules/upgrade/params" + sdk "github.com/irisnet/irishub/types" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + "strings" +) + +var _ protocol.Protocol = (*ProtocolV1)(nil) + +type ProtocolV1 struct { + version uint64 + cdc *codec.Codec + logger log.Logger + invariantLevel string + + // Manage getting and setting accounts + accountMapper auth.AccountKeeper + feeKeeper auth.FeeKeeper + bankKeeper bank.Keeper + StakeKeeper stake.Keeper + slashingKeeper slashing.Keeper + mintKeeper mint.Keeper + distrKeeper distr.Keeper + protocolKeeper sdk.ProtocolKeeper + govKeeper gov.Keeper + paramsKeeper params.Keeper + serviceKeeper service.Keeper + guardianKeeper guardian.Keeper + upgradeKeeper upgrade.Keeper + + router protocol.Router // handle any kind of message + queryRouter protocol.QueryRouter // router for redirecting query calls + + anteHandler sdk.AnteHandler // ante handler for fee and auth + feeRefundHandler sdk.FeeRefundHandler // fee handler for fee refund + feePreprocessHandler sdk.FeePreprocessHandler // fee handler for fee preprocessor + + // may be nil + initChainer sdk.InitChainer1 // initialize state with validators and state blob + beginBlocker sdk.BeginBlocker // logic to run before any txs + endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes +} + +func NewProtocolV1(version uint64, log log.Logger, pk sdk.ProtocolKeeper, invariantLevel string) *ProtocolV1 { + p0 := ProtocolV1{ + version: version, + logger: log, + protocolKeeper: pk, + invariantLevel: strings.ToLower(strings.TrimSpace(invariantLevel)), + router: protocol.NewRouter(), + queryRouter: protocol.NewQueryRouter(), + } + return &p0 +} + +// load the configuration of this Protocol +func (p *ProtocolV1) Load() { + p.configCodec() + p.configKeepers() + p.configRouters() + p.configFeeHandlers() + p.configParams() +} + +// verison0 don't need the init +func (p *ProtocolV1) Init() { + +} + +// verison0 tx codec +func (p *ProtocolV1) GetCodec() *codec.Codec { + return p.cdc +} + +func (p *ProtocolV1) configCodec() { + p.cdc = MakeCodec() +} + +func MakeCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + distr.RegisterCodec(cdc) + slashing.RegisterCodec(cdc) + gov.RegisterCodec(cdc) + upgrade.RegisterCodec(cdc) + service.RegisterCodec(cdc) + guardian.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} + +func (p *ProtocolV1) GetVersion() uint64 { + return p.version +} + +// create all Keepers +func (p *ProtocolV1) configKeepers() { + // define the AccountKeeper + p.accountMapper = auth.NewAccountKeeper( + p.cdc, + protocol.KeyAccount, // target store + auth.ProtoBaseAccount, // prototype + ) + + // add handlers + p.guardianKeeper = guardian.NewKeeper( + p.cdc, + protocol.KeyGuardian, + guardian.DefaultCodespace, + ) + p.bankKeeper = bank.NewBaseKeeper(p.accountMapper) + p.paramsKeeper = params.NewKeeper( + p.cdc, + protocol.KeyParams, protocol.TkeyParams, + ) + p.feeKeeper = auth.NewFeeKeeper( + p.cdc, + protocol.KeyFee, p.paramsKeeper.Subspace(auth.DefaultParamSpace), + ) + stakeKeeper := stake.NewKeeper( + p.cdc, + protocol.KeyStake, protocol.TkeyStake, + p.bankKeeper, p.paramsKeeper.Subspace(stake.DefaultParamspace), + stake.DefaultCodespace, + ) + p.mintKeeper = mint.NewKeeper(p.cdc, protocol.KeyMint, + p.paramsKeeper.Subspace(mint.DefaultParamSpace), + p.bankKeeper, p.feeKeeper, + ) + p.distrKeeper = distr.NewKeeper( + p.cdc, + protocol.KeyDistr, + p.paramsKeeper.Subspace(distr.DefaultParamspace), + p.bankKeeper, &stakeKeeper, p.feeKeeper, + distr.DefaultCodespace, + ) + p.slashingKeeper = slashing.NewKeeper( + p.cdc, + protocol.KeySlashing, + &stakeKeeper, p.paramsKeeper.Subspace(slashing.DefaultParamspace), + slashing.DefaultCodespace, + ) + + p.govKeeper = gov.NewKeeper( + protocol.KeyGov, + p.cdc, + p.paramsKeeper.Subspace(gov.DefaultParamSpace), + p.paramsKeeper, + p.protocolKeeper, + p.bankKeeper, + p.distrKeeper, + p.guardianKeeper, + &stakeKeeper, + gov.DefaultCodespace, + ) + + p.serviceKeeper = service.NewKeeper( + p.cdc, + protocol.KeyService, + p.bankKeeper, + p.guardianKeeper, + service.DefaultCodespace, + p.paramsKeeper.Subspace(service.DefaultParamSpace), + ) + + // register the staking hooks + // NOTE: StakeKeeper above are passed by reference, + // so that it can be modified like below: + p.StakeKeeper = *stakeKeeper.SetHooks( + NewHooks(p.distrKeeper.Hooks(), p.slashingKeeper.Hooks())) + + p.upgradeKeeper = upgrade.NewKeeper(p.cdc, protocol.KeyUpgrade, p.protocolKeeper, p.StakeKeeper) +} + +// configure all Routers +func (p *ProtocolV1) configRouters() { + p.router. + AddRoute("bank", bank.NewHandler(p.bankKeeper)). + AddRoute("stake", stake.NewHandler(p.StakeKeeper)). + AddRoute("slashing", slashing.NewHandler(p.slashingKeeper)). + AddRoute("distr", distr.NewHandler(p.distrKeeper)). + AddRoute("gov", gov.NewHandler(p.govKeeper)). + AddRoute("service", service.NewHandler(p.serviceKeeper)). + AddRoute("guardian", guardian.NewHandler(p.guardianKeeper)) + p.queryRouter. + AddRoute("gov", gov.NewQuerier(p.govKeeper)). + AddRoute("stake", stake.NewQuerier(p.StakeKeeper, p.cdc)) +} + +// configure all Stores +func (p *ProtocolV1) configFeeHandlers() { + p.anteHandler = auth.NewAnteHandler(p.accountMapper, p.feeKeeper) + p.feeRefundHandler = auth.NewFeeRefundHandler(p.accountMapper, p.feeKeeper) + p.feePreprocessHandler = auth.NewFeePreprocessHandler(p.feeKeeper) +} + +// configure all Stores +func (p *ProtocolV1) GetKVStoreKeyList() []*sdk.KVStoreKey { + return []*sdk.KVStoreKey{ + protocol.KeyMain, + protocol.KeyAccount, + protocol.KeyStake, + protocol.KeyMint, + protocol.KeyDistr, + protocol.KeySlashing, + protocol.KeyGov, + protocol.KeyFee, + protocol.KeyParams, + protocol.KeyUpgrade, + protocol.KeyService, + protocol.KeyGuardian} +} + +// configure all Stores +func (p *ProtocolV1) configParams() { + + params.RegisterParamSet(&mint.Params{}, &slashing.Params{}, &service.Params{}, &auth.Params{}, &stake.Params{}, &distr.Params{}) + + params.SetParamReadWriter(p.paramsKeeper.Subspace(params.GovParamspace).WithTypeTable( + params.NewTypeTable( + upgradeparams.UpgradeParameter.GetStoreKey(), upgradeparams.Params{}, + )), + &upgradeparams.UpgradeParameter, ) +} + +// application updates every end block +func (p *ProtocolV1) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + // mint new tokens for this new block + tags := mint.BeginBlocker(ctx, p.mintKeeper) + + // distribute rewards from previous block + distr.BeginBlocker(ctx, req, p.distrKeeper) + + slashTags := slashing.BeginBlocker(ctx, req, p.slashingKeeper) + + tags = tags.AppendTags(slashTags) + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +func (p *ProtocolV1) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + tags := gov.EndBlocker(ctx, p.govKeeper) + validatorUpdates := stake.EndBlocker(ctx, p.StakeKeeper) + tags = tags.AppendTags(service.EndBlocker(ctx, p.serviceKeeper)) + tags = tags.AppendTags(upgrade.EndBlocker(ctx, p.upgradeKeeper)) + + p.assertRuntimeInvariants(ctx) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Tags: tags, + } +} + +// custom logic for iris initialization +// just 0 version need Initchainer +func (p *ProtocolV1) InitChainer(ctx sdk.Context, DeliverTx sdk.DeliverTx, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + + var genesisFileState GenesisFileState + err := p.cdc.UnmarshalJSON(stateJSON, &genesisFileState) + if err != nil { + panic(err) + } + + genesisState := convertToGenesisState(genesisFileState) + // sort by account number to maintain consistency + sort.Slice(genesisState.Accounts, func(i, j int) bool { + return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber + }) + + // load the accounts + for _, gacc := range genesisState.Accounts { + acc := gacc.ToAccount() + acc.AccountNumber = p.accountMapper.GetNextAccountNumber(ctx) + p.accountMapper.SetGenesisAccount(ctx, acc) + } + + //upgrade.InitGenesis(ctx, p.upgradeKeeper, p.Router(), genesisState.UpgradeData) + + // load the initial stake information + validators, err := stake.InitGenesis(ctx, p.StakeKeeper, genesisState.StakeData) + if err != nil { + panic(err) + } + gov.InitGenesis(ctx, p.govKeeper, genesisState.GovData) + + // load the address to pubkey map + auth.InitGenesis(ctx, p.feeKeeper, p.accountMapper, genesisState.AuthData) + slashing.InitGenesis(ctx, p.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) + mint.InitGenesis(ctx, p.mintKeeper, genesisState.MintData) + distr.InitGenesis(ctx, p.distrKeeper, genesisState.DistrData) + err = IrisValidateGenesisState(genesisState) + if err != nil { + panic(err) // TODO find a way to do this w/o panics + } + + if len(genesisState.GenTxs) > 0 { + for _, genTx := range genesisState.GenTxs { + var tx auth.StdTx + err = p.cdc.UnmarshalJSON(genTx, &tx) + if err != nil { + panic(err) + } + bz := p.cdc.MustMarshalBinaryLengthPrefixed(tx) + res := DeliverTx(bz) + if !res.IsOK() { + panic(res.Log) + } + } + + validators = p.StakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + } + + // sanity check + if len(req.Validators) > 0 { + if len(req.Validators) != len(validators) { + panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d)", + len(req.Validators), len(validators))) + } + sort.Sort(abci.ValidatorUpdates(req.Validators)) + sort.Sort(abci.ValidatorUpdates(validators)) + for i, val := range validators { + if !val.Equal(req.Validators[i]) { + panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i)) + } + } + } + + service.InitGenesis(ctx, p.serviceKeeper, genesisState.ServiceData) + guardian.InitGenesis(ctx, p.guardianKeeper, genesisState.GuardianData) + upgrade.InitGenesis(ctx, p.upgradeKeeper, genesisState.UpgradeData) + return abci.ResponseInitChain{ + Validators: validators, + } +} + +func (p *ProtocolV1) GetRouter() protocol.Router { + return p.router +} +func (p *ProtocolV1) GetQueryRouter() protocol.QueryRouter { + return p.queryRouter +} +func (p *ProtocolV1) GetAnteHandler() sdk.AnteHandler { + return p.anteHandler +} +func (p *ProtocolV1) GetFeeRefundHandler() sdk.FeeRefundHandler { + return p.feeRefundHandler +} +func (p *ProtocolV1) GetFeePreprocessHandler() sdk.FeePreprocessHandler { + return p.feePreprocessHandler +} +func (p *ProtocolV1) GetInitChainer() sdk.InitChainer1 { + return p.InitChainer +} +func (p *ProtocolV1) GetBeginBlocker() sdk.BeginBlocker { + return p.BeginBlocker +} +func (p *ProtocolV1) GetEndBlocker() sdk.EndBlocker { + return p.EndBlocker +} + +// Combined Staking Hooks +type Hooks struct { + dh distr.Hooks + sh slashing.Hooks +} + +func NewHooks(dh distr.Hooks, sh slashing.Hooks) Hooks { + return Hooks{dh, sh} +} + +var _ sdk.StakingHooks = Hooks{} + +func (h Hooks) OnValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.OnValidatorCreated(ctx, valAddr) + h.sh.OnValidatorCreated(ctx, valAddr) +} +func (h Hooks) OnValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { + h.dh.OnValidatorModified(ctx, valAddr) + h.sh.OnValidatorModified(ctx, valAddr) +} + +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorRemoved(ctx, consAddr, valAddr) + h.sh.OnValidatorRemoved(ctx, consAddr, valAddr) +} + +func (h Hooks) OnValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorBonded(ctx, consAddr, valAddr) + h.sh.OnValidatorBonded(ctx, consAddr, valAddr) +} + +func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) + h.sh.OnValidatorPowerDidChange(ctx, consAddr, valAddr) +} + +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.dh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) + h.sh.OnValidatorBeginUnbonding(ctx, consAddr, valAddr) +} + +func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationCreated(ctx, delAddr, valAddr) + h.sh.OnDelegationCreated(ctx, delAddr, valAddr) +} + +func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr) + h.sh.OnDelegationSharesModified(ctx, delAddr, valAddr) +} + +func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationRemoved(ctx, delAddr, valAddr) + h.sh.OnDelegationRemoved(ctx, delAddr, valAddr) +} diff --git a/client/gov/cli/query.go b/client/gov/cli/query.go index 957768fb9..1c8b0d248 100644 --- a/client/gov/cli/query.go +++ b/client/gov/cli/query.go @@ -6,7 +6,7 @@ import ( "github.com/irisnet/irishub/client/context" client "github.com/irisnet/irishub/client/gov" "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/modules/gov" + "github.com/irisnet/irishub/app/v1/gov" "github.com/irisnet/irishub/modules/params" sdk "github.com/irisnet/irishub/types" "github.com/spf13/cobra" diff --git a/client/gov/cli/sendtx.go b/client/gov/cli/sendtx.go index 55140de31..25181fc71 100644 --- a/client/gov/cli/sendtx.go +++ b/client/gov/cli/sendtx.go @@ -8,7 +8,7 @@ import ( client "github.com/irisnet/irishub/client/gov" "github.com/irisnet/irishub/client/utils" "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/modules/gov" + "github.com/irisnet/irishub/app/v1/gov" "github.com/irisnet/irishub/modules/params" sdk "github.com/irisnet/irishub/types" "github.com/spf13/cobra" @@ -92,7 +92,7 @@ func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(flagTitle, "", "title of proposal") cmd.Flags().String(flagDescription, "", "description of proposal") - cmd.Flags().String(flagProposalType, "", "proposalType of proposal,eg:ParameterChange/SoftwareUpgrade/SystemHalt/TxTaxUsage") + cmd.Flags().String(flagProposalType, "", "proposalType of proposal,eg:ParameterChange/SoftwareUpgrade/SystemHalt/TxTaxUsage/Text") cmd.Flags().String(flagDeposit, "", "deposit of proposal") //////////////////// iris begin /////////////////////////// cmd.Flags().StringSlice(flagParam, []string{}, "parameter of proposal,eg. [{key:key,value:value,op:update}]") diff --git a/client/gov/lcd/query.go b/client/gov/lcd/query.go index 5390c037d..ebb2ae1fe 100644 --- a/client/gov/lcd/query.go +++ b/client/gov/lcd/query.go @@ -9,7 +9,7 @@ import ( client "github.com/irisnet/irishub/client/gov" "github.com/irisnet/irishub/client/utils" "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/modules/gov" + "github.com/irisnet/irishub/app/v1/gov" sdk "github.com/irisnet/irishub/types" "github.com/pkg/errors" ) diff --git a/client/gov/lcd/sendtx.go b/client/gov/lcd/sendtx.go index 52b4343a1..ac18aac48 100644 --- a/client/gov/lcd/sendtx.go +++ b/client/gov/lcd/sendtx.go @@ -9,7 +9,7 @@ import ( client "github.com/irisnet/irishub/client/gov" "github.com/irisnet/irishub/client/utils" "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/modules/gov" + "github.com/irisnet/irishub/app/v1/gov" sdk "github.com/irisnet/irishub/types" ) diff --git a/version/version.go b/version/version.go index 5281ad7a9..b537209f6 100644 --- a/version/version.go +++ b/version/version.go @@ -8,8 +8,8 @@ import ( ) // Version - Iris Version -const ProtocolVersion = 0 -const Version = "0.10.1" +const ProtocolVersion = 1 +const Version = "0.10.2" // GitCommit set by build flags var GitCommit = ""