Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rounding error on consume liquidity #106

Merged
merged 5 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@
[submodule "lib/pt-v5-twab-controller"]
path = lib/pt-v5-twab-controller
url = https://github.com/generationsoftware/pt-v5-twab-controller
branch = prod-deploy-2
2 changes: 1 addition & 1 deletion lib/prb-math
Submodule prb-math updated 42 files
+0 −9 .github/ISSUE_TEMPLATE/1-bug-report.yml
+27 −3 .github/workflows/ci.yml
+0 −8 .gitmodules
+22 −0 CHANGELOG.md
+16 −20 README.md
+0 −1 foundry.toml
+0 −1 lib/forge-std
+0 −1 lib/prb-test
+6 −6 package.json
+314 −37 pnpm-lock.yaml
+2 −2 remappings.txt
+1 −1 src/Common.sol
+1 −1 src/sd59x18/Errors.sol
+0 −5 src/test/Assertions.sol
+0 −5 src/test/Utils.sol
+0 −1 src/ud2x18/Casting.sol
+1 −1 src/ud60x18/Math.sol
+2 −9 test/Base.t.sol
+0 −1 test/fuzz/sd1x18/casting/Casting.t.sol
+0 −2 test/fuzz/sd59x18/casting/Casting.t.sol
+0 −3 test/fuzz/sd59x18/helpers/Helpers.t.sol
+1 −2 test/fuzz/ud60x18/helpers/Helpers.t.sol
+2 −3 test/unit/sd59x18/SD59x18.t.sol
+0 −1 test/unit/sd59x18/conversion/convert-from/convertFrom.t.sol
+1 −1 test/unit/sd59x18/math/div/div.t.sol
+0 −1 test/unit/sd59x18/math/frac/frac.t.sol
+2 −2 test/unit/sd59x18/math/inv/inv.t.sol
+0 −2 test/unit/sd59x18/math/mul/mul.t.sol
+0 −1 test/unit/sd59x18/math/pow/pow.t.sol
+1 −1 test/unit/sd59x18/math/sqrt/sqrt.t.sol
+2 −3 test/unit/ud60x18/UD60x18.t.sol
+0 −1 test/unit/ud60x18/conversion/convert-from/convertFrom.t.sol
+1 −1 test/unit/ud60x18/math/div/div.t.sol
+1 −1 test/unit/ud60x18/math/gm/gm.t.sol
+1 −1 test/unit/ud60x18/math/inv/inv.t.sol
+0 −2 test/unit/ud60x18/math/ln/ln.t.sol
+0 −2 test/unit/ud60x18/math/log10/log10.t.sol
+0 −2 test/unit/ud60x18/math/log2/log2.t.sol
+0 −2 test/unit/ud60x18/math/mul/mul.t.sol
+0 −1 test/unit/ud60x18/math/pow/pow.t.sol
+1 −1 test/utils/Assertions.sol
+65 −65 test/utils/Utils.sol
5 changes: 2 additions & 3 deletions src/PrizePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { SD59x18, convert, sd } from "prb-math/SD59x18.sol";
import { SD1x18, unwrap, UNIT } from "prb-math/SD1x18.sol";
import { TwabController } from "pt-v5-twab-controller/TwabController.sol";

import { UD34x4, intoUD60x18 as fromUD34x4toUD60x18 } from "./libraries/UD34x4.sol";
import { DrawAccumulatorLib, Observation } from "./libraries/DrawAccumulatorLib.sol";
import { TieredLiquidityDistributor, Tier } from "./abstract/TieredLiquidityDistributor.sol";
import { TierCalculationLib } from "./libraries/TierCalculationLib.sol";
Expand Down Expand Up @@ -194,7 +193,7 @@ contract PrizePool is TieredLiquidityDistributor {
uint8 lastNumTiers,
uint8 numTiers,
uint104 reserve,
UD34x4 prizeTokensPerShare,
uint128 prizeTokensPerShare,
uint48 drawOpenedAt
);

