diff --git a/Makefile b/Makefile index 376855e0..a2a6252d 100644 --- a/Makefile +++ b/Makefile @@ -138,6 +138,9 @@ proto-gen: ### Local Testnet (docker) ### ############################################################################### +start: install + ./scripts/local/start.sh + localnet-alliance-rmi: $(DOCKER) rmi terra-money/localnet-alliance 2>/dev/null; true diff --git a/app/app.go b/app/app.go index bffac4bf..819f9afd 100644 --- a/app/app.go +++ b/app/app.go @@ -62,7 +62,6 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" - govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" @@ -109,6 +108,8 @@ import ( appparams "github.com/terra-money/alliance/app/params" custombankmodule "github.com/terra-money/alliance/custom/bank" custombankkeeper "github.com/terra-money/alliance/custom/bank/keeper" + customgovmodule "github.com/terra-money/alliance/custom/gov" + customgovkeeper "github.com/terra-money/alliance/custom/gov/keeper" "github.com/terra-money/alliance/app/openapiconsole" @@ -153,7 +154,7 @@ var ( // ModuleBasics defines the module BasicManager is in charge of setting up basic, // non-dependant module elements, such as codec registration - // and genesis verification. + // and genesis verification.customgovmodule ModuleBasics = module.NewBasicManager( auth.AppModuleBasic{}, authzmodule.AppModuleBasic{}, @@ -238,7 +239,7 @@ type App struct { SlashingKeeper slashingkeeper.Keeper MintKeeper mintkeeper.Keeper DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper + GovKeeper customgovkeeper.Keeper CrisisKeeper crisiskeeper.Keeper UpgradeKeeper upgradekeeper.Keeper ParamsKeeper paramskeeper.Keeper @@ -510,11 +511,7 @@ func New( AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(alliancemoduletypes.RouterKey, alliancemodule.NewAllianceProposalHandler(app.AllianceKeeper)) govConfig := govtypes.DefaultConfig() - /* - Example of setting gov params: - govConfig.MaxMetadataLen = 10000 - */ - govKeeper := govkeeper.NewKeeper( + govKeeper := customgovkeeper.NewKeeper( appCodec, keys[govtypes.StoreKey], app.AccountKeeper, @@ -563,7 +560,7 @@ func New( feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), - gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + customgovmodule.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName)), distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), @@ -890,6 +887,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(minttypes.ModuleName) paramsKeeper.Subspace(distrtypes.ModuleName) paramsKeeper.Subspace(slashingtypes.ModuleName) + paramsKeeper.Subspace(govtypes.ModuleName) paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govv1.ParamKeyTable()) //nolint:staticcheck paramsKeeper.Subspace(crisistypes.ModuleName) paramsKeeper.Subspace(ibctransfertypes.ModuleName) diff --git a/cmd/allianced/cmd/root.go b/cmd/allianced/cmd/root.go index cf59390e..1dca8707 100644 --- a/cmd/allianced/cmd/root.go +++ b/cmd/allianced/cmd/root.go @@ -39,7 +39,7 @@ import ( "github.com/terra-money/alliance/app/params" ) -// NewRootCmd creates a new root command for simd. It is called once in the +// NewRootCmd creates a new root command for allianced. It is called once in the // main function. func NewRootCmd() (*cobra.Command, params.EncodingConfig) { encodingConfig := app.MakeTestEncodingConfig() @@ -54,7 +54,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { WithViper("") // In app, we don't use any prefix for env variables. rootCmd := &cobra.Command{ - Use: "simd", + Use: "allianced", Short: "simulation app", PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { // set the default command outputs diff --git a/custom/gov/keeper/keeper.go b/custom/gov/keeper/keeper.go new file mode 100644 index 00000000..eb39eaa3 --- /dev/null +++ b/custom/gov/keeper/keeper.go @@ -0,0 +1,212 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + accountkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + "github.com/cosmos/cosmos-sdk/x/gov/types" + v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + custombankkeeper "github.com/terra-money/alliance/custom/bank/keeper" + alliancekeeper "github.com/terra-money/alliance/x/alliance/keeper" + alliancetypes "github.com/terra-money/alliance/x/alliance/types" +) + +type Keeper struct { + govkeeper.Keeper + + key storetypes.StoreKey + ak *alliancekeeper.Keeper + sk *stakingkeeper.Keeper + acck accountkeeper.AccountKeeper + bk *custombankkeeper.Keeper +} + +var _ = govkeeper.Keeper{} + +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + authKeeper accountkeeper.AccountKeeper, + bankKeeper custombankkeeper.Keeper, + stakingKeeper *stakingkeeper.Keeper, + router *baseapp.MsgServiceRouter, + config types.Config, + authority string, +) Keeper { + govKeeper := govkeeper.NewKeeper(cdc, + key, + authKeeper, + bankKeeper, + stakingKeeper, + router, + config, + authority, + ) + + keeper := Keeper{ + Keeper: *govKeeper, + ak: nil, + bk: nil, + sk: nil, + acck: authKeeper, + key: key, + } + return keeper +} + +func (k *Keeper) RegisterKeepers(ak *alliancekeeper.Keeper, bk *custombankkeeper.Keeper, sk *stakingkeeper.Keeper) { + k.ak = ak + k.bk = bk + k.sk = sk +} + +// deleteVote deletes a vote from a given proposalID and voter from the store +func (k Keeper) deleteVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) { + store := ctx.KVStore(k.key) + store.Delete(types.VoteKey(proposalID, voterAddr)) +} + +func (k *Keeper) Tally(ctx sdk.Context, proposal v1.Proposal) (passes bool, burnDeposits bool, tallyResults v1.TallyResult) { + results := make(map[v1.VoteOption]sdk.Dec) + results[v1.OptionYes] = sdk.ZeroDec() + results[v1.OptionAbstain] = sdk.ZeroDec() + results[v1.OptionNo] = sdk.ZeroDec() + results[v1.OptionNoWithVeto] = sdk.ZeroDec() + + totalVotingPower := sdk.ZeroDec() + currValidators := make(map[string]v1.ValidatorGovInfo) + + // fetch all the bonded validators, insert them into currValidators + k.sk.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) (stop bool) { + currValidators[validator.GetOperator().String()] = v1.NewValidatorGovInfo( + validator.GetOperator(), + validator.GetBondedTokens(), + validator.GetDelegatorShares(), + sdk.ZeroDec(), + v1.WeightedVoteOptions{}, + ) + + return false + }) + + k.IterateVotes(ctx, proposal.Id, func(vote v1.Vote) bool { + // if validator, just record it in the map + voter := sdk.MustAccAddressFromBech32(vote.Voter) + + valAddrStr := sdk.ValAddress(voter.Bytes()).String() + if val, ok := currValidators[valAddrStr]; ok { + val.Vote = vote.Options + currValidators[valAddrStr] = val + } + + // iterate over all delegations from voter, deduct from any delegated-to validators + k.sk.IterateDelegations(ctx, voter, func(index int64, delegation stakingtypes.DelegationI) (stop bool) { + valAddrStr := delegation.GetValidatorAddr().String() + + if val, ok := currValidators[valAddrStr]; ok { + // There is no need to handle the special case that validator address equal to voter address. + // Because voter's voting power will tally again even if there will be deduction of voter's voting power from validator. + val.DelegatorDeductions = val.DelegatorDeductions.Add(delegation.GetShares()) + currValidators[valAddrStr] = val + + // delegation shares * bonded / total shares + votingPower := delegation.GetShares().MulInt(val.BondedTokens).Quo(val.DelegatorShares) + + for _, option := range vote.Options { + weight, _ := sdk.NewDecFromStr(option.Weight) + subPower := votingPower.Mul(weight) + results[option.Option] = results[option.Option].Add(subPower) + } + totalVotingPower = totalVotingPower.Add(votingPower) + } + + return false + }) + + // iterate over all alliance asset delegations from voter, change option to abstain + k.ak.IterateDelegations(ctx, func(delegation alliancetypes.Delegation) (stop bool) { + valAddr := delegation.DelegatorAddress + + if val, ok := currValidators[valAddr]; ok { + // delegation shares * bonded / total shares + votingPower := delegation.Shares.MulInt(val.BondedTokens).Quo(val.DelegatorShares) + + for _, option := range vote.Options { + weight, _ := sdk.NewDecFromStr(option.Weight) + subPower := votingPower.Mul(weight) + results[option.Option] = results[option.Option].Sub(subPower) + results[v1.OptionAbstain] = results[v1.OptionAbstain].Add(subPower) + } + } + + return false + }) + k.deleteVote(ctx, vote.ProposalId, voter) + return false + }) + + // iterate over the validators again to tally their voting power + for _, val := range currValidators { + if len(val.Vote) == 0 { + continue + } + + sharesAfterDeductions := val.DelegatorShares.Sub(val.DelegatorDeductions) + votingPower := sharesAfterDeductions.MulInt(val.BondedTokens).Quo(val.DelegatorShares) + + for _, option := range val.Vote { + weight, _ := sdk.NewDecFromStr(option.Weight) + subPower := votingPower.Mul(weight) + results[option.Option] = results[option.Option].Add(subPower) + } + totalVotingPower = totalVotingPower.Add(votingPower) + } + + tallyParams := k.GetParams(ctx) + tallyResults = v1.NewTallyResultFromMap(results) + + // TODO: Upgrade the spec to cover all of these cases & remove pseudocode. + // If there is no staked coins, the proposal fails + if k.sk.TotalBondedTokens(ctx).IsZero() { + return false, false, tallyResults + } + + // If there is not enough quorum of votes, the proposal fails + percentVoting := totalVotingPower.Quo(sdk.NewDecFromInt(k.sk.TotalBondedTokens(ctx))) + quorum, _ := sdk.NewDecFromStr(tallyParams.Quorum) + if percentVoting.LT(quorum) { + return false, false, tallyResults + } + + // If no one votes (everyone abstains), proposal fails + if totalVotingPower.Sub(results[v1.OptionAbstain]).Equal(sdk.ZeroDec()) { + return false, false, tallyResults + } + + // If more than 1/3 of voters veto, proposal fails + vetoThreshold, _ := sdk.NewDecFromStr(tallyParams.VetoThreshold) + if results[v1.OptionNoWithVeto].Quo(totalVotingPower).GT(vetoThreshold) { + return false, true, tallyResults + } + + // If more than 1/2 of non-abstaining voters vote Yes, proposal passes + threshold, _ := sdk.NewDecFromStr(tallyParams.Threshold) + if results[v1.OptionYes].Quo(totalVotingPower.Sub(results[v1.OptionAbstain])).GT(threshold) { + return true, false, tallyResults + } + + // If more than 1/2 of non-abstaining voters vote No, proposal fails + return false, false, tallyResults +} + +// SetHooks sets the hooks for governance +func (k *Keeper) SetHooks(gh types.GovHooks) *Keeper { + k.Keeper = *k.Keeper.SetHooks(gh) + return k +} diff --git a/custom/gov/module.go b/custom/gov/module.go new file mode 100644 index 00000000..6732e660 --- /dev/null +++ b/custom/gov/module.go @@ -0,0 +1,50 @@ +package gov + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + govmodule "github.com/cosmos/cosmos-sdk/x/gov" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + "github.com/cosmos/cosmos-sdk/x/gov/types" + + customgovkeeper "github.com/terra-money/alliance/custom/gov/keeper" +) + +type AppModule struct { + govmodule.AppModule + keeper *customgovkeeper.Keeper + ss types.ParamSubspace +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper customgovkeeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + ss types.ParamSubspace, +) AppModule { + govmodule := govmodule.NewAppModule(cdc, &keeper.Keeper, accountKeeper, bankKeeper, ss) + + return AppModule{ + AppModule: govmodule, + keeper: &keeper, + ss: ss, + } +} + +// RegisterServices registers module services. +// NOTE: Overriding this method as not doing so will cause a panic +// when trying to force this custom keeper into a govkeeper +func (am AppModule) RegisterServices(cfg module.Configurator) { + m := govkeeper.NewMigrator(&am.keeper.Keeper, am.ss) + if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil { + panic(fmt.Sprintf("failed to migrate x/gov from version 1 to 2: %v", err)) + } + + if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil { + panic(fmt.Sprintf("failed to migrate x/gov from version 2 to 3: %v", err)) + } +} diff --git a/scripts/local/start.sh b/scripts/local/start.sh new file mode 100755 index 00000000..d452d2da --- /dev/null +++ b/scripts/local/start.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +BINARY=allianced +CHAIN_DIR=$(pwd)/.testnet +CHAINID_1=alliance-testnet-1 + +VAL_MNEMONIC_1="clock post desk civil pottery foster expand merit dash seminar song memory figure uniform spice circle try happy obvious trash crime hybrid hood cushion" +WALLET_MNEMONIC_1="banner spread envelope side kite person disagree path silver will brother under couch edit food venture squirrel civil budget number acquire point work mass" + +P2PPORT_1=16656 +RPCPORT_1=16657 +RESTPORT_1=1316 +ROSETTA_1=8080 +GRPCPORT_1=8090 +GRPCWEB_1=8091 + +# Stop if it is already running +if pgrep -x "$BINARY" >/dev/null; then + echo "Terminating $BINARY..." + killall $BINARY +fi + +echo "Removing previous data..." +rm -rf $CHAIN_DIR/$CHAINID_1 + +if ! mkdir -p $CHAIN_DIR/$CHAINID_1 2>/dev/null; then + echo "Failed to create chain folder. Aborting..." + exit 1 +fi + +$BINARY init test --home $CHAIN_DIR/$CHAINID_1 --chain-id=$CHAINID_1 + +echo "Adding genesis accounts..." +echo $VAL_MNEMONIC_1 | $BINARY keys add val1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_1 | $BINARY keys add wallet1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test + +VAL1_ADDR=$($BINARY keys show val1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +WALLET1_ADDR=$($BINARY keys show wallet1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) + +$BINARY add-genesis-account $VAL1_ADDR 1000000000000stake --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $WALLET1_ADDR 1000000000000stake --home $CHAIN_DIR/$CHAINID_1 + +echo "Creating and collecting gentx..." +$BINARY gentx val1 6500000000stake --home $CHAIN_DIR/$CHAINID_1 --chain-id $CHAINID_1 --keyring-backend test +sleep 2 +$BINARY collect-gentxs --home $CHAIN_DIR/$CHAINID_1 + +echo "Changing app.toml defaults and ports in and config.toml files..." +sed -i -e 's#"tcp://0.0.0.0:26656"#"tcp://0.0.0.0:'"$P2PPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's#"tcp://127.0.0.1:26657"#"tcp://0.0.0.0:'"$RPCPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#"tcp://localhost:1317"#"tcp://0.0.0.0:'"$RESTPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#":8080"#":'"$ROSETTA_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml + +echo "Changing genesis.json defaults..." +sed -i -e 's/"voting_period": "172800s"/"voting_period": "10s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json +sed -i -e 's/"reward_delay_time": "604800s"/"reward_delay_time": "10s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json + +echo "Starting $CHAINID_1 in $CHAIN_DIR..." +echo "Creating log file at $CHAIN_DIR/$CHAINID_1.log" +$BINARY start --log_level trace --log_format json --home $CHAIN_DIR/$CHAINID_1 --pruning=nothing --grpc.address="0.0.0.0:$GRPCPORT_1" --grpc-web.address="0.0.0.0:$GRPCWEB_1" > $CHAIN_DIR/$CHAINID_1.log 2>&1 & +