Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into robert/configurator
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-zaremba committed Mar 7, 2022
2 parents bbef5cd + 667be6b commit 61b80ed
Show file tree
Hide file tree
Showing 9 changed files with 879 additions and 308 deletions.
1 change: 0 additions & 1 deletion api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
462 changes: 231 additions & 231 deletions api/regen/ecocredit/v1beta1/tx.pulsar.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions proto/regen/ecocredit/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ message MsgCreateClass {
// metadata is any arbitrary metadata to attached to the credit class.
bytes metadata = 3;

// credit_type_name describes the type of credit (e.g. "carbon", "biodiversity").
string credit_type_name = 4;
// credit_type_abbrev describes the abbreviation of a credit type (e.g. "C", "BIO").
string credit_type_abbrev = 4;
}

// MsgCreateClassResponse is the Msg/CreateClass response type.
Expand Down
165 changes: 165 additions & 0 deletions x/ecocredit/server/core/create_batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package core

import (
"context"
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
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"
"github.com/regen-network/regen-ledger/x/ecocredit/v1beta1"
"google.golang.org/protobuf/types/known/timestamppb"
)

// 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 (k Keeper) CreateBatch(ctx context.Context, req *v1beta1.MsgCreateBatch) (*v1beta1.MsgCreateBatchResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
projectID := req.ProjectId

projectInfo, err := k.stateStore.ProjectInfoStore().GetByName(ctx, projectID)
if err != nil {
return nil, err
}

classInfo, err := k.stateStore.ClassInfoStore().Get(ctx, projectInfo.ClassId)
if err != nil {
return nil, err
}

err = k.assertClassIssuer(ctx, projectInfo.ClassId, req.Issuer)
if err != nil {
return nil, err
}

batchSeqNo, err := k.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
}

startDate, endDate := timestamppb.New(req.StartDate.UTC()), timestamppb.New(req.EndDate.UTC())
rowID, err := k.stateStore.BatchInfoStore().InsertReturningID(ctx, &ecocreditv1beta1.BatchInfo{
ProjectId: projectInfo.Id,
BatchDenom: batchDenom,
Metadata: req.Metadata,
StartDate: startDate,
EndDate: endDate,
})
if err != nil {
return nil, err
}

var p ecocredit.Params
k.params.GetParamSet(sdkCtx, &p)
creditType, err := k.getCreditType(classInfo.CreditType, p.CreditTypes)
if err != nil {
return nil, err
}
maxDecimalPlaces := creditType.Precision

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 = sdkCtx.EventManager().EmitTypedEvent(&v1beta1.EventRetire{
Retirer: recipient.String(),
BatchDenom: batchDenom,
Amount: retired.String(),
Location: issuance.RetirementLocation,
}); err != nil {
return nil, err
}
}
if err = k.stateStore.BatchBalanceStore().Insert(ctx, &ecocreditv1beta1.BatchBalance{
Address: recipient,
BatchId: rowID,
Tradable: tradable.String(),
Retired: retired.String(),
}); err != nil {
return nil, err
}

if err = sdkCtx.EventManager().EmitTypedEvent(&v1beta1.EventReceive{
Recipient: recipient.String(),
BatchDenom: batchDenom,
RetiredAmount: retired.String(),
TradableAmount: tradable.String(),
}); err != nil {
return nil, err
}

sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "batch issuance")
}

if err = k.stateStore.BatchSupplyStore().Insert(ctx, &ecocreditv1beta1.BatchSupply{
BatchId: rowID,
TradableAmount: tradableSupply.String(),
RetiredAmount: retiredSupply.String(),
CancelledAmount: math.NewDecFromInt64(0).String(),
}); err != nil {
return nil, err
}

totalAmount, err := tradableSupply.Add(retiredSupply)
if err != nil {
return nil, err
}

if err = sdkCtx.EventManager().EmitTypedEvent(&v1beta1.EventCreateBatch{
ClassId: classInfo.Name,
BatchDenom: batchDenom,
Issuer: req.Issuer,
TotalAmount: totalAmount.String(),
StartDate: startDate.String(),
EndDate: endDate.String(),
ProjectLocation: projectInfo.ProjectLocation,
ProjectId: projectInfo.Name,
}); err != nil {
return nil, err
}

return &v1beta1.MsgCreateBatchResponse{BatchDenom: batchDenom}, nil
}

// getBatchSeqNo gets the batch sequence number
func (k Keeper) getBatchSeqNo(ctx context.Context, projectID string) (uint64, error) {
var seq uint64 = 1
batchSeq, err := k.stateStore.BatchSequenceStore().Get(ctx, projectID)
if err != nil {
if !ormerrors.IsNotFound(err) {
return 0, err
}
} else {
seq = batchSeq.NextBatchId
}

if err = k.stateStore.BatchSequenceStore().Save(ctx, &ecocreditv1beta1.BatchSequence{
ProjectId: projectID,
NextBatchId: seq + 1,
}); err != nil {
return 0, err
}

return seq, err
}
154 changes: 154 additions & 0 deletions x/ecocredit/server/core/create_batch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package core

import (
"context"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
ecocreditv1beta1 "github.com/regen-network/regen-ledger/api/regen/ecocredit/v1beta1"
"github.com/regen-network/regen-ledger/x/ecocredit"
"github.com/regen-network/regen-ledger/x/ecocredit/v1beta1"
"gotest.tools/v3/assert"
"testing"
"time"
)

