Skip to content

Commit

Permalink
fix: Tier module fixes and improvements (part 3) (#10)
Browse files Browse the repository at this point in the history
* Add tier module events and attributes

* Emit corresponding events on Lock, Unlock, Redelegate, and CompleteUnlocking

* Improve CancelUnlocking to search for unbonding delegation entry by creationHeight

* Add IterateValidators and TotalBondedTokens to expected staking keeper, define corresponding mocks

* Fix calculation bug in TotalAmountByAddr caused by delAddr shadowing, add TestTotalAmountByAddr

* Fix SubtractLockup to remove lockup if subtracting the whole locked amount, add TestHasLockup and TestGetUnlockingLockup

* Refactor calculateCredit and related tests

* Add SaveLockup, refactor SetLockup, fix GetLockup and removeUnlockingLockup

* Add TestGetLockup and TestGetLockups, fix/improve existing tests

* Use SaveLockup instead of SetLockup in InitGenesis

* Update grpc query tests

* Improve CancelUnlocking to support partial unlocks and fix bug in existing logic

* Improve TestCancelUnlocking to verify subsequent and partial unlocks, fix/update existing tests

* Improve SubtractLockup and SubtractUnlockingLockup logic to handle invalid amounts

* Add TestSubtractUnlockingLockup, improve TestSubtractLockup

* Improve Lock, Unlock, and Redelegate to handle invalid amounts, fix/improve related tests

* Add ErrInvalidAmount

* Fix typo in ErrUnauthorized, add setupMsgServer and basic TestMsgServer

* Add TestMsgUpdateParams

* Add TestMsgLock

* Add TestMsgUnlock

* Add TestMsgRedelegate

* Update TestMsgLock, TestMsgUnlock, and TestMsgUpdateParams, fix error messages

* Add TestMsgCancelUnlocking, minor fixes
  • Loading branch information
iverc authored Dec 18, 2024
1 parent 9a8ec65 commit e484969
Show file tree
Hide file tree
Showing 23 changed files with 1,495 additions and 261 deletions.
25 changes: 25 additions & 0 deletions testutil/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,31 @@ func (mr *MockStakingKeeperRecorder) GetValidator(ctx, addr interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidator", reflect.TypeOf((*MockStakingKeeper)(nil).GetValidator), ctx, addr)
}

func (m *MockStakingKeeper) IterateValidators(ctx context.Context, fn func(index int64, validator stakingtypes.ValidatorI) (stop bool)) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IterateValidators", ctx, fn)
ret0, _ := ret[0].(error)
return ret0
}

func (mr *MockStakingKeeperRecorder) IterateValidators(ctx, fn interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateValidators", reflect.TypeOf((*MockStakingKeeper)(nil).IterateValidators), ctx, fn)
}

func (m *MockStakingKeeper) TotalBondedTokens(ctx context.Context) (math.Int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TotalBondedTokens", ctx)
ret0, _ := ret[0].(math.Int)
ret1, _ := ret[1].(error)
return ret0, ret1
}

func (mr *MockStakingKeeperRecorder) TotalBondedTokens(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalBondedTokens", reflect.TypeOf((*MockStakingKeeper)(nil).TotalBondedTokens), ctx)
}

