Skip to content

Commit

Permalink
sync latest spec with latest code and fix some core logics
Browse files Browse the repository at this point in the history
fix core logics
before:
* there can be bug because chunk's status is changed to unpairing but, current paired chunk's status is still Paired and chunk have paired insurance id even if it is unpairing chunk.
after:
if paired insurance of paired chunk have invalid insurance, then unpairing it and add it to out insurances to hande just like other unpairing chunks.

add missing invariant checks
* newly added RedelegationInfosInvariant was not included

chore
* refactor variables name in invariants.go
* use lsm's own event key types, not other module's.
* add module name to each event
  • Loading branch information
zsystm committed Jul 24, 2023
1 parent a9e3316 commit fc34e6e
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 132 deletions.
60 changes: 31 additions & 29 deletions x/liquidstaking/keeper/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
UnpairingForUnstakingChunkInfosInvariant(k))
ir.RegisterRoute(types.ModuleName, "withdraw-insurance-requests",
WithdrawInsuranceRequestsInvariant(k))
ir.RegisterRoute(types.ModuleName, "redelegation-infos",
RedelegationInfosInvariant(k))
}

func AllInvariants(k Keeper) sdk.Invariant {
Expand Down Expand Up @@ -65,9 +67,9 @@ func ChunksInvariant(k Keeper) sdk.Invariant {
k.IterateAllChunks(ctx, func(chunk types.Chunk) bool {
switch chunk.Status {
case types.CHUNK_STATUS_PAIRING:
// must have empty paired insurance
// must have empty paired pairedIns
if chunk.HasPairedInsurance() {
msg += fmt.Sprintf("pairing chunk(id: %d) have non-empty paired insurance\n", chunk.Id)
msg += fmt.Sprintf("pairing chunk(id: %d) have non-empty paired pairedIns\n", chunk.Id)
brokenCount++
return false
}
Expand All @@ -86,27 +88,27 @@ func ChunksInvariant(k Keeper) sdk.Invariant {
brokenCount++
return false
}
insurance, found := k.GetInsurance(ctx, chunk.PairedInsuranceId)
pairedIns, found := k.GetInsurance(ctx, chunk.PairedInsuranceId)
if !found {
msg += fmt.Sprintf("not found paired insurance for paired chunk(id: %d)\n", chunk.Id)
brokenCount++
return false
}
if insurance.Status != types.INSURANCE_STATUS_PAIRED {
msg += fmt.Sprintf("paired chunk(id: %d) have paired insurance with invalid status: %s\n", chunk.Id, insurance.Status)
if pairedIns.Status != types.INSURANCE_STATUS_PAIRED {
msg += fmt.Sprintf("paired chunk(id: %d) have paired insurance with invalid status: %s\n", chunk.Id, pairedIns.Status)
brokenCount++
return false
}
// must have valid Delegation object
delegation, found := k.stakingKeeper.GetDelegation(ctx, chunk.DerivedAddress(), insurance.GetValidator())
delegation, found := k.stakingKeeper.GetDelegation(ctx, chunk.DerivedAddress(), pairedIns.GetValidator())
if !found {
msg += fmt.Sprintf("not found delegation for paired chunk(id: %d)\n", chunk.Id)
brokenCount++
return false
}
delShares := delegation.GetShares()
if delShares.LT(types.ChunkSize.ToDec()) {
msg += fmt.Sprintf("delegation tokens of paired chunk(id: %d) is less than chunk size tokens: %s\n", chunk.Id, delShares.String())
msg += fmt.Sprintf("paired chunk's delegation sharesis less than chunk size tokens: %s(chunkId: %d)\n", delShares.String(), chunk.Id)
brokenCount++
return false
}
Expand All @@ -117,7 +119,7 @@ func ChunksInvariant(k Keeper) sdk.Invariant {
brokenCount++
return false
}
insurance, found := k.GetInsurance(ctx, chunk.UnpairingInsuranceId)
unpairingIns, found := k.GetInsurance(ctx, chunk.UnpairingInsuranceId)
if !found {
msg += fmt.Sprintf("not found unpairing insurance for unpairing chunk(id: %d)\n", chunk.Id)
brokenCount++
Expand All @@ -130,7 +132,7 @@ func ChunksInvariant(k Keeper) sdk.Invariant {
return false
}
// must have unbonding delegation
ubd, found := k.stakingKeeper.GetUnbondingDelegation(ctx, chunk.DerivedAddress(), insurance.GetValidator())
ubd, found := k.stakingKeeper.GetUnbondingDelegation(ctx, chunk.DerivedAddress(), unpairingIns.GetValidator())
if !found {
msg += fmt.Sprintf("not found unbonding delegation for unpairing chunk(id: %d)\n", chunk.Id)
brokenCount++
Expand Down Expand Up @@ -162,69 +164,69 @@ func InsurancesInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
msg := ""
brokenCount := 0
k.IterateAllInsurances(ctx, func(insurance types.Insurance) bool {
switch insurance.Status {
k.IterateAllInsurances(ctx, func(ins types.Insurance) bool {
switch ins.Status {
case types.INSURANCE_STATUS_PAIRING:
// must have empty chunk
if insurance.HasChunk() {
msg += fmt.Sprintf("pairing insurance(id: %d) have non-empty paired chunk\n", insurance.Id)
if ins.HasChunk() {
msg += fmt.Sprintf("pairing insurance(id: %d) have non-empty paired chunk\n", ins.Id)
brokenCount++
return false
}
case types.INSURANCE_STATUS_PAIRED:
// must have paired chunk
if !insurance.HasChunk() {
msg += fmt.Sprintf("paired insurance(id: %d) have empty paired chunk\n", insurance.Id)
if !ins.HasChunk() {
msg += fmt.Sprintf("paired insurance(id: %d) have empty paired chunk\n", ins.Id)
brokenCount++
return false
}
chunk, found := k.GetChunk(ctx, insurance.ChunkId)
chunk, found := k.GetChunk(ctx, ins.ChunkId)
if !found {
msg += fmt.Sprintf("not found paired chunk for paired insurance(id: %d)\n", insurance.Id)
msg += fmt.Sprintf("not found paired chunk for paired insurance(id: %d)\n", ins.Id)
brokenCount++
return false
}
if chunk.Status != types.CHUNK_STATUS_PAIRED {
msg += fmt.Sprintf("paired insurance(id: %d) have invalid paired chunk status: %s\n", insurance.Id, chunk.Status)
msg += fmt.Sprintf("paired insurance(id: %d) have invalid paired chunk status: %s\n", ins.Id, chunk.Status)
brokenCount++
return false
}
case types.INSURANCE_STATUS_UNPAIRING:
// must have chunk to protect
if !insurance.HasChunk() {
msg += fmt.Sprintf("unpairing insurance(id: %d) have empty chunk to protect\n", insurance.Id)
if !ins.HasChunk() {
msg += fmt.Sprintf("unpairing insurance(id: %d) have empty chunk to protect\n", ins.Id)
brokenCount++
return false
}
_, found := k.GetChunk(ctx, insurance.ChunkId)
_, found := k.GetChunk(ctx, ins.ChunkId)
if !found {
msg += fmt.Sprintf("not found chunk to protect for unpairing insurance(id: %d)\n", insurance.Id)
msg += fmt.Sprintf("not found chunk to protect for unpairing insurance(id: %d)\n", ins.Id)
brokenCount++
return false
}

case types.INSURANCE_STATUS_UNPAIRED:
// must have empty chunk
if insurance.HasChunk() {
msg += fmt.Sprintf("unpaired insurance(id: %d) have non-empty paired chunk\n", insurance.Id)
if ins.HasChunk() {
msg += fmt.Sprintf("unpaired insurance(id: %d) have non-empty paired chunk\n", ins.Id)
brokenCount++
return false
}
case types.INSURANCE_STATUS_UNPAIRING_FOR_WITHDRAWAL:
// must have chunk to protect
if !insurance.HasChunk() {
msg += fmt.Sprintf("unpairing for withdrawal insurance(id: %d) have empty chunk to protect\n", insurance.Id)
if !ins.HasChunk() {
msg += fmt.Sprintf("unpairing for withdrawal insurance(id: %d) have empty chunk to protect\n", ins.Id)
brokenCount++
return false
}
_, found := k.GetChunk(ctx, insurance.ChunkId)
_, found := k.GetChunk(ctx, ins.ChunkId)
if !found {
msg += fmt.Sprintf("not found chunk to protect for unpairing for withdrawal insurance(id: %d)\n", insurance.Id)
msg += fmt.Sprintf("not found chunk to protect for unpairing for withdrawal insurance(id: %d)\n", ins.Id)
brokenCount++
return false
}
default:
msg += fmt.Sprintf("insurance(id: %d) have invalid status: %s\n", insurance.Id, insurance.Status)
msg += fmt.Sprintf("insurance(id: %d) have invalid status: %s\n", ins.Id, ins.Status)
brokenCount++
return false
}
Expand Down
66 changes: 35 additions & 31 deletions x/liquidstaking/keeper/liquidstaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ func (k Keeper) CoverRedelegationPenalty(ctx sdk.Context) {
// 2. Deduct dynamic fee from remaining and burn it.
// 3. Send rest of rewards to reward module account.
func (k Keeper) CollectRewardAndFee(
ctx sdk.Context,
dynamicFeeRate sdk.Dec,
chunk types.Chunk,
ins types.Insurance,
ctx sdk.Context, dynamicFeeRate sdk.Dec, chunk types.Chunk, ins types.Insurance,
) {
// At upper callstack(=DistributeReward), we withdrawed delegation reward of chunk.
// So balance of chunk is delegation reward.
Expand Down Expand Up @@ -200,8 +197,9 @@ func (k Keeper) HandleQueuedLiquidUnstakes(ctx sdk.Context) []types.Chunk {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeBeginLiquidUnstake,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkIds, strings.Join(chunkIds, ", ")),
sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
})
}
Expand All @@ -222,6 +220,7 @@ func (k Keeper) HandleUnprocessedQueuedLiquidUnstakes(ctx sdk.Context) {
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeDeleteQueuedLiquidUnstake,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyDelegator, info.DelegatorAddress),
),
})
Expand Down Expand Up @@ -263,6 +262,7 @@ func (k Keeper) HandleQueuedWithdrawInsuranceRequests(ctx sdk.Context) []types.I
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeBeginWithdrawInsurance,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyInsuranceIds, strings.Join(withdrawnInsIds, ", ")),
),
})
Expand Down Expand Up @@ -305,9 +305,10 @@ func (k Keeper) GetAllRePairableChunksAndOutInsurances(ctx sdk.Context) (
case types.CHUNK_STATUS_PAIRED:
pairedIns, validator, _ := k.mustValidatePairedChunk(ctx, chunk)
if err := k.ValidateValidator(ctx, validator); err != nil {
outInsurances = append(outInsurances, pairedIns)
chunk.SetStatus(types.CHUNK_STATUS_UNPAIRING)
k.SetChunk(ctx, chunk)
k.startUnpairing(ctx, pairedIns, chunk)
chunk = k.mustGetChunk(ctx, chunk.Id)
unpairingIns := k.mustGetInsurance(ctx, chunk.UnpairingInsuranceId)
outInsurances = append(outInsurances, unpairingIns)
} else {
validPairedInsuranceMap[pairedIns.Id] = struct{}{}
}
Expand Down Expand Up @@ -413,7 +414,6 @@ func (k Keeper) RePairRankedInsurances(
if newRankInIns.GetValidator().Equals(outIns.GetValidator()) {
// get chunk by outIns.ChunkId
chunk := k.mustGetChunk(ctx, outIns.ChunkId)
// TODO: outIns is removed at next epoch? and also it covers penalty if slashing happened after?
k.rePairChunkAndInsurance(ctx, chunk, newRankInIns, outIns)
hasSameValidator = true
// mark outIns as handled, so we will not handle it again
Expand All @@ -426,6 +426,14 @@ func (k Keeper) RePairRankedInsurances(
}
}

// Which ranked-out insurances are not handled yet?
remainedOutInsurances := make([]types.Insurance, 0)
for _, outIns := range rankOutInsurances {
if _, ok := handledOutInsurances[outIns.Id]; !ok {
remainedOutInsurances = append(remainedOutInsurances, outIns)
}
}

// pairing chunks are immediately pairable, so just delegate it.
var pairingChunks []types.Chunk
pairingChunks = k.GetAllPairingChunks(ctx)
Expand Down Expand Up @@ -455,27 +463,19 @@ func (k Keeper) RePairRankedInsurances(
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
stakingtypes.EventTypeDelegate,
types.EventTypeDelegate,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(types.AttributeKeyInsuranceId, fmt.Sprintf("%d", newIns.Id)),
sdk.NewAttribute(stakingtypes.AttributeKeyDelegator, chunk.DerivedAddress().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(types.AttributeKeyDelegator, chunk.DerivedAddress().String()),
sdk.NewAttribute(types.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, types.ChunkSize.String()),
sdk.NewAttribute(stakingtypes.AttributeKeyNewShares, newShares.String()),
sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()),
sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueReasonPairingChunkPaired),
),
)
}

// Which ranked-out insurances are not handled yet?
remainedOutInsurances := make([]types.Insurance, 0)
for _, outIns := range rankOutInsurances {
if _, ok := handledOutInsurances[outIns.Id]; !ok {
remainedOutInsurances = append(remainedOutInsurances, outIns)
}
}

// reset handledOutInsurances to track which out insurances are handled
handledOutInsurances = make(map[uint64]struct{})
// rest of rankOutInsurances are replaced with newInsurancesWithDifferentValidators by re-delegation
Expand Down Expand Up @@ -520,10 +520,11 @@ func (k Keeper) RePairRankedInsurances(
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBeginRedelegate,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(stakingtypes.AttributeKeySrcValidator, outIns.GetValidator().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyDstValidator, newIns.GetValidator().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeySrcValidator, outIns.GetValidator().String()),
sdk.NewAttribute(types.AttributeKeyDstValidator, newIns.GetValidator().String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
)
k.rePairChunkAndInsurance(ctx, chunk, newIns, outIns)
Expand Down Expand Up @@ -557,9 +558,10 @@ func (k Keeper) RePairRankedInsurances(
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBeginUndelegate,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(stakingtypes.AttributeKeyValidator, outIns.GetValidator().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeyValidator, outIns.GetValidator().String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueReasonNoCandidateIns),
),
)
Expand Down Expand Up @@ -1402,9 +1404,10 @@ func (k Keeper) mustUndelegate(
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBeginUndelegate,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(stakingtypes.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
sdk.NewAttribute(types.AttributeKeyReason, reason),
),
)
Expand All @@ -1426,10 +1429,10 @@ func (k Keeper) mustDelegatePenaltyAmt(
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(types.AttributeKeyInsuranceId, fmt.Sprintf("%d", insId)),
sdk.NewAttribute(stakingtypes.AttributeKeyDelegator, chunk.DerivedAddress().String()),
sdk.NewAttribute(stakingtypes.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(types.AttributeKeyDelegator, chunk.DerivedAddress().String()),
sdk.NewAttribute(types.AttributeKeyValidator, validator.GetOperator().String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()),
sdk.NewAttribute(stakingtypes.AttributeKeyNewShares, newShares.String()),
sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()),
sdk.NewAttribute(types.AttributeKeyReason, reason),
),
)
Expand Down Expand Up @@ -1500,6 +1503,7 @@ func (k Keeper) rePairChunkAndInsurance(ctx sdk.Context, chunk types.Chunk, newI
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRePairedWithNewInsurance,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)),
sdk.NewAttribute(types.AttributeKeyNewInsuranceId, fmt.Sprintf("%d", newIns.Id)),
sdk.NewAttribute(types.AttributeKeyOutInsuranceId, fmt.Sprintf("%d", outIns.Id)),
Expand Down
4 changes: 2 additions & 2 deletions x/liquidstaking/spec/05_begin_block.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# BeginBlock

The end block logic is executed at the end of each epoch.
The begin block logic is executed at the end of each epoch.

## Cover Redelegation Penalty

Expand All @@ -11,5 +11,5 @@ The end block logic is executed at the end of each epoch.
- calc diff between entry.SharesDst and dstDel.Shares
- if calc is positive (meaning there is a penalty during the redelegation period)
- calc penalty amt which is the token value of shares lost due to slashing
- send penalty amt of tokens to chunk (if unpairing insurance balance is below penalty amt, send all insurance's balance to chunk)
- send penalty amt of tokens to chunk
- chunk delegate additional shares corresponding penalty amt
Loading

0 comments on commit fc34e6e

Please sign in to comment.