func TestCreateBatch_Valid(t *testing.T) {
t.Parallel()
s := setupBase(t)
_, projectName := batchTestSetup(t, s.ctx, s.stateStore, s.addr)
_,_, addr2 := testdata.KeyTestPubAddr()

any := gomock.Any()
s.paramsKeeper.EXPECT().GetParamSet(any, any).Do(func(any interface{}, p *ecocredit.Params) {
p.AllowlistEnabled = false
p.CreditClassFee = types.NewCoins(types.NewInt64Coin("foo", 20))
p.CreditTypes = []*ecocredit.CreditType{{Name: "carbon", Abbreviation: "C", Unit: "tonne", Precision: 6}}
}).Times(1)

start, end := time.Now(), time.Now()
res, err := s.k.CreateBatch(s.ctx, &v1beta1.MsgCreateBatch{
Issuer: s.addr.String(),
ProjectId: "PRO",
Issuance: []*v1beta1.MsgCreateBatch_BatchIssuance{
{
Recipient: s.addr.String(),
TradableAmount: "10",
RetiredAmount: "5.3",
},
{
Recipient: addr2.String(),
TradableAmount: "2.4",
RetiredAmount: "3.4",
},
},
Metadata: nil,
StartDate: &start,
EndDate: &end,
})
totalTradable := "12.4"
totalRetired := "8.7"

// check the batch
batch, err := s.stateStore.BatchInfoStore().Get(s.ctx, 1)
assert.NilError(t, err, "unexpected error: %w", err)
assert.Equal(t, res.BatchDenom, batch.BatchDenom)

// check the supply was set
sup, err := s.stateStore.BatchSupplyStore().Get(s.ctx, 1)
assert.NilError(t, err)
assert.Equal(t, totalTradable, sup.TradableAmount, "got %s", sup.TradableAmount)
assert.Equal(t, totalRetired, sup.RetiredAmount, "got %s", sup.RetiredAmount)
assert.Equal(t, "0", sup.CancelledAmount, "got %s", sup.CancelledAmount)

// check balances were allocated
bal, err := s.stateStore.BatchBalanceStore().Get(s.ctx, s.addr, 1)
assert.NilError(t, err)
assert.Equal(t, "10", bal.Tradable)
assert.Equal(t, "5.3", bal.Retired)

bal2, err := s.stateStore.BatchBalanceStore().Get(s.ctx, addr2, 1)
assert.NilError(t, err)
assert.Equal(t, "2.4", bal2.Tradable)
assert.Equal(t, "3.4", bal2.Retired)

// check sequence number
seq, err := s.stateStore.BatchSequenceStore().Get(s.ctx, projectName)
assert.NilError(t, err)
assert.Equal(t, uint64(2), seq.NextBatchId)
}

func TestCreateBatch_BadPrecision(t *testing.T) {
t.Parallel()
s := setupBase(t)
batchTestSetup(t, s.ctx, s.stateStore, s.addr)

any := gomock.Any()
s.paramsKeeper.EXPECT().GetParamSet(any, any).Do(func(any interface{}, p *ecocredit.Params) {
p.AllowlistEnabled = false
p.CreditClassFee = types.NewCoins(types.NewInt64Coin("foo", 20))
p.CreditTypes = []*ecocredit.CreditType{{Name: "carbon", Abbreviation: "C", Unit: "tonne", Precision: 6}}
}).Times(1)

start, end := time.Now(), time.Now()
_, err := s.k.CreateBatch(s.ctx, &v1beta1.MsgCreateBatch{
Issuer: s.addr.String(),
ProjectId: "PRO",
Issuance: []*v1beta1.MsgCreateBatch_BatchIssuance{
{
Recipient: s.addr.String(),
TradableAmount: "10.1234567891111",
},
},
Metadata: nil,
StartDate: &start,
EndDate: &end,
})
assert.ErrorContains(t, err, "exceeds maximum decimal places")
}

func TestCreateBatch_UnauthorizedIssuer(t *testing.T) {
t.Parallel()
s := setupBase(t)
batchTestSetup(t, s.ctx, s.stateStore, s.addr)
_, err := s.k.CreateBatch(s.ctx, &v1beta1.MsgCreateBatch{
ProjectId: "PRO",
Issuer: "FooBarBaz",
})
assert.ErrorContains(t, err, "is not an issuer for the class")
}

func TestCreateBatch_ProjectNotFound(t *testing.T) {
t.Parallel()
s := setupBase(t)

_, err := s.k.CreateBatch(s.ctx, &v1beta1.MsgCreateBatch{
ProjectId: "none",
})
assert.ErrorContains(t, err, "not found")
}

// creates a class "C01", with a single class issuer, and a project "PRO"
func batchTestSetup(t *testing.T, ctx context.Context, ss ecocreditv1beta1.StateStore, addr types.AccAddress) (className, projectName string){
className, projectName = "C01", "PRO"
cid, err := ss.ClassInfoStore().InsertReturningID(ctx, &ecocreditv1beta1.ClassInfo{
Name: className,
Admin: addr,
Metadata: nil,
CreditType: "C",
})
assert.NilError(t, err)
err = ss.ClassIssuerStore().Insert(ctx, &ecocreditv1beta1.ClassIssuer{
ClassId: cid,
Issuer: addr,
})
assert.NilError(t, err)
_, err = ss.ProjectInfoStore().InsertReturningID(ctx, &ecocreditv1beta1.ProjectInfo{
Name: projectName,
ClassId: 1,
ProjectLocation: "",
Metadata: nil,
})
assert.NilError(t, err)
return
}
Loading

0 comments on commit 61b80ed

Please sign in to comment.