Skip to content

Commit

Permalink
fix: division by zero issues (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmic-vagabond authored Nov 29, 2023
1 parent d4dc696 commit 326301f
Show file tree
Hide file tree
Showing 31 changed files with 236 additions and 23 deletions.
15 changes: 15 additions & 0 deletions app/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ func (min MinCommissionDecorator) CalculateValidatorProjectedVotingPower(ctx sdk
projectedTotalDelegatedTokens := totalDelegatedTokens.Add(delegateAmount)
projectedValidatorTokens := delegateAmount

// Ensure projectedTotalDelegatedTokens is not zero to avoid division by zero
if projectedTotalDelegatedTokens.IsZero() {
return sdk.ZeroDec()
}

return projectedValidatorTokens.Quo(projectedTotalDelegatedTokens).Mul(sdk.NewDec(100))
}

Expand All @@ -89,6 +94,11 @@ func (min MinCommissionDecorator) CalculateDelegateProjectedVotingPower(ctx sdk.
projectedTotalDelegatedTokens := totalDelegatedTokens.Add(delegateAmount)
projectedValidatorTokens := validatorTokens.Add(delegateAmount)

// Ensure projectedTotalDelegatedTokens is not zero to avoid division by zero
if projectedTotalDelegatedTokens.IsZero() {
return sdk.ZeroDec()
}

return projectedValidatorTokens.Quo(projectedTotalDelegatedTokens).Mul(sdk.NewDec(100))
}

Expand All @@ -99,6 +109,11 @@ func (min MinCommissionDecorator) CalculateRedelegateProjectedVotingPower(ctx sd

projectedValidatorTokens := validatorTokens.Add(delegateAmount)

// Ensure projectedTotalDelegatedTokens is not zero to avoid division by zero
if projectedTotalDelegatedTokens.IsZero() {
return sdk.ZeroDec()
}

return projectedValidatorTokens.Quo(projectedTotalDelegatedTokens).Mul(sdk.NewDec(100))
}

Expand Down
5 changes: 5 additions & 0 deletions x/amm/keeper/calc_in_route_spot_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func (k Keeper) CalcInRouteSpotPrice(ctx sdk.Context, tokenIn sdk.Coin, routes [
availableLiquidity = poolAsset.Token
}

// Ensure tokenIn.Amount is not zero to avoid division by zero
if tokenIn.IsZero() {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
}

// Calculate the spot price given the initial token in and the final token out
spotPrice := sdk.NewDecFromInt(tokensIn[0].Amount).Quo(sdk.NewDecFromInt(tokenIn.Amount))

Expand Down
5 changes: 5 additions & 0 deletions x/amm/keeper/calc_out_route_spot_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func (k Keeper) CalcOutRouteSpotPrice(ctx sdk.Context, tokenOut sdk.Coin, routes
availableLiquidity = poolAsset.Token
}

// Ensure tokenIn.Amount is not zero to avoid division by zero
if tokenOut.IsZero() {
return sdk.ZeroDec(), sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.Coin{}, types.ErrAmountTooLow
}

// Calculate the spot price given the initial token in and the final token in
spotPrice := sdk.NewDecFromInt(tokensOut[0].Amount).Quo(sdk.NewDecFromInt(tokenOut.Amount))

Expand Down
16 changes: 15 additions & 1 deletion x/amm/keeper/msg_server_feed_multiple_external_liquidity.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,22 @@ func (k msgServer) FeedMultipleExternalLiquidity(goCtx context.Context, msg *typ
return nil, err
}

// Ensure tvl is not zero to avoid division by zero
if tvl.IsZero() {
return nil, types.ErrAmountTooLow
}

elRatio := elValue.Quo(tvl)
elRatio = elRatio.Quo(LiquidityRatioFromPriceDepth(elDepth))

// calculate liquidity ratio
liquidityRatio := LiquidityRatioFromPriceDepth(elDepth)

// Ensure tvl is not zero to avoid division by zero
if liquidityRatio.IsZero() {
return nil, types.ErrAmountTooLow
}

elRatio = elRatio.Quo(liquidityRatio)
if elRatio.LT(sdk.OneDec()) {
elRatio = sdk.OneDec()
}
Expand Down
11 changes: 11 additions & 0 deletions x/amm/types/calc_exit_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ func CalcExitValueWithoutSlippage(ctx sdk.Context, oracleKeeper OracleKeeper, ac
totalShares := pool.GetTotalShares()
var refundedShares sdk.Dec
refundedShares = sdk.NewDecFromInt(exitingShares)

// Ensure totalShares is not zero to avoid division by zero
if totalShares.IsZero() {
return sdk.ZeroDec(), ErrAmountTooLow
}

exitValue := tvl.Mul(refundedShares).Quo(sdk.NewDecFromInt(totalShares.Amount))

if exitingShares.GTE(totalShares.Amount) {
Expand Down Expand Up @@ -97,6 +103,11 @@ func CalcExitPool(ctx sdk.Context, oracleKeeper OracleKeeper, pool Pool, account
return sdk.Coins{}, err
}

// Ensure tokenPrice is not zero to avoid division by zero
if tokenPrice.IsZero() {
return sdk.Coins{}, ErrAmountTooLow
}

oracleOutAmount := exitValueWithoutSlippage.Quo(tokenPrice)

newAssetPools, err := pool.NewPoolAssetsAfterSwap(
Expand Down
13 changes: 11 additions & 2 deletions x/amm/types/calc_in_amt_given_out.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,21 @@ func (p Pool) CalcInAmtGivenOut(

poolPostSwapOutBalance := poolTokenOutBalance.Sub(sdk.NewDecFromInt(tokenOut.Amount))
// (x_0)(y_0) = (x_0 + in)(y_0 - out)
tokenAmountIn := solveConstantFunctionInvariant(
tokenAmountIn, err := solveConstantFunctionInvariant(
poolTokenOutBalance, poolPostSwapOutBalance,
outWeight,
poolTokenInBalance,
inWeight,
).Neg()
)
if err != nil {
return sdk.Coin{}, err
}
tokenAmountIn = tokenAmountIn.Neg()

// Ensure (1 - swapfee) is not zero to avoid division by zero
if sdk.OneDec().Sub(swapFee).IsZero() {
return sdk.Coin{}, ErrAmountTooLow
}

// We deduct a swap fee on the input asset. The swap happens by following the invariant curve on the input * (1 - swap fee)
// and then the swap fee is added to the pool.
Expand Down
5 changes: 4 additions & 1 deletion x/amm/types/calc_out_amt_given_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,16 @@ func (p Pool) CalcOutAmtGivenIn(

// deduct swapfee on the tokensIn
// delta balanceOut is positive(tokens inside the pool decreases)
tokenAmountOut := solveConstantFunctionInvariant(
tokenAmountOut, err := solveConstantFunctionInvariant(
poolTokenInBalance,
poolPostSwapInBalance,
inWeight,
poolTokenOutBalance,
outWeight,
)
if err != nil {
return sdk.Coin{}, err
}

// We ignore the decimal component, as we round down the token amount out.
tokenAmountOutInt := tokenAmountOut.TruncateInt()
Expand Down
8 changes: 6 additions & 2 deletions x/amm/types/pool_calc_join_pool_shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ func (p *Pool) calcSingleAssetJoin(tokenIn sdk.Coin, spreadFactor sdk.Dec, token
return sdk.ZeroInt(), errors.New("pool misconfigured, total weight = 0")
}
normalizedWeight := sdk.NewDecFromInt(tokenInPoolAsset.Weight).Quo(sdk.NewDecFromInt(totalWeight))
return calcPoolSharesOutGivenSingleAssetIn(
poolShares, err := calcPoolSharesOutGivenSingleAssetIn(
sdk.NewDecFromInt(tokenInPoolAsset.Token.Amount),
normalizedWeight,
sdk.NewDecFromInt(totalShares),
sdk.NewDecFromInt(tokenIn.Amount),
spreadFactor,
).TruncateInt(), nil
)
if err != nil {
return sdk.ZeroInt(), err
}
return poolShares.TruncateInt(), nil
}

// CalcJoinPoolShares calculates the number of shares created to join pool with the provided amount of `tokenIn`.
Expand Down
4 changes: 4 additions & 0 deletions x/amm/types/pool_join_pool_no_swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ func (p *Pool) JoinPool(ctx sdk.Context, oracleKeeper OracleKeeper, accountedPoo
if err != nil {
return sdk.ZeroInt(), err
}
// Ensure tvl is not zero to avoid division by zero
if tvl.IsZero() {
return sdk.ZeroInt(), ErrAmountTooLow
}

newAssetPools, err := p.NewPoolAssetsAfterSwap(
tokensIn,
Expand Down
26 changes: 20 additions & 6 deletions x/amm/types/solve_constant_function_invariant.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,28 @@ func solveConstantFunctionInvariant(
tokenWeightFixed,
tokenBalanceUnknownBefore,
tokenWeightUnknown sdk.Dec,
) sdk.Dec {
) (sdk.Dec, error) {
// Ensure tokenWeightUnknown is not zero to avoid division by zero
if tokenWeightUnknown.IsZero() {
return sdk.ZeroDec(), ErrAmountTooLow
}

// weightRatio = (weightX/weightY)
weightRatio := tokenWeightFixed.Quo(tokenWeightUnknown)

// Ensure tokenBalanceFixedAfter is not zero to avoid division by zero
if tokenBalanceFixedAfter.IsZero() {
return sdk.ZeroDec(), ErrAmountTooLow
}

// y = balanceXBefore/balanceXAfter
y := tokenBalanceFixedBefore.Quo(tokenBalanceFixedAfter)

// amountY = balanceY * (1 - (y ^ weightRatio))
yToWeightRatio := Pow(y, weightRatio)
paranthetical := sdk.OneDec().Sub(yToWeightRatio)
amountY := tokenBalanceUnknownBefore.Mul(paranthetical)
return amountY
return amountY, nil
}

// feeRatio returns the fee ratio that is defined as follows:
Expand All @@ -46,7 +56,7 @@ func calcPoolSharesOutGivenSingleAssetIn(
poolShares,
tokenAmountIn,
spreadFactor sdk.Dec,
) sdk.Dec {
) (sdk.Dec, error) {
// deduct spread factor on the in asset.
// We don't charge spread factor on the token amount that we imagine as unswapped (the normalized weight).
// So effective_swapfee = spread factor * (1 - normalized_token_weight)
Expand All @@ -61,11 +71,15 @@ func calcPoolSharesOutGivenSingleAssetIn(
// The number of new shares we need to make is then `old_shares * ((k'/k) - 1)`
// Whats very cool, is that this turns out to be the exact same `solveConstantFunctionInvariant` code
// with the answer's sign reversed.
poolAmountOut := solveConstantFunctionInvariant(
poolAmountOut, err := solveConstantFunctionInvariant(
tokenBalanceIn.Add(tokenAmountInAfterFee),
tokenBalanceIn,
normalizedTokenWeightIn,
poolShares,
sdk.OneDec()).Neg()
return poolAmountOut
sdk.OneDec())
if err != nil {
return sdk.Dec{}, err
}
poolAmountOut = poolAmountOut.Neg()
return poolAmountOut, nil
}
5 changes: 5 additions & 0 deletions x/amm/types/swap_in_amt_given_out.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func (p *Pool) SwapInAmtGivenOut(
// actualSlippageAmount = balancer slippage(resizedAmount)
oracleInAmount := sdk.NewDecFromInt(tokenOut.Amount).Mul(outTokenPrice).Quo(inTokenPrice)

// Ensure p.PoolParams.ExternalLiquidityRatio is not zero to avoid division by zero
if p.PoolParams.ExternalLiquidityRatio.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), ErrAmountTooLow
}

resizedAmount := sdk.NewDecFromInt(tokenOut.Amount).Quo(p.PoolParams.ExternalLiquidityRatio).RoundInt()
slippageAmount, err = p.CalcGivenOutSlippage(
ctx,
Expand Down
14 changes: 14 additions & 0 deletions x/amm/types/swap_out_amt_given_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func (p Pool) StackedRatioFromSnapshot(ctx sdk.Context, oracleKeeper OracleKeepe
stackedRatio := sdk.ZeroDec()
for index, asset := range snapshot.PoolAssets {
assetDiff := sdk.NewDecFromInt(p.PoolAssets[index].Token.Amount.Sub(asset.Token.Amount).Abs())
// Ensure asset.Token is not zero to avoid division by zero
if asset.Token.IsZero() {
asset.Token.Amount = sdk.OneInt()
}
assetStacked := assetDiff.Quo(sdk.NewDecFromInt(asset.Token.Amount))
stackedRatio = stackedRatio.Add(assetStacked)
}
Expand All @@ -93,6 +97,10 @@ func (p Pool) WeightDistanceFromTarget(ctx sdk.Context, oracleKeeper OracleKeepe
distance := targetWeights[i].Weight.Sub(oracleWeights[i].Weight).Abs()
distanceSum = distanceSum.Add(distance)
}
// Ensure len(p.PoolAssets) is not zero to avoid division by zero
if len(p.PoolAssets) == 0 {
return sdk.ZeroDec()
}
return distanceSum.Quo(sdk.NewDec(int64(len(p.PoolAssets))))
}

Expand Down Expand Up @@ -179,6 +187,12 @@ func (p *Pool) SwapOutAmtGivenIn(
// resizedAmount = tokenIn / externalLiquidityRatio
// actualSlippageAmount = balancer slippage(resizedAmount)
oracleOutAmount := sdk.NewDecFromInt(tokenIn.Amount).Mul(inTokenPrice).Quo(outTokenPrice)

// Ensure p.PoolParams.ExternalLiquidityRatio is not zero to avoid division by zero
if p.PoolParams.ExternalLiquidityRatio.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), ErrAmountTooLow
}

resizedAmount := sdk.NewDecFromInt(tokenIn.Amount).Quo(p.PoolParams.ExternalLiquidityRatio).RoundInt()
slippageAmount, err = p.CalcGivenInSlippage(
ctx,
Expand Down
5 changes: 5 additions & 0 deletions x/commitment/keeper/msg_server_vest_now.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func (k msgServer) VestNow(goCtx context.Context, msg *types.MsgVestNow) (*types
return nil, err
}

// Ensure vestingInfo.VestNowFactor is not zero to avoid division by zero
if vestingInfo.VestNowFactor.IsZero() {
return nil, types.ErrInvalidAmount
}

vestAmount := msg.Amount.Quo(vestingInfo.VestNowFactor)
withdrawCoins := sdk.NewCoins(sdk.NewCoin(vestingInfo.VestingDenom, vestAmount))

Expand Down
2 changes: 1 addition & 1 deletion x/commitment/keeper/vest.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (k Keeper) VestTokens(ctx sdk.Context, epochIdentifier string) error {
for index := len(commitments.VestingTokens) - 1; index >= 0; index-- {
vesting := commitments.VestingTokens[index]
vesting.CurrentEpoch = vesting.CurrentEpoch + 1
if vesting.CurrentEpoch > vesting.NumEpochs || vesting.UnvestedAmount.IsZero() {
if vesting.NumEpochs == 0 || vesting.CurrentEpoch > vesting.NumEpochs || vesting.UnvestedAmount.IsZero() {
continue
}

Expand Down
15 changes: 15 additions & 0 deletions x/incentive/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,26 @@ func (k Keeper) ProcessUpdateIncentiveParams(ctx sdk.Context) bool {

params := k.GetParams(ctx)

// Ensure distribution epoch is not zero to avoid division by zero
if params.DistributionEpochForLpsInBlocks == 0 || params.DistributionEpochForStakersInBlocks == 0 {
return false
}

for _, inflation := range listTimeBasedInflations {
if inflation.StartBlockHeight > uint64(ctx.BlockHeight()) || inflation.EndBlockHeight < uint64(ctx.BlockHeight()) {
continue
}

totalBlocksPerYear := sdk.NewInt(int64(inflation.EndBlockHeight - inflation.StartBlockHeight + 1))

// ptypes.DaysPerYear is guaranteed to be positive as it is defined as a constant
allocationEpochInblocks := totalBlocksPerYear.Quo(sdk.NewInt(ptypes.DaysPerYear))
if len(params.LpIncentives) == 0 {
totalDistributionEpochPerYear := totalBlocksPerYear.Quo(sdk.NewInt(params.DistributionEpochForLpsInBlocks))
// If totalDistributionEpochPerYear is zero, we skip this inflation to avoid division by zero
if totalBlocksPerYear == sdk.ZeroInt() {
continue
}
currentEpochInBlocks := sdk.NewInt(ctx.BlockHeight() - int64(inflation.StartBlockHeight)).Mul(totalDistributionEpochPerYear).Quo(totalBlocksPerYear)
maxEdenPerAllocation := sdk.NewInt(int64(inflation.Inflation.LmRewards)).Mul(allocationEpochInblocks).Quo(totalBlocksPerYear)
params.LpIncentives = append(params.LpIncentives, types.IncentiveInfo{
Expand All @@ -140,6 +151,10 @@ func (k Keeper) ProcessUpdateIncentiveParams(ctx sdk.Context) bool {

if len(params.StakeIncentives) == 0 {
totalDistributionEpochPerYear := totalBlocksPerYear.Quo(sdk.NewInt(params.DistributionEpochForStakersInBlocks))
// If totalDistributionEpochPerYear is zero, we skip this inflation to avoid division by zero
if totalBlocksPerYear == sdk.ZeroInt() {
continue
}
currentEpochInBlocks := sdk.NewInt(ctx.BlockHeight() - int64(inflation.StartBlockHeight)).Mul(totalDistributionEpochPerYear).Quo(totalBlocksPerYear)
maxEdenPerAllocation := sdk.NewInt(int64(inflation.Inflation.IcsStakingRewards)).Mul(allocationEpochInblocks).Quo(totalBlocksPerYear)
params.StakeIncentives = append(params.StakeIncentives, types.IncentiveInfo{
Expand Down
10 changes: 10 additions & 0 deletions x/incentive/keeper/apr.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func (k Keeper) CalculateApr(ctx sdk.Context, query *types.QueryAprRequest) (sdk
k.UpdateTotalCommitmentInfo(ctx, baseCurrency)
totalStakedSnapshot := k.tci.TotalElysBonded.Add(k.tci.TotalEdenEdenBoostCommitted)

// Ensure totalStakedSnapshot is not zero to avoid division by zero
if totalStakedSnapshot.IsZero() {
return sdk.ZeroInt(), nil
}

// Calculate
edenAmountPerEpochStakersPerDay := stkIncentive.EdenAmountPerYear.Mul(stkIncentive.AllocationEpochInBlocks).Quo(stkIncentive.TotalBlocksPerYear)

Expand Down Expand Up @@ -119,6 +124,11 @@ func (k Keeper) CalculateApr(ctx sdk.Context, query *types.QueryAprRequest) (sdk
k.UpdateTotalCommitmentInfo(ctx, baseCurrency)
totalStakedSnapshot := k.tci.TotalElysBonded.Add(k.tci.TotalEdenEdenBoostCommitted)

// Ensure totalStakedSnapshot is not zero to avoid division by zero
if totalStakedSnapshot.IsZero() {
return sdk.ZeroInt(), nil
}

// Usdc apr for elys staking = (24 hour dex rewards in USDC generated for stakers) * 365*100/ {price ( elys/usdc)*( sum of (elys staked, Eden committed, Eden boost committed))}
// we multiply 10 as we have use 10elys as input in the price estimation
apr := amtDexRewardPerDay.MulInt(sdk.NewInt(ptypes.DaysPerYear)).MulInt(sdk.NewInt(100)).MulInt(sdk.NewInt(10)).QuoInt(edenPrice).QuoInt(totalStakedSnapshot)
Expand Down
10 changes: 10 additions & 0 deletions x/incentive/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ func (k Keeper) UpdateStakersRewardsUnclaimed(ctx sdk.Context, stakeIncentive ty
// Calculate eden amount per epoch
params := k.GetParams(ctx)

// Ensure stakeIncentive.TotalBlocksPerYear or stakeIncentive.AllocationEpochInBlocks are not zero to avoid division by zero
if stakeIncentive.TotalBlocksPerYear.IsZero() || stakeIncentive.AllocationEpochInBlocks.IsZero() {
return sdkerrors.Wrap(types.ErrNoNonInflationaryParams, "invalid inflationary params")
}

// Calculate
edenAmountPerEpochStakersPerDay := stakeIncentive.EdenAmountPerYear.Mul(stakeIncentive.AllocationEpochInBlocks).Quo(stakeIncentive.TotalBlocksPerYear)

Expand Down Expand Up @@ -353,6 +358,11 @@ func (k Keeper) UpdateLPRewardsUnclaimed(ctx sdk.Context, lpIncentive types.Ince
// Proxy TVL = 20*0.3+30*0.5+40*1.0
totalProxyTVL := k.CalculateProxyTVL(ctx, baseCurrency)

// Ensure lpIncentive.TotalBlocksPerYear or lpIncentive.AllocationEpochInBlocks are not zero to avoid division by zero
if lpIncentive.TotalBlocksPerYear.IsZero() || lpIncentive.AllocationEpochInBlocks.IsZero() {
return sdkerrors.Wrap(types.ErrNoNonInflationaryParams, "invalid inflationary params")
}

// Calculate eden amount per epoch
edenAmountPerEpochLPsPerDay := lpIncentive.EdenAmountPerYear.Mul(lpIncentive.AllocationEpochInBlocks).Quo(lpIncentive.TotalBlocksPerYear)

Expand Down
Loading

0 comments on commit 326301f

Please sign in to comment.