Skip to content

Commit

Permalink
fix(x/interchainstaking/types): fuzz & fix DetermineAllocationsForUnd…
Browse files Browse the repository at this point in the history
…elegation

This change introduces a fuzzer for DetermineAllocationsForUndelegation
as well as fixes for the identified non-defensive bugs that were
reported from ~1.5hr of fuzzing.

Fixes quicksilver-zone#1544
Fixes quicksilver-zone#1545
Fixes quicksilver-zone#1546
  • Loading branch information
odeke-em committed May 1, 2024
1 parent 9b4b2b9 commit e657913
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 69 deletions.
13 changes: 11 additions & 2 deletions x/interchainstaking/types/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,21 @@ func (vi ValidatorIntents) MustGetForValoper(valoper string) *ValidatorIntent {
func (vi ValidatorIntents) Normalize() ValidatorIntents {
total := sdk.ZeroDec()
for _, i := range vi {
total = total.AddMut(i.Weight)
if !i.Weight.IsNil() {
total = total.AddMut(i.Weight)
}
}

out := make(ValidatorIntents, 0)

if total.IsZero() {
return out
}

for _, i := range vi {
out = append(out, &ValidatorIntent{ValoperAddress: i.ValoperAddress, Weight: i.Weight.Quo(total)})
if !i.Weight.IsNil() {
out = append(out, &ValidatorIntent{ValoperAddress: i.ValoperAddress, Weight: i.Weight.Quo(total)})
}
}
return out.Sort()
}
Expand Down
3 changes: 3 additions & 0 deletions x/interchainstaking/types/rebalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ func CalculateAllocationDeltas(
if !ok {
target = &ValidatorIntent{ValoperAddress: valoper, Weight: sdk.ZeroDec()}
}
if target.Weight.IsNil() {
continue
}
targetAmount := target.Weight.MulInt(currentSum).TruncateInt()

// diff between target and current allocations
Expand Down
34 changes: 29 additions & 5 deletions x/interchainstaking/types/redemptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ func DetermineAllocationsForUndelegation(currentAllocations map[string]math.Int,
return outWeights, fmt.Errorf("amount was invalid, expected sdk.Coins of length 1, got length %d", len(amount))
}

if !amount[0].Amount.IsPositive() {
if availablePerValidator == nil {
availablePerValidator = make(map[string]math.Int)
}

if amount[0].Amount.IsNil() || !amount[0].Amount.IsPositive() {
return outWeights, fmt.Errorf("amount was invalid, expected positive value, got %s", amount[0].Amount.String())
}
input := amount[0].Amount
Expand All @@ -52,7 +56,10 @@ func DetermineAllocationsForUndelegation(currentAllocations map[string]math.Int,
for idx := range overAllocated {
// use Amount+1 in the line below to avoid 1 remaining where truncation leaves 1 remaining - e.g. 1000 => 333/333/333 + 1.
outWeights[overAllocated[idx].ValoperAddress] = sdk.NewDecFromInt(overAllocated[idx].Amount).Quo(sdk.NewDecFromInt(sum)).Mul(sdk.NewDecFromInt(overAllocationSplit)).TruncateInt()
if outWeights[overAllocated[idx].ValoperAddress].GT(availablePerValidator[overAllocated[idx].ValoperAddress]) {
overAlloc := availablePerValidator[overAllocated[idx].ValoperAddress]
if overAlloc.IsNil() {
availablePerValidator[overAllocated[idx].ValoperAddress] = sdk.ZeroInt()
} else if outWeights[overAllocated[idx].ValoperAddress].GT(overAlloc) {
// use up all of overAllocated[idx] and set available to zero.
outWeights[overAllocated[idx].ValoperAddress] = availablePerValidator[overAllocated[idx].ValoperAddress]
availablePerValidator[overAllocated[idx].ValoperAddress] = sdk.ZeroInt()
Expand Down Expand Up @@ -132,7 +139,12 @@ func DetermineAllocationsForUndelegation(currentAllocations map[string]math.Int,
// remove validators with no remaining balance from intents, and split remaining amount proportionally.
newTargetAllocations := make(ValidatorIntents, 0, len(targetAllocations))
for idx := range targetAllocations.Sort() {
if !availablePerValidator[targetAllocations[idx].ValoperAddress].IsZero() {
targetAlloc := availablePerValidator[targetAllocations[idx].ValoperAddress]
if targetAlloc.IsNil() {
continue
}

if !targetAlloc.IsZero() {
newTargetAllocations = append(newTargetAllocations, targetAllocations[idx])
}
}
Expand Down Expand Up @@ -170,8 +182,17 @@ func DetermineAllocationsForUndelegation(currentAllocations map[string]math.Int,
// the delta calculations on the next run.
dust := amount[0].Amount.Sub(outSum)
for idx := 0; idx <= len(deltas)-1; idx++ {
if dust.LTE(availablePerValidator[deltas[idx].ValoperAddress]) {
outWeights[deltas[idx].ValoperAddress] = outWeights[deltas[idx].ValoperAddress].Add(dust)
gotForValoper := availablePerValidator[deltas[idx].ValoperAddress]
if gotForValoper.IsNil() {
continue
}
if dust.LTE(gotForValoper) {
outWeight := outWeights[deltas[idx].ValoperAddress]
if outWeight.IsNil() {
outWeight = sdk.ZeroInt()
}

outWeights[deltas[idx].ValoperAddress] = outWeight.Add(dust)
break
}
}
Expand All @@ -189,6 +210,9 @@ func DetermineAllocationsForUndelegationPredef(currentAllocations map[string]mat
return outWeights, fmt.Errorf("amount was invalid, expected positive value, got %d", amount[0].Amount.Int64())
}
input := amount[0].Amount
if currentSum.IsNil() {
currentSum = sdk.ZeroInt()
}
underAllocated, overAllocated := CalculateAllocationDeltas(currentAllocations, lockedAllocations, currentSum /* .Sub(input) */, targetAllocations, make(map[string]math.Int))

outSum := sdk.ZeroInt()
Expand Down
Loading

0 comments on commit e657913

Please sign in to comment.