Skip to content

Commit 270021b

Browse files
authored
feat: Improve burnAllCredits (#53)
* Iterate lockups instead of all balances in burnAllCredits, improve telemetry in resetAllCredits * Update TestBurnAllCredits, improve TestResetAllCredits
1 parent a292298 commit 270021b

4 files changed

Lines changed: 94 additions & 73 deletions

File tree

app/params/params.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const (
1313
CreditDenom = "credit"
1414
CreditName = "Source Credit"
1515
CreditSymbol = "CREDIT"
16-
CreditDescription = "Credit is the utility token for access services on SourceHub. Non transferrable."
16+
CreditDescription = "Credit is the utility token for access services on SourceHub. Non-transferable."
1717
CreditFeeMultiplier = 10
1818

1919
DefaultBondDenom = MicroOpenDenom

x/tier/keeper/credit.go

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -119,47 +119,47 @@ func (k *Keeper) burnAllCredits(ctx context.Context, epochNumber int64) (err err
119119
)
120120
}()
121121

122-
// We iterate through all the balances to find and burn the credits. It could be
123-
// improved to iterate through the lockup records because credits are NOT transferrable.
122+
// Track unused credits to calculate credit utilization
124123
unusedCredits := math.ZeroInt()
125-
cb := func(addr sdk.AccAddress, coin sdk.Coin) (stop bool) {
126-
if coin.Denom != appparams.MicroCreditDenom {
127-
return false
128-
}
129124

125+
// Iterating over the lockup records directly is safe because credits are non-transferable.
126+
cb := func(delAddr sdk.AccAddress, valAddr sdk.ValAddress, lockup types.Lockup) {
127+
coin := k.bankKeeper.GetBalance(ctx, delAddr, appparams.MicroCreditDenom)
128+
// Return if ucredit amount is not positive
129+
if !coin.Amount.IsPositive() {
130+
return
131+
}
130132
coins := sdk.NewCoins(coin)
131-
132-
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, coins)
133+
// Send ucredits from delegator address to the tier module account
134+
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delAddr, types.ModuleName, coins)
133135
if err != nil {
134-
err = errorsmod.Wrapf(err, "send %s ucredit from %s to module", coins, addr)
135-
return true
136+
err = errorsmod.Wrapf(err, "send %s ucredit from %s to module", coins, delAddr)
137+
return
136138
}
137-
139+
// Burn ucredits from the tier module account
138140
err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins)
139141
if err != nil {
140142
err = errorsmod.Wrapf(err, "burn %s ucredit", coins)
141-
return true
143+
return
142144
}
143-
144-
unusedCredits.Add(coin.Amount)
145-
146-
return false
145+
// Update the unused credit amount
146+
unusedCredits = unusedCredits.Add(coin.Amount)
147147
}
148148

149-
k.bankKeeper.IterateAllBalances(ctx, cb)
149+
k.mustIterateLockups(ctx, cb)
150150

151151
totalCredits := k.getTotalCreditAmount(ctx)
152152
if totalCredits.IsPositive() {
153153
creditUtilization, err := unusedCredits.ToLegacyDec().Quo(totalCredits.ToLegacyDec()).Float64()
154154
if err != nil {
155155
return errorsmod.Wrap(err, "calculate credit utilization")
156156
}
157-
158157
// Update credit utilization gauge
159158
telemetry.ModuleSetGauge(
160159
types.ModuleName,
161160
float32(creditUtilization),
162-
metrics.CreditUtilization, fmt.Sprintf("%s_%d", metrics.Epoch, epochNumber),
161+
metrics.CreditUtilization,
162+
fmt.Sprintf("%s_%d", metrics.Epoch, epochNumber),
163163
)
164164
}
165165

@@ -170,13 +170,24 @@ func (k *Keeper) burnAllCredits(ctx context.Context, epochNumber int64) (err err
170170
}
171171

