diff --git a/x/gov/keeper/migrations.go b/x/gov/keeper/migrations.go index c643c6dd7b99..e0134012258f 100644 --- a/x/gov/keeper/migrations.go +++ b/x/gov/keeper/migrations.go @@ -17,5 +17,5 @@ func NewMigrator(keeper Keeper) Migrator { // Migrate1to2 migrates from version 1 to 2. func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v042.MigrateStore(ctx, m.keeper.storeKey) + return v042.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc) } diff --git a/x/gov/legacy/v040/types.go b/x/gov/legacy/v040/types.go new file mode 100644 index 000000000000..babaa772b697 --- /dev/null +++ b/x/gov/legacy/v040/types.go @@ -0,0 +1,302 @@ +// Package v040 is take from: +// https://github.com/cosmos/cosmos-sdk/blob/v0.41.1/x/gov/types/vote.go +// by copy-pasted only the relevants parts for Vote. +package v040 + +import ( + "fmt" + "io" + math_bits "math/bits" + + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +type Vote struct { + ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty" yaml:"proposal_id"` + Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty"` + Option types.VoteOption `protobuf:"varint,3,opt,name=option,proto3,enum=cosmos.gov.v1beta1.VoteOption" json:"option,omitempty"` +} + +func (m *Vote) Reset() { *m = Vote{} } +func (*Vote) ProtoMessage() {} + +func (m *Vote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ProposalId != 0 { + n += 1 + sovGov(uint64(m.ProposalId)) + } + l = len(m.Voter) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + if m.Option != 0 { + n += 1 + sovGov(uint64(m.Option)) + } + return n +} + +func (m *Vote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Vote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Option != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.Option)) + i-- + dAtA[i] = 0x18 + } + if len(m.Voter) > 0 { + i -= len(m.Voter) + copy(dAtA[i:], m.Voter) + i = encodeVarintGov(dAtA, i, uint64(len(m.Voter))) + i-- + dAtA[i] = 0x12 + } + if m.ProposalId != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.ProposalId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintGov(dAtA []byte, offset int, v uint64) int { + offset -= sovGov(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} + +func sovGov(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} + +func (m *Vote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Vote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Vote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposalId", wireType) + } + m.ProposalId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProposalId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Option", wireType) + } + m.Option = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Option |= types.VoteOption(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGov(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGov + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +func skipGov(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGov + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGov + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGov + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGov = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGov = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGov = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/gov/legacy/v040/vote.go b/x/gov/legacy/v040/vote.go new file mode 100644 index 000000000000..59bde951070b --- /dev/null +++ b/x/gov/legacy/v040/vote.go @@ -0,0 +1,78 @@ +// Package v040 is copy-pasted from: +// https://github.com/cosmos/cosmos-sdk/blob/v0.41.1/x/gov/types/vote.go +package v040 + +import ( + "fmt" + + yaml "gopkg.in/yaml.v2" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// NewVote creates a new Vote instance +//nolint:interfacer +func NewVote(proposalID uint64, voter sdk.AccAddress, option types.VoteOption) Vote { + return Vote{proposalID, voter.String(), option} +} + +func (v Vote) String() string { + out, _ := yaml.Marshal(v) + return string(out) +} + +// Votes is a collection of Vote objects +type Votes []Vote + +// Equal returns true if two slices (order-dependant) of votes are equal. +func (v Votes) Equal(other Votes) bool { + if len(v) != len(other) { + return false + } + + for i, vote := range v { + if vote.String() != other[i].String() { + return false + } + } + + return true +} + +func (v Votes) String() string { + if len(v) == 0 { + return "[]" + } + out := fmt.Sprintf("Votes for Proposal %d:", v[0].ProposalId) + for _, vot := range v { + out += fmt.Sprintf("\n %s: %s", vot.Voter, vot.Option) + } + return out +} + +// Empty returns whether a vote is empty. +func (v Vote) Empty() bool { + return v.String() == Vote{}.String() +} + +// VoteOptionFromString returns a VoteOption from a string. It returns an error +// if the string is invalid. +func VoteOptionFromString(str string) (types.VoteOption, error) { + option, ok := types.VoteOption_value[str] + if !ok { + return types.OptionEmpty, fmt.Errorf("'%s' is not a valid vote option", str) + } + return types.VoteOption(option), nil +} + +// ValidVoteOption returns true if the vote option is valid and false otherwise. +func ValidVoteOption(option types.VoteOption) bool { + if option == types.OptionYes || + option == types.OptionAbstain || + option == types.OptionNo || + option == types.OptionNoWithVeto { + return true + } + return false +} diff --git a/x/gov/legacy/v042/store.go b/x/gov/legacy/v042/store.go index acf824a16139..84eee997f4dd 100644 --- a/x/gov/legacy/v042/store.go +++ b/x/gov/legacy/v042/store.go @@ -1,10 +1,12 @@ package v042 import ( + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/address" v040gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v040" + "github.com/cosmos/cosmos-sdk/x/gov/types" ) const proposalIDLen = 8 @@ -30,14 +32,37 @@ func migratePrefixProposalAddress(store sdk.KVStore, prefixBz []byte) { } } +// migrateWeightedVotes migrates the ADR-037 weighted votes. +func migrateWeightedVotes(store sdk.KVStore, cdc codec.BinaryMarshaler) error { + iterator := sdk.KVStorePrefixIterator(store, v040gov.VotesKeyPrefix) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var oldVote v040gov.Vote + err := cdc.UnmarshalBinaryBare(iterator.Value(), &oldVote) + if err != nil { + return err + } + + newVote := &types.Vote{ + ProposalId: oldVote.ProposalId, + Voter: oldVote.Voter, + Options: []types.WeightedVoteOption{{Option: types.VoteOption(oldVote.Option), Weight: sdk.NewDec(1)}}, + } + bz, err := cdc.MarshalBinaryBare(newVote) + store.Set(iterator.Key(), bz) + } + + return nil +} + // MigrateStore performs in-place store migrations from v0.40 to v0.42. The // migration includes: // // - Change addresses to be length-prefixed. -func MigrateStore(ctx sdk.Context, storeKey sdk.StoreKey) error { +func MigrateStore(ctx sdk.Context, storeKey sdk.StoreKey, cdc codec.BinaryMarshaler) error { store := ctx.KVStore(storeKey) migratePrefixProposalAddress(store, v040gov.DepositsKeyPrefix) migratePrefixProposalAddress(store, v040gov.VotesKeyPrefix) - - return nil + return migrateWeightedVotes(store, cdc) } diff --git a/x/gov/legacy/v042/store_test.go b/x/gov/legacy/v042/store_test.go index 6bcb056a9c99..6eaeab7e9bcb 100644 --- a/x/gov/legacy/v042/store_test.go +++ b/x/gov/legacy/v042/store_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,6 +17,7 @@ import ( ) func TestStoreMigration(t *testing.T) { + cdc := simapp.MakeTestEncodingConfig().Marshaler govKey := sdk.NewKVStoreKey("gov") ctx := testutil.DefaultContext(govKey, sdk.NewTransientStoreKey("transient_test")) store := ctx.KVStore(govKey) @@ -23,53 +25,57 @@ func TestStoreMigration(t *testing.T) { _, _, addr1 := testdata.KeyTestPubAddr() proposalID := uint64(6) now := time.Now() - // Use dummy value for all keys. - value := []byte("foo") + // Use dummy value for keys where we don't test values. + dummyValue := []byte("foo") + // Use real values for votes, as we're testing weighted votes. + oldVote := v040gov.Vote{ProposalId: 1, Voter: "foobar", Option: types.OptionNoWithVeto} + oldVoteValue := cdc.MustMarshalBinaryBare(&oldVote) + newVote := types.Vote{ProposalId: 1, Voter: "foobar", Options: types.WeightedVoteOptions{{Option: types.OptionNoWithVeto, Weight: sdk.NewDec(1)}}} + newVoteValue := cdc.MustMarshalBinaryBare(&newVote) testCases := []struct { - name string - oldKey []byte - newKey []byte + name string + oldKey, oldValue, newKey, newValue []byte }{ { "ProposalKey", - v040gov.ProposalKey(proposalID), - types.ProposalKey(proposalID), + v040gov.ProposalKey(proposalID), dummyValue, + types.ProposalKey(proposalID), dummyValue, }, { "ActiveProposalQueue", - v040gov.ActiveProposalQueueKey(proposalID, now), - types.ActiveProposalQueueKey(proposalID, now), + v040gov.ActiveProposalQueueKey(proposalID, now), dummyValue, + types.ActiveProposalQueueKey(proposalID, now), dummyValue, }, { "InactiveProposalQueue", - v040gov.InactiveProposalQueueKey(proposalID, now), - types.InactiveProposalQueueKey(proposalID, now), + v040gov.InactiveProposalQueueKey(proposalID, now), dummyValue, + types.InactiveProposalQueueKey(proposalID, now), dummyValue, }, { "ProposalIDKey", - v040gov.ProposalIDKey, - types.ProposalIDKey, + v040gov.ProposalIDKey, dummyValue, + types.ProposalIDKey, dummyValue, }, { "DepositKey", - v040gov.DepositKey(proposalID, addr1), - types.DepositKey(proposalID, addr1), + v040gov.DepositKey(proposalID, addr1), dummyValue, + types.DepositKey(proposalID, addr1), dummyValue, }, { "VotesKeyPrefix", - v040gov.VoteKey(proposalID, addr1), - types.VoteKey(proposalID, addr1), + v040gov.VoteKey(proposalID, addr1), oldVoteValue, + types.VoteKey(proposalID, addr1), newVoteValue, }, } // Set all the old keys to the store for _, tc := range testCases { - store.Set(tc.oldKey, value) + store.Set(tc.oldKey, tc.oldValue) } // Run migrations. - err := v042gov.MigrateStore(ctx, govKey) + err := v042gov.MigrateStore(ctx, govKey, cdc) require.NoError(t, err) // Make sure the new keys are set and old keys are deleted. @@ -79,7 +85,7 @@ func TestStoreMigration(t *testing.T) { if bytes.Compare(tc.oldKey, tc.newKey) != 0 { require.Nil(t, store.Get(tc.oldKey)) } - require.Equal(t, value, store.Get(tc.newKey)) + require.Equal(t, tc.newValue, store.Get(tc.newKey)) }) } }