From 2178d73017fec759effcd69957c56578484febc2 Mon Sep 17 00:00:00 2001 From: technicallyty <48813565+tytech3@users.noreply.github.com> Date: Mon, 7 Feb 2022 11:18:10 -0800 Subject: [PATCH] chore: move new code to core package --- x/ecocredit/client/testsuite/query.go | 2139 +++++++++++------------ x/ecocredit/server/core/genesis.go | 30 + x/ecocredit/server/core/invariants.go | 217 +++ x/ecocredit/server/core/msg_server.go | 1187 +++++++++++++ x/ecocredit/server/core/operations.go | 20 + x/ecocredit/server/core/query_server.go | 579 ++++++ x/ecocredit/server/core/server.go | 82 + x/ecocredit/server/store.go | 42 - 8 files changed, 3179 insertions(+), 1117 deletions(-) create mode 100644 x/ecocredit/server/core/genesis.go create mode 100644 x/ecocredit/server/core/invariants.go create mode 100644 x/ecocredit/server/core/msg_server.go create mode 100644 x/ecocredit/server/core/operations.go create mode 100644 x/ecocredit/server/core/query_server.go create mode 100644 x/ecocredit/server/core/server.go delete mode 100644 x/ecocredit/server/store.go diff --git a/x/ecocredit/client/testsuite/query.go b/x/ecocredit/client/testsuite/query.go index 94f3b1e2df..2eb042aa44 100644 --- a/x/ecocredit/client/testsuite/query.go +++ b/x/ecocredit/client/testsuite/query.go @@ -1,1077 +1,1066 @@ package testsuite -import ( - "encoding/json" - "fmt" - - "github.com/cosmos/cosmos-sdk/client/flags" - - "github.com/regen-network/regen-ledger/types/testutil/cli" - "github.com/regen-network/regen-ledger/x/ecocredit" - "github.com/regen-network/regen-ledger/x/ecocredit/client" -) - -func (s *IntegrationTestSuite) TestQueryClasses() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedClasses []string - }{ - { - name: "too many args", - args: []string{"abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 0 arg(s), received 1", - }, - { - name: "no pagination flags", - args: []string{}, - expectErr: false, - expectedClasses: []string{"C01", "C02", "C03", "C04"}, - }, - { - name: "limit 2", - args: []string{ - fmt.Sprintf("--%s=2", flags.FlagLimit), - }, - expectErr: false, - expectedClasses: []string{"C01", "C02"}, - }, - { - name: "limit 2, offset 2", - args: []string{ - fmt.Sprintf("--%s=2", flags.FlagLimit), - fmt.Sprintf("--%s=2", flags.FlagOffset), - }, - expectErr: false, - expectedClasses: []string{"C03", "C04"}, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryClassesCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryClassesResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - - classIDs := make([]string, len(res.Classes)) - for i, class := range res.Classes { - classIDs[i] = class.ClassId - } - - s.Require().Equal(tc.expectedClasses, classIDs) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryClassInfo() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedClassInfo *ecocredit.ClassInfo - }{ - { - name: "missing args", - args: []string{}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"abcde", "abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid credit class", - args: []string{"abcde"}, - expectErr: true, - expectedErrMsg: "not found: invalid request", - }, - { - name: "valid credit class", - args: []string{s.classInfo.ClassId}, - expectErr: false, - expectedClassInfo: &ecocredit.ClassInfo{ - ClassId: s.classInfo.ClassId, - Admin: s.classInfo.Admin, - Issuers: s.classInfo.Issuers, - Metadata: s.classInfo.Metadata, - CreditType: s.classInfo.CreditType, - NumBatches: 4, - }, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryClassInfoCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryClassInfoResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expectedClassInfo, res.Info) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBatches() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedBatchDenoms []string - }{ - { - name: "missing args", - args: []string{}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"abcde", "abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid project id", - args: []string{"abcd-e"}, - expectErr: true, - expectedErrMsg: "invalid project id", - }, - { - name: "existing project no batches", - args: []string{"P02"}, - expectErr: false, - expectedBatchDenoms: []string{}, - }, - { - name: "no pagination flags", - args: []string{"P01"}, - expectErr: false, - expectedBatchDenoms: []string{ - "C01-20210101-20210201-001", - "C01-20210101-20210201-002", - "C01-20210101-20210201-003", - "C01-20210101-20210201-004", - }, - }, - { - name: "limit 2", - args: []string{ - "P01", - fmt.Sprintf("--%s=2", flags.FlagLimit), - }, - expectErr: false, - expectedBatchDenoms: []string{ - "C01-20210101-20210201-001", - "C01-20210101-20210201-002", - }, - }, - { - name: "limit 2, offset 2", - args: []string{ - "P01", - fmt.Sprintf("--%s=2", flags.FlagLimit), - fmt.Sprintf("--%s=2", flags.FlagOffset), - }, - expectErr: false, - expectedBatchDenoms: []string{ - "C01-20210101-20210201-003", - "C01-20210101-20210201-004", - }, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBatchesCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBatchesResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - - batchDenoms := make([]string, len(res.Batches)) - for i, batch := range res.Batches { - batchDenoms[i] = batch.BatchDenom - } - - s.Require().Equal(tc.expectedBatchDenoms, batchDenoms) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBatchInfo() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedBatchInfo *ecocredit.BatchInfo - }{ - { - name: "missing args", - args: []string{}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"abcde", "abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "malformed batch denom", - args: []string{"abcde"}, - expectErr: true, - expectedErrMsg: "invalid denom", - }, - { - name: "non-existent credit batch", - args: []string{"A00-00000000-00000000-000"}, - expectErr: true, - expectedErrMsg: "not found", - }, - { - name: "valid credit batch", - args: []string{s.batchInfo.BatchDenom}, - expectErr: false, - expectedBatchInfo: s.batchInfo, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBatchInfoCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBatchInfoResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expectedBatchInfo, res.Info) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBalance() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedTradableAmount string - expectedRetiredAmount string - }{ - { - name: "missing args", - args: []string{}, - expectErr: true, - expectedErrMsg: "Error: accepts 2 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"abcde", "abcde", "abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 2 arg(s), received 3", - }, - { - name: "invalid credit batch", - args: []string{"abcde", s.network.Validators[0].Address.String()}, - expectErr: true, - expectedErrMsg: "invalid denom", - }, - { - name: "valid credit batch and invalid account", - args: []string{s.batchInfo.BatchDenom, "abcde"}, - expectErr: true, - expectedTradableAmount: "0", - expectedRetiredAmount: "0", - }, - { - name: "valid credit batch and account with no funds", - args: []string{s.batchInfo.BatchDenom, s.network.Validators[2].Address.String()}, - expectErr: false, - expectedTradableAmount: "0", - expectedRetiredAmount: "0", - }, - { - name: "valid credit batch and account with enough funds", - args: []string{s.batchInfo.BatchDenom, s.network.Validators[0].Address.String()}, - expectErr: false, - expectedTradableAmount: "100", - expectedRetiredAmount: "0.000001", - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBalanceCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBalanceResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expectedTradableAmount, res.TradableAmount) - s.Require().Equal(tc.expectedRetiredAmount, res.RetiredAmount) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQuerySupply() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedTradableSupply string - expectedRetiredSupply string - }{ - { - name: "missing args", - args: []string{}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"abcde", "abcde"}, - expectErr: true, - expectedErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid credit batch", - args: []string{"abcde"}, - expectErr: true, - expectedErrMsg: "invalid denom", - }, - { - name: "valid credit batch", - args: []string{s.batchInfo.BatchDenom}, - expectErr: false, - expectedTradableSupply: "100", - expectedRetiredSupply: "0.000001", - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QuerySupplyCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QuerySupplyResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expectedTradableSupply, res.TradableSupply) - s.Require().Equal(tc.expectedRetiredSupply, res.RetiredSupply) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryCreditTypes() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expectErr bool - expectedErrMsg string - expectedCreditType []*ecocredit.CreditType - }{ - { - name: "should give credit type", - args: []string{}, - expectErr: false, - expectedErrMsg: "", - expectedCreditType: []*ecocredit.CreditType{s.classInfo.CreditType}, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryCreditTypesCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expectErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expectedErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryCreditTypesResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expectedCreditType, res.CreditTypes) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryParams() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - require := s.Require() - - cmd := client.QueryParamsCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, []string{}) - require.NoError(err) - - var params ecocredit.QueryParamsResponse - err = json.Unmarshal(out.Bytes(), ¶ms) - require.NoError(err) - - require.Equal(ecocredit.DefaultParams(), *params.Params) -} - -func (s *IntegrationTestSuite) TestQuerySellOrder() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrder *ecocredit.SellOrder - }{ - { - name: "missing args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"foo", "bar"}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid sell order", - args: []string{"foo"}, - expErr: true, - expErrMsg: "invalid sell order", - }, - { - name: "valid", - args: []string{"1"}, - expErr: false, - expErrMsg: "", - expOrder: s.sellOrders[0], - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QuerySellOrderCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QuerySellOrderResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrder, res.SellOrder) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQuerySellOrders() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrders []*ecocredit.SellOrder - }{ - { - name: "too many args", - args: []string{"foo"}, - expErr: true, - expErrMsg: "Error: accepts 0 arg(s), received 1", - }, - { - name: "valid", - args: []string{}, - expErr: false, - expErrMsg: "", - expOrders: s.sellOrders, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QuerySellOrdersCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QuerySellOrdersResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrders, res.SellOrders) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQuerySellOrdersByAddress() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrders []*ecocredit.SellOrder - }{ - { - name: "missing args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"foo", "bar"}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid address", - args: []string{"foo"}, - expErr: true, - expErrMsg: "invalid request", - }, - { - name: "valid", - args: []string{val.Address.String()}, - expErr: false, - expErrMsg: "", - expOrders: s.sellOrders, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QuerySellOrdersByAddressCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QuerySellOrdersByAddressResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrders, res.SellOrders) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQuerySellOrdersByBatchDenom() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrders []*ecocredit.SellOrder - }{ - { - name: "missing args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"foo", "bar"}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid denom", - args: []string{"foo"}, - expErr: true, - expErrMsg: "invalid request", - }, - { - name: "valid", - args: []string{batchDenom}, - expErr: false, - expErrMsg: "", - expOrders: s.sellOrders, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QuerySellOrdersByBatchDenomCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QuerySellOrdersByBatchDenomResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrders, res.SellOrders) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBuyOrder() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrder *ecocredit.BuyOrder - }{ - { - name: "missing args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"foo", "bar"}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid buy order", - args: []string{"foo"}, - expErr: true, - expErrMsg: "invalid buy order", - }, - // TODO: filtered buy orders required #623 - //{ - // name: "valid", - // args: []string{"1"}, - // expErr: false, - // expErrMsg: "", - // expOrder: &ecocredit.BuyOrder{ - // BuyOrderId: 1, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - //}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBuyOrderCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBuyOrderResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrder, res.BuyOrder) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBuyOrders() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrders []*ecocredit.BuyOrder - }{ - { - name: "too many args", - args: []string{"foo"}, - expErr: true, - expErrMsg: "Error: accepts 0 arg(s), received 1", - }, - // TODO: filtered buy orders required #623 - //{ - // name: "valid", - // args: []string{}, - // expErr: false, - // expErrMsg: "", - // expOrders: []*ecocredit.BuyOrder{ - // { - // BuyOrderId: 1, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // { - // BuyOrderId: 2, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // { - // BuyOrderId: 3, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // }, - //}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBuyOrdersCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBuyOrdersResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrders, res.BuyOrders) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryBuyOrdersByAddress() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expOrders []*ecocredit.BuyOrder - }{ - { - name: "missing args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "too many args", - args: []string{"foo", "bar"}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 2", - }, - { - name: "invalid address", - args: []string{"foo"}, - expErr: true, - expErrMsg: "invalid request", - }, - // TODO: filtered buy orders required #623 - //{ - // name: "valid", - // args: []string{val.Address.String()}, - // expErr: false, - // expErrMsg: "", - // expOrders: []*ecocredit.BuyOrder{ - // { - // BuyOrderId: 1, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // { - // BuyOrderId: 2, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // { - // BuyOrderId: 3, - // Buyer: val.Address.String(), - // Quantity: "1", - // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, - // DisableAutoRetire: false, - // }, - // }, - //}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryBuyOrdersByAddressCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryBuyOrdersByAddressResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expOrders, res.BuyOrders) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryAllowedAskDenoms() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expDenoms []*ecocredit.AskDenom - }{ - { - name: "too many args", - args: []string{"foo"}, - expErr: true, - expErrMsg: "Error: accepts 0 arg(s), received 1", - }, - // TODO: AllowAskDenom not yet implemented #624 - //{ - // name: "valid", - // args: []string{}, - // expErr: false, - // expErrMsg: "", - // expDenoms: []*ecocredit.AskDenom{ - // { - // Denom: "regen", - // DisplayDenom: "uregen", - // Exponent: 6, - // }, - // }, - //}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryAllowedAskDenomsCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryAllowedAskDenomsResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Equal(tc.expDenoms, res.AskDenoms) - } - }) - } -} - -func (s *IntegrationTestSuite) TestQueryProjects() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - expLen int - }{ - { - name: "no args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "no projects found ", - args: []string{"CA10"}, - expErr: false, - expErrMsg: "", - }, - { - name: "valid query", - args: []string{"C01"}, - expErr: false, - expErrMsg: "", - expLen: 3, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryProjectsCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(out.String(), tc.expErrMsg) - } else { - s.Require().NoError(err, out.String()) - - var res ecocredit.QueryProjectsResponse - s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - s.Require().Len(res.Projects, tc.expLen) - } - }) - } - -} - -func (s *IntegrationTestSuite) TestQueryProjectInfo() { - val := s.network.Validators[0] - clientCtx := val.ClientCtx - clientCtx.OutputFormat = "JSON" - require := s.Require() - - cmd := client.QueryProjectsCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, []string{"C01"}) - require.NoError(err) - var res ecocredit.QueryProjectsResponse - require.NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - require.GreaterOrEqual(len(res.Projects), 1) - project := res.Projects[0] - - testCases := []struct { - name string - args []string - expErr bool - expErrMsg string - }{ - { - name: "no args", - args: []string{}, - expErr: true, - expErrMsg: "Error: accepts 1 arg(s), received 0", - }, - { - name: "invalid project id ", - args: []string{"A@a@"}, - expErr: true, - expErrMsg: "invalid project id", - }, - { - name: "not found", - args: []string{"P100"}, - expErr: true, - expErrMsg: "not found", - }, - { - name: "valid query", - args: []string{project.ProjectId}, - expErr: false, - expErrMsg: "", - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - cmd := client.QueryProjectInfoCmd() - out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) - if tc.expErr { - require.Error(err) - require.Contains(out.String(), tc.expErrMsg) - } else { - require.NoError(err, out.String()) - - var res ecocredit.QueryProjectInfoResponse - require.NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) - require.Equal(project, res.Info) - } - }) - } - -} +//func (s *IntegrationTestSuite) TestQueryClasses() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedClasses []string +// }{ +// { +// name: "too many args", +// args: []string{"abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 0 arg(s), received 1", +// }, +// { +// name: "no pagination flags", +// args: []string{}, +// expectErr: false, +// expectedClasses: []string{"C01", "C02", "C03", "C04"}, +// }, +// { +// name: "limit 2", +// args: []string{ +// fmt.Sprintf("--%s=2", flags.FlagLimit), +// }, +// expectErr: false, +// expectedClasses: []string{"C01", "C02"}, +// }, +// { +// name: "limit 2, offset 2", +// args: []string{ +// fmt.Sprintf("--%s=2", flags.FlagLimit), +// fmt.Sprintf("--%s=2", flags.FlagOffset), +// }, +// expectErr: false, +// expectedClasses: []string{"C03", "C04"}, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryClassesCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryClassesResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// +// classIDs := make([]string, len(res.Classes)) +// for i, class := range res.Classes { +// classIDs[i] = class.ClassId +// } +// +// s.Require().Equal(tc.expectedClasses, classIDs) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryClassInfo() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedClassInfo *ecocredit.ClassInfo +// }{ +// { +// name: "missing args", +// args: []string{}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"abcde", "abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid credit class", +// args: []string{"abcde"}, +// expectErr: true, +// expectedErrMsg: "not found: invalid request", +// }, +// { +// name: "valid credit class", +// args: []string{s.classInfo.ClassId}, +// expectErr: false, +// expectedClassInfo: &ecocredit.ClassInfo{ +// ClassId: s.classInfo.ClassId, +// Admin: s.classInfo.Admin, +// Issuers: s.classInfo.Issuers, +// Metadata: s.classInfo.Metadata, +// CreditType: s.classInfo.CreditType, +// NumBatches: 4, +// }, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryClassInfoCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryClassInfoResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expectedClassInfo, res.Info) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBatches() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedBatchDenoms []string +// }{ +// { +// name: "missing args", +// args: []string{}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"abcde", "abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid project id", +// args: []string{"abcd-e"}, +// expectErr: true, +// expectedErrMsg: "invalid project id", +// }, +// { +// name: "existing project no batches", +// args: []string{"P02"}, +// expectErr: false, +// expectedBatchDenoms: []string{}, +// }, +// { +// name: "no pagination flags", +// args: []string{"P01"}, +// expectErr: false, +// expectedBatchDenoms: []string{ +// "C01-20210101-20210201-001", +// "C01-20210101-20210201-002", +// "C01-20210101-20210201-003", +// "C01-20210101-20210201-004", +// }, +// }, +// { +// name: "limit 2", +// args: []string{ +// "P01", +// fmt.Sprintf("--%s=2", flags.FlagLimit), +// }, +// expectErr: false, +// expectedBatchDenoms: []string{ +// "C01-20210101-20210201-001", +// "C01-20210101-20210201-002", +// }, +// }, +// { +// name: "limit 2, offset 2", +// args: []string{ +// "P01", +// fmt.Sprintf("--%s=2", flags.FlagLimit), +// fmt.Sprintf("--%s=2", flags.FlagOffset), +// }, +// expectErr: false, +// expectedBatchDenoms: []string{ +// "C01-20210101-20210201-003", +// "C01-20210101-20210201-004", +// }, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBatchesCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBatchesResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// +// batchDenoms := make([]string, len(res.Batches)) +// for i, batch := range res.Batches { +// batchDenoms[i] = batch.BatchDenom +// } +// +// s.Require().Equal(tc.expectedBatchDenoms, batchDenoms) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBatchInfo() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedBatchInfo *ecocredit.BatchInfo +// }{ +// { +// name: "missing args", +// args: []string{}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"abcde", "abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "malformed batch denom", +// args: []string{"abcde"}, +// expectErr: true, +// expectedErrMsg: "invalid denom", +// }, +// { +// name: "non-existent credit batch", +// args: []string{"A00-00000000-00000000-000"}, +// expectErr: true, +// expectedErrMsg: "not found", +// }, +// { +// name: "valid credit batch", +// args: []string{s.batchInfo.BatchDenom}, +// expectErr: false, +// expectedBatchInfo: s.batchInfo, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBatchInfoCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBatchInfoResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expectedBatchInfo, res.Info) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBalance() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedTradableAmount string +// expectedRetiredAmount string +// }{ +// { +// name: "missing args", +// args: []string{}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 2 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"abcde", "abcde", "abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 2 arg(s), received 3", +// }, +// { +// name: "invalid credit batch", +// args: []string{"abcde", s.network.Validators[0].Address.String()}, +// expectErr: true, +// expectedErrMsg: "invalid denom", +// }, +// { +// name: "valid credit batch and invalid account", +// args: []string{s.batchInfo.BatchDenom, "abcde"}, +// expectErr: true, +// expectedTradableAmount: "0", +// expectedRetiredAmount: "0", +// }, +// { +// name: "valid credit batch and account with no funds", +// args: []string{s.batchInfo.BatchDenom, s.network.Validators[2].Address.String()}, +// expectErr: false, +// expectedTradableAmount: "0", +// expectedRetiredAmount: "0", +// }, +// { +// name: "valid credit batch and account with enough funds", +// args: []string{s.batchInfo.BatchDenom, s.network.Validators[0].Address.String()}, +// expectErr: false, +// expectedTradableAmount: "100", +// expectedRetiredAmount: "0.000001", +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBalanceCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBalanceResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expectedTradableAmount, res.TradableAmount) +// s.Require().Equal(tc.expectedRetiredAmount, res.RetiredAmount) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQuerySupply() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedTradableSupply string +// expectedRetiredSupply string +// }{ +// { +// name: "missing args", +// args: []string{}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"abcde", "abcde"}, +// expectErr: true, +// expectedErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid credit batch", +// args: []string{"abcde"}, +// expectErr: true, +// expectedErrMsg: "invalid denom", +// }, +// { +// name: "valid credit batch", +// args: []string{s.batchInfo.BatchDenom}, +// expectErr: false, +// expectedTradableSupply: "100", +// expectedRetiredSupply: "0.000001", +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QuerySupplyCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QuerySupplyResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expectedTradableSupply, res.TradableSupply) +// s.Require().Equal(tc.expectedRetiredSupply, res.RetiredSupply) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryCreditTypes() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expectErr bool +// expectedErrMsg string +// expectedCreditType []*ecocredit.CreditType +// }{ +// { +// name: "should give credit type", +// args: []string{}, +// expectErr: false, +// expectedErrMsg: "", +// expectedCreditType: []*ecocredit.CreditType{s.classInfo.CreditType}, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryCreditTypesCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expectErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expectedErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryCreditTypesResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expectedCreditType, res.CreditTypes) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryParams() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// require := s.Require() +// +// cmd := client.QueryParamsCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, []string{}) +// require.NoError(err) +// +// var params ecocredit.QueryParamsResponse +// err = json.Unmarshal(out.Bytes(), ¶ms) +// require.NoError(err) +// +// require.Equal(ecocredit.DefaultParams(), *params.Params) +//} +// +//func (s *IntegrationTestSuite) TestQuerySellOrder() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrder *ecocredit.SellOrder +// }{ +// { +// name: "missing args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"foo", "bar"}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid sell order", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "invalid sell order", +// }, +// { +// name: "valid", +// args: []string{"1"}, +// expErr: false, +// expErrMsg: "", +// expOrder: s.sellOrders[0], +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QuerySellOrderCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QuerySellOrderResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrder, res.SellOrder) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQuerySellOrders() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrders []*ecocredit.SellOrder +// }{ +// { +// name: "too many args", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "Error: accepts 0 arg(s), received 1", +// }, +// { +// name: "valid", +// args: []string{}, +// expErr: false, +// expErrMsg: "", +// expOrders: s.sellOrders, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QuerySellOrdersCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QuerySellOrdersResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrders, res.SellOrders) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQuerySellOrdersByAddress() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrders []*ecocredit.SellOrder +// }{ +// { +// name: "missing args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"foo", "bar"}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid address", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "invalid request", +// }, +// { +// name: "valid", +// args: []string{val.Address.String()}, +// expErr: false, +// expErrMsg: "", +// expOrders: s.sellOrders, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QuerySellOrdersByAddressCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QuerySellOrdersByAddressResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrders, res.SellOrders) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQuerySellOrdersByBatchDenom() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrders []*ecocredit.SellOrder +// }{ +// { +// name: "missing args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"foo", "bar"}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid denom", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "invalid request", +// }, +// { +// name: "valid", +// args: []string{batchDenom}, +// expErr: false, +// expErrMsg: "", +// expOrders: s.sellOrders, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QuerySellOrdersByBatchDenomCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QuerySellOrdersByBatchDenomResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrders, res.SellOrders) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBuyOrder() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrder *ecocredit.BuyOrder +// }{ +// { +// name: "missing args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"foo", "bar"}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid buy order", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "invalid buy order", +// }, +// // TODO: filtered buy orders required #623 +// //{ +// // name: "valid", +// // args: []string{"1"}, +// // expErr: false, +// // expErrMsg: "", +// // expOrder: &ecocredit.BuyOrder{ +// // BuyOrderId: 1, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// //}, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBuyOrderCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBuyOrderResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrder, res.BuyOrder) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBuyOrders() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrders []*ecocredit.BuyOrder +// }{ +// { +// name: "too many args", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "Error: accepts 0 arg(s), received 1", +// }, +// // TODO: filtered buy orders required #623 +// //{ +// // name: "valid", +// // args: []string{}, +// // expErr: false, +// // expErrMsg: "", +// // expOrders: []*ecocredit.BuyOrder{ +// // { +// // BuyOrderId: 1, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // { +// // BuyOrderId: 2, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // { +// // BuyOrderId: 3, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // }, +// //}, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBuyOrdersCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBuyOrdersResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrders, res.BuyOrders) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryBuyOrdersByAddress() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expOrders []*ecocredit.BuyOrder +// }{ +// { +// name: "missing args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "too many args", +// args: []string{"foo", "bar"}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 2", +// }, +// { +// name: "invalid address", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "invalid request", +// }, +// // TODO: filtered buy orders required #623 +// //{ +// // name: "valid", +// // args: []string{val.Address.String()}, +// // expErr: false, +// // expErrMsg: "", +// // expOrders: []*ecocredit.BuyOrder{ +// // { +// // BuyOrderId: 1, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // { +// // BuyOrderId: 2, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // { +// // BuyOrderId: 3, +// // Buyer: val.Address.String(), +// // Quantity: "1", +// // BidPrice: &sdk.Coin{Denom: "regen", Amount: sdk.NewInt(100)}, +// // DisableAutoRetire: false, +// // }, +// // }, +// //}, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryBuyOrdersByAddressCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryBuyOrdersByAddressResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expOrders, res.BuyOrders) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryAllowedAskDenoms() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expDenoms []*ecocredit.AskDenom +// }{ +// { +// name: "too many args", +// args: []string{"foo"}, +// expErr: true, +// expErrMsg: "Error: accepts 0 arg(s), received 1", +// }, +// // TODO: AllowAskDenom not yet implemented #624 +// //{ +// // name: "valid", +// // args: []string{}, +// // expErr: false, +// // expErrMsg: "", +// // expDenoms: []*ecocredit.AskDenom{ +// // { +// // Denom: "regen", +// // DisplayDenom: "uregen", +// // Exponent: 6, +// // }, +// // }, +// //}, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryAllowedAskDenomsCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryAllowedAskDenomsResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Equal(tc.expDenoms, res.AskDenoms) +// } +// }) +// } +//} +// +//func (s *IntegrationTestSuite) TestQueryProjects() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// expLen int +// }{ +// { +// name: "no args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "no projects found ", +// args: []string{"CA10"}, +// expErr: false, +// expErrMsg: "", +// }, +// { +// name: "valid query", +// args: []string{"C01"}, +// expErr: false, +// expErrMsg: "", +// expLen: 3, +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryProjectsCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// s.Require().Error(err) +// s.Require().Contains(out.String(), tc.expErrMsg) +// } else { +// s.Require().NoError(err, out.String()) +// +// var res ecocredit.QueryProjectsResponse +// s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// s.Require().Len(res.Projects, tc.expLen) +// } +// }) +// } +// +//} +// +//func (s *IntegrationTestSuite) TestQueryProjectInfo() { +// val := s.network.Validators[0] +// clientCtx := val.ClientCtx +// clientCtx.OutputFormat = "JSON" +// require := s.Require() +// +// cmd := client.QueryProjectsCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, []string{"C01"}) +// require.NoError(err) +// var res ecocredit.QueryProjectsResponse +// require.NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// require.GreaterOrEqual(len(res.Projects), 1) +// project := res.Projects[0] +// +// testCases := []struct { +// name string +// args []string +// expErr bool +// expErrMsg string +// }{ +// { +// name: "no args", +// args: []string{}, +// expErr: true, +// expErrMsg: "Error: accepts 1 arg(s), received 0", +// }, +// { +// name: "invalid project id ", +// args: []string{"A@a@"}, +// expErr: true, +// expErrMsg: "invalid project id", +// }, +// { +// name: "not found", +// args: []string{"P100"}, +// expErr: true, +// expErrMsg: "not found", +// }, +// { +// name: "valid query", +// args: []string{project.ProjectId}, +// expErr: false, +// expErrMsg: "", +// }, +// } +// +// for _, tc := range testCases { +// s.Run(tc.name, func() { +// cmd := client.QueryProjectInfoCmd() +// out, err := cli.ExecTestCLICmd(clientCtx, cmd, tc.args) +// if tc.expErr { +// require.Error(err) +// require.Contains(out.String(), tc.expErrMsg) +// } else { +// require.NoError(err, out.String()) +// +// var res ecocredit.QueryProjectInfoResponse +// require.NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) +// require.Equal(project, res.Info) +// } +// }) +// } +// +//} diff --git a/x/ecocredit/server/core/genesis.go b/x/ecocredit/server/core/genesis.go new file mode 100644 index 0000000000..0ce3977306 --- /dev/null +++ b/x/ecocredit/server/core/genesis.go @@ -0,0 +1,30 @@ +package core + +import ( + "encoding/json" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/orm/types/ormjson" + regentypes "github.com/regen-network/regen-ledger/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// InitGenesis performs genesis initialization for the ecocredit module. It +// returns no validator updates. +func (s serverImpl) InitGenesis(ctx regentypes.Context, cdc codec.Codec, data json.RawMessage) ([]abci.ValidatorUpdate, error) { + goCtx := ctx.Context.Context() + target, err := ormjson.NewRawMessageSource(data) + if err != nil { + return nil, err + } + return []abci.ValidatorUpdate{}, s.db.ImportJSON(goCtx, target) +} + +// ExportGenesis will dump the ecocredit module state into a serializable GenesisState. +func (s serverImpl) ExportGenesis(ctx regentypes.Context, cdc codec.Codec) (json.RawMessage, error) { + target := ormjson.NewRawMessageTarget() + err := s.db.ExportJSON(ctx.Context.Context(), target) + if err != nil { + return nil, err + } + return target.JSON() +} diff --git a/x/ecocredit/server/core/invariants.go b/x/ecocredit/server/core/invariants.go new file mode 100644 index 0000000000..f8e0a89c06 --- /dev/null +++ b/x/ecocredit/server/core/invariants.go @@ -0,0 +1,217 @@ +package core + +import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + ecocreditv1beta1 "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1beta1" + "github.com/regen-network/regen-ledger/types/math" + "github.com/regen-network/regen-ledger/x/ecocredit" +) + +// RegisterInvariants registers the ecocredit module invariants. +func (s serverImpl) RegisterInvariants(ir sdk.InvariantRegistry) { + ir.RegisterRoute(ecocredit.ModuleName, "tradable-supply", s.tradableSupplyInvariant()) + ir.RegisterRoute(ecocredit.ModuleName, "retired-supply", s.retiredSupplyInvariant()) +} + +func (s serverImpl) tradableSupplyInvariant() sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + supplyStore := s.batchSupplyStore + batchStore := s.batchInfoStore + balanceStore := s.batchBalanceStore + return tradableSupplyInvariant(supplyStore, balanceStore, batchStore, ctx) + } +} + +func tradableSupplyInvariant(batchSupplyStore ecocreditv1beta1.BatchSupplyStore, balanceStore ecocreditv1beta1.BatchBalanceStore, batchInfoStore ecocreditv1beta1.BatchInfoStore, ctx sdk.Context) (string, bool) { + var ( + msg string + broken bool + ) + calTradableSupplies := make(map[string]math.Dec) + + err := iterateBalances(balanceStore, batchInfoStore, true, ctx, func(denom, supply string) bool { + balance, err := math.NewNonNegativeDecFromString(supply) + if err != nil { + broken = true + msg += fmt.Sprintf("error while parsing tradable balance %v", err) + } + if supply, ok := calTradableSupplies[denom]; ok { + supply, err := math.SafeAddBalance(supply, balance) + if err != nil { + broken = true + msg += fmt.Sprintf("error adding credit batch tradable supply %v", err) + } + calTradableSupplies[denom] = supply + } else { + calTradableSupplies[denom] = balance + } + + return false + }) + if err != nil { + msg = fmt.Sprintf("error querying credit balances tradable supply %v", err) + } + + if err := iterateSupplies(batchSupplyStore, batchInfoStore, true, ctx, func(denom string, s string) bool { + supply, err := math.NewNonNegativeDecFromString(s) + if err != nil { + broken = true + msg += fmt.Sprintf("error while parsing tradable supply for denom: %s", denom) + } + if s1, ok := calTradableSupplies[denom]; ok { + if supply.Cmp(s1) != 0 { + broken = true + msg += fmt.Sprintf("tradable supply is incorrect for %s credit batch, expected %v, got %v", denom, supply, s1) + } + } else { + broken = true + msg += fmt.Sprintf("tradable supply is not found for %s credit batch", denom) + } + return false + }); err != nil { + msg = fmt.Sprintf("error querying credit tradable supply %v", err) + } + + return msg, broken +} + +func (s serverImpl) retiredSupplyInvariant() sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + supplyStore := s.batchSupplyStore + balanceStore := s.batchBalanceStore + batchStore := s.batchInfoStore + return retiredSupplyInvariant(supplyStore, balanceStore, batchStore, ctx) + } +} + +func retiredSupplyInvariant(supplyStore ecocreditv1beta1.BatchSupplyStore, balanceStore ecocreditv1beta1.BatchBalanceStore, batchInfoStore ecocreditv1beta1.BatchInfoStore, ctx sdk.Context) (string, bool) { + var ( + msg string + broken bool + ) + calRetiredSupplies := make(map[string]math.Dec) + err := iterateBalances(balanceStore, batchInfoStore, false, ctx, func(denom, supply string) bool { + balance, err := math.NewNonNegativeDecFromString(supply) + if err != nil { + broken = true + msg += fmt.Sprintf("error while parsing retired balance %v", err) + } + if supply, ok := calRetiredSupplies[denom]; ok { + supply, err := math.SafeAddBalance(balance, supply) + if err != nil { + broken = true + msg += fmt.Sprintf("error adding credit batch retired supply %v", err) + } + calRetiredSupplies[denom] = supply + } else { + calRetiredSupplies[denom] = balance + } + return false + }) + if err != nil { + msg = fmt.Sprintf("error querying credit retired balances %v", err) + } + + if err := iterateSupplies(supplyStore, batchInfoStore, false, ctx, func(denom, s string) bool { + supply, err := math.NewNonNegativeDecFromString(s) + if err != nil { + broken = true + msg += fmt.Sprintf("error while parsing reired supply for denom: %s", denom) + } + if s1, ok := calRetiredSupplies[denom]; ok { + if supply.Cmp(s1) != 0 { + broken = true + msg += fmt.Sprintf("retired supply is incorrect for %s credit batch, expected %v, got %v", denom, supply, s1) + } + } else { + broken = true + msg += fmt.Sprintf("retired supply is not found for %s credit batch", denom) + } + + return false + }); err != nil { + msg = fmt.Sprintf("error querying credit retired supply %v", err) + } + + return msg, broken +} + +func iterateBalances(store ecocreditv1beta1.BatchBalanceStore, batchStore ecocreditv1beta1.BatchInfoStore, tradable bool, sdkCtx sdk.Context, cb func(denom, balance string) bool) error { + ctx := sdkCtx.Context() + it, err := store.List(ctx, &ecocreditv1beta1.BatchBalanceBatchIdAddressIndexKey{}) + if err != nil { + return err + } + // we cache denoms here so we don't have to ORM query each time + batchIdToDenom := make(map[uint64]string) + for it.Next() { + val, err := it.Value() + if err != nil { + return err + } + var batchDenom string + if d, ok := batchIdToDenom[val.BatchId]; ok { + batchDenom = d + } else { + batch, err := batchStore.Get(ctx, val.BatchId) + if err != nil { + return err + } + if batch == nil { + panic(fmt.Sprintf("batch was nil with batch id: %d and denom %s", val.BatchId, batchDenom)) + } + batchDenom = batch.BatchDenom + batchIdToDenom[val.BatchId] = batchDenom + } + + var balance string + if tradable { + balance = val.Tradable + } else { + balance = val.Retired + } + + if cb(batchDenom, balance) { + break + } + } + return nil +} + +func iterateSupplies(supplyStore ecocreditv1beta1.BatchSupplyStore, batchStore ecocreditv1beta1.BatchInfoStore, tradable bool, sdkCtx sdk.Context, cb func(denom, supply string) bool) error { + ctx := sdkCtx.Context() + it, err := supplyStore.List(ctx, &ecocreditv1beta1.BatchSupplyBatchIdIndexKey{}) + if err != nil { + return err + } + // we cache denoms here so we don't have to ORM query each time + batchIdToDenom := make(map[uint64]string) + for it.Next() { + val, err := it.Value() + if err != nil { + return err + } + var batchDenom string + if d, ok := batchIdToDenom[val.BatchId]; ok { + batchDenom = d + } else { + batch, err := batchStore.Get(ctx, val.BatchId) + if err != nil { + return err + } + batchDenom = batch.BatchDenom + batchIdToDenom[val.BatchId] = batchDenom + } + var supply string + if tradable { + supply = val.TradableAmount + } else { + supply = val.RetiredAmount + } + if cb(batchDenom, supply) { + break + } + } + return nil +} diff --git a/x/ecocredit/server/core/msg_server.go b/x/ecocredit/server/core/msg_server.go new file mode 100644 index 0000000000..95ea217b9a --- /dev/null +++ b/x/ecocredit/server/core/msg_server.go @@ -0,0 +1,1187 @@ +package core + +import ( + "context" + "fmt" + "github.com/cosmos/cosmos-sdk/orm/types/ormerrors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ecocreditv1beta1 "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1beta1" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/types/math" + "github.com/regen-network/regen-ledger/x/ecocredit" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// TODO: Revisit this once we have proper gas fee framework. +// Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072 +const gasCostPerIteration = uint64(10) + +// CreateClass creates a new class of ecocredit +// +// The admin is charged a fee for creating the class. This is controlled by +// the global parameter CreditClassFee, which can be updated through the +// governance process. +func (s serverImpl) CreateClass(ctx context.Context, req *ecocredit.MsgCreateClass) (*ecocredit.MsgCreateClassResponse, error) { + regenCtx := types.UnwrapSDKContext(ctx) + sdkCtx := sdk.UnwrapSDKContext(ctx) + // Charge the admin a fee to create the credit class + adminAddress, err := sdk.AccAddressFromBech32(req.Admin) + if err != nil { + return nil, err + } + + var params ecocredit.Params + s.paramSpace.GetParamSet(regenCtx.Context, ¶ms) + if params.AllowlistEnabled && !s.isCreatorAllowListed(regenCtx, params.AllowedClassCreators, adminAddress) { + return nil, sdkerrors.ErrUnauthorized.Wrapf("%s is not allowed to create credit classes", adminAddress.String()) + } + + err = s.chargeCreditClassFee(regenCtx.Context, adminAddress) + if err != nil { + return nil, err + } + + //creditType, err := s.creditTypeStore.GetByName(ctx, req.CreditTypeName) + //if err != nil { + // return nil, err + //} + + creditType, err := s.getCreditType(sdkCtx, req.CreditTypeName) + if err != nil { + return nil, err + } + + classSeq, err := s.getClassSequenceNo(ctx, req.CreditTypeName) + if err != nil { + return nil, fmt.Errorf("error getting class sequence") + } + classID := ecocredit.FormatClassID(creditType.Abbreviation, classSeq) + + // TODO(Tyler): waiting for PR that should make this should return the row ID, should include in event and response. + _, err = s.classInfoStore.InsertReturningID(ctx, &ecocreditv1beta1.ClassInfo{ + Name: classID, + Admin: req.Admin, + Metadata: req.Metadata, + CreditType: req.CreditTypeName, + }) + if err != nil { + return nil, err + } + + for _, issuer := range req.Issuers { + if err = s.classIssuerStore.Insert(ctx, &ecocreditv1beta1.ClassIssuer{ + ClassId: classID, + Issuer: issuer, + }); err != nil { + return nil, err + } + } + + err = regenCtx.EventManager().EmitTypedEvent(&ecocredit.EventCreateClass{ + ClassId: classID, + Admin: req.Admin, + }) + if err != nil { + return nil, err + } + + return &ecocredit.MsgCreateClassResponse{ClassId: classID}, nil +} + +// CreateProject creates a new project. +func (s serverImpl) CreateProject(ctx context.Context, req *ecocredit.MsgCreateProject) (*ecocredit.MsgCreateProjectResponse, error) { + sdkCtx := types.UnwrapSDKContext(ctx) + classID := req.ClassId + classInfo, err := s.classInfoStore.GetByName(ctx, classID) + if err != nil { + return nil, err + } + + err = s.assertClassIssuer(ctx, classID, req.Issuer) + if err != nil { + return nil, err + } + + projectID := req.ProjectId + if projectID == "" { + found := false + for !found { + projectID, err = s.genProjectID(ctx, classInfo.Id, classInfo.Name) + if err != nil { + return nil, err + } + found, err = s.projectInfoStore.HasByClassIdName(ctx, classInfo.Id, projectID) + if err != nil { + return nil, err + } + sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "project id sequence") + } + } + + // TODO(Tyler): update this to handle the id it returns and put it in event and response. + _, err = s.projectInfoStore.InsertReturningID(ctx, &ecocreditv1beta1.ProjectInfo{ + Name: projectID, + ClassId: classInfo.Id, + ProjectLocation: req.ProjectLocation, + Metadata: req.Metadata, + Issuer: req.Issuer, + }) + if err != nil { + return nil, err + } + + if err := sdkCtx.EventManager().EmitTypedEvent(&ecocredit.EventCreateProject{ + ClassId: classID, + ProjectId: projectID, + Issuer: req.Issuer, + ProjectLocation: req.ProjectLocation, + }); err != nil { + return nil, err + } + + return &ecocredit.MsgCreateProjectResponse{ + ProjectId: projectID, + }, nil +} + +// CreateBatch creates a new batch of credits. +// Credits in the batch must not have more decimal places than the credit type's specified precision. +func (s serverImpl) CreateBatch(ctx context.Context, req *ecocredit.MsgCreateBatch) (*ecocredit.MsgCreateBatchResponse, error) { + regenCtx := types.UnwrapSDKContext(ctx) + sdkCtx := sdk.UnwrapSDKContext(ctx) + projectID := req.ProjectId + + projectInfo, err := s.projectInfoStore.GetByName(ctx, projectID) + if err != nil { + return nil, err + } + + classInfo, err := s.classInfoStore.Get(ctx, projectInfo.ClassId) + if err != nil { + return nil, err + } + + err = s.assertClassIssuer(ctx, classInfo.Name, req.Issuer) + if err != nil { + return nil, err + } + + creditType, err := s.getCreditType(sdkCtx, classInfo.CreditType) + if err != nil { + return nil, err + } + + maxDecimalPlaces := creditType.Precision + + batchSeqNo, err := s.getBatchSeqNo(ctx, projectID) + if err != nil { + return nil, err + } + + batchDenom, err := ecocredit.FormatDenom(classInfo.Name, batchSeqNo, req.StartDate, req.EndDate) + if err != nil { + return nil, err + } + + // TODO(Tyler): this should return the ID. we need to use it. wait for update. + id, err := s.batchInfoStore.InsertReturningID(ctx, &ecocreditv1beta1.BatchInfo{ + ProjectId: projectInfo.Id, + BatchDenom: batchDenom, + Metadata: req.Metadata, + StartDate: timestamppb.New(req.StartDate.UTC()), + EndDate: timestamppb.New(req.EndDate.UTC()), + }) + if err != nil { + return nil, err + } + newBatchID := id + + tradableSupply, retiredSupply := math.NewDecFromInt64(0), math.NewDecFromInt64(0) + + for _, issuance := range req.Issuance { + decs, err := getNonNegativeFixedDecs(maxDecimalPlaces, issuance.TradableAmount, issuance.RetiredAmount) + if err != nil { + return nil, err + } + tradable, retired := decs[0], decs[1] + + recipient, _ := sdk.AccAddressFromBech32(issuance.Recipient) + if !tradable.IsZero() { + tradableSupply, err = tradableSupply.Add(tradable) + if err != nil { + return nil, err + } + } + if !retired.IsZero() { + retiredSupply, err = retiredSupply.Add(retired) + if err != nil { + return nil, err + } + if err = regenCtx.EventManager().EmitTypedEvent(&ecocredit.EventRetire{ + Retirer: recipient.String(), + BatchDenom: batchDenom, + Amount: retired.String(), + Location: issuance.RetirementLocation, + }); err != nil { + return nil, err + } + } + if err = s.batchBalanceStore.Insert(ctx, &ecocreditv1beta1.BatchBalance{ + Address: recipient, + BatchId: newBatchID, + Tradable: tradable.String(), + Retired: retired.String(), + }); err != nil { + return nil, err + } + + if err = regenCtx.EventManager().EmitTypedEvent(&ecocredit.EventReceive{ + Recipient: recipient.String(), + BatchDenom: batchDenom, + RetiredAmount: tradable.String(), + TradableAmount: retired.String(), + }); err != nil { + return nil, err + } + + regenCtx.GasMeter().ConsumeGas(gasCostPerIteration, "batch issuance") + } + + if err = s.batchSupplyStore.Insert(ctx, &ecocreditv1beta1.BatchSupply{ + BatchId: newBatchID, + TradableAmount: tradableSupply.String(), + RetiredAmount: retiredSupply.String(), + CancelledAmount: math.NewDecFromInt64(0).String(), + }); err != nil { + return nil, err + } + + // TODO(Tyler): put id here + return &ecocredit.MsgCreateBatchResponse{BatchDenom: batchDenom}, nil +} + +// Send sends credits to a recipient. +// Send also retires credits if the amount to retire is specified in the request. +func (s serverImpl) Send(ctx context.Context, req *ecocredit.MsgSend) (*ecocredit.MsgSendResponse, error) { + sdkCtx := types.UnwrapSDKContext(ctx) + sender, _ := sdk.AccAddressFromBech32(req.Sender) + recipient, _ := sdk.AccAddressFromBech32(req.Recipient) + + for _, credit := range req.Credits { + err := s.sendEcocredits(ctx, credit, recipient, sender) + if err != nil { + return nil, err + } + if err = sdkCtx.EventManager().EmitTypedEvent(&ecocredit.EventReceive{ + Sender: req.Sender, + Recipient: req.Recipient, + BatchDenom: credit.BatchDenom, + TradableAmount: credit.TradableAmount, + RetiredAmount: credit.RetiredAmount, + }); err != nil { + return nil, err + } + sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "send ecocredits") + } + return &ecocredit.MsgSendResponse{}, nil +} + +// Retire credits to the specified location. +// WARNING: retiring credits is permanent. Retired credits cannot be un-retired. +func (s serverImpl) Retire(ctx context.Context, req *ecocredit.MsgRetire) (*ecocredit.MsgRetireResponse, error) { + sdkCtx := types.UnwrapSDKContext(ctx) + holder, _ := sdk.AccAddressFromBech32(req.Holder) + + for _, credit := range req.Credits { + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, credit.BatchDenom) + if err != nil { + return nil, err + } + creditType, err := s.getCreditTypeFromBatchDenom(ctx, batch.BatchDenom) + if err != nil { + return nil, err + } + userBalance, err := s.batchBalanceStore.Get(ctx, holder, batch.Id) + if err != nil { + return nil, err + } + + decs, err := getNonNegativeFixedDecs(creditType.Precision, credit.Amount, userBalance.Tradable) + if err != nil { + return nil, err + } + amtToRetire, userTradableBalance := decs[0], decs[1] + + userTradableBalance, err = userTradableBalance.Sub(amtToRetire) + if err != nil { + return nil, err + } + if userTradableBalance.IsNegative() { + return nil, ecocredit.ErrInsufficientFunds.Wrapf("cannot retire %s credits with a balance of %s", credit.Amount, userBalance.Tradable) + } + userRetiredBalance, err := math.NewNonNegativeFixedDecFromString(userBalance.Retired, creditType.Precision) + if err != nil { + return nil, err + } + userRetiredBalance, err = userRetiredBalance.Add(amtToRetire) + if err != nil { + return nil, err + } + batchSupply, err := s.batchSupplyStore.Get(ctx, batch.Id) + if err != nil { + return nil, err + } + decs, err = getNonNegativeFixedDecs(creditType.Precision, batchSupply.RetiredAmount, batchSupply.TradableAmount) + if err != nil { + return nil, err + } + supplyRetired, supplyTradable := decs[0], decs[1] + supplyRetired, err = supplyRetired.Add(amtToRetire) + if err != nil { + return nil, err + } + supplyTradable, err = supplyTradable.Sub(amtToRetire) + if err != nil { + return nil, err + } + + if err = s.batchBalanceStore.Update(ctx, &ecocreditv1beta1.BatchBalance{ + Address: holder, + BatchId: batch.Id, + Tradable: userTradableBalance.String(), + Retired: userRetiredBalance.String(), + }); err != nil { + return nil, err + } + err = s.batchSupplyStore.Update(ctx, &ecocreditv1beta1.BatchSupply{ + BatchId: batch.Id, + TradableAmount: supplyTradable.String(), + RetiredAmount: supplyRetired.String(), + CancelledAmount: batchSupply.CancelledAmount, + }) + if err = sdkCtx.EventManager().EmitTypedEvent(&ecocredit.EventRetire{ + Retirer: req.Holder, + BatchDenom: credit.BatchDenom, + Amount: credit.Amount, + Location: req.Location, + }); err != nil { + return nil, err + } + sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "retire ecocredits") + } + return &ecocredit.MsgRetireResponse{}, nil +} + +// Cancel credits, removing them from the supply and balance of the holder +func (s serverImpl) Cancel(ctx context.Context, req *ecocredit.MsgCancel) (*ecocredit.MsgCancelResponse, error) { + sdkCtx := types.UnwrapSDKContext(ctx) + holder, _ := sdk.AccAddressFromBech32(req.Holder) + + for _, credit := range req.Credits { + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, credit.BatchDenom) + if err != nil { + return nil, err + + } + creditType, err := s.getCreditTypeFromBatchDenom(ctx, batch.BatchDenom) + if err != nil { + return nil, err + } + precision := creditType.Precision + + userBalance, err := s.batchBalanceStore.Get(ctx, holder, batch.Id) + if err != nil { + return nil, err + } + batchSupply, err := s.batchSupplyStore.Get(ctx, batch.Id) + if err != nil { + return nil, err + } + decs, err := getNonNegativeFixedDecs(precision, credit.Amount, batchSupply.TradableAmount, userBalance.Tradable, batchSupply.CancelledAmount) + if err != nil { + return nil, err + } + amtToCancelDec, supplyTradable, userBalTradable, cancelledDec := decs[0], decs[1], decs[2], decs[3] + userBalTradable, err = math.SafeSubBalance(userBalTradable, amtToCancelDec) + if err != nil { + return nil, err + } + supplyTradable, err = math.SafeSubBalance(supplyTradable, amtToCancelDec) + if err != nil { + return nil, err + } + cancelledDec, err = cancelledDec.Add(amtToCancelDec) + if err != nil { + return nil, err + } + if err = s.batchBalanceStore.Update(ctx, &ecocreditv1beta1.BatchBalance{ + Address: holder, + BatchId: batch.Id, + Tradable: userBalTradable.String(), + Retired: userBalance.Retired, + }); err != nil { + return nil, err + } + if err = s.batchSupplyStore.Update(ctx, &ecocreditv1beta1.BatchSupply{ + BatchId: batch.Id, + TradableAmount: supplyTradable.String(), + RetiredAmount: batchSupply.RetiredAmount, + CancelledAmount: cancelledDec.String(), + }); err != nil { + return nil, err + } + if err = sdkCtx.EventManager().EmitTypedEvent(&ecocredit.EventCancel{ + Canceller: holder.String(), + BatchDenom: credit.BatchDenom, + Amount: credit.Amount, + }); err != nil { + return nil, err + } + sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "cancel ecocredits") + } + return &ecocredit.MsgCancelResponse{}, nil +} + +func (s serverImpl) UpdateClassAdmin(ctx context.Context, req *ecocredit.MsgUpdateClassAdmin) (*ecocredit.MsgUpdateClassAdminResponse, error) { + classInfo, err := s.classInfoStore.GetByName(ctx, req.ClassId) + if err != nil { + return nil, err + } + if classInfo.Admin != req.Admin { + return nil, sdkerrors.ErrUnauthorized.Wrapf("expected admin %s, got %s", classInfo.Admin, req.Admin) + } + classInfo.Admin = req.NewAdmin + if err = s.classInfoStore.Update(ctx, classInfo); err != nil { + return nil, err + } + return &ecocredit.MsgUpdateClassAdminResponse{}, err +} + +func (s serverImpl) UpdateClassIssuers(ctx context.Context, req *ecocredit.MsgUpdateClassIssuers) (*ecocredit.MsgUpdateClassIssuersResponse, error) { + class, err := s.classInfoStore.GetByName(ctx, req.ClassId) + if err != nil { + return nil, err + } + if class.Admin != req.Admin { + return nil, sdkerrors.ErrUnauthorized.Wrapf("expected admin %s, got %s", class.Admin, req.Admin) + } + + // delete the old issuers + if err = s.classIssuerStore.DeleteBy(ctx, ecocreditv1beta1.ClassIssuerClassIdIssuerIndexKey{}.WithClassId(class.Name)); err != nil { + return nil, err + } + + // add the new issuers + for _, issuer := range req.Issuers { + if err = s.classIssuerStore.Insert(ctx, &ecocreditv1beta1.ClassIssuer{ + ClassId: req.ClassId, + Issuer: issuer, + }); err != nil { + return nil, err + } + } + return &ecocredit.MsgUpdateClassIssuersResponse{}, nil +} + +func (s serverImpl) UpdateClassMetadata(ctx context.Context, req *ecocredit.MsgUpdateClassMetadata) (*ecocredit.MsgUpdateClassMetadataResponse, error) { + classInfo, err := s.classInfoStore.GetByName(ctx, req.ClassId) + if err != nil { + return nil, err + } + if classInfo.Admin != req.Admin { + return nil, sdkerrors.ErrUnauthorized.Wrapf("expected admin %s, got %s", classInfo.Admin, req.Admin) + } + classInfo.Metadata = req.Metadata + if err = s.classInfoStore.Update(ctx, classInfo); err != nil { + return nil, err + } + return &ecocredit.MsgUpdateClassMetadataResponse{}, err +} + +// Sell creates new sell orders for credits +// TODO: update this function with ORM +func (s serverImpl) Sell(ctx context.Context, sell *ecocredit.MsgSell) (*ecocredit.MsgSellResponse, error) { + panic("implement me") + //ctx := types.UnwrapSDKContext(goCtx) + //owner := req.Owner + //store := ctx.KVStore(s.storeKey) + // + //ownerAddr, err := sdk.AccAddressFromBech32(owner) + //if err != nil { + // return nil, err + //} + // + //sellOrderIds := make([]uint64, len(req.Orders)) + // + //for i, order := range req.Orders { + // + // // verify expiration is in the future + // if order.Expiration != nil && order.Expiration.Before(ctx.BlockTime()) { + // return nil, sdkerrors.ErrInvalidRequest.Wrapf("expiration must be in the future: %s", order.Expiration) + // } + // + // err = verifyCreditBalance(store, ownerAddr, order.BatchDenom, order.Quantity) + // if err != nil { + // return nil, err + // } + // + // // TODO: Verify that AskPrice.Denom is in AllowAskDenom #624 + // + // orderID, err := s.createSellOrder(ctx, owner, order) + // if err != nil { + // return nil, err + // } + // + // sellOrderIds[i] = orderID + // err = ctx.EventManager().EmitTypedEvent(&ecocredit.EventSell{ + // OrderId: orderID, + // BatchDenom: order.BatchDenom, + // Quantity: order.Quantity, + // AskPrice: order.AskPrice, + // DisableAutoRetire: order.DisableAutoRetire, + // Expiration: order.Expiration, + // }) + // if err != nil { + // return nil, err + // } + // + // ctx.GasMeter().ConsumeGas(gasCostPerIteration, "create sell order") + //} + // + //return &ecocredit.MsgSellResponse{SellOrderIds: sellOrderIds}, nil +} + +// TODO: implement this with ORM +func (s serverImpl) createSellOrder(ctx types.Context, owner string, o *ecocredit.MsgSell_Order) (uint64, error) { + panic("impl me!") + //orderID := s.sellOrderTable.Sequence().PeekNextVal(ctx) + //_, err := s.sellOrderTable.Create(ctx, &ecocredit.SellOrder{ + // Owner: owner, + // OrderId: orderID, + // BatchDenom: o.BatchDenom, + // Quantity: o.Quantity, + // AskPrice: o.AskPrice, + // DisableAutoRetire: o.DisableAutoRetire, + // Expiration: o.Expiration, + //}) + //return orderID, err +} + +// UpdateSellOrders updates existing sell orders for credits +// TODO: impl with ORM +func (s serverImpl) UpdateSellOrders(ctx context.Context, orders *ecocredit.MsgUpdateSellOrders) (*ecocredit.MsgUpdateSellOrdersResponse, error) { + panic("implement me") + //ctx := types.UnwrapSDKContext(goCtx) + //owner := req.Owner + //store := ctx.KVStore(s.storeKey) + // + //ownerAddr, err := sdk.AccAddressFromBech32(owner) + //if err != nil { + // return nil, err + //} + // + //for _, update := range req.Updates { + // + // // verify expiration is in the future + // if update.NewExpiration != nil && update.NewExpiration.Before(ctx.BlockTime()) { + // return nil, sdkerrors.ErrInvalidRequest.Wrapf("expiration must be in the future: %s", update.NewExpiration) + // } + // + // sellOrder, err := s.getSellOrder(ctx, update.SellOrderId) + // if err != nil { + // return nil, ecocredit.ErrInvalidSellOrder.Wrapf("sell order id %d not found", update.SellOrderId) + // } + // + // if req.Owner != sellOrder.Owner { + // return nil, sdkerrors.ErrUnauthorized.Wrapf("signer is not the owner of sell order id %d", update.SellOrderId) + // } + // + // // TODO: Verify that NewAskPrice.Denom is in AllowAskDenom #624 + // + // err = verifyCreditBalance(store, ownerAddr, sellOrder.BatchDenom, update.NewQuantity) + // if err != nil { + // return nil, err + // } + // + // sellOrder.Quantity = update.NewQuantity + // sellOrder.AskPrice = update.NewAskPrice + // sellOrder.DisableAutoRetire = update.DisableAutoRetire + // sellOrder.Expiration = update.NewExpiration + // + // err = s.sellOrderTable.Update(ctx, sellOrder.OrderId, sellOrder) + // if err != nil { + // return nil, err + // } + // + // err = ctx.EventManager().EmitTypedEvent(&ecocredit.EventUpdateSellOrder{ + // Owner: owner, + // SellOrderId: sellOrder.OrderId, + // BatchDenom: sellOrder.BatchDenom, + // NewQuantity: sellOrder.Quantity, + // NewAskPrice: sellOrder.AskPrice, + // DisableAutoRetire: sellOrder.DisableAutoRetire, + // NewExpiration: sellOrder.Expiration, + // }) + // if err != nil { + // return nil, err + // } + // + // ctx.GasMeter().ConsumeGas(gasCostPerIteration, "update sell order") + //} + // + //return &ecocredit.MsgUpdateSellOrdersResponse{}, nil +} + +// Buy creates new buy orders for credits +func (s serverImpl) Buy(ctx context.Context, buy *ecocredit.MsgBuy) (*ecocredit.MsgBuyResponse, error) { + panic("implement me") + //ctx := types.UnwrapSDKContext(goCtx) + //sdkCtx := sdk.UnwrapSDKContext(goCtx) + //store := ctx.KVStore(s.storeKey) + //buyer := req.Buyer + // + //buyerAddr, err := sdk.AccAddressFromBech32(buyer) + //if err != nil { + // return nil, err + //} + // + //buyOrderIds := make([]uint64, len(req.Orders)) + // + //for i, order := range req.Orders { + // + // // verify expiration is in the future + // if order.Expiration != nil && order.Expiration.Before(ctx.BlockTime()) { + // return nil, sdkerrors.ErrInvalidRequest.Wrapf("expiration must be in the future: %s", order.Expiration) + // } + // + // balances := s.bankKeeper.SpendableCoins(sdkCtx, buyerAddr) + // bidPrice := order.BidPrice + // balanceAmount := balances.AmountOf(bidPrice.Denom) + // + // // TODO: Verify that bidPrice.Denom is in AllowAskDenom #624 + // + // // get decimal amount of credits desired for purchase + // creditsDesired, err := math.NewPositiveDecFromString(order.Quantity) + // if err != nil { + // return nil, err + // } + // + // // calculate the amount of coin to send for purchase + // coinToSend, err := getCoinNeeded(creditsDesired, bidPrice) + // if err != nil { + // return nil, err + // } + // + // // verify buyer has sufficient balance in coin + // if balanceAmount.LT(coinToSend.Amount) { + // return nil, sdkerrors.ErrInsufficientFunds.Wrapf("insufficient balance: got %s, needed at least: %s", balanceAmount.String(), coinToSend.Amount.String()) + // } + // + // switch order.Selection.Sum.(type) { + // case *ecocredit.MsgBuy_Order_Selection_SellOrderId: + // + // sellOrderId := order.Selection.GetSellOrderId() + // sellOrder, err := s.getSellOrder(ctx, sellOrderId) + // if err != nil { + // return nil, err + // } + // + // sellerAddr, err := sdk.AccAddressFromBech32(sellOrder.Owner) + // if err != nil { + // return nil, err + // } + // + // // verify bid price and ask price denoms match + // if bidPrice.Denom != sellOrder.AskPrice.Denom { + // return nil, sdkerrors.ErrInvalidRequest.Wrapf("bid price denom does not match ask price denom: got %s, expected: %s", bidPrice.Denom, sellOrder.AskPrice.Denom) + // } + // + // // verify bid price is greater than or equal to ask price + // if bidPrice.Amount.LT(sellOrder.AskPrice.Amount) { + // return nil, sdkerrors.ErrInvalidRequest.Wrapf("bid price too low: got %s, needed at least: %s", bidPrice.String(), sellOrder.AskPrice.String()) + // } + // + // // verify seller has sufficient balance in credits + // err = verifyCreditBalance(store, sellerAddr, sellOrder.BatchDenom, sellOrder.Quantity) + // if err != nil { + // return nil, ecocredit.ErrInvalidSellOrder.Wrap(err.Error()) + // } + // + // // get decimal amount of credits available for purchase + // creditsAvailable, err := math.NewDecFromString(sellOrder.Quantity) + // if err != nil { + // return nil, ecocredit.ErrInvalidSellOrder.Wrap(err.Error()) + // } + // + // creditsToReceive := creditsDesired + // + // // check if credits desired is more than credits available + // if creditsDesired.Cmp(creditsAvailable) == 1 { + // + // // error if partial fill disabled + // if order.DisablePartialFill { + // return nil, ecocredit.ErrInsufficientFunds.Wrap("sell order does not have sufficient credits to fill the buy order") + // } + // + // creditsToReceive = creditsAvailable + // + // // recalculate coinToSend if creditsToReceive is not creditsDesired + // coinToSend, err = getCoinNeeded(creditsToReceive, bidPrice) + // if err != nil { + // return nil, err + // } + // } + // + // // send coin to the seller account + // err = s.bankKeeper.SendCoins(sdkCtx, buyerAddr, sellerAddr, sdk.Coins{coinToSend}) + // if err != nil { + // return nil, err + // } + // + // // error if auto-retire is required for given sell order + // if !sellOrder.DisableAutoRetire && order.DisableAutoRetire { + // return nil, ecocredit.ErrInvalidBuyOrder.Wrapf("auto-retire is required for sell order %d", sellOrder.OrderId) + // } + // + // // error if auto-retire is required and missing location + // if !sellOrder.DisableAutoRetire && order.RetirementLocation == "" { + // return nil, ecocredit.ErrInvalidBuyOrder.Wrapf("retirement location is required for sell order %d", sellOrder.OrderId) + // } + // + // // declare credit for send message + // credit := &ecocredit.MsgSend_SendCredits{ + // BatchDenom: sellOrder.BatchDenom, + // } + // + // // set tradable or retired amount depending on auto-retire + // if sellOrder.DisableAutoRetire && order.DisableAutoRetire { + // credit.RetiredAmount = "0" + // credit.TradableAmount = creditsToReceive.String() + // } else { + // credit.RetiredAmount = creditsToReceive.String() + // credit.RetirementLocation = order.RetirementLocation + // credit.TradableAmount = "0" + // } + // + // // send credits to the buyer account + // err = s.sendEcocredits(ctx, credit, store, sellerAddr, buyerAddr) + // if err != nil { + // return nil, err + // } + // + // // get remaining credits in sell order + // creditsRemaining, err := creditsAvailable.Sub(creditsToReceive) + // if err != nil { + // return nil, err + // } + // + // if creditsRemaining.IsZero() { + // + // // delete sell order if no remaining credits + // if err := s.sellOrderTable.Delete(ctx, sellOrder.OrderId); err != nil { + // return nil, err + // } + // + // } else { + // sellOrder.Quantity = creditsRemaining.String() + // + // // update sell order quantity with remaining credits + // err = s.sellOrderTable.Update(ctx, sellOrder.OrderId, sellOrder) + // if err != nil { + // return nil, err + // } + // } + // + // // TODO: do we want to store a direct buy order? #623 + // buyOrderID := s.buyOrderTable.Sequence().NextVal(ctx) + // buyOrderIds[i] = buyOrderID + // + // err = ctx.EventManager().EmitTypedEvent(&ecocredit.EventBuyOrderCreated{ + // BuyOrderId: buyOrderID, + // SellOrderId: sellOrderId, + // Quantity: order.Quantity, + // BidPrice: order.BidPrice, + // DisableAutoRetire: order.DisableAutoRetire, + // DisablePartialFill: order.DisablePartialFill, + // RetirementLocation: order.RetirementLocation, + // Expiration: order.Expiration, + // }) + // if err != nil { + // return nil, err + // } + // + // err = ctx.EventManager().EmitTypedEvent(&ecocredit.EventBuyOrderFilled{ + // BuyOrderId: buyOrderID, + // SellOrderId: sellOrderId, + // BatchDenom: sellOrder.BatchDenom, + // Quantity: creditsToReceive.String(), + // TotalPrice: &coinToSend, + // }) + // if err != nil { + // return nil, err + // } + // + // // TODO: implement processing for filter option #623 + // //case *ecocredit.MsgBuy_Order_Selection_Filter: + // + // default: + // return nil, sdkerrors.ErrInvalidRequest + // } + // + // ctx.GasMeter().ConsumeGas(gasCostPerIteration, "create buy order") + //} + // + //return &ecocredit.MsgBuyResponse{BuyOrderIds: buyOrderIds}, nil +} + +// TODO: impl with ORM +func (s serverImpl) createBuyOrder(ctx types.Context, buyer string, o *ecocredit.MsgBuy_Order) (uint64, error) { + panic("impl me!") + //orderID := s.buyOrderTable.Sequence().PeekNextVal(ctx) + //selection := ecocredit.BuyOrder_Selection{ + // Sum: &ecocredit.BuyOrder_Selection_SellOrderId{ + // SellOrderId: o.Selection.GetSellOrderId(), + // }, + //} + //_, err := s.buyOrderTable.Create(ctx, &ecocredit.BuyOrder{ + // Buyer: buyer, + // BuyOrderId: orderID, + // Selection: &selection, + // Quantity: o.Quantity, + // BidPrice: o.BidPrice, + // DisableAutoRetire: o.DisableAutoRetire, + // DisablePartialFill: o.DisablePartialFill, + // Expiration: o.Expiration, + //}) + //return orderID, err +} + +// AllowAskDenom adds a new ask denom +// TODO: impl with ORM +func (s serverImpl) AllowAskDenom(ctx context.Context, denom *ecocredit.MsgAllowAskDenom) (*ecocredit.MsgAllowAskDenomResponse, error) { + panic("implement me") + // ctx := types.UnwrapSDKContext(goCtx) + // + //rootAddress := s.accountKeeper.GetModuleAddress(govtypes.ModuleName).String() + // + //if req.RootAddress != rootAddress { + // return nil, sdkerrors.ErrUnauthorized.Wrapf("root address must be governance module address, got: %s, expected: %s", req.RootAddress, rootAddress) + //} + // + //err := s.askDenomTable.Create(ctx, &ecocredit.AskDenom{ + // Denom: req.Denom, + // DisplayDenom: req.DisplayDenom, + // Exponent: req.Exponent, + //}) + //if err != nil { + // return nil, err + //} + // + //err = ctx.EventManager().EmitTypedEvent(&ecocredit.EventAllowAskDenom{ + // Denom: req.Denom, + // DisplayDenom: req.DisplayDenom, + // Exponent: req.Exponent, + //}) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.MsgAllowAskDenomResponse{}, nil +} + +func (s serverImpl) CreateBasket(ctx context.Context, basket *ecocredit.MsgCreateBasket) (*ecocredit.MsgCreateBasketResponse, error) { + panic("implement me") +} + +func (s serverImpl) AddToBasket(ctx context.Context, basket *ecocredit.MsgAddToBasket) (*ecocredit.MsgAddToBasketResponse, error) { + panic("implement me") +} + +func (s serverImpl) TakeFromBasket(ctx context.Context, basket *ecocredit.MsgTakeFromBasket) (*ecocredit.MsgTakeFromBasketResponse, error) { + panic("implement me") +} + +func (s serverImpl) PickFromBasket(ctx context.Context, basket *ecocredit.MsgPickFromBasket) (*ecocredit.MsgPickFromBasketResponse, error) { + panic("implement me") +} + +// ------- UTILITIES ------ + +// Checks if the given address is in the allowlist of credit class creators +func (s serverImpl) isCreatorAllowListed(ctx types.Context, allowlist []string, designer sdk.Address) bool { + for _, addr := range allowlist { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "credit class creators allowlist") + allowListedAddr, _ := sdk.AccAddressFromBech32(addr) + if designer.Equals(allowListedAddr) { + return true + } + } + return false +} + +// AssertClassIssuer makes sure that the issuer is part of issuers of given classID. +// Returns ErrUnauthorized otherwise. +func (s serverImpl) assertClassIssuer(goCtx context.Context, classID, issuer string) error { + it, err := s.classIssuerStore.List(goCtx, ecocreditv1beta1.ClassIssuerClassIdIssuerIndexKey{}.WithClassId(classID)) + if err != nil { + return err + } + + defer it.Close() + for it.Next() { + v, err := it.Value() + if err != nil { + return err + } + if v.Issuer == issuer { + return nil + } + } + return sdkerrors.ErrUnauthorized.Wrapf("%s is not an issuer for class %s", issuer, classID) +} + +func (s serverImpl) genProjectID(ctx context.Context, classRowID uint64, classID string) (string, error) { + var nextID uint64 + projectSeqNo, err := s.projectSeqStore.Get(ctx, classRowID) + switch err { + case ormerrors.NotFound: + nextID = 1 + case nil: + nextID = projectSeqNo.NextProjectId + default: + return "", err + } + + if err = s.projectSeqStore.Save(ctx, &ecocreditv1beta1.ProjectSequence{ + ClassId: classRowID, + NextProjectId: nextID + 1, + }); err != nil { + return "", err + } + + return ecocredit.FormatProjectID(classID, nextID), nil +} + +func (s serverImpl) getBatchSeqNo(ctx context.Context, projectID string) (uint64, error) { + var seq uint64 + batchSeq, err := s.batchSeqStore.Get(ctx, projectID) + + switch err { + case ormerrors.NotFound: + seq = 1 + case nil: + seq = batchSeq.NextBatchId + default: + return 0, err + } + if err = s.batchSeqStore.Save(ctx, &ecocreditv1beta1.BatchSequence{ + ProjectId: projectID, + NextBatchId: seq + 1, + }); err != nil { + return 0, err + } + + return seq, err +} + +func (s serverImpl) sendEcocredits(ctx context.Context, credit *ecocredit.MsgSend_SendCredits, to, from sdk.AccAddress) error { + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, credit.BatchDenom) + if err != nil { + return err + } + creditType, err := s.getCreditTypeFromBatchDenom(ctx, batch.BatchDenom) + if err != nil { + return err + } + precision := creditType.Precision + + batchSupply, err := s.batchSupplyStore.Get(ctx, batch.Id) + if err != nil { + return err + } + fromBalance, err := s.batchBalanceStore.Get(ctx, from, batch.Id) + if err != nil { + if err == ormerrors.NotFound { + return ecocredit.ErrInsufficientFunds.Wrapf("you do not have any credits from batch %s", batch.BatchDenom) + } + return err + } + + toBalance, err := s.batchBalanceStore.Get(ctx, to, batch.Id) + if err != nil { + if err == ormerrors.NotFound { + toBalance = &ecocreditv1beta1.BatchBalance{ + Address: to, + BatchId: batch.Id, + Tradable: "0", + Retired: "0", + } + } else { + return err + } + } + decs, err := getNonNegativeFixedDecs(precision, toBalance.Tradable, toBalance.Retired, fromBalance.Tradable, fromBalance.Retired, credit.TradableAmount, credit.RetiredAmount, batchSupply.TradableAmount, batchSupply.RetiredAmount) + if err != nil { + return err + } + toTradableBalance, toRetiredBalance, + fromTradableBalance, fromRetiredBalance, + sendAmtTradable, sendAmtRetired, + batchSupplyTradable, batchSupplyRetired := decs[0], decs[1], decs[2], decs[3], decs[4], decs[5], decs[6], decs[7] + + if !sendAmtTradable.IsZero() { + fromTradableBalance, err = math.SafeSubBalance(fromTradableBalance, sendAmtTradable) + if err != nil { + return err + } + toTradableBalance, err = toTradableBalance.Add(sendAmtTradable) + if err != nil { + return err + } + } + + didRetire := false + if !sendAmtRetired.IsZero() { + didRetire = true + fromTradableBalance, err = math.SafeSubBalance(fromTradableBalance, sendAmtRetired) + if err != nil { + return err + } + toRetiredBalance, err = toRetiredBalance.Add(sendAmtRetired) + if err != nil { + return err + } + batchSupplyRetired, err = batchSupplyRetired.Add(sendAmtRetired) + if err != nil { + return err + } + batchSupplyTradable, err = batchSupplyTradable.Sub(sendAmtRetired) + if err != nil { + return err + } + } + // update the "to" balance + if err := s.batchBalanceStore.Save(ctx, &ecocreditv1beta1.BatchBalance{ + Address: to, + BatchId: batch.Id, + Tradable: toTradableBalance.String(), + Retired: toRetiredBalance.String(), + }); err != nil { + return err + } + + // update the "from" balance + if err := s.batchBalanceStore.Update(ctx, &ecocreditv1beta1.BatchBalance{ + Address: from, + BatchId: batch.Id, + Tradable: fromTradableBalance.String(), + Retired: fromRetiredBalance.String(), + }); err != nil { + return err + } + // update the "retired" balance only if credits were retired + if didRetire { + if err := s.batchSupplyStore.Update(ctx, &ecocreditv1beta1.BatchSupply{ + BatchId: batch.Id, + TradableAmount: batchSupplyTradable.String(), + RetiredAmount: batchSupplyRetired.String(), + CancelledAmount: batchSupply.CancelledAmount, + }); err != nil { + return err + } + if err = sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(&ecocredit.EventRetire{ + Retirer: to.String(), + BatchDenom: credit.BatchDenom, + Amount: sendAmtRetired.String(), + Location: credit.RetirementLocation, + }); err != nil { + return err + } + } + return nil +} + +func getNonNegativeFixedDecs(precision uint32, decimals ...string) ([]math.Dec, error) { + decs := make([]math.Dec, len(decimals)) + for i, decimal := range decimals { + dec, err := math.NewNonNegativeFixedDecFromString(decimal, precision) + if err != nil { + return nil, err + } + decs[i] = dec + } + return decs, nil +} + +func (s serverImpl) getCreditTypeFromBatchDenom(ctx context.Context, denom string) (ecocredit.CreditType, error) { + classId := ecocredit.GetClassIdFromBatchDenom(denom) + classInfo, err := s.classInfoStore.GetByName(ctx, classId) + if err != nil { + return ecocredit.CreditType{}, err + } + return s.getCreditType(sdk.UnwrapSDKContext(ctx), classInfo.CreditType) +} + +func (s serverImpl) getClassSequenceNo(ctx context.Context, ctype string) (uint64, error) { + var seq uint64 + classSeq, err := s.classSeqStore.Get(ctx, ctype) + switch err { + case nil: + seq = classSeq.NextClassId + case ormerrors.NotFound: + seq = 1 + default: + return 0, err + } + err = s.classSeqStore.Save(ctx, &ecocreditv1beta1.ClassSequence{ + CreditType: ctype, + NextClassId: seq + 1, + }) + return seq, err +} + +func (s serverImpl) getCreditType(ctx sdk.Context, creditTypeName string) (ecocredit.CreditType, error) { + creditTypes := s.getAllCreditTypes(ctx) + creditTypeName = ecocredit.NormalizeCreditTypeName(creditTypeName) + for _, creditType := range creditTypes { + // credit type name's stored via params have enforcement on normalization, so we can be sure they will already + // be normalized here. + if creditType.Name == creditTypeName { + return *creditType, nil + } + } + return ecocredit.CreditType{}, sdkerrors.ErrInvalidType.Wrapf("%s is not a valid credit type", creditTypeName) +} + +func (s serverImpl) getAllCreditTypes(ctx sdk.Context) []*ecocredit.CreditType { + var params ecocredit.Params + s.paramSpace.GetParamSet(ctx, ¶ms) + return params.CreditTypes +} + +func (s serverImpl) getCreditClassFee(ctx sdk.Context) sdk.Coins { + var params ecocredit.Params + s.paramSpace.GetParamSet(ctx, ¶ms) + return params.CreditClassFee +} + +func (s serverImpl) chargeCreditClassFee(ctx sdk.Context, creatorAddr sdk.AccAddress) error { + creditClassFee := s.getCreditClassFee(ctx) + + // Move the fee to the ecocredit module's account + err := s.bankKeeper.SendCoinsFromAccountToModule(ctx, creatorAddr, ecocredit.ModuleName, creditClassFee) + if err != nil { + return err + } + + // Burn the coins + // TODO: Update this implementation based on the discussion at + // https://github.com/regen-network/regen-ledger/issues/351 + err = s.bankKeeper.BurnCoins(ctx, ecocredit.ModuleName, creditClassFee) + if err != nil { + return err + } + + return nil +} diff --git a/x/ecocredit/server/core/operations.go b/x/ecocredit/server/core/operations.go new file mode 100644 index 0000000000..a6e5722ab6 --- /dev/null +++ b/x/ecocredit/server/core/operations.go @@ -0,0 +1,20 @@ +package core + +import ( + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +) + +// WeightedOperations returns all the ecocredit module operations with their respective weights. +// TODO: sim refactor PR +func (s serverImpl) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + //key := s.storeKey.(servermodule.RootModuleKey) + //queryClient := ecocredit.NewQueryClient(key) + // + //return simulation.WeightedOperations( + // simState.AppParams, simState.Cdc, + // s.accountKeeper, s.bankKeeper, + // queryClient, + //) + return nil +} diff --git a/x/ecocredit/server/core/query_server.go b/x/ecocredit/server/core/query_server.go new file mode 100644 index 0000000000..5e0b0800d8 --- /dev/null +++ b/x/ecocredit/server/core/query_server.go @@ -0,0 +1,579 @@ +package core + +import ( + "context" + queryv1beta1 "github.com/cosmos/cosmos-sdk/api/cosmos/base/query/v1beta1" + "github.com/cosmos/cosmos-sdk/orm/model/ormlist" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" + ecocreditv1beta1 "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1beta1" + "github.com/regen-network/regen-ledger/x/ecocredit" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Classes queries for all credit classes with pagination. +func (s serverImpl) Classes(ctx context.Context, request *ecocredit.QueryClassesRequest) (*ecocredit.QueryClassesResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if request.Pagination == nil { + request.Pagination = &query.PageRequest{} + } + p := request.Pagination + it, err := s.classInfoStore.List(ctx, &ecocreditv1beta1.ClassInfoPrimaryKey{}, ormlist.Paginate(&queryv1beta1.PageRequest{ + Key: p.Key, + Offset: p.Offset, + Limit: p.Limit, + CountTotal: p.CountTotal, + Reverse: p.Reverse, + })) + if err != nil { + return nil, err + } + infos := make([]*ecocredit.ClassInfo, 0) + for it.Next() { + info, err := it.Value() + if err != nil { + return nil, err + } + infos = append(infos, &ecocredit.ClassInfo{ + ClassId: info.Name, + Admin: info.Admin, + Metadata: info.Metadata, + CreditTypeName: info.CreditType, + }) + } + return nil, err +} + +// ClassInfo queries for information on a credit class. +func (s serverImpl) ClassInfo(ctx context.Context, request *ecocredit.QueryClassInfoRequest) (*ecocredit.QueryClassInfoResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if err := ecocredit.ValidateClassID(request.ClassId); err != nil { + return nil, err + } + classInfo, err := s.classInfoStore.GetByName(ctx, request.ClassId) + if err != nil { + return nil, err + } + + issuers := make([]string, 0) + it, err := s.classIssuerStore.List(ctx, ecocreditv1beta1.ClassIssuerClassIdIssuerIndexKey{}.WithClassId(request.ClassId)) + if err != nil { + return nil, err + } + for it.Next() { + val, err := it.Value() + if err != nil { + return nil, err + } + issuers = append(issuers, val.Issuer) + } + + return &ecocredit.QueryClassInfoResponse{Info: &ecocredit.ClassInfo{ + ClassId: request.ClassId, + Admin: classInfo.Admin, + Metadata: classInfo.Metadata, + CreditTypeName: classInfo.CreditType, + }}, nil +} + +func (s serverImpl) ClassIssuers(ctx context.Context, request *ecocredit.QueryClassIssuersRequest) (*ecocredit.QueryClassIssuersResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if request.Pagination == nil { + request.Pagination = &query.PageRequest{} + } + p := request.Pagination + if err := ecocredit.ValidateClassID(request.ClassId); err != nil { + return nil, err + } + + it, err := s.classIssuerStore.List(ctx, ecocreditv1beta1.ClassIssuerClassIdIssuerIndexKey{}.WithClassId(request.ClassId), ormlist.Paginate(&queryv1beta1.PageRequest{ + Key: p.Key, + Offset: p.Offset, + Limit: p.Limit, + CountTotal: p.CountTotal, + Reverse: p.Reverse, + })) + if err != nil { + return nil, err + } + + issuers := make([]string, 0) + for it.Next() { + issuer, err := it.Value() + if err != nil { + return nil, err + } + issuers = append(issuers, issuer.Issuer) + } + pr := it.PageResponse() + + return &ecocredit.QueryClassIssuersResponse{ + Issuers: issuers, + Pagination: &query.PageResponse{ + NextKey: pr.NextKey, + Total: pr.Total, + }, + }, nil +} + +// Projects queries projects of a given credit batch. +func (s serverImpl) Projects(ctx context.Context, request *ecocredit.QueryProjectsRequest) (*ecocredit.QueryProjectsResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if request.Pagination == nil { + request.Pagination = &query.PageRequest{} + } + p := request.Pagination + cInfo, err := s.classInfoStore.GetByName(ctx, request.ClassId) + if err != nil { + return nil, err + } + it, err := s.projectInfoStore.List(ctx, ecocreditv1beta1.ProjectInfoClassIdNameIndexKey{}.WithClassId(cInfo.Id), ormlist.Paginate(&queryv1beta1.PageRequest{ + Key: p.Key, + Offset: p.Offset, + Limit: p.Limit, + CountTotal: p.CountTotal, + Reverse: p.Reverse, + })) + if err != nil { + return nil, err + } + projectInfos := make([]*ecocredit.ProjectInfo, 0) + for it.Next() { + info, err := it.Value() + if err != nil { + return nil, err + } + classInfo, err := s.classInfoStore.Get(ctx, info.ClassId) + if err != nil { + return nil, err + } + projectInfos = append(projectInfos, &ecocredit.ProjectInfo{ + ProjectId: info.Name, + ClassId: classInfo.Name, + Issuer: info.Issuer, + ProjectLocation: info.ProjectLocation, + Metadata: info.Metadata, + }) + } + pg := it.PageResponse() + return &ecocredit.QueryProjectsResponse{ + Projects: projectInfos, + Pagination: &query.PageResponse{ + NextKey: pg.NextKey, + Total: pg.Total, + }, + }, nil +} + +func (s serverImpl) ProjectInfo(ctx context.Context, request *ecocredit.QueryProjectInfoRequest) (*ecocredit.QueryProjectInfoResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if err := ecocredit.ValidateProjectID(request.ProjectId); err != nil { + return nil, err + } + pInfo, err := s.projectInfoStore.GetByName(ctx, request.ProjectId) + if err != nil { + return nil, err + } + + cInfo, err := s.classInfoStore.Get(ctx, pInfo.ClassId) + if err != nil { + return nil, err + } + + return &ecocredit.QueryProjectInfoResponse{Info: &ecocredit.ProjectInfo{ + ProjectId: request.ProjectId, + ClassId: cInfo.Name, + Issuer: pInfo.Issuer, + ProjectLocation: pInfo.ProjectLocation, + Metadata: pInfo.Metadata, + }}, nil +} + +// Batches queries for all batches in the given credit class. +func (s serverImpl) Batches(ctx context.Context, request *ecocredit.QueryBatchesRequest) (*ecocredit.QueryBatchesResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if request.Pagination == nil { + request.Pagination = &query.PageRequest{} + } + p := request.Pagination + project, err := s.projectInfoStore.GetByName(ctx, request.ProjectId) + if err != nil { + return nil, err + } + it, err := s.batchInfoStore.List(ctx, ecocreditv1beta1.BatchInfoProjectIdIndexKey{}.WithProjectId(project.Id), ormlist.Paginate(&queryv1beta1.PageRequest{ + Key: p.Key, + Offset: p.Offset, + Limit: p.Limit, + CountTotal: p.CountTotal, + Reverse: p.Reverse, + })) + if err != nil { + return nil, err + } + + projectName := request.ProjectId + batches := make([]*ecocredit.BatchInfo, 0) + for it.Next() { + batch, err := it.Value() + if err != nil { + return nil, err + } + start := batch.StartDate.AsTime() + end := batch.EndDate.AsTime() + batches = append(batches, &ecocredit.BatchInfo{ + ProjectId: projectName, + BatchDenom: batch.BatchDenom, + Metadata: batch.Metadata, + StartDate: &start, + EndDate: &end, + }) + } + pr := it.PageResponse() + return &ecocredit.QueryBatchesResponse{ + Batches: batches, + Pagination: &query.PageResponse{ + NextKey: pr.NextKey, + Total: pr.Total, + }, + }, nil +} + +// BatchInfo queries for information on a credit batch. +func (s serverImpl) BatchInfo(ctx context.Context, request *ecocredit.QueryBatchInfoRequest) (*ecocredit.QueryBatchInfoResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if err := ecocredit.ValidateDenom(request.BatchDenom); err != nil { + return nil, err + } + + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, request.BatchDenom) + if err != nil { + return nil, err + } + + project, err := s.projectInfoStore.Get(ctx, batch.ProjectId) + if err != nil { + return nil, err + } + + start := batch.StartDate.AsTime() + end := batch.EndDate.AsTime() + return &ecocredit.QueryBatchInfoResponse{ + Info: &ecocredit.BatchInfo{ + ProjectId: project.Name, + BatchDenom: request.BatchDenom, + Metadata: batch.Metadata, + StartDate: &start, + EndDate: &end, + }, + }, nil +} + +// Balance queries the balance (both tradable and retired) of a given credit +// batch for a given account. +func (s serverImpl) Balance(ctx context.Context, req *ecocredit.QueryBalanceRequest) (*ecocredit.QueryBalanceResponse, error) { + if req == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + if err := ecocredit.ValidateDenom(req.BatchDenom); err != nil { + return nil, err + } + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, req.BatchDenom) + if err != nil { + return nil, err + } + if batch == nil { + return nil, sdkerrors.ErrNotFound.Wrapf("batch with denom %s not found", req.BatchDenom) + } + addr, _ := sdk.AccAddressFromBech32(req.Account) + + balance, err := s.batchBalanceStore.Get(ctx, addr, batch.Id) + if err != nil { + return nil, err + } + if balance == nil { + return &ecocredit.QueryBalanceResponse{ + TradableAmount: "0", + RetiredAmount: "0", + }, nil + } + return &ecocredit.QueryBalanceResponse{ + TradableAmount: balance.Tradable, + RetiredAmount: balance.Retired, + }, nil +} + +// Supply queries the supply (tradable, retired, cancelled) of a given credit batch. +func (s serverImpl) Supply(ctx context.Context, request *ecocredit.QuerySupplyRequest) (*ecocredit.QuerySupplyResponse, error) { + if request == nil { + return nil, status.Errorf(codes.InvalidArgument, "empty request") + } + + if err := ecocredit.ValidateDenom(request.BatchDenom); err != nil { + return nil, err + } + + batch, err := s.batchInfoStore.GetByBatchDenom(ctx, request.BatchDenom) + if err != nil { + return nil, err + } + + supply, err := s.batchSupplyStore.Get(ctx, batch.Id) + if err != nil { + return nil, err + } + + return &ecocredit.QuerySupplyResponse{ + TradableSupply: supply.TradableAmount, + RetiredSupply: supply.RetiredAmount, + CancelledAmount: supply.CancelledAmount, + }, nil +} + +// CreditTypes queries the list of allowed types that credit classes can have. +func (s serverImpl) CreditTypes(ctx context.Context, _ *ecocredit.QueryCreditTypesRequest) (*ecocredit.QueryCreditTypesResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + creditTypes := s.getAllCreditTypes(sdkCtx) + return &ecocredit.QueryCreditTypesResponse{CreditTypes: creditTypes}, nil +} + +// Params queries the ecocredit module parameters. +func (s serverImpl) Params(ctx context.Context, _ *ecocredit.QueryParamsRequest) (*ecocredit.QueryParamsResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + var params ecocredit.Params + s.paramSpace.GetParamSet(sdkCtx, ¶ms) + return &ecocredit.QueryParamsResponse{Params: ¶ms}, nil +} + +// SellOrder queries for information about a sell order by its ID +// TODO: impl with ORM +func (s serverImpl) SellOrder(goCtx context.Context, request *ecocredit.QuerySellOrderRequest) (*ecocredit.QuerySellOrderResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //sellOrder, err := s.getSellOrder(ctx, request.SellOrderId) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QuerySellOrderResponse{SellOrder: sellOrder}, nil +} + +// SellOrders queries for all sell orders with pagination. +// TODO: impl with ORM +func (s serverImpl) SellOrders(goCtx context.Context, request *ecocredit.QuerySellOrdersRequest) (*ecocredit.QuerySellOrdersResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //ordersIter, err := s.sellOrderTable.PrefixScan(ctx, 1, math.MaxUint64) + //if err != nil { + // return nil, err + //} + // + //var orders []*ecocredit.SellOrder + //pageResp, err := orm.Paginate(ordersIter, request.Pagination, &orders) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QuerySellOrdersResponse{ + // SellOrders: orders, + // Pagination: pageResp, + //}, nil +} + +// SellOrdersByAddress queries for all sell orders by address with pagination. +// TODO: impl with ORM +func (s serverImpl) SellOrdersByAddress(goCtx context.Context, request *ecocredit.QuerySellOrdersByAddressRequest) (*ecocredit.QuerySellOrdersByAddressResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //addr, err := sdk.AccAddressFromBech32(request.Address) + //if err != nil { + // return nil, err + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //ordersIter, err := s.sellOrderByAddressIndex.GetPaginated(ctx, addr.Bytes(), request.Pagination) + //if err != nil { + // return nil, err + //} + // + //var orders []*ecocredit.SellOrder + //pageResp, err := orm.Paginate(ordersIter, request.Pagination, &orders) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QuerySellOrdersByAddressResponse{ + // SellOrders: orders, + // Pagination: pageResp, + //}, nil +} + +// SellOrdersByBatchDenom queries for all sell orders by address with pagination. +// TODO: impl with ORM +func (s serverImpl) SellOrdersByBatchDenom(goCtx context.Context, request *ecocredit.QuerySellOrdersByBatchDenomRequest) (*ecocredit.QuerySellOrdersByBatchDenomResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //if err := ecocredit.ValidateDenom(request.BatchDenom); err != nil { + // return nil, err + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //ordersIter, err := s.sellOrderByBatchDenomIndex.GetPaginated(ctx, request.BatchDenom, request.Pagination) + //if err != nil { + // return nil, err + //} + // + //var orders []*ecocredit.SellOrder + //pageResp, err := orm.Paginate(ordersIter, request.Pagination, &orders) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QuerySellOrdersByBatchDenomResponse{ + // SellOrders: orders, + // Pagination: pageResp, + //}, nil +} + +// BuyOrder queries for information about a buy order by its ID +// TODO: impl with ORM +func (s serverImpl) BuyOrder(goCtx context.Context, request *ecocredit.QueryBuyOrderRequest) (*ecocredit.QueryBuyOrderResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //buyOrder, err := s.getBuyOrder(ctx, request.BuyOrderId) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QueryBuyOrderResponse{BuyOrder: buyOrder}, nil +} + +// BuyOrders queries for all buy orders with pagination. +// TODO: impl with ORM +func (s serverImpl) BuyOrders(goCtx context.Context, request *ecocredit.QueryBuyOrdersRequest) (*ecocredit.QueryBuyOrdersResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //ordersIter, err := s.buyOrderTable.PrefixScan(ctx, 1, math.MaxUint64) + //if err != nil { + // return nil, err + //} + // + //var orders []*ecocredit.BuyOrder + //pageResp, err := orm.Paginate(ordersIter, request.Pagination, &orders) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QueryBuyOrdersResponse{ + // BuyOrders: orders, + // Pagination: pageResp, + //}, nil +} + +// BuyOrdersByAddress queries for all buy orders by address with pagination. +// TODO: impl with ORM +func (s serverImpl) BuyOrdersByAddress(goCtx context.Context, request *ecocredit.QueryBuyOrdersByAddressRequest) (*ecocredit.QueryBuyOrdersByAddressResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //addr, err := sdk.AccAddressFromBech32(request.Address) + //if err != nil { + // return nil, err + //} + // + //ordersIter, err := s.buyOrderByAddressIndex.GetPaginated(ctx, addr.Bytes(), request.Pagination) + //if err != nil { + // return nil, err + //} + // + //var orders []*ecocredit.BuyOrder + //pageResp, err := orm.Paginate(ordersIter, request.Pagination, &orders) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QueryBuyOrdersByAddressResponse{ + // BuyOrders: orders, + // Pagination: pageResp, + //}, nil +} + +// AllowedAskDenoms queries for all allowed ask denoms with pagination. +// TODO: impl with ORM +func (s serverImpl) AllowedAskDenoms(goCtx context.Context, request *ecocredit.QueryAllowedAskDenomsRequest) (*ecocredit.QueryAllowedAskDenomsResponse, error) { + panic("impl me!") + //if request == nil { + // return nil, status.Errorf(codes.InvalidArgument, "empty request") + //} + // + //ctx := types.UnwrapSDKContext(goCtx) + //denomsIter, err := s.askDenomTable.PrefixScan(ctx, nil, nil) + //if err != nil { + // return nil, err + //} + // + //var denoms []*ecocredit.AskDenom + //pageResp, err := orm.Paginate(denomsIter, request.Pagination, &denoms) + //if err != nil { + // return nil, err + //} + // + //return &ecocredit.QueryAllowedAskDenomsResponse{ + // AskDenoms: denoms, + // Pagination: pageResp, + //}, nil +} + +// TODO: baskets PR queries + +func (s serverImpl) Basket(ctx context.Context, request *ecocredit.QueryBasketRequest) (*ecocredit.QueryBasketResponse, error) { + panic("implement me") +} + +func (s serverImpl) Baskets(ctx context.Context, request *ecocredit.QueryBasketsRequest) (*ecocredit.QueryBasketsResponse, error) { + panic("implement me") +} + +func (s serverImpl) BasketCredits(ctx context.Context, request *ecocredit.QueryBasketCreditsRequest) (*ecocredit.QueryBasketCreditsResponse, error) { + panic("implement me") +} diff --git a/x/ecocredit/server/core/server.go b/x/ecocredit/server/core/server.go new file mode 100644 index 0000000000..d207c6ae60 --- /dev/null +++ b/x/ecocredit/server/core/server.go @@ -0,0 +1,82 @@ +package core + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/orm/model/ormdb" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + ecocreditv1beta1 "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1beta1" + "github.com/regen-network/regen-ledger/types/module/server" + "github.com/regen-network/regen-ledger/types/ormstore" + "github.com/regen-network/regen-ledger/x/ecocredit" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type serverImpl struct { + storeKey sdk.StoreKey + + paramSpace paramtypes.Subspace + bankKeeper ecocredit.BankKeeper + accountKeeper ecocredit.AccountKeeper + + db ormdb.ModuleDB + + creditTypeStore ecocreditv1beta1.CreditTypeStore + + batchInfoStore ecocreditv1beta1.BatchInfoStore + batchSupplyStore ecocreditv1beta1.BatchSupplyStore + batchBalanceStore ecocreditv1beta1.BatchBalanceStore + batchSeqStore ecocreditv1beta1.BatchSequenceStore + + projectInfoStore ecocreditv1beta1.ProjectInfoStore + projectSeqStore ecocreditv1beta1.ProjectSequenceStore + + classInfoStore ecocreditv1beta1.ClassInfoStore + classIssuerStore ecocreditv1beta1.ClassIssuerStore + classSeqStore ecocreditv1beta1.ClassSequenceStore +} + +var ecocreditSchema = ormdb.ModuleSchema{ + FileDescriptors: map[uint32]protoreflect.FileDescriptor{1: ecocreditv1beta1.File_regen_ecocredit_v1beta1_state_proto}, + Prefix: nil, +} + +func newServer(storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, + accountKeeper ecocredit.AccountKeeper, bankKeeper ecocredit.BankKeeper, cdc codec.Codec) serverImpl { + + s := serverImpl{ + storeKey: storeKey, + paramSpace: paramSpace, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + } + + db, err := ormstore.NewStoreKeyDB(ecocreditSchema, storeKey, ormdb.ModuleDBOptions{}) + if err != nil { + panic(err) + } + s.db = db + + stateStore, err := ecocreditv1beta1.NewStateStore(db) + if err != nil { + panic(err) + } + s.creditTypeStore, s.classInfoStore, s.classSeqStore, s.projectInfoStore, + s.projectSeqStore, s.batchInfoStore, s.batchSeqStore, s.batchBalanceStore, + s.batchSupplyStore, s.classIssuerStore = stateStore.CreditTypeStore(), stateStore.ClassInfoStore(), stateStore.ClassSequenceStore(), + stateStore.ProjectInfoStore(), stateStore.ProjectSequenceStore(), stateStore.BatchInfoStore(), + stateStore.BatchSequenceStore(), stateStore.BatchBalanceStore(), stateStore.BatchSupplyStore(), stateStore.ClassIssuerStore() + + return s +} + +func RegisterServices(configurator server.Configurator, paramSpace paramtypes.Subspace, accountKeeper ecocredit.AccountKeeper, + bankKeeper ecocredit.BankKeeper) ecocredit.MsgServer { + impl := newServer(configurator.ModuleKey(), paramSpace, accountKeeper, bankKeeper, configurator.Marshaler()) + ecocredit.RegisterMsgServer(configurator.MsgServer(), impl) + ecocredit.RegisterQueryServer(configurator.QueryServer(), impl) + configurator.RegisterGenesisHandlers(impl.InitGenesis, impl.ExportGenesis) + configurator.RegisterWeightedOperationsHandler(impl.WeightedOperations) + configurator.RegisterInvariantsHandler(impl.RegisterInvariants) + return impl +} diff --git a/x/ecocredit/server/store.go b/x/ecocredit/server/store.go deleted file mode 100644 index d53586c84e..0000000000 --- a/x/ecocredit/server/store.go +++ /dev/null @@ -1,42 +0,0 @@ -package server - -import ( - "github.com/cosmos/cosmos-sdk/orm/types/kv" - storetypes "github.com/cosmos/cosmos-sdk/types" -) - -type storeWrapper struct { - store storetypes.KVStore -} - -func (k storeWrapper) Set(key, value []byte) error { - k.store.Set(key, value) - return nil -} - -func (k storeWrapper) Delete(key []byte) error { - k.store.Delete(key) - return nil -} - -func (k storeWrapper) Get(key []byte) ([]byte, error) { - x := k.store.Get(key) - return x, nil -} - -func (k storeWrapper) Has(key []byte) (bool, error) { - x := k.store.Has(key) - return x, nil -} - -func (k storeWrapper) Iterator(start, end []byte) (kv.Iterator, error) { - x := k.store.Iterator(start, end) - return x, nil -} - -func (k storeWrapper) ReverseIterator(start, end []byte) (kv.Iterator, error) { - x := k.store.ReverseIterator(start, end) - return x, nil -} - -var _ kv.Store = &storeWrapper{}