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: edge case for StableOracleMath::calcSpotPrice #242

Merged
merged 3 commits into from
Jan 30, 2025
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
242 changes: 122 additions & 120 deletions .gas-snapshot

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion script/optimized-deployer-meta
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"constant_product_hash": "0x93ab8e061520b6e2ef0a5b8f74d079422e344d2269d2d6b76aa9dba408ba5e21",
"factory_hash": "0x87b0f73fafcf4bb41e013c8423dc679f6885527007d6c3f1e1834a670cbaadc5",
"stable_hash": "0xf9f3866be96541eb0fbce49bd3ce66a5dc99c27f2526d58eeee70d1ff112dc66"
"stable_hash": "0x5c8200d1be920e4489ac1d1114a128d8ce361b4ce74f0eda29e4c391475cbea8"
}
2 changes: 1 addition & 1 deletion script/unoptimized-deployer-meta
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"constant_product_hash": "0xc2e3d64a67ce9ef6ddb3d62e8ada01f1d4122ad898554a3c42ca632fab578a3b",
"factory_hash": "0x09a9ce1ed77c95be4842dddd771939e048b8bfe2837863be3a2766b1c13ea5a2",
"stable_hash": "0x3424fe2949efc0235e6b588a8b2e55e0235dc14c18b6d2c4e55f97f668d5cfc0"
"stable_hash": "0xe192aaf23a5c5767d96344be00ccd160681aded49290915eefa4a6e0a0028b76"
}
2 changes: 1 addition & 1 deletion src/ReservoirDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ contract ReservoirDeployer {
bytes32 public constant FACTORY_HASH = bytes32(0x87b0f73fafcf4bb41e013c8423dc679f6885527007d6c3f1e1834a670cbaadc5);
bytes32 public constant CONSTANT_PRODUCT_HASH =
bytes32(0x93ab8e061520b6e2ef0a5b8f74d079422e344d2269d2d6b76aa9dba408ba5e21);
bytes32 public constant STABLE_HASH = bytes32(0xf9f3866be96541eb0fbce49bd3ce66a5dc99c27f2526d58eeee70d1ff112dc66);
bytes32 public constant STABLE_HASH = bytes32(0x5c8200d1be920e4489ac1d1114a128d8ce361b4ce74f0eda29e4c391475cbea8);

// Deployment addresses.
GenericFactory public factory;
Expand Down
4 changes: 2 additions & 2 deletions src/ReservoirPair.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20, RGT {
/// @notice Updates reserves with new balances.
/// @notice On the first call per block, accumulate price oracle using previous instant prices and write the new
/// instant prices.
/// @dev The price is not updated on subsequent swaps as manipulating
/// the instantaneous price does not materially affect the TWAP, especially when using clamped pricing.
/// @dev The instantaneous price is updated on subsequent swaps to prevent attackers from only having to manipulate
/// the first trade in the block to successfully affect the TWAP.
function _update(
uint256 aBalance0,
uint256 aBalance1,
Expand Down
13 changes: 4 additions & 9 deletions src/libraries/StableOracleMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,14 @@ library StableOracleMath {

uint256 by = b.mulWad(reserve1);
uint256 ay2 = (a * reserve1).mulWad(reserve1);
if (by > axy2 + ay2) return 1e18;
if (by >= axy2 + ay2) return 1e18;
// dx = a.x.y.2 + a.y^2 - b.y
uint256 derivativeX = axy2 + ay2 - by;
uint256 derivativeX = by < axy2 + ay2 ? axy2 + ay2 - by : 1;

// dy = a.x.y.2 + a.x^2 - b.x
uint256 bx = (b.mulWad(reserve0));
uint256 ax2 = (a * reserve0).mulWad(reserve0);
if (bx > axy2 + ax2) return 1e18;
uint256 derivativeY = axy2 + ax2 - bx;

// This is to prevent division by 0 which happens if reserve0 and reserve1 are sufficiently small (~1e6 after normalization) which can brick the pair
// If the reserves are that small, their prices will not be serving as price oracles, thus this is safe.
if (derivativeY == 0) return 1e18;
// dy = a.x.y.2 + a.x^2 - b.x
uint256 derivativeY = bx < axy2 + ax2 ? axy2 + ax2 - bx : 1;

// The rounding direction is irrelevant as we're about to introduce a much larger error when converting to log
// space. We use `divWadUp` as it prevents the result from being zero, which would make the logarithm revert. A
Expand Down
17 changes: 17 additions & 0 deletions test/unit/StablePair.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ contract StablePairTest is BaseTest {
assertEq(_stablePair.balanceOf(address(this)), lAdditionalLpTokens);
}

function testMint_VerySmallAmounts(uint256 aTokenAAmt, uint256 aTokenCAmt) external {
// assume
uint256 lTokenAAmt = bound(aTokenAAmt, 1e3, 1e9);
uint256 lTokenCAmt = bound(aTokenCAmt, 1e3, 1e9);

// arrange
StablePair lPair = StablePair(_createPair(address(_tokenA), address(_tokenC), 1));
_tokenA.mint(address(lPair), lTokenAAmt);
_tokenC.mint(address(lPair), lTokenCAmt);

// act
lPair.mint(address(this));

// assert
assertGt(lPair.balanceOf(address(this)), 0);
}

function testMint_Reenter() external {
// arrange
vm.prank(address(_factory));
Expand Down
9 changes: 9 additions & 0 deletions test/unit/libraries/StableOracleMath.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,13 @@ contract StableOracleMathTest is Test {
// assert
assertApproxEqRel(lSpotEstimated, lSpotCalculated, 0.000001e18); // 1% of 1bp, or a 0.0001% error
}

function testCalcLogPrice_VerySmallAmounts(uint256 aReserve0, uint256 aReserve1) external {
// assume
uint256 lReserve0 = bound(aReserve0, 1, 1e12);
uint256 lReserve1 = bound(aReserve1, 1, 1e12);

// act - this should never revert for all non-zero values of reserve0 and reserve1
StableOracleMath.calcLogPrice(100000, lReserve0, lReserve1);
}
}