func (m *MockStakingKeeper) Delegate(ctx context.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc stakingtypes.BondStatus, validator stakingtypes.Validator, subtractAccount bool) (math.LegacyDec, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delegate", ctx, delAddr, bondAmt, tokenSrc, validator, subtractAccount)
Expand Down
43 changes: 43 additions & 0 deletions x/tier/keeper/calculate_credit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package keeper

import (
"cosmossdk.io/math"

"github.com/sourcenetwork/sourcehub/x/tier/types"
)

// calculateCredit calculates the reward earned on the lockingAmt.
// lockingAmt is stacked up on top of the lockedAmt to earn at the highest eligible reward.
func calculateCredit(rateList []types.Rate, lockedAmt, lockingAmt math.Int) math.Int {
credit := math.ZeroInt()
stakedAmt := lockedAmt.Add(lockingAmt)

// Iterate from the highest reward rate to the lowest.
for _, r := range rateList {
// Continue if the total lock does not reach the current rate requirement.
if stakedAmt.LT(r.Amount) {
continue
}

lower := math.MaxInt(r.Amount, lockedAmt)
diff := stakedAmt.Sub(lower)

diffDec := math.LegacyNewDecFromInt(diff)
rateDec := math.LegacyNewDec(r.Rate)

// rateDec MUST have 2 decimals of precision for the calculation to be correct.
amt := diffDec.Mul(rateDec).Quo(math.LegacyNewDec(100))
credit = credit.Add(amt.TruncateInt())

// Subtract the lock that has been rewarded.
stakedAmt = stakedAmt.Sub(diff)
lockingAmt = lockingAmt.Sub(diff)

// Break if all the new lock has been rewarded.
if lockingAmt.IsZero() {
break
}
}

return credit
}
99 changes: 99 additions & 0 deletions x/tier/keeper/calculate_credit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package keeper

import (
"fmt"
"reflect"
"testing"

"cosmossdk.io/math"

"github.com/sourcenetwork/sourcehub/x/tier/types"
)

func Test_CalculateCredit(t *testing.T) {
rateList := []types.Rate{
{Amount: math.NewInt(300), Rate: 150},
{Amount: math.NewInt(200), Rate: 120},
{Amount: math.NewInt(100), Rate: 110},
{Amount: math.NewInt(0), Rate: 100},
}

tests := []struct {
lockedAmt int64
lockingAmt int64
want int64
}{
{
lockedAmt: 100,
lockingAmt: 0,
want: 0,
},
{
lockedAmt: 250,
lockingAmt: 0,
want: 0,
},
{
lockedAmt: 0,
lockingAmt: 100,
want: 100,
},
{
lockedAmt: 0,
lockingAmt: 200,
want: (100 * 1.0) + (100 * 1.1),
},
{
lockedAmt: 0,
lockingAmt: 250,
want: (100 * 1.0) + (100 * 1.1) + (50 * 1.2),
},
{
lockedAmt: 0,
lockingAmt: 300,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2),
},
{
lockedAmt: 0,
lockingAmt: 350,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2) + (50 * 1.5),
},
{
lockedAmt: 0,
lockingAmt: 600,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2) + (300 * 1.5),
},
{
lockedAmt: 100,
lockingAmt: 100,
want: (100 * 1.1),
},
{
lockedAmt: 200,
lockingAmt: 100,
want: (100 * 1.2),
},
{
lockedAmt: 150,
lockingAmt: 150,
want: (50 * 1.1) + (100 * 1.2),
},
{
lockedAmt: 50,
lockingAmt: 400,
want: (50 * 1.0) + (100 * 1.1) + (100 * 1.2) + (150 * 1.5),
},
}
for _, tt := range tests {
name := fmt.Sprintf("%d adds %d", tt.lockedAmt, tt.lockingAmt)
oldLock := math.NewInt(tt.lockedAmt)
newLock := math.NewInt(tt.lockingAmt)
want := math.NewInt(tt.want)

t.Run(name, func(t *testing.T) {
if got := calculateCredit(rateList, oldLock, newLock); !reflect.DeepEqual(got, want) {
t.Errorf("calculateCredit() = %v, want %v", got, tt.want)
}
})
}
}
41 changes: 2 additions & 39 deletions x/tier/keeper/credit.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (k Keeper) proratedCredit(ctx context.Context, delAddr sdk.AccAddress, lock
// Calculate the reward credits earned on the new lock.
rates := k.GetParams(ctx).RewardRates
lockedAmt := k.TotalAmountByAddr(ctx, delAddr)
credit := CalculateCredit(rates, lockedAmt, lockingAmt)
credit := calculateCredit(rates, lockedAmt, lockingAmt)

// Pro-rate the credit based on the time elapsed in the current epoch.
epochInfo := k.epochsKeeper.GetEpochInfo(ctx, types.EpochIdentifier)
Expand Down Expand Up @@ -116,7 +116,7 @@ func (k Keeper) resetAllCredits(ctx context.Context) error {

for delStrAddr, amt := range lockedAmts {
delAddr := sdk.MustAccAddressFromBech32(delStrAddr)
credit := CalculateCredit(rates, math.ZeroInt(), amt)
credit := calculateCredit(rates, math.ZeroInt(), amt)
err := k.MintCredit(ctx, delAddr, credit)
if err != nil {
return errorsmod.Wrapf(err, "mint %s to %s", credit, delAddr)
Expand All @@ -125,40 +125,3 @@ func (k Keeper) resetAllCredits(ctx context.Context) error {

return nil
}

// CalculateCredit calculates the reward earned on the lockingAmt.
// lockingAmt is stacked up on top of the lockedAmt to earn at the
// highest eligible reward.
func CalculateCredit(rateList []types.Rate, lockedAmt, lockingAmt math.Int) math.Int {
credit := math.ZeroInt()
stakedAmt := lockedAmt.Add(lockingAmt)

// Iterate from the highest reward rate to the lowest.
for _, r := range rateList {
// Continue if the total lock does not reach the current rate requirement.
if stakedAmt.LT(r.Amount) {
continue
}

lower := math.MaxInt(r.Amount, lockedAmt)
diff := stakedAmt.Sub(lower)

diffDec := math.LegacyNewDecFromInt(diff)
rateDec := math.LegacyNewDec(r.Rate)

// rateDec MUST have 2 decimals of precision for the calculation to be correct.
amt := diffDec.Mul(rateDec).Quo(math.LegacyNewDec(100))
credit = credit.Add(amt.TruncateInt())

// Subtract the lock that has been rewarded.
stakedAmt = stakedAmt.Sub(diff)
lockingAmt = lockingAmt.Sub(diff)

// Break if all the new lock has been rewarded.
if lockingAmt.IsZero() {
break
}
}

return credit
}
92 changes: 0 additions & 92 deletions x/tier/keeper/credit_test.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,14 @@
package keeper_test

import (
"fmt"
"reflect"
"testing"

"cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
testutil "github.com/sourcenetwork/sourcehub/testutil"
"github.com/sourcenetwork/sourcehub/x/tier/keeper"
"github.com/sourcenetwork/sourcehub/x/tier/types"
)

func Test_CalculateCredit(t *testing.T) {
rateList := []types.Rate{
{Amount: math.NewInt(300), Rate: 150},
{Amount: math.NewInt(200), Rate: 120},
{Amount: math.NewInt(100), Rate: 110},
{Amount: math.NewInt(0), Rate: 100},
}

tests := []struct {
lockedAmt int64
lockingAmt int64
want int64
}{
{
lockedAmt: 100,
lockingAmt: 0,
want: 0,
},
{
lockedAmt: 250,
lockingAmt: 0,
want: 0,
},
{
lockedAmt: 0,
lockingAmt: 100,
want: 100,
},
{
lockedAmt: 0,
lockingAmt: 200,
want: (100 * 1.0) + (100 * 1.1),
},
{
lockedAmt: 0,
lockingAmt: 250,
want: (100 * 1.0) + (100 * 1.1) + (50 * 1.2),
},
{
lockedAmt: 0,
lockingAmt: 300,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2),
},
{
lockedAmt: 0,
lockingAmt: 350,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2) + (50 * 1.5),
},
{
lockedAmt: 0,
lockingAmt: 600,
want: (100 * 1.0) + (100 * 1.1) + (100 * 1.2) + (300 * 1.5),
},
{
lockedAmt: 100,
lockingAmt: 100,
want: (100 * 1.1),
},
{
lockedAmt: 200,
lockingAmt: 100,
want: (100 * 1.2),
},
{
lockedAmt: 150,
lockingAmt: 150,
want: (50 * 1.1) + (100 * 1.2),
},
{
lockedAmt: 50,
lockingAmt: 400,
want: (50 * 1.0) + (100 * 1.1) + (100 * 1.2) + (150 * 1.5),
},
}
for _, tt := range tests {
name := fmt.Sprintf("%d adds %d", tt.lockedAmt, tt.lockingAmt)
oldLock := math.NewInt(tt.lockedAmt)
newLock := math.NewInt(tt.lockingAmt)
want := math.NewInt(tt.want)

t.Run(name, func(t *testing.T) {
if got := keeper.CalculateCredit(rateList, oldLock, newLock); !reflect.DeepEqual(got, want) {
t.Errorf("CalculateCredit() = %v, want %v", got, tt.want)
}
})
}
}

func Test_MintCredit(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading

0 comments on commit e484969

Please sign in to comment.