Skip to content

Commit

Permalink
feat: fix expansion rounding issue (#548)
Browse files Browse the repository at this point in the history
  • Loading branch information
philbow61 authored Nov 25, 2024
1 parent 6295656 commit ea5d779
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 22 deletions.
7 changes: 6 additions & 1 deletion contracts/goodDollar/GoodDollarExchangeProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ contract GoodDollarExchangeProvider is IGoodDollarExchangeProvider, BancorExchan
PoolExchange memory exchange = getPoolExchange(exchangeId);

UD60x18 scaledRatio = wrap(uint256(exchange.reserveRatio) * 1e10);
UD60x18 newRatio = scaledRatio.mul(wrap(reserveRatioScalar));

// The division and multiplication by 1e10 here ensures that the new ratio used for calculating the amount to mint
// is the same as the one set in the exchange but only scaled to 18 decimals.
// Ignored, because the division and multiplication by 1e10 is needed see comment above.
// slither-disable-next-line divide-before-multiply
UD60x18 newRatio = wrap((unwrap(scaledRatio.mul(wrap(reserveRatioScalar))) / 1e10) * 1e10);

uint32 newRatioUint = uint32(unwrap(newRatio) / 1e10);
require(newRatioUint > 0, "New ratio must be greater than 0");
Expand Down
39 changes: 18 additions & 21 deletions test/unit/goodDollar/GoodDollarExchangeProvider.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,12 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
function test_mintFromExpansion_whenValidReserveRatioScalar_shouldReturnCorrectAmountAndEmit() public {
// reserveRatioScalar is (1-0.000288617289022312) based of 10% yearly expansion rate
// Formula: amountToMint = (tokenSupply * reserveRatio - tokenSupply * newRatio) / newRatio
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * (1-0.000288617289022312) = 0.285631817919071438
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.285631817919071438) / 0.285631817919071438
// ≈ 2_020_904,291074052815139287
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * (1-0.000288617289022312)
// newRatio = 0.28563181 (only 8 decimals)
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.28563181) / 0.28563181
// ≈ 2_021_098,420375517698816528
uint32 expectedReserveRatio = 28563181;
uint256 expectedAmountToMint = 2020904291074052815139287;
uint256 expectedAmountToMint = 2021098420375517698816528;
uint256 priceBefore = exchangeProvider.currentPrice(exchangeId);

vm.expectEmit(true, true, true, true);
Expand All @@ -371,18 +372,17 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
"Token supply should increase by minted amount"
);
assertEq(poolExchangeAfter.reserveRatio, expectedReserveRatio, "Reserve ratio should be updated correctly");
// 0.01% relative error tolerance because of precision loss when new reserve ratio is calculated
assertApproxEqRel(priceBefore, priceAfter, 1e18 * 0.0001, "Price should remain within 0.01% of initial price");
assertEq(priceBefore, priceAfter, "Price should remain unchanged");
}

function test_mintFromExpansion_withSmallReserveRatioScalar_shouldReturnCorrectAmount() public {
uint256 smallReserveRatioScalar = 1e18 * 0.00001; // 0.001%
// Formula: amountToMint = (tokenSupply * reserveRatio - tokenSupply * newRatio) / newRatio
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * 1e13/1e18 = 0.0000028571428
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.0000028571428) /0.0000028571428
// amountToMint ≈ 699993000000000
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * 1e13/1e18 = 0.00000285 (only 8 decimals)
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.00000285) /0.00000285
// amountToMint ≈ 701.747.371.929.824,561403508771929824
uint32 expectedReserveRatio = 285;
uint256 expectedAmountToMint = 699993000000000 * 1e18;
uint256 expectedAmountToMint = 701747371929824561403508771929824;
uint256 priceBefore = exchangeProvider.currentPrice(exchangeId);

vm.expectEmit(true, true, true, true);
Expand All @@ -400,18 +400,17 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
"Token supply should increase by minted amount"
);
assertEq(poolExchangeAfter.reserveRatio, expectedReserveRatio, "Reserve ratio should be updated correctly");
// 1% relative error tolerance because of precision loss when new reserve ratio is calculated
assertApproxEqRel(priceBefore, priceAfter, 1e18 * 0.01, "Price should remain within 1% of initial price");
assertEq(priceBefore, priceAfter, "Price should remain unchanged");
}

function test_mintFromExpansion_withLargeReserveRatioScalar_shouldReturnCorrectAmount() public {
uint256 largeReserveRatioScalar = 1e18 - 1; // Just below 100%
// Formula: amountToMint = (tokenSupply * reserveRatio - tokenSupply * newRatio) / newRatio
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * (1e18 -1)/1e18 ≈ 0.285714279999999999
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.285714279999999999) /0.285714279999999999
// amountToMint ≈ 0.00000002450000049000
// newRatio = reserveRatio * reserveRatioScalar = 0.28571428 * (1e18 -1)/1e18 ≈ 0.28571427 (only 8 decimals)
// amountToMint = (7_000_000_000 * 0.28571428 - 7_000_000_000 * 0.28571427) /0.28571427
// amountToMint ≈ 245.00001347500074112504
uint32 expectedReserveRatio = 28571427;
uint256 expectedAmountToMint = 24500000490;
uint256 expectedAmountToMint = 245000013475000741125;
uint256 priceBefore = exchangeProvider.currentPrice(exchangeId);

vm.expectEmit(true, true, true, true);
Expand All @@ -429,8 +428,7 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
"Token supply should increase by minted amount"
);
assertEq(poolExchangeAfter.reserveRatio, expectedReserveRatio, "Reserve ratio should be updated correctly");
// 0.01% relative error tolerance because of precision loss when new reserve ratio is calculated
assertApproxEqRel(priceBefore, priceAfter, 1e18 * 0.0001, "Price should remain within 0.01% of initial price");
assertEq(priceBefore, priceAfter, "Price should remain unchanged");
}

function test_mintFromExpansion_withMultipleConsecutiveExpansions_shouldMintCorrectly() public {
Expand Down Expand Up @@ -469,7 +467,7 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
1e18 * 0.0001, // 0.01% relative error tolerance because of precision loss when new reserve ratio is calculated
"Reserve ratio should be updated correctly within 0.01% tolerance"
);
assertApproxEqRel(initialPrice, priceAfter, 1e18 * 0.0001, "Price should remain within 0.01% of initial price");
assertEq(initialPrice, priceAfter, "Price should remain unchanged");
}

function testFuzz_mintFromExpansion(uint256 _reserveRatioScalar) public {
Expand Down Expand Up @@ -497,8 +495,7 @@ contract GoodDollarExchangeProviderTest_mintFromExpansion is GoodDollarExchangeP
initialTokenSupply + amountToMint,
"Token supply should increase by minted amount"
);
// 1% relative error tolerance because of precision loss when new reserve ratio is calculated
assertApproxEqRel(priceBefore, priceAfter, 1e18 * 0.01, "Price should remain within 1% of initial price");
assertEq(priceBefore, priceAfter, "Price should remain unchanged");
}
}

Expand Down

0 comments on commit ea5d779

Please sign in to comment.