Expand Down Expand Up @@ -668,7 +667,7 @@ contract PrizePool is TieredLiquidityDistributor {
(uint104 newReserve, ) = _computeNewDistributions(
_numTiers,
lastAwardedDrawId_ == 0 ? _numTiers : computeNextNumberOfTiers(claimCount),
fromUD34x4toUD60x18(prizeTokenPerShare),
prizeTokenPerShare,
getTotalContributedBetween(lastAwardedDrawId_ + 1, getDrawIdToAward())
);

Expand Down
136 changes: 59 additions & 77 deletions src/abstract/TieredLiquidityDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { SafeCast } from "openzeppelin/utils/math/SafeCast.sol";
import { SD59x18, sd } from "prb-math/SD59x18.sol";
import { UD60x18, ud, convert } from "prb-math/UD60x18.sol";

import { UD34x4, fromUD60x18 as fromUD60x18toUD34x4, intoUD60x18 as fromUD34x4toUD60x18 } from "../libraries/UD34x4.sol";
import { TierCalculationLib } from "../libraries/TierCalculationLib.sol";

/// @notice Struct that tracks tier liquidity information.
Expand All @@ -16,7 +15,7 @@ import { TierCalculationLib } from "../libraries/TierCalculationLib.sol";
struct Tier {
uint24 drawId;
uint104 prizeSize;
UD34x4 prizeTokenPerShare;
uint128 prizeTokenPerShare;
}

/// @notice Emitted when the number of tiers is less than the minimum number of tiers.
Expand Down Expand Up @@ -117,7 +116,7 @@ contract TieredLiquidityDistributor {
/// @dev This is an ever-increasing exchange rate that is used to calculate the prize liquidity for each tier.
/// @dev Each tier holds a separate tierPrizeTokenPerShare; the delta between the tierPrizeTokenPerShare and
/// the prizeTokenPerShare * tierShares is the available liquidity they have.
UD34x4 public prizeTokenPerShare;
uint128 public prizeTokenPerShare;

/// @notice The number of tiers for the last awarded draw. The last tier is the canary tier.
uint8 public numberOfTiers;
Expand Down Expand Up @@ -222,12 +221,11 @@ contract TieredLiquidityDistributor {
}

uint8 numTiers = numberOfTiers;
UD34x4 _prizeTokenPerShare = prizeTokenPerShare;
UD60x18 _prizeTokenPerShareUD60x18 = fromUD34x4toUD60x18(_prizeTokenPerShare);
(uint96 deltaReserve, UD60x18 newPrizeTokenPerShare) = _computeNewDistributions(
uint128 _prizeTokenPerShare = prizeTokenPerShare;
(uint96 deltaReserve, uint128 newPrizeTokenPerShare) = _computeNewDistributions(
numTiers,
_nextNumberOfTiers,
_prizeTokenPerShareUD60x18,
_prizeTokenPerShare,
_prizeTokenLiquidity
);

Expand All @@ -240,13 +238,13 @@ contract TieredLiquidityDistributor {
prizeSize: _computePrizeSize(
i,
_nextNumberOfTiers,
_prizeTokenPerShareUD60x18,
_prizeTokenPerShare,
newPrizeTokenPerShare
)
});
}

prizeTokenPerShare = fromUD60x18toUD34x4(newPrizeTokenPerShare);
prizeTokenPerShare = newPrizeTokenPerShare;
numberOfTiers = _nextNumberOfTiers;
_lastAwardedDrawId = _awardingDraw;
lastAwardedDrawAwardedAt = uint48(block.timestamp);
Expand All @@ -263,30 +261,30 @@ contract TieredLiquidityDistributor {
function _computeNewDistributions(
uint8 _numberOfTiers,
uint8 _nextNumberOfTiers,
UD60x18 _currentPrizeTokenPerShare,
uint128 _currentPrizeTokenPerShare,
uint256 _prizeTokenLiquidity
) internal view returns (uint96 deltaReserve, UD60x18 newPrizeTokenPerShare) {
UD60x18 reclaimedLiquidity;
) internal view returns (uint96 deltaReserve, uint128 newPrizeTokenPerShare) {
uint256 reclaimedLiquidity;
{
// need to redistribute to the canary tier and any new tiers (if expanding)
uint8 start = _computeReclamationStart(_numberOfTiers, _nextNumberOfTiers);
uint8 end = _numberOfTiers;
for (uint8 i = start; i < end; i++) {
reclaimedLiquidity = reclaimedLiquidity.add(
reclaimedLiquidity = reclaimedLiquidity + (
_getTierRemainingLiquidity(
fromUD34x4toUD60x18(_tiers[i].prizeTokenPerShare),
_tiers[i].prizeTokenPerShare,
_currentPrizeTokenPerShare,
i
_numShares(i, _numberOfTiers)
)
);
}
}

uint256 totalNewLiquidity = _prizeTokenLiquidity + convert(reclaimedLiquidity);
uint256 totalNewLiquidity = _prizeTokenLiquidity + reclaimedLiquidity;
uint256 nextTotalShares = computeTotalShares(_nextNumberOfTiers);
uint256 deltaPrizeTokensPerShare = totalNewLiquidity / nextTotalShares;

newPrizeTokenPerShare = _currentPrizeTokenPerShare.add(convert(deltaPrizeTokensPerShare));
newPrizeTokenPerShare = SafeCast.toUint128(_currentPrizeTokenPerShare + deltaPrizeTokensPerShare);

deltaReserve = SafeCast.toUint96(
// reserve portion of new liquidity
Expand Down Expand Up @@ -328,8 +326,8 @@ contract TieredLiquidityDistributor {
tier.prizeSize = _computePrizeSize(
_tier,
_numberOfTiers,
fromUD34x4toUD60x18(tier.prizeTokenPerShare),
fromUD34x4toUD60x18(prizeTokenPerShare)
tier.prizeTokenPerShare,
prizeTokenPerShare
);
}
return tier;
Expand Down Expand Up @@ -364,13 +362,12 @@ contract TieredLiquidityDistributor {
/// @param _tier The tier number
/// @param _liquidity The amount of liquidity to consume
function _consumeLiquidity(Tier memory _tierStruct, uint8 _tier, uint104 _liquidity) internal {
uint8 _tierShares = _numShares(_tier, numberOfTiers);
uint104 remainingLiquidity = SafeCast.toUint104(
convert(
_getTierRemainingLiquidity(
fromUD34x4toUD60x18(_tierStruct.prizeTokenPerShare),
fromUD34x4toUD60x18(prizeTokenPerShare),
_tier
)
_getTierRemainingLiquidity(
_tierStruct.prizeTokenPerShare,
prizeTokenPerShare,
_tierShares
)
);

Expand All @@ -388,10 +385,18 @@ contract TieredLiquidityDistributor {
emit ReserveConsumed(excess);
_tierStruct.prizeTokenPerShare = prizeTokenPerShare;
} else {
_tierStruct.prizeTokenPerShare = UD34x4.wrap(
UD34x4.unwrap(_tierStruct.prizeTokenPerShare) +
UD34x4.unwrap(fromUD60x18toUD34x4(convert(_liquidity).div(convert(_numShares(_tier, numberOfTiers)))))
);
uint8 _remainder = uint8(_liquidity % _tierShares);
uint8 _roundUpConsumption = _remainder == 0 ? 0 : _tierShares - _remainder;
if (_roundUpConsumption > 0) {
// We must round up our tier prize token per share value to ensure we don't over-award the tier's
// liquidity, but any extra rounded up consumption can be contributed to the reserve so every wei
// is accounted for.
_reserve += _roundUpConsumption;
}

// We know that the rounded up `liquidity` won't exceed the `remainingLiquidity` since the `remainingLiquidity`
// is an integer multiple of `_tierShares` and we check above that `_liquidity <= remainingLiquidity`.
_tierStruct.prizeTokenPerShare += SafeCast.toUint104(uint256(_liquidity) + _roundUpConsumption) / _tierShares;
}

_tiers[_tier] = _tierStruct;
Expand All @@ -406,18 +411,19 @@ contract TieredLiquidityDistributor {
function _computePrizeSize(
uint8 _tier,
uint8 _numberOfTiers,
UD60x18 _tierPrizeTokenPerShare,
UD60x18 _prizeTokenPerShare
uint128 _tierPrizeTokenPerShare,
uint128 _prizeTokenPerShare
) internal view returns (uint104) {
uint256 prizeSize;
if (_prizeTokenPerShare.gt(_tierPrizeTokenPerShare)) {
prizeSize = _computePrizeSize(
_tierPrizeTokenPerShare,
_prizeTokenPerShare,
convert(TierCalculationLib.prizeCount(_tier)),
_numShares(_tier, _numberOfTiers)
);
}
uint256 prizeCount = TierCalculationLib.prizeCount(_tier);
uint256 remainingTierLiquidity = _getTierRemainingLiquidity(
_tierPrizeTokenPerShare,
_prizeTokenPerShare,
_numShares(_tier, _numberOfTiers)
);

uint256 prizeSize = convert(
convert(remainingTierLiquidity).mul(tierLiquidityUtilizationRate).div(convert(prizeCount))
);

return prizeSize > type(uint104).max ? type(uint104).max : uint104(prizeSize);
}
Expand All @@ -438,41 +444,17 @@ contract TieredLiquidityDistributor {
return result;
}

/// @notice Computes the prize size with the given parameters.
/// @param _tierPrizeTokenPerShare The prizeTokenPerShare of the Tier struct
/// @param _prizeTokenPerShare The global prizeTokenPerShare
/// @param _fractionalPrizeCount The prize count as UD60x18
/// @param _shares The number of shares that the tier has
/// @return The prize size
function _computePrizeSize(
UD60x18 _tierPrizeTokenPerShare,
UD60x18 _prizeTokenPerShare,
UD60x18 _fractionalPrizeCount,
uint8 _shares
) internal view returns (uint256) {
return
convert(
_prizeTokenPerShare.sub(_tierPrizeTokenPerShare).mul(convert(_shares)).mul(tierLiquidityUtilizationRate).div(
_fractionalPrizeCount
)
);
}

/// @notice Computes the remaining liquidity available to a tier.
/// @param _tier The tier to compute the liquidity for
/// @return The remaining liquidity
function getTierRemainingLiquidity(uint8 _tier) public view returns (uint256) {
uint8 _numTiers = numberOfTiers;
if (TierCalculationLib.isValidTier(_tier, _numTiers)) {
UD60x18 remaining = _getTierRemainingLiquidity(
fromUD34x4toUD60x18(_getTier(_tier, _numTiers).prizeTokenPerShare),
fromUD34x4toUD60x18(prizeTokenPerShare),
_tier
);
uint result = convert(
remaining
return _getTierRemainingLiquidity(
_getTier(_tier, _numTiers).prizeTokenPerShare,
prizeTokenPerShare,
_numShares(_tier, _numTiers)
);
return result;
} else {
return 0;
}
Expand All @@ -481,17 +463,17 @@ contract TieredLiquidityDistributor {
/// @notice Computes the remaining tier liquidity.
/// @param _tierPrizeTokenPerShare The prizeTokenPerShare of the Tier struct
/// @param _prizeTokenPerShare The global prizeTokenPerShare
/// @param _tierShares The number of shares for the tier
/// @return The remaining available liquidity
function _getTierRemainingLiquidity(
UD60x18 _tierPrizeTokenPerShare,
UD60x18 _prizeTokenPerShare,
uint8 _tier
) internal view returns (UD60x18) {
uint8 numShares = _numShares(_tier, numberOfTiers);
UD60x18 result =
_tierPrizeTokenPerShare.gte(_prizeTokenPerShare)
? ud(0)
: _prizeTokenPerShare.sub(_tierPrizeTokenPerShare).mul(convert(numShares));
uint128 _tierPrizeTokenPerShare,
uint128 _prizeTokenPerShare,
uint8 _tierShares
) internal pure returns (uint256) {
uint256 result =
_tierPrizeTokenPerShare >= _prizeTokenPerShare
? 0
: uint256(_prizeTokenPerShare - _tierPrizeTokenPerShare) * _tierShares;
return result;
}

Expand Down
70 changes: 0 additions & 70 deletions src/libraries/UD34x4.sol

This file was deleted.

Loading
Loading