From fae569fd2cbb8ba0a1f85e0c805b84c5613911d2 Mon Sep 17 00:00:00 2001 From: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com> Date: Mon, 9 May 2022 08:44:02 -0700 Subject: [PATCH] test(x/ecocredit): basket put acceptance tests (#1065) * test(x/ecocredit): basket acceptance tests * update put acceptance tests * add put check balance steps * add put balance and supply steps * consolidate steps with background * simplify steps and use scenario outlines * Update x/ecocredit/server/basket/keeper_test.go Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com> * add credit type util test * clean up id and key names * check ok on new int * update credit and token amount scenarios * clean up and add exceeds max decimal * clean up language * fix merge * fix miscommit Co-authored-by: Tyler <48813565+technicallyty@users.noreply.github.com> --- x/ecocredit/basket/features/msg_put.feature | 148 +++++ x/ecocredit/basket/msg_put.go | 17 +- x/ecocredit/basket/msg_put_test.go | 132 +---- x/ecocredit/core/utils.go | 16 +- x/ecocredit/core/utils_test.go | 10 + x/ecocredit/features/basket/put.feature | 53 -- x/ecocredit/features/basket/put_date.feature | 30 - x/ecocredit/server/basket/create_test.go | 14 +- .../basket/features/msg_create.feature} | 0 .../server/basket/features/msg_put.feature | 266 +++++++++ .../basket/features/msg_take.feature} | 0 x/ecocredit/server/basket/keeper_test.go | 8 +- x/ecocredit/server/basket/msg_put_test.go | 561 ++++++++++++++++++ x/ecocredit/server/basket/put_test.go | 467 --------------- x/ecocredit/server/basket/take_test.go | 22 +- x/ecocredit/server/basket/util_test.go | 49 +- 16 files changed, 1112 insertions(+), 681 deletions(-) create mode 100644 x/ecocredit/basket/features/msg_put.feature delete mode 100644 x/ecocredit/features/basket/put.feature delete mode 100644 x/ecocredit/features/basket/put_date.feature rename x/ecocredit/{features/basket/create.feature => server/basket/features/msg_create.feature} (100%) create mode 100644 x/ecocredit/server/basket/features/msg_put.feature rename x/ecocredit/{features/basket/take.feature => server/basket/features/msg_take.feature} (100%) create mode 100644 x/ecocredit/server/basket/msg_put_test.go delete mode 100644 x/ecocredit/server/basket/put_test.go diff --git a/x/ecocredit/basket/features/msg_put.feature b/x/ecocredit/basket/features/msg_put.feature new file mode 100644 index 0000000000..15bda6812b --- /dev/null +++ b/x/ecocredit/basket/features/msg_put.feature @@ -0,0 +1,148 @@ +Feature: MsgPut + + Scenario: a valid message + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + { + "batch_denom": "C01-001-20200101-20210101-001", + "amount": "100" + } + ] + } + """ + When the message is validated + Then expect no error + + Scenario: an error is returned if owner is empty + Given the message + """ + {} + """ + When the message is validated + Then expect the error "empty address string is not allowed: invalid request" + + Scenario: an error is returned if owner is not a bech32 address + Given the message + """ + { + "owner": "foo" + } + """ + When the message is validated + Then expect the error "decoding bech32 failed: invalid bech32 string length 3: invalid request" + + Scenario: an error is returned if basket denom is empty + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27" + } + """ + When the message is validated + Then expect the error "basket denom cannot be empty: invalid request" + + Scenario: an error is returned if basket denom is not formatted + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "1" + } + """ + When the message is validated + Then expect the error "1 is not a valid basket denom: invalid request" + + Scenario: an error is returned if credit list is empty + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT" + } + """ + When the message is validated + Then expect the error "credits cannot be empty: invalid request" + + Scenario: an error is returned if a credit batch denom is empty + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + {} + ] + } + """ + When the message is validated + Then expect the error "credit batch denom cannot be empty: invalid request" + + Scenario: an error is returned if a credit batch denom is not formatted + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + { + "batch_denom": "foo" + } + ] + } + """ + When the message is validated + Then expect the error "invalid denom: expected format A00-000-00000000-00000000-000: parse error: invalid request" + + Scenario: an error is returned if a credit amount is empty + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + { + "batch_denom": "C01-001-20200101-20210101-001" + } + ] + } + """ + When the message is validated + Then expect the error "credit amount cannot be empty: invalid request" + + Scenario: an error is returned if a credit amount is not an integer + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + { + "batch_denom": "C01-001-20200101-20210101-001", + "amount": "foo" + } + ] + } + """ + When the message is validated + Then expect the error "parse mantissa: foo: invalid decimal string: invalid decimal string: invalid request" + + Scenario: an error is returned if a credit amount is less than zero + Given the message + """ + { + "owner": "cosmos1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27", + "basket_denom": "NCT", + "credits": [ + { + "batch_denom": "C01-001-20200101-20210101-001", + "amount": "-100" + } + ] + } + """ + When the message is validated + Then expect the error "expected a positive decimal, got -100: invalid decimal string: invalid request" diff --git a/x/ecocredit/basket/msg_put.go b/x/ecocredit/basket/msg_put.go index e08098b2bf..68a0c1bf56 100644 --- a/x/ecocredit/basket/msg_put.go +++ b/x/ecocredit/basket/msg_put.go @@ -28,20 +28,35 @@ func (m MsgPut) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(m.Owner); err != nil { return sdkerrors.ErrInvalidRequest.Wrapf(err.Error()) } + + if len(m.BasketDenom) == 0 { + return sdkerrors.ErrInvalidRequest.Wrap("basket denom cannot be empty") + } + if err := sdk.ValidateDenom(m.BasketDenom); err != nil { return sdkerrors.ErrInvalidRequest.Wrapf("%s is not a valid basket denom", m.BasketDenom) } + if len(m.Credits) > 0 { for _, credit := range m.Credits { + if len(credit.BatchDenom) == 0 { + return sdkerrors.ErrInvalidRequest.Wrap("credit batch denom cannot be empty") + } + if err := core.ValidateBatchDenom(credit.BatchDenom); err != nil { return sdkerrors.ErrInvalidRequest.Wrap(err.Error()) } + + if len(credit.Amount) == 0 { + return sdkerrors.ErrInvalidRequest.Wrap("credit amount cannot be empty") + } + if _, err := math.NewPositiveDecFromString(credit.Amount); err != nil { return sdkerrors.ErrInvalidRequest.Wrap(err.Error()) } } } else { - return sdkerrors.ErrInvalidRequest.Wrap("no credits were specified to put into the basket") + return sdkerrors.ErrInvalidRequest.Wrap("credits cannot be empty") } return nil diff --git a/x/ecocredit/basket/msg_put_test.go b/x/ecocredit/basket/msg_put_test.go index 4441f38833..3aefc792bf 100644 --- a/x/ecocredit/basket/msg_put_test.go +++ b/x/ecocredit/basket/msg_put_test.go @@ -2,116 +2,40 @@ package basket import ( "testing" - "time" + "github.com/gogo/protobuf/jsonpb" + "github.com/regen-network/gocuke" "github.com/stretchr/testify/require" +) - "github.com/cosmos/cosmos-sdk/testutil/testdata" +type msgPutSuite struct { + t gocuke.TestingT + msg *MsgPut + err error +} - "github.com/regen-network/regen-ledger/x/ecocredit/core" -) +func TestMsgPut(t *testing.T) { + gocuke.NewRunner(t, &msgPutSuite{}).Path("./features/msg_put.feature").Run() +} -func TestMsgPut_ValidateBasic(t *testing.T) { - t.Parallel() +func (s *msgPutSuite) Before(t gocuke.TestingT) { + s.t = t +} - _, _, addr := testdata.KeyTestPubAddr() - t1, t2 := time.Now(), time.Now() - denom, err := core.FormatBatchDenom("C02-001", 1, &t1, &t2) - require.NoError(t, err) +func (s *msgPutSuite) TheMessage(a gocuke.DocString) { + s.msg = &MsgPut{} + err := jsonpb.UnmarshalString(a.Content, s.msg) + require.NoError(s.t, err) +} + +func (s *msgPutSuite) TheMessageIsValidated() { + s.err = s.msg.ValidateBasic() +} - type fields struct { - Owner string - BasketDenom string - Credits []*BasketCredit - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - { - name: "valid", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "100.5302"}}, - }, - }, - { - name: "bad addr", - fields: fields{ - Owner: "oops!", - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "100.5302"}}, - }, - wantErr: true, - }, - { - name: "no credits", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - }, - wantErr: true, - }, - { - name: "bad batch denom", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: "bad bad not good!", Amount: "100.5302"}}, - }, - wantErr: true, - }, - { - name: "bad amount", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "100.52.302.35.2"}}, - }, - wantErr: true, - }, - { - name: "zero amount", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "0"}}, - }, - wantErr: true, - }, - { - name: "negative amount", - fields: fields{ - Owner: addr.String(), - BasketDenom: "COOL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "-50.329"}}, - }, - wantErr: true, - }, - { - name: "bad basket denom", - fields: fields{ - Owner: addr.String(), - BasketDenom: "CO:OL", - Credits: []*BasketCredit{{BatchDenom: denom, Amount: "100"}}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() +func (s *msgPutSuite) ExpectTheError(a string) { + require.EqualError(s.t, s.err, a) +} - m := MsgPut{ - Owner: tt.fields.Owner, - BasketDenom: tt.fields.BasketDenom, - Credits: tt.fields.Credits, - } - if err := m.ValidateBasic(); (err != nil) != tt.wantErr { - t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } +func (s *msgPutSuite) ExpectNoError() { + require.NoError(s.t, s.err) } diff --git a/x/ecocredit/core/utils.go b/x/ecocredit/core/utils.go index 4cf204ccb4..e564190e7c 100644 --- a/x/ecocredit/core/utils.go +++ b/x/ecocredit/core/utils.go @@ -6,6 +6,7 @@ import ( "sort" "strings" "time" + "unicode" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -129,7 +130,7 @@ func ValidateProjectId(projectId string) error { func ValidateBatchDenom(denom string) error { matches := regexBatchDenom.FindStringSubmatch(denom) if matches == nil { - return ecocredit.ErrParseFailure.Wrapf("invalid denom: %s", denom) + return ecocredit.ErrParseFailure.Wrap("invalid denom: expected format A00-000-00000000-00000000-000") } return nil } @@ -161,6 +162,19 @@ func GetClassIdFromBatchDenom(denom string) string { return s.String() } +// GetCreditTypeAbbrevFromClassId returns the credit type abbreviation in a credit class id +func GetCreditTypeAbbrevFromClassId(classId string) string { + var s strings.Builder + for _, r := range classId { + if !unicode.IsNumber(r) { + s.WriteRune(r) + continue + } + break + } + return s.String() +} + // exponent prefix map https://en.wikipedia.org/wiki/Metric_prefix var exponentPrefixMap = map[uint32]string{ 0: "", diff --git a/x/ecocredit/core/utils_test.go b/x/ecocredit/core/utils_test.go index d58f901159..37d7511672 100644 --- a/x/ecocredit/core/utils_test.go +++ b/x/ecocredit/core/utils_test.go @@ -16,6 +16,7 @@ func TestUtils(t *testing.T) { t.Run("TestFormatBatchDenom", rapid.MakeCheck(testFormatBatchDenom)) t.Run("TestInvalidBatchDenom", rapid.MakeCheck(testInvalidBatchDenom)) t.Run("TestGetClassIdFromBatchDenom", rapid.MakeCheck(testGetClassIdFromBatchDenom)) + t.Run("GetCreditTypeAbbrevFromClassId", rapid.MakeCheck(testGetCreditTypeAbbrevFromClassId)) } func testFormatClassId(t *rapid.T) { @@ -95,6 +96,15 @@ func testGetClassIdFromBatchDenom(t *rapid.T) { require.Equal(t, classId, result) } +func testGetCreditTypeAbbrevFromClassId(t *rapid.T) { + creditType := genCreditType.Draw(t, "creditType").(*CreditType) + classSeq := rapid.Uint64().Draw(t, "classSeq").(uint64) + + classId := FormatClassId(creditType.Abbreviation, classSeq) + result := GetCreditTypeAbbrevFromClassId(classId) + require.Equal(t, creditType.Abbreviation, result) +} + // genCreditType generates an empty credit type with a random valid abbreviation var genCreditType = rapid.Custom(func(t *rapid.T) *CreditType { abbr := rapid.StringMatching(`[A-Z]{1,3}`).Draw(t, "abbr").(string) diff --git a/x/ecocredit/features/basket/put.feature b/x/ecocredit/features/basket/put.feature deleted file mode 100644 index aa0b3a3c57..0000000000 --- a/x/ecocredit/features/basket/put.feature +++ /dev/null @@ -1,53 +0,0 @@ -Feature: Put - - Scenario: user provides a valid basket denom - Given basket with denom eco.dC.Foo exists - When user tries to put credits into a basket - And user provides basket denom eco.dC.Foo - Then credits are put into the basket - - Scenario: user provides an invalid basket denom - Given basket with denom eco.dX.Foo does not exist - When user tries to put credits into a basket - And user provides basket denom eco.dX.Foo - Then credits are NOT put into the basket - - Scenario: user has enough credits - Given user has a positive credit balance - When user tries to put credits into a basket - And user provides an amount of credits less than or equal to user credit balance - Then credits are put into the basket - And credits are deducted from user credit balance - - Scenario: user does not have enough credits - Given user has a positive credit balance - When user tries to put credits into a basket - And user provides an amount of credits more than user credit balance - Then credits are NOT put into the basket - And credits are NOT deducted from user credit balance - - Scenario: user provides an invalid credit denom - Given credit denom X01-20200101-20210101-001 does not exist - When user tries to put credits into a basket - And user provides credits with denom X01-20200101-20210101-001 - Then credits are NOT put into the basket - - Scenario: user provides an invalid amount of credits - When user tries to put credits into a basket - And user provides credits with an amount equal to or less than zero - Then credits are NOT put into the basket - - Scenario: credits do not satisfy minimum start date - When user tries to put credits into a basket - And user provides credits with a start date before the minimum start date - Then credits are NOT put into the basket - - Scenario: credits do not satisfy start date window - When user tries to put credits into a basket - And user provides credits with a start date before the start date window - Then credits are NOT put into the basket - - Scenario: credits must be in allowed credit classes - When user tries to put credits into a basket - And user provides credits from a credit class that is not in the list of allowed credit classes - Then credits are NOT put into the basket diff --git a/x/ecocredit/features/basket/put_date.feature b/x/ecocredit/features/basket/put_date.feature deleted file mode 100644 index eb9e96acd5..0000000000 --- a/x/ecocredit/features/basket/put_date.feature +++ /dev/null @@ -1,30 +0,0 @@ -Feature: Put Date - - Scenario Outline: batch start date is more than basket start date calculated from years into the past - Given a current block timestamp of - And a basket with date criteria years into the past of - And a user owns credits from a batch with start date of - When the user attempts to put the credits into the basket - Then the credits are NOT put into the basket - - Examples: - | x | y | z | - | "2022-04-01" | "10" | "2011-01-01" | - | "2022-04-01" | "10" | "2011-04-01" | - | "2022-04-01" | "10" | "2011-07-01" | - - Scenario Outline: batch start date is equal to or less than basket start date calculated from years into the past - Given a current block timestamp of - And a basket with date criteria years into the past of - And a user owns credits from a batch with start date of - When the user attempts to put the credits into the basket - Then the credits are put into the basket - - Examples: - | x | y | z | - | "2022-04-01" | "10" | "2012-01-01" | - | "2022-04-01" | "10" | "2012-04-01" | - | "2022-04-01" | "10" | "2012-07-01" | - | "2022-04-01" | "10" | "2013-01-01" | - | "2022-04-01" | "10" | "2013-04-01" | - | "2022-04-01" | "10" | "2013-07-01" | diff --git a/x/ecocredit/server/basket/create_test.go b/x/ecocredit/server/basket/create_test.go index 877228433e..e164df72f2 100644 --- a/x/ecocredit/server/basket/create_test.go +++ b/x/ecocredit/server/basket/create_test.go @@ -54,7 +54,7 @@ func TestInvalidCreditType(t *testing.T) { // non-existent credit type should fail _, err := s.k.Create(s.ctx, &basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), CreditTypeAbbrev: "F", Fee: sdk.Coins{basketFee}, }) @@ -62,7 +62,7 @@ func TestInvalidCreditType(t *testing.T) { // exponent < precision should fail _, err = s.k.Create(s.ctx, &basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), CreditTypeAbbrev: "C", Exponent: 2, Fee: sdk.Coins{basketFee}, @@ -76,7 +76,7 @@ func TestDuplicateDenom(t *testing.T) { gmAny := gomock.Any() fee := sdk.Coin{Denom: "foo", Amount: sdk.NewInt(10)} mc := basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), CreditTypeAbbrev: "C", Exponent: 6, Name: "foo", @@ -113,7 +113,7 @@ func TestInvalidClass(t *testing.T) { // class doesn't exist _, err := s.k.Create(s.ctx, &basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), CreditTypeAbbrev: "C", Exponent: 6, Name: "foo", @@ -124,7 +124,7 @@ func TestInvalidClass(t *testing.T) { // mismatch credit type and class's credit type _, err = s.k.Create(s.ctx, &basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), CreditTypeAbbrev: "C", Exponent: 6, Name: "bar", @@ -171,7 +171,7 @@ func TestValidBasket(t *testing.T) { }).Times(1) _, err := s.k.Create(s.ctx, &basket.MsgCreate{ - Curator: s.addr.String(), + Curator: s.addrs[0].String(), Description: "hi", Name: "foo", CreditTypeAbbrev: "C", @@ -184,7 +184,7 @@ func TestValidBasket(t *testing.T) { bskt, err := s.stateStore.BasketTable().GetByBasketDenom(s.ctx, "eco.uC.foo") assert.NilError(t, err) - assert.Equal(t, s.addr.String(), sdk.AccAddress(bskt.Curator).String()) + assert.Equal(t, s.addrs[0].String(), sdk.AccAddress(bskt.Curator).String()) assert.Equal(t, "eco.uC.foo", bskt.BasketDenom) assert.Equal(t, uint32(6), bskt.Exponent) assert.Equal(t, "C", bskt.CreditTypeAbbrev) diff --git a/x/ecocredit/features/basket/create.feature b/x/ecocredit/server/basket/features/msg_create.feature similarity index 100% rename from x/ecocredit/features/basket/create.feature rename to x/ecocredit/server/basket/features/msg_create.feature diff --git a/x/ecocredit/server/basket/features/msg_put.feature b/x/ecocredit/server/basket/features/msg_put.feature new file mode 100644 index 0000000000..9fb2bcae42 --- /dev/null +++ b/x/ecocredit/server/basket/features/msg_put.feature @@ -0,0 +1,266 @@ +Feature: MsgPut + + Credits can be put into a basket: + - when the basket exists + - when the credit batch exists + - when the credit class is allowed + - when the user has a credit balance + - when the user has the credit amount + - when the credit amount does not exceed maximum decimal places + - when the credit batch start date is more than or equal to minimum start date + - when the credit batch start date is within or at the limit of start date window + - when the credit batch start date is more than or equal to years in the past + - the user credit balance is updated + - the basket credit balance is updated + - the user token balance is updated + - the basket token supply is updated + - the response includes basket token amount received + + Rule: The basket must exist + + Background: + Given alice owns credits + + Scenario: basket exists + Given a basket with denom "NCT" + When alice attempts to put credits into basket "NCT" + Then expect no error + + Scenario: basket does not exist + When alice attempts to put credits into basket "NCT" + Then expect the error "basket NCT not found: not found" + + Rule: The credit batch must exist + + Background: + Given a basket + + Scenario: batch denom exists + Given alice owns credits from credit batch "C01-20200101-20210101-001" + When alice attempts to put credits from credit batch "C01-20200101-20210101-001" into the basket + Then expect no error + + Scenario: batch denom does not exist + When alice attempts to put credits from credit batch "C01-20200101-20210101-001" into the basket + Then expect the error "could not get batch C01-20200101-20210101-001: not found: invalid request" + + Rule: The credit batch must be from a credit class that is allowed in the basket + + Background: + Given a basket with allowed credit class "C01" + + Scenario: credit class is allowed + Given alice owns credits from credit batch "C01-20200101-20210101-001" + When alice attempts to put credits from credit batch "C01-20200101-20210101-001" into the basket + Then expect no error + + Scenario: credit class is not allowed + Given alice owns credits from credit batch "A01-20200101-20210101-001" + When alice attempts to put credits from credit batch "A01-20200101-20210101-001" into the basket + Then expect the error "credit class A01 is not allowed in this basket: invalid request" + + Rule: The user must have a credit balance for the credits being put into the basket + + Background: + Given a basket + + Scenario: user has a credit balance + Given alice owns credits from credit batch "C01-20200101-20210101-001" + When alice attempts to put credits from credit batch "C01-20200101-20210101-001" into the basket + Then expect no error + + Scenario: user does not have a credit balance + Given alice owns credits from credit batch "C01-20200101-20210101-001" + When bob attempts to put credits from credit batch "C01-20200101-20210101-001" into the basket + Then expect error contains "could not get batch C01-20200101-20210101-001 balance" + + Rule: The user must have a credit balance more than or equal to the credits being put into the basket + + Background: + Given a basket + + Scenario Outline: user owns more than or equal amount of credits being put into the basket + Given alice owns credit amount "" + When alice attempts to put credit amount "" into the basket + Then expect no error + + Examples: + | description | balance-before | credit-amount | + | more than | 100 | 50 | + | equal to | 100 | 100 | + + Scenario: user owns less than amount of credits being put into the basket + Given alice owns credit amount "100" + When alice attempts to put credit amount "150" into the basket + Then expect error contains "cannot put 150 credits into the basket with a balance of 100" + + Rule: Credit amount must not exceed maximum decimal places + + Scenario Outline: credit amount does not exceed maximum decimal places + Given a basket with exponent "" + And alice owns credit amount "" + When alice attempts to put credit amount "" into the basket with exponent + Then expect no error + + Examples: + | description | exponent | credit-amount | + | no decimals | 0 | 2 | + | one decimal | 1 | 2.5 | + | two decimals | 2 | 2.25 | + + Scenario Outline: credit amount exceeds maximum decimal places + Given a basket with exponent "" + And alice owns credit amount "" + When alice attempts to put credit amount "" into the basket with exponent + Then expect error contains "exceeds maximum decimal places" + + Examples: + | description | exponent | credit-amount | + | no decimals | 0 | 2.5 | + | one decimal | 1 | 2.25 | + | two decimals | 2 | 2.333 | + + Rule: Credits from a batch with a start date more than basket minimum start date cannot be put into the basket + + Background: + Given a basket with minimum start date "2021-01-01" + + Scenario Outline: batch start date less than or equal to minimum start date + Given alice owns credits with start date "" + When alice attempts to put credits into the basket + Then expect no error + + Examples: + | description | batch-start-date | + | less than | 2022-01-01 | + | equal to | 2021-01-01 | + + Scenario: batch start date more than minimum start date + Given alice owns credits with start date "2020-01-01" + When alice attempts to put credits into the basket + Then expect error contains "cannot put a credit from a batch with start date" + + Rule: Credits from a batch with a start date outside basket start date window cannot be put into the basket + + Background: + Given the block time "2022-01-01" + And a basket with start date window "31536000" + + Scenario Outline: batch start date within or at the limit of basket start date window + Given alice owns credits with start date "" + When alice attempts to put credits into the basket + Then expect no error + + Examples: + | description | batch-start-date | + | less than | 2022-01-01 | + | equal to | 2021-01-01 | + + Scenario: batch start date outside of basket start date window + Given alice owns credits with start date "2020-01-01" + When alice attempts to put credits into the basket + Then expect error contains "cannot put a credit from a batch with start date" + + Rule: Credits from a batch with a start date more than basket years in the past cannot be put into the basket + + Scenario Outline: batch start date less than or equal to years in the past + Given the block time "2022-04-01" + And a basket with years in the past "10" + And alice owns credits with start date "" + When alice attempts to put credits into the basket + Then expect no error + + Examples: + | description | batch-start-date | + | year equal, day before | 2012-01-01 | + | year equal, day equal | 2012-04-01 | + | year equal, day after | 2012-07-01 | + | year after, day before | 2013-01-01 | + | year after, day equal | 2013-04-01 | + | year after, day after | 2013-07-01 | + + Scenario Outline: batch start date more than years in the past + Given the block time "2022-04-01" + And a basket with years in the past "10" + And alice owns credits with start date "" + When alice attempts to put credits into the basket + Then expect error contains "cannot put a credit from a batch with start date" + + Examples: + | description | batch-start-date | + | year before, day before | 2011-01-01 | + | year before, day equal | 2011-04-01 | + | year before, day after | 2011-07-01 | + + Rule: The user credit balance is updated when credits are put into the basket + + Scenario: user credit balance is updated + Given a basket + And alice owns credit amount "100" + When alice attempts to put credit amount "100" into the basket + Then alice has a credit balance with amount "0" + + # no failing scenario - state transitions only occur upon successful message execution + + Rule: The basket credit balance is updated when credits are put into the basket + + Scenario: basket credit balance is updated + Given a basket + And alice owns credit amount "100" + When alice attempts to put credit amount "100" into the basket + Then the basket has a credit balance with amount "100" + + # no failing scenario - state transitions only occur upon successful message execution + + Rule: The user token balance is updated when credits are put into the basket + + Scenario Outline: user token balance is updated + Given a basket with exponent "" + And alice owns credit amount "" + When alice attempts to put credit amount "" into the basket with exponent + And alice has a basket token balance with amount "" + + Examples: + | description | exponent | credit-amount | token-amount | + | exponent zero, amount whole | 0 | 2 | 2 | + | exponent non-zero, amount whole | 6 | 2 | 2000000 | + | exponent non-zero, amount decimal | 6 | 2.5 | 2500000 | + + # no failing scenario - state transitions only occur upon successful message execution + + Rule: The basket token supply is updated when credits are put into the basket + + Scenario Outline: basket token supply is updated + Given a basket with exponent "" + And alice owns credit amount "" + When alice attempts to put credit amount "" into the basket with exponent + Then the basket token has a total supply with amount "" + + Examples: + | description | exponent | credit-amount | token-amount | + | exponent zero, amount whole | 0 | 2 | 2 | + | exponent non-zero, amount whole | 6 | 2 | 2000000 | + | exponent non-zero, amount decimal | 6 | 2.5 | 2500000 | + + # no failing scenario - state transitions only occur upon successful message execution + + Rule: The message response includes basket token amount received when credits are put into the basket + + Scenario Outline: message response includes basket token amount received + Given a basket with exponent "" + And alice owns credit amount "" + When alice attempts to put credit amount "" into the basket with exponent + Then expect the response + """ + { + "amount_received": "" + } + """ + + Examples: + | description | exponent | credit-amount | token-amount | + | exponent zero, amount whole | 0 | 2 | 2 | + | exponent non-zero, amount whole | 6 | 2 | 2000000 | + | exponent non-zero, amount decimal | 6 | 2.5 | 2500000 | + + # no failing scenario - response should always be empty when message execution fails diff --git a/x/ecocredit/features/basket/take.feature b/x/ecocredit/server/basket/features/msg_take.feature similarity index 100% rename from x/ecocredit/features/basket/take.feature rename to x/ecocredit/server/basket/features/msg_take.feature diff --git a/x/ecocredit/server/basket/keeper_test.go b/x/ecocredit/server/basket/keeper_test.go index 15929d51bc..347902881f 100644 --- a/x/ecocredit/server/basket/keeper_test.go +++ b/x/ecocredit/server/basket/keeper_test.go @@ -32,7 +32,7 @@ type baseSuite struct { ctx context.Context k basket.Keeper ctrl *gomock.Controller - addr sdk.AccAddress + addrs []sdk.AccAddress stateStore api.StateStore coreStore ecocreditApi.StateStore bankKeeper *mocks2.MockBankKeeper @@ -79,7 +79,11 @@ func setupBase(t gocuke.TestingT) *baseSuite { Unit: "metric ton C02", Precision: 6, })) - _, _, s.addr = testdata.KeyTestPubAddr() + + // add test addresses + _, _, addr1 := testdata.KeyTestPubAddr() + _, _, addr2 := testdata.KeyTestPubAddr() + s.addrs = append(s.addrs, addr1, addr2) return s } diff --git a/x/ecocredit/server/basket/msg_put_test.go b/x/ecocredit/server/basket/msg_put_test.go new file mode 100644 index 0000000000..3fb20fb17d --- /dev/null +++ b/x/ecocredit/server/basket/msg_put_test.go @@ -0,0 +1,561 @@ +package basket_test + +import ( + "strconv" + "strings" + "testing" + "time" + + "github.com/gogo/protobuf/jsonpb" + "github.com/regen-network/gocuke" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + + sdk "github.com/cosmos/cosmos-sdk/types" + + api "github.com/regen-network/regen-ledger/api/regen/ecocredit/basket/v1" + coreapi "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/types/math" + "github.com/regen-network/regen-ledger/x/ecocredit/basket" + "github.com/regen-network/regen-ledger/x/ecocredit/core" +) + +type putSuite struct { + *baseSuite + alice sdk.AccAddress + bob sdk.AccAddress + classId string + creditTypeAbbrev string + batchDenom string + basketDenom string + tradableCredits string + res *basket.MsgPutResponse + err error +} + +func TestPut(t *testing.T) { + gocuke.NewRunner(t, &putSuite{}).Path("./features/msg_put.feature").Run() +} + +func (s *putSuite) Before(t gocuke.TestingT) { + s.baseSuite = setupBase(t) + s.alice = s.addrs[0] + s.bob = s.addrs[1] + s.classId = "C01" + s.creditTypeAbbrev = "C" + s.batchDenom = "C01-20200101-20210101-001" + s.basketDenom = "NCT" + s.tradableCredits = "100" +} + +func (s *putSuite) ABasket() { + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: s.creditTypeAbbrev, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithExponent(a string) { + exponent, err := strconv.ParseUint(a, 10, 32) + require.NoError(s.t, err) + + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: s.creditTypeAbbrev, + Exponent: uint32(exponent), + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithDenom(a string) { + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: a, + CreditTypeAbbrev: s.creditTypeAbbrev, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithAllowedCreditClass(a string) { + creditTypeAbbrev := core.GetCreditTypeAbbrevFromClassId(a) + + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: creditTypeAbbrev, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: a, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithMinimumStartDate(a string) { + minStartDate, err := types.ParseDate("start date", a) + require.NoError(s.t, err) + + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: s.creditTypeAbbrev, + DateCriteria: &api.DateCriteria{ + MinStartDate: timestamppb.New(minStartDate), + }, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithStartDateWindow(a string) { + startDateWindow, err := strconv.ParseInt(a, 10, 32) + require.NoError(s.t, err) + + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: s.creditTypeAbbrev, + DateCriteria: &api.DateCriteria{ + StartDateWindow: &durationpb.Duration{ + Seconds: startDateWindow, + }, + }, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) ABasketWithYearsInThePast(a string) { + yearsInThePast, err := strconv.ParseUint(a, 10, 32) + require.NoError(s.t, err) + + basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: s.basketDenom, + CreditTypeAbbrev: s.creditTypeAbbrev, + DateCriteria: &api.DateCriteria{ + YearsInThePast: uint32(yearsInThePast), + }, + }) + require.NoError(s.t, err) + + err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: basketId, + ClassId: s.classId, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) AliceOwnsCredits() { + classId := core.GetClassIdFromBatchDenom(s.batchDenom) + creditTypeAbbrev := core.GetCreditTypeAbbrevFromClassId(classId) + + classKey, err := s.coreStore.ClassTable().InsertReturningID(s.ctx, &coreapi.Class{ + Id: classId, + CreditTypeAbbrev: creditTypeAbbrev, + }) + require.NoError(s.t, err) + + projectKey, err := s.coreStore.ProjectTable().InsertReturningID(s.ctx, &coreapi.Project{ + ClassKey: classKey, + }) + require.NoError(s.t, err) + + batchKey, err := s.coreStore.BatchTable().InsertReturningID(s.ctx, &coreapi.Batch{ + ProjectKey: projectKey, + Denom: s.batchDenom, + }) + require.NoError(s.t, err) + + err = s.coreStore.BatchBalanceTable().Insert(s.ctx, &coreapi.BatchBalance{ + BatchKey: batchKey, + Address: s.alice, + Tradable: s.tradableCredits, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) AliceOwnsCreditAmount(a string) { + classId := core.GetClassIdFromBatchDenom(s.batchDenom) + creditTypeAbbrev := core.GetCreditTypeAbbrevFromClassId(classId) + + classKey, err := s.coreStore.ClassTable().InsertReturningID(s.ctx, &coreapi.Class{ + Id: classId, + CreditTypeAbbrev: creditTypeAbbrev, + }) + require.NoError(s.t, err) + + projectKey, err := s.coreStore.ProjectTable().InsertReturningID(s.ctx, &coreapi.Project{ + ClassKey: classKey, + }) + require.NoError(s.t, err) + + batchKey, err := s.coreStore.BatchTable().InsertReturningID(s.ctx, &coreapi.Batch{ + ProjectKey: projectKey, + Denom: s.batchDenom, + }) + require.NoError(s.t, err) + + err = s.coreStore.BatchBalanceTable().Insert(s.ctx, &coreapi.BatchBalance{ + BatchKey: batchKey, + Address: s.alice, + Tradable: a, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) AliceOwnsCreditsFromCreditBatch(a string) { + classId := core.GetClassIdFromBatchDenom(a) + creditTypeAbbrev := core.GetCreditTypeAbbrevFromClassId(classId) + + classKey, err := s.coreStore.ClassTable().InsertReturningID(s.ctx, &coreapi.Class{ + Id: classId, + CreditTypeAbbrev: creditTypeAbbrev, + }) + require.NoError(s.t, err) + + projectKey, err := s.coreStore.ProjectTable().InsertReturningID(s.ctx, &coreapi.Project{ + ClassKey: classKey, + }) + require.NoError(s.t, err) + + batchKey, err := s.coreStore.BatchTable().InsertReturningID(s.ctx, &coreapi.Batch{ + ProjectKey: projectKey, + Denom: a, + }) + require.NoError(s.t, err) + + err = s.coreStore.BatchBalanceTable().Insert(s.ctx, &coreapi.BatchBalance{ + BatchKey: batchKey, + Address: s.alice, + Tradable: s.tradableCredits, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) AliceOwnsCreditsWithStartDate(a string) { + startDate, err := time.Parse("2006-01-02", a) + require.NoError(s.t, err) + + classKey, err := s.coreStore.ClassTable().InsertReturningID(s.ctx, &coreapi.Class{ + Id: s.classId, + CreditTypeAbbrev: s.creditTypeAbbrev, + }) + require.NoError(s.t, err) + + pKey, err := s.coreStore.ProjectTable().InsertReturningID(s.ctx, &coreapi.Project{ + ClassKey: classKey, + }) + require.NoError(s.t, err) + + batchKey, err := s.coreStore.BatchTable().InsertReturningID(s.ctx, &coreapi.Batch{ + ProjectKey: pKey, + Denom: s.batchDenom, + StartDate: timestamppb.New(startDate), + }) + require.NoError(s.t, err) + + err = s.coreStore.BatchBalanceTable().Insert(s.ctx, &coreapi.BatchBalance{ + BatchKey: batchKey, + Address: s.alice, + Tradable: s.tradableCredits, + }) + require.NoError(s.t, err) +} + +func (s *putSuite) TheBlockTime(a string) { + blockTime, err := types.ParseDate("block time", a) + require.NoError(s.t, err) + + s.sdkCtx = s.sdkCtx.WithBlockTime(blockTime) + s.ctx = sdk.WrapSDKContext(s.sdkCtx) +} + +func (s *putSuite) AliceAttemptsToPutCreditsIntoBasket(a string) { + amount, ok := sdk.NewIntFromString(s.tradableCredits) + require.True(s.t, ok) + + coins := sdk.NewCoins(sdk.NewCoin(s.basketDenom, amount)) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.alice, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.alice.String(), + BasketDenom: a, + Credits: []*basket.BasketCredit{ + { + BatchDenom: s.batchDenom, + Amount: s.tradableCredits, + }, + }, + }) +} + +func (s *putSuite) AliceAttemptsToPutCreditAmountIntoTheBasket(a string) { + amount, ok := sdk.NewIntFromString(a) + require.True(s.t, ok) + + coins := sdk.NewCoins(sdk.NewCoin(s.basketDenom, amount)) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.alice, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.alice.String(), + BasketDenom: s.basketDenom, + Credits: []*basket.BasketCredit{ + { + BatchDenom: s.batchDenom, + Amount: a, + }, + }, + }) +} + +func (s *putSuite) AliceAttemptsToPutCreditAmountIntoTheBasketWithExponent(a string) { + coins := s.calculateExpectedCoins(a) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.alice, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.res, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.alice.String(), + BasketDenom: s.basketDenom, + Credits: []*basket.BasketCredit{ + { + BatchDenom: s.batchDenom, + Amount: a, + }, + }, + }) +} + +func (s *putSuite) AliceAttemptsToPutCreditsFromCreditBatchIntoTheBasket(a string) { + amount, ok := sdk.NewIntFromString(s.tradableCredits) + require.True(s.t, ok) + + coins := sdk.NewCoins(sdk.NewCoin(s.basketDenom, amount)) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.alice, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.alice.String(), + BasketDenom: s.basketDenom, + Credits: []*basket.BasketCredit{ + { + BatchDenom: a, + Amount: s.tradableCredits, + }, + }, + }) +} + +func (s *putSuite) BobAttemptsToPutCreditsFromCreditBatchIntoTheBasket(a string) { + amount, ok := sdk.NewIntFromString(s.tradableCredits) + require.True(s.t, ok) + + coins := sdk.NewCoins(sdk.NewCoin(s.basketDenom, amount)) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.bob, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.bob.String(), + BasketDenom: s.basketDenom, + Credits: []*basket.BasketCredit{ + { + BatchDenom: a, + Amount: s.tradableCredits, + }, + }, + }) +} + +func (s *putSuite) AliceAttemptsToPutCreditsIntoTheBasket() { + amount, ok := sdk.NewIntFromString(s.tradableCredits) + require.True(s.t, ok) + + coins := sdk.NewCoins(sdk.NewCoin(s.basketDenom, amount)) + + s.bankKeeper.EXPECT(). + MintCoins(s.sdkCtx, basket.BasketSubModuleName, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + s.bankKeeper.EXPECT(). + SendCoinsFromModuleToAccount(s.sdkCtx, basket.BasketSubModuleName, s.alice, coins). + Return(nil). + AnyTimes() // not expected on failed attempt + + _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ + Owner: s.alice.String(), + BasketDenom: s.basketDenom, + Credits: []*basket.BasketCredit{ + { + BatchDenom: s.batchDenom, + Amount: s.tradableCredits, + }, + }, + }) +} + +func (s *putSuite) TheBasketHasACreditBalanceWithAmount(a string) { + basket, err := s.stateStore.BasketTable().GetByBasketDenom(s.ctx, s.basketDenom) + require.NoError(s.t, err) + + balance, err := s.stateStore.BasketBalanceTable().Get(s.ctx, basket.Id, s.batchDenom) + require.NoError(s.t, err) + + require.Equal(s.t, a, balance.Balance) +} + +func (s *putSuite) TheBasketTokenHasATotalSupplyWithAmount(a string) { + basket, err := s.stateStore.BasketTable().GetByBasketDenom(s.ctx, s.basketDenom) + require.NoError(s.t, err) + + amount, err := strconv.ParseInt(a, 10, 32) + require.NoError(s.t, err) + + coin := sdk.NewInt64Coin(s.basketDenom, amount) + + s.bankKeeper.EXPECT(). + GetSupply(s.sdkCtx, basket.BasketDenom). + Return(coin). + Times(1) + + supply := s.bankKeeper.GetSupply(s.sdkCtx, s.basketDenom) + require.Equal(s.t, coin, supply) +} + +func (s *putSuite) AliceHasACreditBalanceWithAmount(a string) { + batch, err := s.coreStore.BatchTable().GetByDenom(s.ctx, s.batchDenom) + require.NoError(s.t, err) + + balance, err := s.coreStore.BatchBalanceTable().Get(s.ctx, s.alice, batch.Key) + require.NoError(s.t, err) + + require.Equal(s.t, a, balance.Tradable) +} + +func (s *putSuite) AliceHasABasketTokenBalanceWithAmount(a string) { + basket, err := s.stateStore.BasketTable().GetByBasketDenom(s.ctx, s.basketDenom) + require.NoError(s.t, err) + + amount, err := strconv.ParseInt(a, 10, 32) + require.NoError(s.t, err) + + coin := sdk.NewInt64Coin(basket.BasketDenom, amount) + + s.bankKeeper.EXPECT(). + GetBalance(s.sdkCtx, s.alice, basket.BasketDenom). + Return(coin). + AnyTimes() + + balance := s.bankKeeper.GetBalance(s.sdkCtx, s.alice, basket.BasketDenom) + require.Equal(s.t, coin, balance) +} + +func (s *putSuite) ExpectNoError() { + require.NoError(s.t, s.err) +} + +func (s *putSuite) ExpectTheError(a string) { + require.EqualError(s.t, s.err, a) +} + +func (s *putSuite) ExpectErrorContains(a string) { + require.ErrorContains(s.t, s.err, a) +} + +func (s *putSuite) ExpectTheResponse(a gocuke.DocString) { + res := &basket.MsgPutResponse{} + err := jsonpb.UnmarshalString(a.Content, res) + require.NoError(s.t, err) + + require.Equal(s.t, res, s.res) +} + +func (s *putSuite) calculateExpectedCoins(amount string) sdk.Coins { + basket, err := s.stateStore.BasketTable().GetByBasketDenom(s.ctx, s.basketDenom) + require.NoError(s.t, err) + + dec, err := math.NewPositiveFixedDecFromString(amount, basket.Exponent) + if err != nil && strings.Contains(err.Error(), "exceeds maximum decimal places") { + // expected coins irrelevant if amount exceeds maximum decimal places + return sdk.Coins{} + } + require.NoError(s.t, err) + + tokenAmt, err := math.NewDecFinite(1, int32(basket.Exponent)).MulExact(dec) + require.NoError(s.t, err) + + amtInt, err := tokenAmt.BigInt() + require.NoError(s.t, err) + + return sdk.Coins{sdk.NewCoin(s.basketDenom, sdk.NewIntFromBigInt(amtInt))} +} diff --git a/x/ecocredit/server/basket/put_test.go b/x/ecocredit/server/basket/put_test.go deleted file mode 100644 index b67d17c245..0000000000 --- a/x/ecocredit/server/basket/put_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package basket_test - -import ( - "strconv" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/regen-network/gocuke" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - "gotest.tools/v3/assert" - - "github.com/cosmos/cosmos-sdk/orm/types/ormerrors" - sdk "github.com/cosmos/cosmos-sdk/types" - - api "github.com/regen-network/regen-ledger/api/regen/ecocredit/basket/v1" - ecoApi "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1" - ecocreditapi "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1" - "github.com/regen-network/regen-ledger/types/math" - "github.com/regen-network/regen-ledger/x/ecocredit" - "github.com/regen-network/regen-ledger/x/ecocredit/basket" -) - -func TestPut_Valid(t *testing.T) { - t.Parallel() - s := setupBase(t) - gmAny := gomock.Any() - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - userStartingBalance, basketStartingBalance, amtToDeposit := math.NewDecFromInt64(10), math.NewDecFromInt64(0), math.NewDecFromInt64(3) - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{classId}) - insertBatch(t, s, batchDenom, timestamppb.Now()) - insertBatchBalance(t, s, s.addr, 1, userStartingBalance.String()) - insertClass(t, s, "C01", "C") - s.bankKeeper.EXPECT().MintCoins(gmAny, gmAny, gmAny).Return(nil).Times(2) - s.bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gmAny, gmAny, gmAny, gmAny).Return(nil).Times(2) - - // can put 3 credits in basket - res, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: amtToDeposit.String()}, - }, - }) - assert.NilError(t, err) - assert.Equal(t, res.AmountReceived, "3000000") // 3 credits 10^6 * 3 = 3M - assertCreditsDeposited(t, s, userStartingBalance, basketStartingBalance, amtToDeposit, s.addr, 1, 1, batchDenom) - - // can put 3 more credits in basket - res, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: amtToDeposit.String()}, - }, - }) - assert.NilError(t, err) - assert.Equal(t, res.AmountReceived, "3000000") // 3 credits 10^6 * 3 - assertCreditsDeposited(t, s, math.NewDecFromInt64(7), math.NewDecFromInt64(3), amtToDeposit, s.addr, 1, 1, batchDenom) -} - -func TestPut_BasketNotFound(t *testing.T) { - t.Parallel() - s := setupBase(t) - - _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "FOO", - Credits: nil, - }) - assert.ErrorContains(t, err, ormerrors.NotFound.Error()) -} - -func TestPut_BatchNotFound(t *testing.T) { - t.Parallel() - s := setupBase(t) - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - userStartingBalance, _ := math.NewDecFromInt64(10), math.NewDecFromInt64(0) - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{classId}) - - // can put all credits in basket - _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: userStartingBalance.String()}, - }, - }) - assert.ErrorContains(t, err, ormerrors.NotFound.Error()) -} - -func TestPut_YearsIntoPast(t *testing.T) { - t.Parallel() - s := setupBase(t) - gmAny := gomock.Any() - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{classId}) - currentTime, err := time.Parse("2006", "2020") - assert.NilError(t, err) - s.sdkCtx = s.sdkCtx.WithBlockTime(currentTime) - s.ctx = sdk.WrapSDKContext(s.sdkCtx) - - // too long ago should fail - fourYearsAgo, err := time.Parse("2006", "2016") - assert.NilError(t, err) - insertBatch(t, s, batchDenom, timestamppb.New(fourYearsAgo)) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: "10"}, - }, - }) - assert.ErrorContains(t, err, "basket that requires an earliest start date") - - // exactly 3 years into the past should work - threeYearsAgo, err := time.Parse("2006", "2017") - assert.NilError(t, err) - otherBatchDenom := "C01-001-000000-0000000-002" - insertBatch(t, s, otherBatchDenom, timestamppb.New(threeYearsAgo)) - insertBatchBalance(t, s, s.addr, 2, "10") - insertClass(t, s, "C01", "C") - s.bankKeeper.EXPECT().MintCoins(gmAny, gmAny, gmAny).Return(nil).Times(1) - s.bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gmAny, gmAny, gmAny, gmAny).Return(nil).Times(1) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: otherBatchDenom, Amount: "10"}, - }, - }) - assert.NilError(t, err) -} - -func TestPut_MinStartDate(t *testing.T) { - t.Parallel() - s := setupBase(t) - gmAny := gomock.Any() - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - currentTime, err := time.Parse("2006", "2020") - assert.NilError(t, err) - - // make a basket with min start date as 2020 - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{MinStartDate: timestamppb.New(currentTime)}, []string{classId}) - s.sdkCtx = s.sdkCtx.WithBlockTime(currentTime) - s.ctx = sdk.WrapSDKContext(s.sdkCtx) - - // make a batch 1 year before min start date - _2019, err := time.Parse("2006", "2019") - assert.NilError(t, err) - insertBatch(t, s, batchDenom, timestamppb.New(_2019)) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: "10"}, - }, - }) - assert.ErrorContains(t, err, "basket that requires an earliest start date") - - // should pass with batch start date == min start date - otherBatchDenom := "C01-001-000000-0000000-002" - insertBatch(t, s, otherBatchDenom, timestamppb.New(currentTime)) - insertBatchBalance(t, s, s.addr, 2, "10") - insertClass(t, s, "C01", "C") - s.bankKeeper.EXPECT().MintCoins(gmAny, gmAny, gmAny).Return(nil).Times(1) - s.bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gmAny, gmAny, gmAny, gmAny).Return(nil).Times(1) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: otherBatchDenom, Amount: "10"}, - }, - }) - assert.NilError(t, err) -} - -func TestPut_Window(t *testing.T) { - t.Parallel() - s := setupBase(t) - gmAny := gomock.Any() - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - currentTime, err := time.Parse("2006", "2020") - assert.NilError(t, err) - - // make a basket with StartDateWindow of 1 year. with block time forced to 2020, this means only credits 2019 and up are accepted. - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{StartDateWindow: &durationpb.Duration{Seconds: 31560000}}, []string{classId}) - s.sdkCtx = s.sdkCtx.WithBlockTime(currentTime) - s.ctx = sdk.WrapSDKContext(s.sdkCtx) - - // make a batch 1 year before min start date - _2018, err := time.Parse("2006", "2018") - assert.NilError(t, err) - insertBatch(t, s, batchDenom, timestamppb.New(_2018)) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: "10"}, - }, - }) - assert.ErrorContains(t, err, "basket that requires an earliest start date") - - // should pass with batch start date exactly 1 year before block time. (1 year window). - otherBatchDenom := "C01-001-000000-0000000-002" - _2019, err := time.Parse("2006", "2019") - assert.NilError(t, err) - insertBatch(t, s, otherBatchDenom, timestamppb.New(_2019)) - insertBatchBalance(t, s, s.addr, 2, "10") - insertClass(t, s, "C01", "C") - s.bankKeeper.EXPECT().MintCoins(gmAny, gmAny, gmAny).Return(nil).Times(1) - s.bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gmAny, gmAny, gmAny, gmAny).Return(nil).Times(1) - _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: otherBatchDenom, Amount: "10"}, - }, - }) - assert.NilError(t, err) -} - -func TestPut_InsufficientCredits(t *testing.T) { - t.Parallel() - s := setupBase(t) - batchDenom, classId := "C01-001-0000000-0000000-001", "C01" - userStartingBalance := math.NewDecFromInt64(10) - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{classId}) - insertBatch(t, s, batchDenom, timestamppb.Now()) - insertBatchBalance(t, s, s.addr, 1, userStartingBalance.String()) - insertClass(t, s, classId, "C") - - // can put 3 credits in basket - _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: "1000000"}, - }, - }) - assert.ErrorContains(t, err, ecocredit.ErrInsufficientCredits.Error()) -} - -func TestPut_ClassNotAccepted(t *testing.T) { - t.Parallel() - s := setupBase(t) - batchDenom := "C01-001-0000000-0000000-001" - userStartingBalance := math.NewDecFromInt64(10) - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{"C02"}) - insertBatch(t, s, batchDenom, timestamppb.Now()) - insertBatchBalance(t, s, s.addr, 1, userStartingBalance.String()) - - // can put 3 credits in basket - _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: "1000000"}, - }, - }) - assert.ErrorContains(t, err, "credit class C01 is not allowed in this basket") -} - -func TestPut_BadCreditType(t *testing.T) { - t.Parallel() - s := setupBase(t) - batchDenom := "C01-001-0000000-0000000-001" - userStartingBalance := math.NewDecFromInt64(10) - insertBasket(t, s, "foo", "basket", "C", &api.DateCriteria{YearsInThePast: 3}, []string{"C01"}) - insertBatch(t, s, batchDenom, timestamppb.Now()) - insertBatchBalance(t, s, s.addr, 1, userStartingBalance.String()) - insertClass(t, s, "C01", "F") - - _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: "foo", - Credits: []*basket.BasketCredit{ - {BatchDenom: batchDenom, Amount: userStartingBalance.String()}, - }, - }) - assert.ErrorContains(t, err, "basket requires credit type C but a credit with type F was given") -} - -func insertBasket(t *testing.T, s *baseSuite, denom, name, ctAbbrev string, criteria *api.DateCriteria, classes []string) { - id, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ - BasketDenom: denom, - Name: name, - DisableAutoRetire: false, - CreditTypeAbbrev: ctAbbrev, - DateCriteria: criteria, - Exponent: 6, - Curator: s.addr, - }) - assert.NilError(t, err) - - for _, class := range classes { - assert.NilError(t, s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ - BasketId: id, - ClassId: class, - })) - } -} - -func insertBatchBalance(t *testing.T, s *baseSuite, user sdk.AccAddress, batchKey uint64, amount string) { - assert.NilError(t, s.coreStore.BatchBalanceTable().Insert(s.ctx, &ecoApi.BatchBalance{ - BatchKey: batchKey, - Address: user, - Tradable: amount, - Retired: "", - Escrowed: "", - })) -} - -func insertClass(t *testing.T, s *baseSuite, name, creditTypeAbb string) { - assert.NilError(t, s.coreStore.ClassTable().Insert(s.ctx, &ecoApi.Class{ - Id: name, - Admin: s.addr, - Metadata: "", - CreditTypeAbbrev: creditTypeAbb, - })) -} - -func insertBatch(t *testing.T, s *baseSuite, batchDenom string, startDate *timestamppb.Timestamp) { - assert.NilError(t, s.coreStore.BatchTable().Insert(s.ctx, &ecoApi.Batch{ - ProjectKey: 1, - Denom: batchDenom, - Metadata: "", - StartDate: startDate, - EndDate: nil, - })) -} - -func assertCreditsDeposited(t *testing.T, s *baseSuite, startingUserBalance, startingBasketBalance, amountPut math.Dec, user sdk.AccAddress, batchKey, basketId uint64, batchDenom string) { - userBal, err := s.coreStore.BatchBalanceTable().Get(s.ctx, user, batchKey) - assert.NilError(t, err) - userTradable, err := math.NewDecFromString(userBal.Tradable) - assert.NilError(t, err) - - basketBal, err := s.stateStore.BasketBalanceTable().Get(s.ctx, basketId, batchDenom) - assert.NilError(t, err) - basketBalAmt, err := math.NewDecFromString(basketBal.Balance) - assert.NilError(t, err) - - expectedUserBal, err := startingUserBalance.Sub(amountPut) - assert.NilError(t, err) - - expectedBasketBalance, err := startingBasketBalance.Add(amountPut) - assert.NilError(t, err) - - assert.Check(t, expectedUserBal.Equal(userTradable)) - assert.Check(t, expectedBasketBalance.Equal(basketBalAmt)) -} - -type putSuite struct { - *baseSuite - basketDenom string - classId string - creditType string - batchDenom string - batchStartDate *timestamppb.Timestamp - tradableCredits string - err error -} - -func TestPutDate(t *testing.T) { - gocuke.NewRunner(t, &putSuite{}).Path("../../features/basket/put_date.feature").Run() -} - -func (s *putSuite) Before(t gocuke.TestingT) { - s.baseSuite = setupBase(t) - s.tradableCredits = "5" - s.classId = "batch" - s.creditType = "C" -} - -func (s *putSuite) ACurrentBlockTimestampOf(a string) { - blockTime, err := time.Parse("2006-01-02", a) - assert.NilError(s.t, err) - - s.sdkCtx = s.sdkCtx.WithBlockTime(blockTime) - s.ctx = sdk.WrapSDKContext(s.sdkCtx) -} - -func (s *putSuite) ABasketWithDateCriteriaYearsIntoThePastOf(a string) { - yearsInThePast, err := strconv.ParseUint(a, 10, 32) - assert.NilError(s.t, err) - - s.basketDenom = "basket-" + a - - basketId, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ - BasketDenom: s.basketDenom, - CreditTypeAbbrev: s.creditType, - DateCriteria: &api.DateCriteria{YearsInThePast: uint32(yearsInThePast)}, - }) - assert.NilError(s.t, err) - - err = s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ - BasketId: basketId, - ClassId: s.classId, - }) - assert.NilError(s.t, err) -} - -func (s *putSuite) AUserOwnsCreditsFromABatchWithStartDateOf(a string) { - startDate, err := time.Parse("2006-01-02", a) - assert.NilError(s.t, err) - - s.batchDenom = "batch-" + a - s.batchStartDate = timestamppb.New(startDate) - - key, err := s.coreStore.ClassTable().InsertReturningID(s.ctx, &ecocreditapi.Class{ - Id: s.classId, - CreditTypeAbbrev: s.creditType, - }) - assert.NilError(s.t, err) - - key, err = s.coreStore.ProjectTable().InsertReturningID(s.ctx, &ecocreditapi.Project{ClassKey: key}) - assert.NilError(s.t, err) - - key, err = s.coreStore.BatchTable().InsertReturningID(s.ctx, &ecocreditapi.Batch{ - ProjectKey: 1, - Denom: s.batchDenom, - StartDate: s.batchStartDate, - }) - assert.NilError(s.t, err) - - err = s.coreStore.BatchBalanceTable().Insert(s.ctx, &ecocreditapi.BatchBalance{ - BatchKey: key, - Address: s.addr, - Tradable: s.tradableCredits, - }) - assert.NilError(s.t, err) -} - -func (s *putSuite) TheUserAttemptsToPutTheCreditsIntoTheBasket() { - gmAny := gomock.Any() - tokenInt, _ := sdk.NewIntFromString(s.tradableCredits) - tokenAmount := sdk.NewCoins(sdk.NewCoin(s.basketDenom, tokenInt)) - - s.bankKeeper.EXPECT(). - MintCoins(gmAny, basket.BasketSubModuleName, tokenAmount). - Return(nil).AnyTimes() // only called if valid start date - - s.bankKeeper.EXPECT(). - SendCoinsFromModuleToAccount(gmAny, basket.BasketSubModuleName, s.addr, tokenAmount). - Return(nil).AnyTimes() // only called if valid start date - - _, s.err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), - BasketDenom: s.basketDenom, - Credits: []*basket.BasketCredit{ - { - BatchDenom: s.batchDenom, - Amount: s.tradableCredits, - }, - }, - }) -} - -func (s *putSuite) TheCreditsArePutIntoTheBasket() { - assert.ErrorIs(s.t, s.err, nil) -} - -func (s *putSuite) TheCreditsAreNotPutIntoTheBasket() { - assert.ErrorContains(s.t, s.err, "cannot put a credit from a batch with start date") -} diff --git a/x/ecocredit/server/basket/take_test.go b/x/ecocredit/server/basket/take_test.go index 27ff60dcbd..32d0ccf872 100644 --- a/x/ecocredit/server/basket/take_test.go +++ b/x/ecocredit/server/basket/take_test.go @@ -99,7 +99,7 @@ func TestTakeMustRetire(t *testing.T) { // foo requires RetireOnTake _, err := s.k.Take(s.ctx, &baskettypes.MsgTake{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "foo", Amount: "6.0", RetirementJurisdiction: "", @@ -113,11 +113,11 @@ func TestTakeRetire(t *testing.T) { s := setupTake(t) fooCoins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(6000000))) - s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addr, baskettypes.BasketSubModuleName, fooCoins) + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addrs[0], baskettypes.BasketSubModuleName, fooCoins) s.bankKeeper.EXPECT().BurnCoins(gomock.Any(), baskettypes.BasketSubModuleName, fooCoins) res, err := s.k.Take(s.ctx, &baskettypes.MsgTake{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "foo", Amount: "6000000", RetirementJurisdiction: "US", @@ -151,11 +151,11 @@ func TestTakeTradable(t *testing.T) { s := setupTake(t) barCoins := sdk.NewCoins(sdk.NewCoin("bar", sdk.NewInt(10000000))) - s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addr, baskettypes.BasketSubModuleName, barCoins) + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addrs[0], baskettypes.BasketSubModuleName, barCoins) s.bankKeeper.EXPECT().BurnCoins(gomock.Any(), baskettypes.BasketSubModuleName, barCoins) res, err := s.k.Take(s.ctx, &baskettypes.MsgTake{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "bar", Amount: "10000000", RetireOnTake: false, @@ -190,11 +190,11 @@ func TestTakeTooMuchTradable(t *testing.T) { // Try to take more than what's in the basket, should error. amount := sdk.NewInt(99999999999) barCoins := sdk.NewCoins(sdk.NewCoin("bar", amount)) - s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addr, baskettypes.BasketSubModuleName, barCoins) + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addrs[0], baskettypes.BasketSubModuleName, barCoins) s.bankKeeper.EXPECT().BurnCoins(gomock.Any(), baskettypes.BasketSubModuleName, barCoins) _, err := s.k.Take(s.ctx, &baskettypes.MsgTake{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "bar", Amount: amount.String(), RetireOnTake: false, @@ -209,11 +209,11 @@ func TestTakeAllTradable(t *testing.T) { s := setupTake(t) barCoins := sdk.NewCoins(sdk.NewCoin("bar", sdk.NewInt(11000000))) // == 7C4 + 4C4 - s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addr, baskettypes.BasketSubModuleName, barCoins) + s.bankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), s.addrs[0], baskettypes.BasketSubModuleName, barCoins) s.bankKeeper.EXPECT().BurnCoins(gomock.Any(), baskettypes.BasketSubModuleName, barCoins) res, err := s.k.Take(s.ctx, &baskettypes.MsgTake{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "bar", Amount: "11000000", RetireOnTake: false, @@ -295,11 +295,11 @@ func (s takeSuite) setTradableSupply(batchId uint64, amount string) { func (s takeSuite) getUserBalance(batchDenom string) *ecoApi.BatchBalance { id := s.denomToId[batchDenom] - bal, err := s.coreStore.BatchBalanceTable().Get(s.ctx, s.addr, id) + bal, err := s.coreStore.BatchBalanceTable().Get(s.ctx, s.addrs[0], id) if ormerrors.IsNotFound(err) { bal = &ecoApi.BatchBalance{ BatchKey: id, - Address: s.addr, + Address: s.addrs[0], Tradable: "0", Retired: "0", Escrowed: "0", diff --git a/x/ecocredit/server/basket/util_test.go b/x/ecocredit/server/basket/util_test.go index 21dc78e7a2..cd7b7a0502 100644 --- a/x/ecocredit/server/basket/util_test.go +++ b/x/ecocredit/server/basket/util_test.go @@ -29,15 +29,15 @@ func TestGetBasketBalances(t *testing.T) { initBatch(t, s, 1, batchDenom1, timestamppb.Now()) initBatch(t, s, 2, batchDenom2, timestamppb.Now()) - insertBatchBalance(t, s, s.addr, 1, userStartingBalance.String()) - insertBatchBalance(t, s, s.addr, 2, userStartingBalance.String()) + insertBatchBalance(t, s, s.addrs[0], 1, userStartingBalance.String()) + insertBatchBalance(t, s, s.addrs[0], 2, userStartingBalance.String()) insertBatchBalance(t, s, sdk.AccAddress("abcde"), 2, userStartingBalance.String()) s.bankKeeper.EXPECT().MintCoins(gmAny, gmAny, gmAny).Return(nil).Times(3) s.bankKeeper.EXPECT().SendCoinsFromModuleToAccount(gmAny, gmAny, gmAny, gmAny).Return(nil).Times(3) _, err := s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "foo", Credits: []*basket.BasketCredit{ {BatchDenom: batchDenom1, Amount: amtToDeposit.String()}, @@ -51,7 +51,7 @@ func TestGetBasketBalances(t *testing.T) { require.Equal(t, bIdToBalance[1], amtToDeposit) _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "bar", Credits: []*basket.BasketCredit{ {BatchDenom: batchDenom1, Amount: amtToDeposit.String()}, @@ -60,7 +60,7 @@ func TestGetBasketBalances(t *testing.T) { assert.NilError(t, err) _, err = s.k.Put(s.ctx, &basket.MsgPut{ - Owner: s.addr.String(), + Owner: s.addrs[0].String(), BasketDenom: "bar", Credits: []*basket.BasketCredit{ {BatchDenom: batchDenom2, Amount: amtToDeposit.String()}, @@ -87,3 +87,42 @@ func initBatch(t *testing.T, s *baseSuite, pid uint64, denom string, startDate * EndDate: nil, })) } + +func insertBatchBalance(t *testing.T, s *baseSuite, user sdk.AccAddress, batchKey uint64, amount string) { + assert.NilError(t, s.coreStore.BatchBalanceTable().Insert(s.ctx, &ecoApi.BatchBalance{ + BatchKey: batchKey, + Address: user, + Tradable: amount, + Retired: "", + Escrowed: "", + })) +} + +func insertClass(t *testing.T, s *baseSuite, name, creditTypeAbb string) { + assert.NilError(t, s.coreStore.ClassTable().Insert(s.ctx, &ecoApi.Class{ + Id: name, + Admin: s.addrs[0], + Metadata: "", + CreditTypeAbbrev: creditTypeAbb, + })) +} + +func insertBasket(t *testing.T, s *baseSuite, denom, name, ctAbbrev string, criteria *api.DateCriteria, classes []string) { + id, err := s.stateStore.BasketTable().InsertReturningID(s.ctx, &api.Basket{ + BasketDenom: denom, + Name: name, + DisableAutoRetire: false, + CreditTypeAbbrev: ctAbbrev, + DateCriteria: criteria, + Exponent: 6, + Curator: s.addrs[0], + }) + assert.NilError(t, err) + + for _, class := range classes { + assert.NilError(t, s.stateStore.BasketClassTable().Insert(s.ctx, &api.BasketClass{ + BasketId: id, + ClassId: class, + })) + } +}