Skip to content

Commit

Permalink
feat: gov params for permissioned eco credits (#425)
Browse files Browse the repository at this point in the history
Closes: #425

Add new gov params for permissioned credit creation

Fix error in app/regen/cmd/genaccounts.go - couldn't compile due to error of cdc casting to itself
  • Loading branch information
technicallyty committed Aug 7, 2021
1 parent 3265a86 commit 7b7d2f8
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* add dates as top level fields in credit batches (#393)
* add project location as field in credit batches (#394)
* use dec wrapper for decimal operations (#435)
* add params for an allowlist of permissioned credit designers (#425)

## [1.0.0] - 2021-04-13

Expand Down
3 changes: 1 addition & 2 deletions app/regen/cmd/genaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -116,7 +115,7 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa
}

cdc := clientCtx.Codec
authGenState := authtypes.GetGenesisStateFromAppState(cdc.(codec.Codec), appState)
authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)

accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions docs/modules/ecocredit/protobuf.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ use with the x/params module.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| credit_class_fee | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | credit_class_fee is the fixed fee charged on creation of a new credit class |
| allowed_class_designers | [string](#string) | repeated | allowed_class_designers is an allowlist defining the addresses with the required permissions to create credit classes |
| allowlist_enabled | [bool](#bool) | | allowlist_enabled is a param that enables/disables the allowlist for credit creation |



Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/tendermint/tendermint v0.34.11
github.com/tendermint/tm-db v0.6.4
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 // indirect
)

replace google.golang.org/grpc => google.golang.org/grpc v1.33.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1234,8 +1234,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ=
google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67 h1:VmMSf20ssFK0+u1dscyTH9bU4/M4y+X/xNfkvD6kGtM=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
Expand Down
16 changes: 13 additions & 3 deletions proto/regen/ecocredit/v1alpha1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,22 @@ message BatchInfo {
// use with the x/params module.
message Params {
// credit_class_fee is the fixed fee charged on creation of a new credit class
repeated cosmos.base.v1beta1.Coin credit_class_fee = 1
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
repeated cosmos.base.v1beta1.Coin credit_class_fee = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// allowed_class_designers is an allowlist defining the addresses with
// the required permissions to create credit classes
repeated string allowed_class_designers = 2;

// allowlist_enabled is a param that enables/disables the allowlist for credit
// creation
bool allowlist_enabled = 3;
}

// GenesisState defines the state of the ecocredit module that is needed at genesis
message GenesisState {
// Params contains the updateable global parameters for use with the x/params module
Params params = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"params\""];
}
}
35 changes: 32 additions & 3 deletions x/ecocredit/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ var (
// TODO: Decide a sensible default value
DefaultCreditClassFeeTokens = sdk.NewInt(10000)
KeyCreditClassFee = []byte("CreditClassFee")
KeyAllowedClassDesigners = []byte("AllowedClassDesigners")
KeyAllowlistEnabled = []byte("AllowlistEnabled")
)

func ParamKeyTable() paramtypes.KeyTable {
Expand All @@ -21,6 +23,8 @@ func ParamKeyTable() paramtypes.KeyTable {
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
return paramtypes.ParamSetPairs{
paramtypes.NewParamSetPair(KeyCreditClassFee, &p.CreditClassFee, validateCreditClassFee),
paramtypes.NewParamSetPair(KeyAllowedClassDesigners, &p.AllowedClassDesigners, validateAllowlistCreditDesigners),
paramtypes.NewParamSetPair(KeyAllowlistEnabled, &p.AllowlistEnabled, validateAllowlistEnabled),
}
}

Expand All @@ -37,12 +41,37 @@ func validateCreditClassFee(i interface{}) error {
return nil
}

func NewParams(creditClassFee sdk.Coins) Params {
func validateAllowlistCreditDesigners(i interface{}) error {
v, ok := i.([]string)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
for _, sAddr := range v {
_, err := sdk.AccAddressFromBech32(sAddr)
if err != nil {
return err
}
}
return nil
}

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

return nil
}

func NewParams(creditClassFee sdk.Coins, allowlist []string, allowlistEnabled bool) Params {
return Params{
CreditClassFee: creditClassFee,
CreditClassFee: creditClassFee,
AllowedClassDesigners: allowlist,
AllowlistEnabled: allowlistEnabled,
}
}

func DefaultParams() Params {
return NewParams(sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, DefaultCreditClassFeeTokens)))
return NewParams(sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, DefaultCreditClassFeeTokens)), []string{}, false)
}
134 changes: 134 additions & 0 deletions x/ecocredit/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package ecocredit

import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"testing"
)

func TestDefaultParams(t *testing.T) {
expected := Params{
CreditClassFee: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, DefaultCreditClassFeeTokens)),
AllowedClassDesigners: []string{},
AllowlistEnabled: false,
}
df := DefaultParams()

require.Equal(t, df.String(), expected.String())
}

