Skip to content

Commit

Permalink
add vesting vote
Browse files Browse the repository at this point in the history
  • Loading branch information
beer-1 committed Jul 25, 2024
1 parent 3434001 commit a6107f2
Show file tree
Hide file tree
Showing 12 changed files with 1,340 additions and 137 deletions.
33 changes: 25 additions & 8 deletions x/gov/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"encoding/binary"
"os"
"testing"
"time"

Expand Down Expand Up @@ -424,6 +425,7 @@ func _createTestInput(
bankKeeper,
stakingKeeper,
distKeeper,
movekeeper.NewVestingKeeper(moveKeeper),
msgRouter,
govConfig,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
Expand Down Expand Up @@ -453,11 +455,11 @@ func _createTestInput(
return ctx, keepers
}

func createValidatorWithBalance(
func createValidatorWithCoin(
ctx sdk.Context,
input TestKeepers,
balance int64,
delBalance int64,
balance sdk.Coins,
delBalance sdk.Coins,
index int,
) sdk.ValAddress {
valPubKey := testutilsims.CreateTestPubKeys(index)[index-1]
Expand All @@ -466,10 +468,10 @@ func createValidatorWithBalance(
accAddr := sdk.AccAddress(sdk.AccAddress(pubKey.Address()))
valAddr := sdk.ValAddress(sdk.AccAddress(pubKey.Address()))

input.Faucet.Fund(ctx, accAddr, sdk.NewCoin(bondDenom, math.NewInt(balance)))
input.Faucet.Fund(ctx, accAddr, balance...)

sh := stakingkeeper.NewMsgServerImpl(input.StakingKeeper)
_, err := sh.CreateValidator(ctx, newTestMsgCreateValidator(valAddr, valPubKey, math.NewInt(delBalance)))
_, err := sh.CreateValidator(ctx, newTestMsgCreateValidator(valAddr, valPubKey, delBalance...))
if err != nil {
panic(err)
}
Expand All @@ -484,11 +486,26 @@ func createValidatorWithBalance(
}

// newTestMsgCreateValidator test msg creator
func newTestMsgCreateValidator(address sdk.ValAddress, pubKey cryptotypes.PubKey, amt math.Int) *stakingtypes.MsgCreateValidator {
commission := stakingtypes.NewCommissionRates(math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0))
func newTestMsgCreateValidator(address sdk.ValAddress, pubKey cryptotypes.PubKey, amt ...sdk.Coin) *stakingtypes.MsgCreateValidator {
commission := stakingtypes.NewCommissionRates(math.LegacyNewDecWithPrec(1, 1), math.LegacyNewDecWithPrec(5, 1), math.LegacyNewDec(0))
msg, _ := stakingtypes.NewMsgCreateValidator(
address.String(), pubKey, sdk.NewCoins(sdk.NewCoin(bondDenom, amt)),
address.String(), pubKey, amt,
stakingtypes.NewDescription("homeDir", "", "", "", ""), commission,
)
return msg
}

var vestingModule []byte

func init() {
vestingModule = ReadMoveFile("Vesting")
}

func ReadMoveFile(filename string) []byte {
path := "../../move/keeper/binaries/" + filename + ".mv"
b, err := os.ReadFile(path)
if err != nil {
panic(err)
}
return b
}
52 changes: 45 additions & 7 deletions x/gov/keeper/tally.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import (

// Tally iterates over the votes and updates the tally of a proposal based on the voting power of the
// voters
func (keeper Keeper) Tally(ctx context.Context, params customtypes.Params, proposal customtypes.Proposal) (quorumReached, passed bool, burnDeposits bool, tallyResults v1.TallyResult, err error) {
weights, err := keeper.sk.GetVotingPowerWeights(ctx)
func (k Keeper) Tally(ctx context.Context, params customtypes.Params, proposal customtypes.Proposal) (quorumReached, passed bool, burnDeposits bool, tallyResults v1.TallyResult, err error) {
weights, err := k.sk.GetVotingPowerWeights(ctx)
if err != nil {
return false, false, false, tallyResults, err
}
Expand All @@ -36,8 +36,8 @@ func (keeper Keeper) Tally(ctx context.Context, params customtypes.Params, propo
currValidators := make(map[string]customtypes.ValidatorGovInfo)

// fetch all the bonded validators, insert them into currValidators
err = keeper.sk.IterateBondedValidatorsByPower(ctx, func(validator stakingtypes.ValidatorI) (stop bool, err error) {
valAddr, err := keeper.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
err = k.sk.IterateBondedValidatorsByPower(ctx, func(validator stakingtypes.ValidatorI) (stop bool, err error) {
valAddr, err := k.sk.ValidatorAddressCodec().StringToBytes(validator.GetOperator())
if err != nil {
return false, err
}
Expand All @@ -59,8 +59,29 @@ func (keeper Keeper) Tally(ctx context.Context, params customtypes.Params, propo
return false, false, false, tallyResults, err
}

// fetch vesting table handle if vesting params are provided
var vestingHandle *sdk.AccAddress
if params.Vesting != nil {
vesting := *params.Vesting
moduleAddr, err := k.authKeeper.AddressCodec().StringToBytes(vesting.ModuleAddr)
if err != nil {
return false, false, false, tallyResults, err
}

creatorAddr, err := k.authKeeper.AddressCodec().StringToBytes(vesting.CreatorAddr)
if err != nil {
return false, false, false, tallyResults, err
}

// base denom validation will be done in vesting keeper
vestingHandle, err = k.vestingKeeper.GetVestingHandle(ctx, moduleAddr, vesting.ModuleName, creatorAddr)
if err != nil {
return false, false, false, tallyResults, err
}
}

rng := collections.NewPrefixedPairRange[uint64, sdk.AccAddress](proposal.Id)
err = keeper.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) {
err = k.Votes.Walk(ctx, rng, func(key collections.Pair[uint64, sdk.AccAddress], vote v1.Vote) (bool, error) {
// if validator, just record it in the map
voter := sdk.MustAccAddressFromBech32(vote.Voter)

Expand All @@ -70,8 +91,25 @@ func (keeper Keeper) Tally(ctx context.Context, params customtypes.Params, propo
currValidators[valAddrStr] = val
}

// add vesting voting power if vesting params are provided
if vestingHandle != nil {
amount, err := k.vestingKeeper.GetUnclaimedVestedAmount(ctx, *vestingHandle, voter)
if err != nil {
return false, err
}

if !amount.IsZero() {
votingPower := math.LegacyNewDecFromInt(amount)
for _, option := range vote.Options {
subPower := votingPower.Mul(math.LegacyMustNewDecFromStr(option.Weight))
results[option.Option] = results[option.Option].Add(subPower)
}
totalVotingPower = totalVotingPower.Add(votingPower)
}
}

// iterate over all delegations from voter, deduct from any delegated-to validators
err = keeper.sk.IterateDelegations(ctx, voter, func(delegation stakingtypes.DelegationI) (stop bool, err error) {
err = k.sk.IterateDelegations(ctx, voter, func(delegation stakingtypes.DelegationI) (stop bool, err error) {
valAddrStr := delegation.GetValidatorAddr()

if val, ok := currValidators[valAddrStr]; ok {
Expand Down Expand Up @@ -104,7 +142,7 @@ func (keeper Keeper) Tally(ctx context.Context, params customtypes.Params, propo
return false, err
}

return false, keeper.Votes.Remove(ctx, collections.Join(vote.ProposalId, voter))
return false, k.Votes.Remove(ctx, collections.Join(vote.ProposalId, voter))
})
if err != nil {
return false, false, false, tallyResults, err
Expand Down
145 changes: 140 additions & 5 deletions x/gov/keeper/tally_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package keeper_test

import (
"fmt"
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"cosmossdk.io/math"
"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

"github.com/initia-labs/initia/x/gov/keeper"
"github.com/initia-labs/initia/x/gov/types"
customtypes "github.com/initia-labs/initia/x/gov/types"
movetypes "github.com/initia-labs/initia/x/move/types"

vmtypes "github.com/initia-labs/movevm/types"
)

func Test_isLowThresholdProposal(t *testing.T) {
params := types.DefaultParams()
params := customtypes.DefaultParams()

messages := []sdk.Msg{
&movetypes.MsgExecute{
Expand All @@ -27,7 +33,7 @@ func Test_isLowThresholdProposal(t *testing.T) {
FunctionName: "register_snapshot",
},
}
proposal, err := types.NewProposal(messages, 1, time.Now().UTC(), time.Now().UTC().Add(time.Hour), "", "", "", addrs[0], true)
proposal, err := customtypes.NewProposal(messages, 1, time.Now().UTC(), time.Now().UTC().Add(time.Hour), "", "", "", addrs[0], true)
require.NoError(t, err)
require.True(t, keeper.IsLowThresholdProposal(params, proposal))

Expand All @@ -39,7 +45,136 @@ func Test_isLowThresholdProposal(t *testing.T) {
},
&movetypes.MsgScript{},
}
proposal, err = types.NewProposal(messages, 1, time.Now().UTC(), time.Now().UTC().Add(time.Hour), "", "", "", addrs[0], true)
proposal, err = customtypes.NewProposal(messages, 1, time.Now().UTC(), time.Now().UTC().Add(time.Hour), "", "", "", addrs[0], true)
require.NoError(t, err)
require.False(t, keeper.IsLowThresholdProposal(params, proposal))
}

func setupVesting(t *testing.T, ctx sdk.Context, input TestKeepers) {
err := input.MoveKeeper.PublishModuleBundle(ctx, vmtypes.TestAddress, vmtypes.NewModuleBundle(vmtypes.NewModule(vestingModule)), movetypes.UpgradePolicy_COMPATIBLE)
require.NoError(t, err)

creatorAccAddr := addrs[0]
creatorAddr, err := vmtypes.NewAccountAddressFromBytes(creatorAccAddr)
require.NoError(t, err)

// create vesting table
moduleAddr := vmtypes.TestAddress
moduleName := "Vesting"

metadata, err := movetypes.MetadataAddressFromDenom(bondDenom)
require.NoError(t, err)

err = input.MoveKeeper.ExecuteEntryFunctionJSON(ctx, creatorAddr, moduleAddr, moduleName, "create_vesting_store", []vmtypes.TypeTag{}, []string{fmt.Sprintf("\"%s\"", metadata)})
require.NoError(t, err)

now := time.Now().UTC()
ctx = ctx.WithBlockTime(now)

// add vesting
recipientAccAddr := addrs[1]
recipientAddr, err := vmtypes.NewAccountAddressFromBytes(recipientAccAddr)
require.NoError(t, err)

err = input.MoveKeeper.ExecuteEntryFunctionJSON(ctx, creatorAddr, moduleAddr, moduleName, "add_vesting", []vmtypes.TypeTag{},
[]string{
fmt.Sprintf("\"%s\"", recipientAddr), // recipient
fmt.Sprintf("\"%d\"", 6_000_000), // allocation
fmt.Sprintf("\"%d\"", now.Unix()), // start_time
fmt.Sprintf("\"%d\"", 3600), // vesting_period
fmt.Sprintf("\"%d\"", 1800), // cliff_period
fmt.Sprintf("\"%d\"", 60), // cliff_frequency
},
)
require.NoError(t, err)

// update vesting params
params, err := input.GovKeeper.Params.Get(ctx)
require.NoError(t, err)
params.Vesting = &customtypes.Vesting{
ModuleAddr: movetypes.TestAddr.String(),
ModuleName: "Vesting",
CreatorAddr: creatorAccAddr.String(),
}

err = input.GovKeeper.Params.Set(ctx, params)
require.NoError(t, err)
}

func Test_Tally(t *testing.T) {
ctx, input := createDefaultTestInput(t)

setupVesting(t, ctx, input)

proposal, err := input.GovKeeper.SubmitProposal(ctx, nil, "", "test", "description", addrs[0], false)
require.NoError(t, err)

proposalID := proposal.Id
proposal.Status = v1.StatusVotingPeriod
err = input.GovKeeper.SetProposal(ctx, proposal)
require.NoError(t, err)

proposal, err = input.GovKeeper.Proposals.Get(ctx, proposalID)
require.NoError(t, err)

params, err := input.GovKeeper.Params.Get(ctx)
require.NoError(t, err)

quorumReached, passed, _, _, err := input.GovKeeper.Tally(ctx, params, proposal)
require.NoError(t, err)
require.False(t, quorumReached)
require.False(t, passed)

valAddr1 := createValidatorWithCoin(ctx, input,
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
1,
)
valAddr2 := createValidatorWithCoin(ctx, input,
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
sdk.NewCoins(sdk.NewInt64Coin(bondDenom, 100_000_000)),
2,
)

voterAddr1 := sdk.AccAddress(valAddr1)
voterAddr2 := sdk.AccAddress(valAddr2)

// vote yes
err = input.GovKeeper.AddVote(ctx, proposalID, voterAddr1, v1.WeightedVoteOptions{
{
Option: v1.OptionYes,
Weight: "1",
},
}, "")
require.NoError(t, err)

// vote no
err = input.GovKeeper.AddVote(ctx, proposalID, voterAddr2, v1.WeightedVoteOptions{
{
Option: v1.OptionNo,
Weight: "1",
},
}, "")
require.NoError(t, err)

// add vesting vote
vestingVoter := addrs[1]
err = input.GovKeeper.AddVote(ctx, proposalID, vestingVoter, v1.WeightedVoteOptions{
{
Option: v1.OptionYes,
Weight: "1",
},
}, "")
require.NoError(t, err)

// 15 minutes passed
ctx = ctx.WithBlockTime(time.Now().Add(time.Minute * 15))

quorumReached, passed, burnDeposits, tallyResults, err := input.GovKeeper.Tally(ctx, params, proposal)
require.NoError(t, err)
require.True(t, quorumReached)
require.True(t, passed)
require.False(t, burnDeposits)
require.Equal(t, tallyResults.YesCount, math.LegacyNewDec(1_500_000+100_000_000).TruncateInt().String())
require.Equal(t, tallyResults.NoCount, math.LegacyNewDec(100_000_000).TruncateInt().String())
}
2 changes: 2 additions & 0 deletions x/gov/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type ModuleInputs struct {
BankKeeper types.BankKeeper
StakingKeeper customtypes.StakingKeeper
DistributionKeeper types.DistributionKeeper
VestingKeeper customtypes.VestingKeeper
}

type ModuleOutputs struct {
Expand Down Expand Up @@ -210,6 +211,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs {
in.BankKeeper,
in.StakingKeeper,
in.DistributionKeeper,
in.VestingKeeper,
in.MsgServiceRouter,
defaultConfig,
authority.String(),
Expand Down
6 changes: 6 additions & 0 deletions x/gov/types/expected_keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
context "context"

"cosmossdk.io/core/address"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

stakingtypes "github.com/initia-labs/initia/x/mstaking/types"
Expand All @@ -24,3 +25,8 @@ type StakingKeeper interface {
GetVotingPowerWeights(ctx context.Context) (sdk.DecCoins, error)
ValidatorAddressCodec() address.Codec
}

type VestingKeeper interface {
GetVestingHandle(ctx context.Context, moduleAddr sdk.AccAddress, moduleName string, creator sdk.AccAddress) (*sdk.AccAddress, error)
GetUnclaimedVestedAmount(ctx context.Context, tableHandle, recipientAccAddr sdk.AccAddress) (math.Int, error)
}
Loading

0 comments on commit a6107f2

Please sign in to comment.