Skip to content

Commit

Permalink
feat: basket window integration (#757)
Browse files Browse the repository at this point in the history
* copy new version of state.pb.go

* backport basket.date_criteria

* regenerate proto

* gomod

* adding tests

* formatting

* Update x/ecocredit/basket/msg_create.go

Co-authored-by: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com>

* formatting

* update go doc comments

Co-authored-by: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com>
  • Loading branch information
robert-zaremba and ryanchristo authored Feb 11, 2022
1 parent 9332f40 commit dd9c188
Show file tree
Hide file tree
Showing 13 changed files with 631 additions and 183 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ require (
github.com/stretchr/testify v1.7.0
github.com/tendermint/tendermint v0.34.14
github.com/tendermint/tm-db v0.6.6
golang.org/x/crypto v0.0.0-20220209155544-dad33157f4bf // indirect
google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 // indirect
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 // indirect
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 // indirect
)

require (
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1152,8 +1152,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220209155544-dad33157f4bf h1:gdgmgieTI2lLaGI2N+xEiaCMUgo2XFmAS0rlF8HZoso=
golang.org/x/crypto v0.0.0-20220209155544-dad33157f4bf/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig=
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -1564,8 +1564,8 @@ google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6D
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-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 h1:pwzFiZfBTH/GjBWz1BcDwMBaHBo8mZvpLa7eBKJpFAk=
google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336 h1:RK2ysGpQApbI6U7xn+ROT2rrm08lE/t8AcGqG8XI1CY=
google.golang.org/genproto v0.0.0-20220210181026-6fee9acbd336/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
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
10 changes: 4 additions & 6 deletions proto/regen/ecocredit/basket/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package regen.ecocredit.basket.v1;
option go_package = "github.com/regen-network/regen-ledger/x/ecocredit/basket";

import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "regen/ecocredit/basket/v1/types.proto";
import "cosmos/base/v1beta1/coin.proto";

Expand Down Expand Up @@ -39,8 +38,8 @@ message MsgCreate {
// exponent is the exponent that will be used for converting credits to basket
// tokens and for bank denom metadata. It also limits the precision of
// credit amounts when putting credits into a basket. An exponent of 6 will
// mean that 10^6 units of a basket token will be issued for 1.0 credits and that
// this should be displayed as one unit in user interfaces. It also means
// mean that 10^6 units of a basket token will be issued for 1.0 credits and
// that this should be displayed as one unit in user interfaces. It also means
// that the maximum precision of credit amounts is 6 decimal places so that
// the need to round is eliminated. The exponent must be >= the precision of
// the credit type at the time the basket is created.
Expand All @@ -59,9 +58,8 @@ message MsgCreate {
// allowed_classes are the credit classes allowed to be put in the basket
repeated string allowed_classes = 7;

// min_start_date is the earliest start date for batches of credits allowed
// into the basket. Required.
google.protobuf.Timestamp min_start_date = 8;
// date_criteria is the date criteria for batches admitted to the basket.
DateCriteria date_criteria = 8;

// fee is the fee that the curator will pay to create the basket. It must be
// >= the required Params.basket_creation_fee. We include the fee explicitly
Expand Down
21 changes: 21 additions & 0 deletions proto/regen/ecocredit/basket/v1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package regen.ecocredit.basket.v1;

option go_package = "github.com/regen-network/regen-ledger/x/ecocredit/basket";

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

// BasketCredit represents the information for a credit batch inside a basket.
message BasketCredit {

Expand All @@ -15,3 +18,21 @@ message BasketCredit {
// credit type for this batch.
string amount = 2;
}

// DateCriteria represents a basket credit batch date criteria.
message DateCriteria {

// sum is the oneof representing the date criteria.
oneof sum {
// min_start_date is the earliest start date for batches of credits allowed
// into the basket.
google.protobuf.Timestamp min_start_date = 1;

// start_date_window is a duration of time measured into the past which sets
// a cutoff for batch start dates when adding new credits to the basket.
// Based on the current block timestamp, credits whose start date is before
// `block_timestamp - batch_date_window` will not be allowed into the
// basket.
google.protobuf.Duration start_date_window = 2;
}
}
21 changes: 19 additions & 2 deletions x/basket/spec/protobuf.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- [regen/ecocredit/basket/v1/types.proto](#regen/ecocredit/basket/v1/types.proto)
- [BasketCredit](#regen.ecocredit.basket.v1.BasketCredit)
- [DateCriteria](#regen.ecocredit.basket.v1.DateCriteria)

- [regen/ecocredit/basket/v1/tx.proto](#regen/ecocredit/basket/v1/tx.proto)
- [MsgCreate](#regen.ecocredit.basket.v1.MsgCreate)
Expand Down Expand Up @@ -43,6 +44,22 @@ BasketCredit represents the information for a credit batch inside a basket.




<a name="regen.ecocredit.basket.v1.DateCriteria"></a>

### DateCriteria
DateCriteria represents a basket credit batch date criteria.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| min_start_date | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | min_start_date is the earliest start date for batches of credits allowed into the basket. |
| start_date_window | [google.protobuf.Duration](#google.protobuf.Duration) | | start_date_window is a duration of time measured into the past which sets a cutoff for batch start dates when adding new credits to the basket. Based on the current block timestamp, credits whose start date is before `block_timestamp - batch_date_window` will not be allowed into the basket. |





<!-- end messages -->

<!-- end enums -->
Expand Down Expand Up @@ -71,11 +88,11 @@ MsgCreateBasket is the Msg/CreateBasket request type.
| curator | [string](#string) | | curator is the address of the basket curator who is able to change certain basket settings. |
| name | [string](#string) | | name will be used to create a bank denom for this basket token. |
| display_name | [string](#string) | | display_name will be used to create a bank Metadata display name for this basket token. |
| exponent | [uint32](#uint32) | | exponent is the exponent that will be used for converting credits to basket tokens and for bank denom metadata. An exponent of 6 will mean that 10^6 units of a basket token will be issued for 1.0 credits and that this should be displayed as one unit in user interfaces. The exponent must be >= the precision of the credit type to minimize the need for rounding (rounding may still be needed if the precision changes to be great than the exponent). |
| exponent | [uint32](#uint32) | | exponent is the exponent that will be used for converting credits to basket tokens and for bank denom metadata. It also limits the precision of credit amounts when putting credits into a basket. An exponent of 6 will mean that 10^6 units of a basket token will be issued for 1.0 credits and that this should be displayed as one unit in user interfaces. It also means that the maximum precision of credit amounts is 6 decimal places so that the need to round is eliminated. The exponent must be >= the precision of the credit type at the time the basket is created. |
| disable_auto_retire | [bool](#bool) | | disable_auto_retire allows auto-retirement to be disabled. The credits will be auto-retired if disable_auto_retire is false unless the credits were previously put into the basket by the address picking them from the basket, in which case they will remain tradable. |
| credit_type_name | [string](#string) | | credit_type_name filters against credits from this credit type name. |
| allowed_classes | [string](#string) | repeated | allowed_classes are the credit classes allowed to be put in the basket |
| min_start_date | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | min_start_date is the earliest start date for batches of credits allowed into the basket. |
| date_criteria | [DateCriteria](#regen.ecocredit.basket.v1.DateCriteria) | | date_criteria is the date criteria for batches admitted to the basket. |
| fee | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | fee is the fee that the curator will pay to create the basket. It must be >= the required Params.basket_creation_fee. We include the fee explicitly here so that the curator explicitly acknowledges paying this fee and is not surprised to learn that the paid a big fee and didn't know beforehand. |


Expand Down
48 changes: 33 additions & 15 deletions x/ecocredit/basket/msg_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ const nameMaxLen = 32
const displayNameMinLen = 3
const displayNameMaxLen = 32
const exponentMax = 32
const creditTNameMaxLen = 32
const creditNameMaxLen = 32

var errBadReq = sdkerrors.ErrInvalidRequest

// ValidateBasic does a stateless sanity check on the provided data.
func (m MsgCreate) ValidateBasic() error {
Expand All @@ -26,34 +28,32 @@ func (m MsgCreate) ValidateBasic() error {
// TODO: add proper validation once we will have proper requirements.
// https://github.com/regen-network/regen-ledger/issues/732
if m.Name == "" || len(m.Name) > nameMaxLen {
return sdkerrors.ErrInvalidRequest.Wrap("name must not be empty and must not be longer than 32 characters long")
return errBadReq.Wrapf("name must not be empty and must not be longer than %d characters long", nameMaxLen)
}
if len(m.DisplayName) < displayNameMinLen || len(m.DisplayName) > displayNameMaxLen {
return sdkerrors.ErrInvalidRequest.Wrapf("display_name must be between %d and %d characters long", displayNameMinLen, displayNameMaxLen)
return errBadReq.Wrapf("display_name must be between %d and %d characters long", displayNameMinLen, displayNameMaxLen)
}
if m.Exponent > exponentMax {
return sdkerrors.ErrInvalidRequest.Wrapf("exponent must not be bigger than %d", exponentMax)
return errBadReq.Wrapf("exponent must not be bigger than %d", exponentMax)
}
if m.CreditTypeName == "" {
return sdkerrors.ErrInvalidRequest.Wrap("credit_type_name must be defined")
return errBadReq.Wrap("credit_type_name must be defined")
}
if len(m.CreditTypeName) > creditTNameMaxLen {
return sdkerrors.ErrInvalidRequest.Wrapf("credit_type_name must not be longer than %d", creditTNameMaxLen)
if len(m.CreditTypeName) > creditNameMaxLen {
return errBadReq.Wrapf("credit_type_name must not be longer than %d", creditNameMaxLen)
}
if err := validateDateCriteria(m.DateCriteria); err != nil {
return err
}

if len(m.AllowedClasses) == 0 {
return sdkerrors.ErrInvalidRequest.Wrap("allowed_classes is required")
return errBadReq.Wrap("allowed_classes is required")
}
for i := range m.AllowedClasses {
if m.AllowedClasses[i] == "" {
return sdkerrors.ErrInvalidRequest.Wrapf("allowed_classes[%d] must be defined", i)
return errBadReq.Wrapf("allowed_classes[%d] must be defined", i)
}
}
if err := m.Fee.Validate(); err != nil {
return err
}

return nil
return m.Fee.Validate()
}

// Validate additional validation with access to the state data.
Expand All @@ -78,3 +78,21 @@ func (m MsgCreate) Route() string { return sdk.MsgTypeURL(&m) }

// Type Implements LegacyMsg.
func (m MsgCreate) Type() string { return sdk.MsgTypeURL(&m) }

func validateDateCriteria(d *DateCriteria) error {
if d == nil {
return nil
}
if x := d.GetMinStartDate(); x != nil {
if x.Seconds < -2208992400 { // batch older than 1900-01-01 is an obvious error
return errBadReq.Wrap("date_criteria.min_start_date must be after 1900-01-01")
}
} else if x := d.GetStartDateWindow(); x != nil {
if x.Seconds < 24*3600 {
return errBadReq.Wrap("date_criteria.start_date_window must be at least 1 day")
}
} else {
return errBadReq.Wrapf("unsupported date_criteria value %t", d)
}
return nil
}
97 changes: 78 additions & 19 deletions x/ecocredit/basket/msg_create_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package basket

import (
"fmt"
"testing"
"time"

"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -26,45 +26,104 @@ func TestMsgCreateValidateBasic(t *testing.T) {
name := randstr.String(nameMaxLen)
dName := randstr.String((displayNameMaxLen + displayNameMinLen) / 2)
creditName := randstr.String(10)
start := gogotypes.TimestampNow()
start := &DateCriteria{&DateCriteria_MinStartDate{gogotypes.TimestampNow()}}

classes := []string{"eco_class1"}

tcs := []struct {
id string
msg MsgCreate
err string
}{
{MsgCreate{Curator: "wrong"}, "malformed curator address"},
{MsgCreate{Curator: a, Name: ""}, "name must not be empty"},
{MsgCreate{Curator: a, Name: randstr.String(nameMaxLen + 1)}, "name must not be empty and must not be longer than"},
{MsgCreate{Curator: a, Name: name, DisplayName: ""}, "display_name must be between"},
{MsgCreate{Curator: a, Name: name, DisplayName: randstr.String(displayNameMaxLen + 1)}, "display_name must be between"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax + 1}, "exponent must not be bigger than"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax},
{"curator-1",
MsgCreate{Curator: "wrong"},
"malformed curator address"},
{"name-1",
MsgCreate{Curator: a, Name: ""}, "name must not be empty"},
{"name-2",
MsgCreate{Curator: a, Name: randstr.String(nameMaxLen + 1)},
"name must not be empty and must not be longer than"},
{"name-3",
MsgCreate{Curator: a, Name: name, DisplayName: ""},
"display_name must be between"},
{"name-4",
MsgCreate{Curator: a, Name: name, DisplayName: randstr.String(displayNameMaxLen + 1)},
"display_name must be between"},
{"exponent-1",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax + 1},
"exponent must not be bigger than"},
{"credity_type-1",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax},
"credit_type_name must be defined"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: randstr.String(creditTNameMaxLen + 1)},
{"credity_type-2",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: randstr.String(creditNameMaxLen + 1)},
"credit_type_name must not be longer"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, MinStartDate: start},
{"date_criteria-1",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, DateCriteria: &DateCriteria{}},
"unsupported date_criteria value"},
{"allowed_classes-1",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, DateCriteria: start},
"allowed_classes is required"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, MinStartDate: start, AllowedClasses: []string{"class1", ""}},
{"allowed_classes-2",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, DateCriteria: start, AllowedClasses: []string{"class1", ""}},
"allowed_classes[1] must be defined"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, MinStartDate: start, AllowedClasses: classes, Fee: sdk.Coins{sdk.Coin{Denom: "1a"}}},
{"fee-1",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, DateCriteria: start, AllowedClasses: classes, Fee: sdk.Coins{sdk.Coin{Denom: "1a"}}},
"invalid denom"},
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, MinStartDate: start, AllowedClasses: classes, Fee: sdk.Coins{sdk.Coin{"aa", sdk.NewInt(-1)}}},
{"fee-2", MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: exponentMax, CreditTypeName: creditName, DateCriteria: start, AllowedClasses: classes, Fee: sdk.Coins{sdk.Coin{"aa", sdk.NewInt(-1)}}},
"invalid denom"},

{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: 0, CreditTypeName: creditName, MinStartDate: start, AllowedClasses: classes}, ""},
// nil min_start_time is also OK
{MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: 0, CreditTypeName: creditName, MinStartDate: nil, AllowedClasses: classes}, ""},
{"good-1-fees-not-required",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: 0, CreditTypeName: creditName, DateCriteria: start, AllowedClasses: classes}, ""},
{"good-date-criteria-not-required",
MsgCreate{Curator: a, Name: name, DisplayName: dName, Exponent: 6, CreditTypeName: creditName, DateCriteria: nil, AllowedClasses: classes, Fee: sdk.Coins{sdk.NewInt64Coin("regen", 1)}}, ""},
}

for i, tc := range tcs {
t.Run(fmt.Sprint("test-", i), func(t *testing.T) {
for _, tc := range tcs {
t.Run(tc.id, func(t *testing.T) {
err := tc.msg.ValidateBasic()
errorMatches(t, err, tc.err)
})
}
}

func TestMsgCreateValidateDateCriteria(t *testing.T) {
tcs := []struct {
id string
d DateCriteria
err string
}{
{"nil-min_start_date",
DateCriteria{&DateCriteria_MinStartDate{nil}},
"unsupported date_criteria value"},
{"bad-min_start_date",
DateCriteria{&DateCriteria_MinStartDate{
&gogotypes.Timestamp{Seconds: time.Date(1400, 1, 1, 0, 0, 0, 0, time.UTC).Unix()}}},
"date_criteria.min_start_date must be after"},
{"nil-start_date_window",
DateCriteria{&DateCriteria_StartDateWindow{}},
"unsupported date_criteria value"},
{"nil-start_date_window",
DateCriteria{&DateCriteria_StartDateWindow{
&gogotypes.Duration{Seconds: 3600}}},
"date_criteria.start_date_window must be at least"},

{"good-min_start_date",
DateCriteria{&DateCriteria_MinStartDate{gogotypes.TimestampNow()}},
""},
{"good-start_date_window",
DateCriteria{&DateCriteria_StartDateWindow{
&gogotypes.Duration{Seconds: 3600 * 24 * 2}}},
""},
}
for _, tc := range tcs {
t.Run(tc.id, func(t *testing.T) {
err := validateDateCriteria(&tc.d)
errorMatches(t, err, tc.err)
})
}
}

func TestMsgCreateGetSigners(t *testing.T) {
_, _, addr1 := testdata.KeyTestPubAddr()
m := MsgCreate{Curator: addr1.String(), Name: "name", Exponent: 2}
Expand Down
Loading

0 comments on commit dd9c188

Please sign in to comment.