172172
// resetAllCredits resets all the credits in the system.
173-
func (k *Keeper) resetAllCredits(ctx context.Context) error {
174-
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), metrics.ResetAllCredits, metrics.Latency)
173+
func (k *Keeper) resetAllCredits(ctx context.Context, epochNumber int64) (err error) {
174+
start := time.Now()
175+
176+
defer func() {
177+
metrics.ModuleMeasureSinceWithCounter(
178+
types.ModuleName,
179+
metrics.ResetAllCredits,
180+
start,
181+
err,
182+
[]metrics.Label{
183+
metrics.NewLabel(metrics.Epoch, fmt.Sprintf("%d", epochNumber)),
184+
},
185+
)
186+
}()
175187

176-
// Reward to a delegator is calculated based on the total locked amount
177-
// to all validators. Since each lockup entry only records locked amount
178-
// for a single validator, we need to iterate through all the lockups to
179-
// calculate the total locked amount for each delegator.
188+
// Reward to a delegator is calculated based on the total locked amount to all validators.
189+
// Since each lockup entry only records locked amount for a single validator,
190+
// we need to iterate through all the lockups to calculate the total locked amount for each delegator.
180191
lockedAmts := make(map[string]math.Int)
181192

182193
cb := func(delAddr sdk.AccAddress, valAddr sdk.ValAddress, lockup types.Lockup) {
@@ -197,15 +208,15 @@ func (k *Keeper) resetAllCredits(ctx context.Context) error {
197208
for delStrAddr, amount := range lockedAmts {
198209
delAddr := sdk.MustAccAddressFromBech32(delStrAddr)
199210
credit := calculateCredit(rates, math.ZeroInt(), amount)
200-
err := k.mintCredit(ctx, delAddr, credit)
211+
err = k.mintCredit(ctx, delAddr, credit)
201212
if err != nil {
202213
return errorsmod.Wrapf(err, "mint %s ucredit to %s", credit, delAddr)
203214
}
204215
totalCredit.Add(credit)
205216
}
206217

207218
// Set total credit amount
208-
err := k.setTotalCreditAmount(ctx, totalCredit)
219+
err = k.setTotalCreditAmount(ctx, totalCredit)
209220
if err != nil {
210221
return errorsmod.Wrap(err, "set total credit amount")
211222
}

x/tier/keeper/credit_test.go

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
sdk "github.com/cosmos/cosmos-sdk/types"
99
"github.com/stretchr/testify/require"
1010

11+
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
1112
appparams "github.com/sourcenetwork/sourcehub/app/params"
1213
epochstypes "github.com/sourcenetwork/sourcehub/x/epochs/types"
1314
"github.com/sourcenetwork/sourcehub/x/tier/types"
@@ -364,26 +365,28 @@ func TestProratedCredit(t *testing.T) {
364365

365366
func TestBurnAllCredits(t *testing.T) {
366367
tests := []struct {
367-
name string
368-
creditBalances map[string]int64
369-
openBalances map[string]int64
370-
wantErr bool
368+
name string
369+
lockups map[string]int64
370+
openBalances map[string]int64
371+
wantErr bool
371372
}{
372373
{
373-
name: "Burn all credits successfully (single address)",
374-
creditBalances: map[string]int64{"source1wjj5v5rlf57kayyeskncpu4hwev25ty645p2et": 100},
375-
openBalances: map[string]int64{},
376-
wantErr: false,
374+
name: "Burn all credits successfully (single address)",
375+
lockups: map[string]int64{
376+
"source1wjj5v5rlf57kayyeskncpu4hwev25ty645p2et": 100,
377+
},
378+
openBalances: map[string]int64{},
379+
wantErr: false,
377380
},
378381
{
379-
name: "No addresses have credits",
380-
creditBalances: map[string]int64{},
381-
openBalances: map[string]int64{},
382-
wantErr: false,
382+
name: "No addresses have credits",
383+
lockups: map[string]int64{},
384+
openBalances: map[string]int64{},
385+
wantErr: false,
383386
},
384387
{
385388
name: "Multiple addresses with credits",
386-
creditBalances: map[string]int64{
389+
lockups: map[string]int64{
387390
"source1wjj5v5rlf57kayyeskncpu4hwev25ty645p2et": 50,
388391
"source1m4f5a896t7fzd9vc7pfgmc3fxkj8n24s68fcw9": 150,
389392
},
@@ -392,7 +395,7 @@ func TestBurnAllCredits(t *testing.T) {
392395
},
393396
{
394397
name: "Burn credits when addresses also hold uopen",
395-
creditBalances: map[string]int64{
398+
lockups: map[string]int64{
396399
"source1wjj5v5rlf57kayyeskncpu4hwev25ty645p2et": 80,
397400
"source1m4f5a896t7fzd9vc7pfgmc3fxkj8n24s68fcw9": 200,
398401
},
@@ -404,53 +407,54 @@ func TestBurnAllCredits(t *testing.T) {
404407
},
405408
}
406409

410+
valAddr, err := sdk.ValAddressFromBech32("sourcevaloper1cy0p47z24ejzvq55pu3lesxwf73xnrnd0pzkqm")
411+
require.NoError(t, err)
412+
407413
for _, tt := range tests {
408414
t.Run(tt.name, func(t *testing.T) {
409415
k, ctx := setupKeeper(t)
410416

411-
// Mint and distribute credits
412-
for addrStr, balance := range tt.creditBalances {
417+
err := k.SetParams(ctx, types.DefaultParams())
418+
require.NoError(t, err)
419+
420+
initializeValidator(t, k.GetStakingKeeper().(*stakingkeeper.Keeper), ctx, valAddr, math.NewInt(10_000_000))
421+
422+
// Add lockups
423+
for addrStr, lockupAmount := range tt.lockups {
413424
addr := sdk.MustAccAddressFromBech32(addrStr)
414-
coins := sdk.NewCoins(sdk.NewCoin(appparams.MicroCreditDenom, math.NewInt(balance)))
415-
err := k.GetBankKeeper().MintCoins(ctx, types.ModuleName, coins)
416-
require.NoError(t, err, "MintCoins failed")
417-
err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins)
418-
require.NoError(t, err, "SendCoinsFromModuleToAccount failed")
425+
amt := math.NewInt(lockupAmount)
426+
initializeDelegator(t, &k, ctx, addr, amt)
427+
err = k.Lock(ctx, addr, valAddr, amt)
428+
require.NoError(t, err)
419429
}
420430

421431
// Mint and distribute uopen
422432
for addrStr, balance := range tt.openBalances {
423433
addr := sdk.MustAccAddressFromBech32(addrStr)
424-
coins := sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, math.NewInt(balance)))
425-
err := k.GetBankKeeper().MintCoins(ctx, types.ModuleName, coins)
426-
require.NoError(t, err, "MintCoins uopen failed")
427-
err = k.GetBankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins)
428-
require.NoError(t, err, "SendCoinsFromModuleToAccount uopen failed")
434+
initializeDelegator(t, &k, ctx, addr, math.NewInt(balance))
429435
}
430436

431437
// Burn all credits
432-
err := k.burnAllCredits(ctx, 1)
438+
err = k.burnAllCredits(ctx, 1)
433439
if (err != nil) != tt.wantErr {
434440
t.Errorf("burnAllCredits() error = %v, wantErr %v", err, tt.wantErr)
435441
}
436442

437-
// Verify that all credit balances are zero
438-
for addrStr, origBalance := range tt.creditBalances {
443+
// Verify that all ucredit balances are zero
444+
for addrStr := range tt.lockups {
439445
addr := sdk.MustAccAddressFromBech32(addrStr)
440-
bal := k.GetBankKeeper().GetBalance(ctx, addr, appparams.MicroCreditDenom)
441-
if !bal.IsZero() {
442-
t.Errorf("Expected all credit burned for %s, original = %d, still found = %s",
443-
addrStr, origBalance, bal.Amount)
446+
creditBalance := k.GetBankKeeper().GetBalance(ctx, addr, appparams.MicroCreditDenom)
447+
if !creditBalance.IsZero() {
448+
t.Errorf("Expected all ucredit to be burned for %s, still found = %v", addrStr, creditBalance.Amount)
444449
}
445450
}
446451

447452
// Verify that uopen balances are unchanged
448453
for addrStr, expectedBalance := range tt.openBalances {
449454
addr := sdk.MustAccAddressFromBech32(addrStr)
450-
bal := k.GetBankKeeper().GetBalance(ctx, addr, appparams.DefaultBondDenom)
451-
if !bal.Amount.Equal(math.NewInt(expectedBalance)) {
452-
t.Errorf("Non-credit denom incorrectly burned. For %s, got = %d, expected = %d",
453-
addrStr, bal.Amount.Int64(), expectedBalance)
455+
openBalance := k.GetBankKeeper().GetBalance(ctx, addr, appparams.MicroOpenDenom)
456+
if !openBalance.Amount.Equal(math.NewInt(expectedBalance)) {
457+
t.Errorf("Should not burn uopen. For %s, got = %v, expected = %v", addrStr, openBalance.Amount, expectedBalance)
454458
}
455459
}
456460
})
@@ -545,11 +549,14 @@ func TestResetAllCredits(t *testing.T) {
545549
err := k.SetParams(ctx, types.DefaultParams())
546550
require.NoError(t, err)
547551

552+
initializeValidator(t, k.GetStakingKeeper().(*stakingkeeper.Keeper), ctx, valAddr, math.NewInt(10_000_000))
553+
548554
// Add lockups
549555
for addrStr, lockupAmounts := range tt.lockups {
550556
addr := sdk.MustAccAddressFromBech32(addrStr)
551557
for _, amt := range lockupAmounts {
552-
err = k.AddLockup(ctx, addr, valAddr, math.NewInt(amt))
558+
initializeDelegator(t, &k, ctx, addr, math.NewInt(amt))
559+
err = k.Lock(ctx, addr, valAddr, math.NewInt(amt))
553560
require.NoError(t, err)
554561
}
555562
}
@@ -562,8 +569,13 @@ func TestResetAllCredits(t *testing.T) {
562569
}
563570
}
564571

572+
err = k.burnAllCredits(ctx, 1)
573+
if (err != nil) != tt.wantErr {
574+
t.Errorf("burnAllCredits() error = %v, wantErr %v", err, tt.wantErr)
575+
}
576+
565577
// Reset all credits
566-
err = k.resetAllCredits(ctx)
578+
err = k.resetAllCredits(ctx, 1)
567579
if (err != nil) != tt.wantErr {
568580
t.Errorf("resetAllCredits() error = %v, wantErr %v", err, tt.wantErr)
569581
}
@@ -573,19 +585,17 @@ func TestResetAllCredits(t *testing.T) {
573585
addr := sdk.MustAccAddressFromBech32(addrStr)
574586
bal := k.GetBankKeeper().GetBalance(ctx, addr, appparams.MicroCreditDenom)
575587
if !bal.Amount.Equal(math.NewInt(expected)) {
576-
t.Errorf("Incorrect credit balance for %s, got = %v, expected = %v",
577-
addrStr, bal.Amount, expected)
588+
t.Errorf("Incorrect ucredit balance for %s, got = %v, expected = %v", addrStr, bal.Amount, expected)
578589
}
579590
}
580591

581-
// Addresses not in expectedCredit should have zero credit
592+
// Addresses not in expectedCredit should have zero ucredit
582593
for addrStr := range tt.lockups {
583594
if _, ok := tt.expectedCredit[addrStr]; !ok {
584595
addr := sdk.MustAccAddressFromBech32(addrStr)
585596
bal := k.GetBankKeeper().GetBalance(ctx, addr, appparams.MicroCreditDenom)
586597
if !bal.IsZero() {
587-
t.Errorf("Address %s was not in expectedCredit, but has credit = %v",
588-
addrStr, bal.Amount)
598+
t.Errorf("Address %s was not in expectedCredit, but has ucredit = %v", addrStr, bal.Amount)
589599
}
590600
}
591601
}

x/tier/keeper/epoch_hook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (h EpochHooks) BeforeEpochStart(ctx context.Context, epochIdentifier string
3737
return errorsmod.Wrapf(err, "burn all credits")
3838
}
3939

40-
err = h.keeper.resetAllCredits(ctx)
40+
err = h.keeper.resetAllCredits(ctx, epochNumber)
4141
if err != nil {
4242
return errorsmod.Wrapf(err, "reset all credits")
4343
}

0 commit comments

Comments
 (0)