func Test_validateAllowlistCreditCreators(t *testing.T) {

genAddrs := make([]string, 0, 3)
for i := 0; i < 3; i++ {
genAddrs = append(genAddrs, sdk.MustBech32ifyAddressBytes(sdk.Bech32MainPrefix, []byte(fmt.Sprintf("testaddr-%d", i))))
}

tests := []struct {
name string
args interface{}
wantErr bool
}{
{
name: "valid creator list",
args: genAddrs,
wantErr: false,
},
{
name: "invalid creator list",
args: []string{"bogus", "superbogus", "megabogus"},
wantErr: true,
},
{
name: "mix of invalid/valid",
args: []string{"bogus", genAddrs[0]},
wantErr: true,
},
{
name: "wrong type",
args: []int{1, 2, 3, 4, 5},
wantErr: true,
},
{
name: "not even an array",
args: "bruh",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateAllowlistCreditDesigners(tt.args); (err != nil) != tt.wantErr {
t.Errorf("validateAllowlistCreditCreators() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func Test_validateAllowlistEnabled(t *testing.T) {

tests := []struct {
name string
args interface{}
wantErr bool
}{
{
name: "valid boolean value",
args: true,
wantErr: false,
},
{
name: "valid boolean v2",
args: false,
wantErr: false,
},
{
name: "invalid type",
args: []string{"lol", "rofl"},
wantErr: true,
},
{
name: "seems valid but its not",
args: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateAllowlistEnabled(tt.args); (err != nil) != tt.wantErr {
t.Errorf("validateAllowlistEnabled() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func Test_validateCreditClassFee(t *testing.T) {
tests := []struct {
name string
args interface{}
wantErr bool
}{
{
name: "valid credit fee",
args: sdk.NewCoins(sdk.NewCoin("regen", sdk.NewInt(45)), sdk.NewCoin("osmo", sdk.NewInt(32))),
wantErr: false,
},
{
name: "no fee is valid",
args: sdk.NewCoins(),
wantErr: false,
},
{
name: "invalid type",
args: 45,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateCreditClassFee(tt.args); (err != nil) != tt.wantErr {
t.Errorf("validateCreditClassFee() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
33 changes: 33 additions & 0 deletions x/ecocredit/server/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ func (s serverImpl) CreateClass(goCtx context.Context, req *ecocredit.MsgCreateC
return nil, err
}

if s.allowlistEnabled(ctx.Context) {
allowListed, err := s.isDesignerAllowListed(ctx.Context, designerAddress)
if err != nil {
return nil, err
}
if !allowListed {
return nil, fmt.Errorf("%s is not allowed to create credit classes", designerAddress.String())
}
}

err = s.chargeCreditClassFee(ctx.Context, designerAddress)
if err != nil {
return nil, err
Expand Down Expand Up @@ -463,3 +473,26 @@ func subtractTradableBalanceAndSupply(store sdk.KVStore, holder string, batchDen

return nil
}

// Checks if the given address is in the allowlist of credit class designers
func (s serverImpl) isDesignerAllowListed(ctx sdk.Context, addr sdk.Address) (bool, error) {
var params ecocredit.Params
s.paramSpace.GetParamSet(ctx, &params)
for _, sAddr := range params.AllowedClassDesigners {
allowListedAddr, err := sdk.AccAddressFromBech32(sAddr)
if err != nil {
return false, err
}
if addr.Equals(allowListedAddr) {
return true, nil
}
}
return false, nil
}

// Checks if the allowlist of credit class designers is enabled
func (s serverImpl) allowlistEnabled(ctx sdk.Context) bool {
var params ecocredit.Params
s.paramSpace.GetParamSet(ctx, &params)
return params.AllowlistEnabled
}
69 changes: 69 additions & 0 deletions x/ecocredit/server/testsuite/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,4 +620,73 @@ func (s *IntegrationTestSuite) TestScenario() {
}
})
}

/**** TEST ALLOWLIST CREDIT DESIGNERS ****/
allowlistCases := []struct {
name string
designerAcc sdk.AccAddress
allowlist []string
allowlistEnabled bool
wantErr bool
}{
{
name: "valid allowlist and enabled",
allowlist: []string{s.signers[0].String()},
designerAcc: s.signers[0],
allowlistEnabled: true,
wantErr: false,
},
{
name: "valid multi addrs in allowlist",
allowlist: []string{s.signers[0].String(), s.signers[1].String(), s.signers[2].String()},
designerAcc: s.signers[0],
allowlistEnabled: true,
wantErr: false,
},
{
name: "designer is not part of the allowlist",
allowlist: []string{s.signers[0].String()},
designerAcc: s.signers[1],
allowlistEnabled: true,
wantErr: true,
},
{
name: "valid allowlist but disabled - anyone can create credits",
allowlist: []string{s.signers[0].String()},
designerAcc: s.signers[0],
allowlistEnabled: false,
wantErr: false,
},
{
name: "empty and enabled allowlist - nobody can create credits",
allowlist: []string{},
designerAcc: s.signers[0],
allowlistEnabled: true,
wantErr: true,
},
}

for _, tc := range allowlistCases {
tc := tc
s.Run(tc.name, func() {
s.paramSpace.Set(s.sdkCtx, ecocredit.KeyAllowedClassDesigners, tc.allowlist)
s.paramSpace.Set(s.sdkCtx, ecocredit.KeyAllowlistEnabled, tc.allowlistEnabled)

// fund the designer account
s.Require().NoError(fundAccount(s.bankKeeper, s.sdkCtx, tc.designerAcc, sdk.NewCoins(sdk.NewInt64Coin("stake", 10000))))

createClsRes, err = s.msgClient.CreateClass(s.ctx, &ecocredit.MsgCreateClass{
Designer: tc.designerAcc.String(),
Issuers: []string{issuer1, issuer2},
Metadata: nil,
})
if tc.wantErr {
s.Require().Error(err)
s.Require().Nil(createClsRes)
} else {
s.Require().NoError(err)
s.Require().NotNil(createClsRes)
}
})
}
}
Loading

0 comments on commit 7b7d2f8

Please sign in to comment.