Skip to content

Commit

Permalink
fix(gov): Fix v3 votes migrations (#14214)
Browse files Browse the repository at this point in the history
Co-authored-by: Julien Robert <julien@rbrt.fr>
Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com>
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
(cherry picked from commit 1bd9cbe)

# Conflicts:
#	CHANGELOG.md
#	x/gov/migrations/v3/json_test.go
#	x/gov/migrations/v3/store.go
#	x/gov/migrations/v3/store_test.go
  • Loading branch information
amaury1093 authored and mergify[bot] committed Dec 13, 2022
1 parent 4153b12 commit 795d330
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -52,6 +52,11 @@ Ref: https://keepachangelog.com/en/1.0.0/

### State Machine Breaking

<<<<<<< HEAD
=======
* (x/gov) [#14214](https://github.com/cosmos/cosmos-sdk/pull/14214) Fix gov v0.46 migration to v1 votes.
* (x/group) [#13742](https://github.com/cosmos/cosmos-sdk/pull/13742) Migrate group policy account from module accounts to base account.
>>>>>>> 1bd9cbe9e (fix(gov): Fix v3 votes migrations (#14214))
* (x/group) [#14071](https://github.com/cosmos/cosmos-sdk/pull/14071) Don't re-tally proposal after voting period end if they have been marked as ACCEPTED or REJECTED.

### API Breaking Changes
Expand Down
157 changes: 157 additions & 0 deletions x/gov/migrations/v3/json_test.go
@@ -0,0 +1,157 @@
package v3_test

import (
"encoding/json"
"testing"
"time"

"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/gov"
v3 "github.com/cosmos/cosmos-sdk/x/gov/migrations/v3"
"github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
)

var voter = sdk.MustAccAddressFromBech32("cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh")

func TestMigrateJSON(t *testing.T) {
encodingConfig := moduletestutil.MakeTestEncodingConfig(gov.AppModuleBasic{})
clientCtx := client.Context{}.
WithInterfaceRegistry(encodingConfig.InterfaceRegistry).
WithTxConfig(encodingConfig.TxConfig).
WithCodec(encodingConfig.Codec)

govGenState := v1beta1.DefaultGenesisState()
propTime := time.Unix(1e9, 0)
contentAny, err := codectypes.NewAnyWithValue(v1beta1.NewTextProposal("my title", "my desc").(proto.Message))
require.NoError(t, err)
govGenState.Proposals = v1beta1.Proposals{
v1beta1.Proposal{
ProposalId: 1,
Content: contentAny,
SubmitTime: propTime,
DepositEndTime: propTime,
VotingStartTime: propTime,
VotingEndTime: propTime,
Status: v1beta1.StatusDepositPeriod,
FinalTallyResult: v1beta1.EmptyTallyResult(),
TotalDeposit: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(123))),
},
}
govGenState.Votes = v1beta1.Votes{
v1beta1.Vote{ProposalId: 1, Voter: voter.String(), Option: v1beta1.OptionAbstain},
v1beta1.Vote{ProposalId: 2, Voter: voter.String(), Options: v1beta1.NewNonSplitVoteOption(v1beta1.OptionNo)},
}

migrated, err := v3.MigrateJSON(govGenState)
require.NoError(t, err)

// Make sure the migrated proposal's Msg signer is the gov acct.
require.Equal(t,
authtypes.NewModuleAddress(types.ModuleName).String(),
migrated.Proposals[0].Messages[0].GetCachedValue().(*v1.MsgExecLegacyContent).Authority,
)

bz, err := clientCtx.Codec.MarshalJSON(migrated)
require.NoError(t, err)

// Indent the JSON bz correctly.
var jsonObj map[string]interface{}
err = json.Unmarshal(bz, &jsonObj)
require.NoError(t, err)
indentedBz, err := json.MarshalIndent(jsonObj, "", "\t")
require.NoError(t, err)

// Make sure about:
// - Proposals use MsgExecLegacyContent
expected := `{
"deposit_params": {
"max_deposit_period": "172800s",
"min_deposit": [
{
"amount": "10000000",
"denom": "stake"
}
]
},
"deposits": [],
"params": null,
"proposals": [
{
"deposit_end_time": "2001-09-09T01:46:40Z",
"final_tally_result": {
"abstain_count": "0",
"no_count": "0",
"no_with_veto_count": "0",
"yes_count": "0"
},
"id": "1",
"messages": [
{
"@type": "/cosmos.gov.v1.MsgExecLegacyContent",
"authority": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn",
"content": {
"@type": "/cosmos.gov.v1beta1.TextProposal",
"description": "my desc",
"title": "my title"
}
}
],
"metadata": "",
"status": "PROPOSAL_STATUS_DEPOSIT_PERIOD",
"submit_time": "2001-09-09T01:46:40Z",
"total_deposit": [
{
"amount": "123",
"denom": "stake"
}
],
"voting_end_time": "2001-09-09T01:46:40Z",
"voting_start_time": "2001-09-09T01:46:40Z"
}
],
"starting_proposal_id": "1",
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto_threshold": "0.334000000000000000"
},
"votes": [
{
"metadata": "",
"options": [
{
"option": "VOTE_OPTION_ABSTAIN",
"weight": "1.000000000000000000"
}
],
"proposal_id": "1",
"voter": "cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh"
},
{
"metadata": "",
"options": [
{
"option": "VOTE_OPTION_NO",
"weight": "1.000000000000000000"
}
],
"proposal_id": "2",
"voter": "cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh"
}
],
"voting_params": {
"voting_period": "172800s"
}
}`

require.Equal(t, expected, string(indentedBz))
}
95 changes: 95 additions & 0 deletions x/gov/migrations/v3/store.go
@@ -0,0 +1,95 @@
package v3

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/migrations/v1"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
)

// migrateProposals migrates all legacy proposals into MsgExecLegacyContent
// proposals.
func migrateProposals(store sdk.KVStore, cdc codec.BinaryCodec) error {
propStore := prefix.NewStore(store, v1.ProposalsKeyPrefix)

iter := propStore.Iterator(nil, nil)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
var oldProp govv1beta1.Proposal
err := cdc.Unmarshal(iter.Value(), &oldProp)
if err != nil {
return err
}

newProp, err := convertToNewProposal(oldProp)
if err != nil {
return err
}
bz, err := cdc.Marshal(&newProp)
if err != nil {
return err
}

// Set new value on store.
propStore.Set(iter.Key(), bz)
}

return nil
}

// migrateVotes migrates all v1beta1 weighted votes (with sdk.Dec as weight)
// to v1 weighted votes (with string as weight)
func migrateVotes(store sdk.KVStore, cdc codec.BinaryCodec) error {
votesStore := prefix.NewStore(store, v1.VotesKeyPrefix)

iter := votesStore.Iterator(nil, nil)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
var oldVote govv1beta1.Vote
err := cdc.Unmarshal(iter.Value(), &oldVote)
if err != nil {
return err
}

newVote := govv1.Vote{
ProposalId: oldVote.ProposalId,
Voter: oldVote.Voter,
}
newOptions := make([]*govv1.WeightedVoteOption, len(oldVote.Options))
for i, o := range oldVote.Options {
newOptions[i] = &govv1.WeightedVoteOption{
Option: govv1.VoteOption(o.Option),
Weight: o.Weight.String(), // Convert to decimal string
}
}
newVote.Options = newOptions
bz, err := cdc.Marshal(&newVote)
if err != nil {
return err
}

// Set new value on store.
votesStore.Set(iter.Key(), bz)
}

return nil
}

// MigrateStore performs in-place store migrations from v2 (v0.43) to v3 (v0.46). The
// migration includes:
//
// - Migrate proposals to be Msg-based.
func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error {
store := ctx.KVStore(storeKey)

if err := migrateVotes(store, cdc); err != nil {
return err
}

return migrateProposals(store, cdc)
}
95 changes: 95 additions & 0 deletions x/gov/migrations/v3/store_test.go
@@ -0,0 +1,95 @@
package v3_test

import (
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/gov"
v1gov "github.com/cosmos/cosmos-sdk/x/gov/migrations/v1"
v3gov "github.com/cosmos/cosmos-sdk/x/gov/migrations/v3"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
"github.com/cosmos/cosmos-sdk/x/upgrade"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
)

func TestMigrateStore(t *testing.T) {
cdc := moduletestutil.MakeTestEncodingConfig(upgrade.AppModuleBasic{}, gov.AppModuleBasic{}).Codec
govKey := sdk.NewKVStoreKey("gov")
ctx := testutil.DefaultContext(govKey, sdk.NewTransientStoreKey("transient_test"))
store := ctx.KVStore(govKey)

propTime := time.Unix(1e9, 0)

// Create 2 proposals
prop1, err := v1beta1.NewProposal(v1beta1.NewTextProposal("my title 1", "my desc 1"), 1, propTime, propTime)
require.NoError(t, err)
prop1Bz, err := cdc.Marshal(&prop1)
require.NoError(t, err)
prop2, err := v1beta1.NewProposal(upgradetypes.NewSoftwareUpgradeProposal("my title 2", "my desc 2", upgradetypes.Plan{
Name: "my plan 2",
}), 2, propTime, propTime)
require.NoError(t, err)
prop2Bz, err := cdc.Marshal(&prop2)
require.NoError(t, err)

store.Set(v1gov.ProposalKey(prop1.ProposalId), prop1Bz)
store.Set(v1gov.ProposalKey(prop2.ProposalId), prop2Bz)

// Vote on prop 1
options := []v1beta1.WeightedVoteOption{
{Option: v1beta1.OptionNo, Weight: sdk.MustNewDecFromStr("0.3")},
{Option: v1beta1.OptionYes, Weight: sdk.MustNewDecFromStr("0.7")},
}
vote1 := v1beta1.NewVote(1, voter, options)
vote1Bz := cdc.MustMarshal(&vote1)
store.Set(v1gov.VoteKey(1, voter), vote1Bz)

// Run migrations.
err = v3gov.MigrateStore(ctx, govKey, cdc)
require.NoError(t, err)

var newProp1 v1.Proposal
err = cdc.Unmarshal(store.Get(v1gov.ProposalKey(prop1.ProposalId)), &newProp1)
require.NoError(t, err)
compareProps(t, prop1, newProp1)

var newProp2 v1.Proposal
err = cdc.Unmarshal(store.Get(v1gov.ProposalKey(prop2.ProposalId)), &newProp2)
require.NoError(t, err)
compareProps(t, prop2, newProp2)

var newVote1 v1.Vote
err = cdc.Unmarshal(store.Get(v1gov.VoteKey(prop1.ProposalId, voter)), &newVote1)
require.NoError(t, err)
// Without the votes migration, we would have 300000000000000000 in state,
// because of how sdk.Dec stores itself in state.
require.Equal(t, "0.300000000000000000", newVote1.Options[0].Weight)
require.Equal(t, "0.700000000000000000", newVote1.Options[1].Weight)
}

func compareProps(t *testing.T, oldProp v1beta1.Proposal, newProp v1.Proposal) {
require.Equal(t, oldProp.ProposalId, newProp.Id)
require.Equal(t, oldProp.TotalDeposit.String(), sdk.Coins(newProp.TotalDeposit).String())
require.Equal(t, oldProp.Status.String(), newProp.Status.String())
require.Equal(t, oldProp.FinalTallyResult.Yes.String(), newProp.FinalTallyResult.YesCount)
require.Equal(t, oldProp.FinalTallyResult.No.String(), newProp.FinalTallyResult.NoCount)
require.Equal(t, oldProp.FinalTallyResult.NoWithVeto.String(), newProp.FinalTallyResult.NoWithVetoCount)
require.Equal(t, oldProp.FinalTallyResult.Abstain.String(), newProp.FinalTallyResult.AbstainCount)

newContent := newProp.Messages[0].GetCachedValue().(*v1.MsgExecLegacyContent).Content.GetCachedValue().(v1beta1.Content)
require.Equal(t, oldProp.Content.GetCachedValue().(v1beta1.Content), newContent)

// Compare UNIX times, as a simple Equal gives difference between Local and
// UTC times.
// ref: https://github.com/golang/go/issues/19486#issuecomment-292968278
require.Equal(t, oldProp.SubmitTime.Unix(), newProp.SubmitTime.Unix())
require.Equal(t, oldProp.DepositEndTime.Unix(), newProp.DepositEndTime.Unix())
require.Equal(t, oldProp.VotingStartTime.Unix(), newProp.VotingStartTime.Unix())
require.Equal(t, oldProp.VotingEndTime.Unix(), newProp.VotingEndTime.Unix())
}

0 comments on commit 795d330

Please sign in to comment.