Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecocredit): add credit types state migration #1061

Merged
merged 11 commits into from
May 4, 2022
Next Next commit
feat: add credit type migration
  • Loading branch information
aleem1314 committed Apr 27, 2022
commit 501db7f37e13bdc51e14fd669f40b4b14df829de
24 changes: 22 additions & 2 deletions x/ecocredit/migrations/v3/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/regen-network/regen-ledger/orm"
Expand All @@ -22,7 +23,7 @@ type batchMapT struct {

// MigrateState performs in-place store migrations from v3.0 to v4.0.
func MigrateState(sdkCtx sdk.Context, storeKey storetypes.StoreKey,
cdc codec.Codec, ss api.StateStore) error {
cdc codec.Codec, ss api.StateStore, subspace paramtypes.Subspace) error {
classInfoTableBuilder, err := orm.NewPrimaryKeyTableBuilder(ClassInfoTablePrefix, storeKey, &ClassInfo{}, cdc)
if err != nil {
return err
Expand All @@ -41,6 +42,26 @@ func MigrateState(sdkCtx sdk.Context, storeKey storetypes.StoreKey,
}
creditTypeSeqTable := creditTypeSeqTableBuilder.Build()

if !subspace.HasKeyTable() {
subspace.WithKeyTable(ParamKeyTable())
}

// migrate credit types from params to ORM table
var creditTypes []*CreditType
subspace.Get(sdkCtx, KeyCreditTypes, &creditTypes)

ctx := sdk.WrapSDKContext(sdkCtx)
for _, creditType := range creditTypes {
if err := ss.CreditTypeTable().Insert(ctx, &api.CreditType{
Abbreviation: creditType.Abbreviation,
Name: creditType.Name,
Unit: creditType.Unit,
Precision: creditType.Precision,
}); err != nil {
return err
}
}

// migrate credit classes to ORM v1
classItr, err := classInfoTable.PrefixScan(sdkCtx, nil, nil)
if err != nil {
Expand All @@ -50,7 +71,6 @@ func MigrateState(sdkCtx sdk.Context, storeKey storetypes.StoreKey,

classKeyToClassId := make(map[uint64]string) // map of a class key to a class id
classIdToClassKey := make(map[string]uint64) // map of a class id to a class key
ctx := sdk.WrapSDKContext(sdkCtx)
for {
var classInfo ClassInfo
if _, err := classItr.LoadNext(&classInfo); err != nil {
Expand Down
52 changes: 46 additions & 6 deletions x/ecocredit/migrations/v3/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

regenorm "github.com/regen-network/regen-ledger/orm"

Expand All @@ -31,27 +32,51 @@ func TestMigrations(t *testing.T) {
recipient1 := sdk.AccAddress("recipient1")
recipient2 := sdk.AccAddress("recipient2")

cdc := simapp.MakeTestEncodingConfig().Marshaler

ecocreditKey := sdk.NewKVStoreKey("ecocredit")
tecocreditKey := sdk.NewTransientStoreKey("transient_test")
encCfg := simapp.MakeTestEncodingConfig()
paramStore := paramtypes.NewSubspace(encCfg.Marshaler, encCfg.Amino, ecocreditKey, tecocreditKey, ecocredit.ModuleName)

db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(ecocreditKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(tecocreditKey, sdk.StoreTypeTransient, db)
assert.NilError(t, cms.LoadLatestVersion())
ormCtx := ormtable.WrapContextDefault(ormtest.NewMemoryBackend())
sdkCtx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger()).WithContext(ormCtx)
store := sdkCtx.KVStore(ecocreditKey)

classInfoTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.ClassInfoTablePrefix, ecocreditKey, &v3.ClassInfo{}, cdc)
// testutil.DefaultContext()

paramStore.WithKeyTable(v3.ParamKeyTable())
paramStore.Set(sdkCtx, v3.KeyAllowlistEnabled, true)

ctypes := []*v3.CreditType{
{
Name: "carbon",
Abbreviation: "C",
Unit: "metric ton CO2 equivalent",
Precision: 6,
},
{
Name: "biodiversity",
Abbreviation: "BIO",
Unit: "ton",
Precision: 6,
},
}
paramStore.Set(sdkCtx, v3.KeyCreditTypes, &ctypes)

classInfoTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.ClassInfoTablePrefix, ecocreditKey, &v3.ClassInfo{}, encCfg.Marshaler)
require.NoError(t, err)

classInfoTable := classInfoTableBuilder.Build()
batchInfoTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.BatchInfoTablePrefix, ecocreditKey, &v3.BatchInfo{}, cdc)
batchInfoTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.BatchInfoTablePrefix, ecocreditKey, &v3.BatchInfo{}, encCfg.Marshaler)
require.NoError(t, err)

batchInfoTable := batchInfoTableBuilder.Build()

creditTypeSeqTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.CreditTypeSeqTablePrefix, ecocreditKey, &v3.CreditTypeSeq{}, cdc)
creditTypeSeqTableBuilder, err := regenorm.NewPrimaryKeyTableBuilder(v3.CreditTypeSeqTablePrefix, ecocreditKey, &v3.CreditTypeSeq{}, encCfg.Marshaler)
require.NoError(t, err)

creditTypeSeqTable := creditTypeSeqTableBuilder.Build()
Expand Down Expand Up @@ -147,7 +172,7 @@ func TestMigrations(t *testing.T) {
ss, err := api.NewStateStore(ormdb)
require.Nil(t, err)

err = v3.MigrateState(sdkCtx, ecocreditKey, cdc, ss)
err = v3.MigrateState(sdkCtx, ecocreditKey, encCfg.Marshaler, ss, paramStore)
require.NoError(t, err)

ctx := sdk.WrapSDKContext(sdkCtx)
Expand Down Expand Up @@ -243,6 +268,21 @@ func TestMigrations(t *testing.T) {
require.Equal(t, bs.RetiredAmount, "390")
require.Equal(t, bs.CancelledAmount, "0")

// verify credit types migration
carbon, err := ss.CreditTypeTable().Get(ctx, "C")
require.NoError(t, err)
require.Equal(t, carbon.Abbreviation, ctypes[0].Abbreviation)
require.Equal(t, carbon.Name, ctypes[0].Name)
require.Equal(t, carbon.Precision, ctypes[0].Precision)
require.Equal(t, carbon.Unit, ctypes[0].Unit)

bio, err := ss.CreditTypeTable().Get(ctx, "BIO")
require.NoError(t, err)
require.Equal(t, bio.Abbreviation, ctypes[1].Abbreviation)
require.Equal(t, bio.Name, ctypes[1].Name)
require.Equal(t, bio.Precision, ctypes[1].Precision)
require.Equal(t, bio.Unit, ctypes[1].Unit)

// verify old state is deleted
require.False(t, classInfoTable.Has(sdkCtx, regenorm.RowID("C01")))

Expand Down
158 changes: 158 additions & 0 deletions x/ecocredit/migrations/v3/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package v3

import (
"regexp"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/regen-network/regen-ledger/x/ecocredit"
)

var (
// This is a value of 20 REGEN
DefaultCreditClassFeeTokens = sdk.NewInt(2e7)
DefaultBasketCreationFee = sdk.NewInt(2e7)
KeyCreditClassFee = []byte("CreditClassFee")
KeyAllowedClassCreators = []byte("AllowedClassCreators")
KeyAllowlistEnabled = []byte("AllowlistEnabled")
KeyCreditTypes = []byte("CreditTypes")
KeyBasketCreationFee = []byte("BasketCreationFee")
)

// TODO: Revisit this once we have proper gas fee framework.
// Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
const GasCostPerIteration = uint64(10)

// TODO: remove after we open governance changes for precision
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
const (
PRECISION uint32 = 6
)

// ParamKeyTable returns the parameter key table.
func ParamKeyTable() paramtypes.KeyTable {
return paramtypes.NewKeyTable().RegisterParamSet(&Params{})
}

// Implements params.ParamSet
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
return paramtypes.ParamSetPairs{
paramtypes.NewParamSetPair(KeyCreditClassFee, &p.CreditClassFee, validateCreditClassFee),
paramtypes.NewParamSetPair(KeyAllowedClassCreators, &p.AllowedClassCreators, validateAllowedClassCreators),
paramtypes.NewParamSetPair(KeyAllowlistEnabled, &p.AllowlistEnabled, validateAllowlistEnabled),
paramtypes.NewParamSetPair(KeyCreditTypes, &p.CreditTypes, validateCreditTypes),
paramtypes.NewParamSetPair(KeyBasketCreationFee, &p.BasketCreationFee, validateBasketCreationFee),
}
}

func validateCreditClassFee(i interface{}) error {
v, ok := i.(sdk.Coins)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("invalid parameter type: %T", i)
}

if err := v.Validate(); err != nil {
return err
}

return nil
}

func validateAllowedClassCreators(i interface{}) error {
v, ok := i.([]string)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("invalid parameter type: %T", i)
}
for _, sAddr := range v {
_, err := sdk.AccAddressFromBech32(sAddr)
if err != nil {
return sdkerrors.ErrInvalidAddress.Wrapf("invalid creator address: %s", err.Error())
}
}
return nil
}

func validateAllowlistEnabled(i interface{}) error {
_, ok := i.(bool)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("invalid parameter type: %T", i)
}

return nil
}

func validateCreditTypes(i interface{}) error {
creditTypes, ok := i.([]*CreditType)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("invalid parameter type: %T", i)
}

// ensure no duplicate credit types or abbreviations and that all
// precisions conform to hardcoded PRECISION above
seenTypes := make(map[string]bool)
seenAbbrs := make(map[string]bool)
for _, creditType := range creditTypes {
// Validate name
T := ecocredit.NormalizeCreditTypeName(creditType.Name)
if T != creditType.Name {
return sdkerrors.ErrInvalidRequest.Wrapf("credit type name should be normalized: got %s, should be %s", creditType.Name, T)
}
if creditType.Name == "" {
return sdkerrors.ErrInvalidRequest.Wrap("empty credit type name")
}
if seenTypes[T] {
return sdkerrors.ErrInvalidRequest.Wrapf("duplicate credit type name in request: %s", T)
}

// Validate abbreviation
abbr := creditType.Abbreviation
err := ValidateCreditTypeAbbreviation(abbr)
if err != nil {
return err
}
if seenAbbrs[abbr] {
return sdkerrors.ErrInvalidRequest.Wrapf("duplicate credit type abbreviation: %s", abbr)
}

// Validate precision
// TODO: remove after we open governance changes for precision
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
if creditType.Precision != PRECISION {
return sdkerrors.ErrInvalidRequest.Wrapf("invalid precision %d: precision is currently locked to %d", creditType.Precision, PRECISION)
}

// Validate units
if creditType.Unit == "" {
return sdkerrors.ErrInvalidRequest.Wrap("empty credit type unit")
}

// Mark type and abbr as seen
seenTypes[T] = true
seenAbbrs[abbr] = true
}

return nil
}

// ValidateCreditTypeAbbreviation asserts that the argument is 1-3 latin alphabet uppercase letters
func ValidateCreditTypeAbbreviation(abbr string) error {
reAbbr := regexp.MustCompile(`^[A-Z]{1,3}$`)
matches := reAbbr.FindStringSubmatch(abbr)
if matches == nil {
return sdkerrors.ErrInvalidRequest.Wrapf("credit type abbreviation must be 1-3 uppercase latin letters: got %s", abbr)
}
return nil
}

func validateBasketCreationFee(i interface{}) error {
v, ok := i.(sdk.Coins)
if !ok {
return sdkerrors.ErrInvalidType.Wrapf("invalid parameter type: %T", i)
}

if err := v.Validate(); err != nil {
return err
}

return nil
}
2 changes: 1 addition & 1 deletion x/ecocredit/server/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import (
)

func (s serverImpl) RunMigrations(ctx sdk.Context, cdc codec.Codec) error {
return v3.MigrateState(ctx, s.storeKey, cdc, s.stateStore)
return v3.MigrateState(ctx, s.storeKey, cdc, s.stateStore, s.paramSpace)
}