-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* test: add cases for spot price calculation * fix: safeguards for not reverting * test: use estimates * lint: fix issues * ci: update hash * gas: update snapshot * ci: update hash * ci: update hashes * gas: update snapshot
- Loading branch information
Showing
8 changed files
with
250 additions
and
108 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"constant_product_hash": "0xe174de1f7ab5f7c871f23787d956a8d1b4ebbb3b195eb2d6af27fb3a8c9e812e", | ||
"factory_hash": "0x87b0f73fafcf4bb41e013c8423dc679f6885527007d6c3f1e1834a670cbaadc5", | ||
"stable_hash": "0x3ae886aee24fa2cc0144d24306033a7ed47e91bc0f962e4bffcef5922ae175f5" | ||
"stable_hash": "0x6f62531ebc702a07ab48405ba9437786af3664b83b0da26ec295402590ed738f" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"constant_product_hash": "0x89ba88e63d531f6343d8f30d0baf35d10eb900e4e9e5cf64e857a17d63c27863", | ||
"factory_hash": "0x09a9ce1ed77c95be4842dddd771939e048b8bfe2837863be3a2766b1c13ea5a2", | ||
"stable_hash": "0x2556755e769639b4fbeff548907a3a675f94209a24a97c7fca31eedb76567c2f" | ||
"stable_hash": "0xe1770a7f5ab45f8da1a575db115e93cc8f48d5c768f72090dfe9cc85c62dc8d4" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.10; | ||
|
||
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; | ||
import { StableMath } from "src/libraries/StableMath.sol"; | ||
|
||
// the original implementation without safeguards as implemented by balancer | ||
// https://github.com/balancer/balancer-v2-monorepo/blob/903d34e491a5e9c5d59dabf512c7addf1ccf9bbd/pkg/pool-stable/contracts/meta/StableOracleMath.sol | ||
library StableOracleMathCanonical { | ||
using FixedPointMathLib for uint256; | ||
|
||
function calcSpotPrice(uint256 amplificationParameter, uint256 reserve0, uint256 reserve1) | ||
internal | ||
view | ||
returns (uint256 spotPrice) | ||
{ | ||
// // | ||
// 2.a.x.y + a.y^2 + b.y // | ||
// spot price Y/X = - dx/dy = ----------------------- // | ||
// 2.a.x.y + a.x^2 + b.x // | ||
// // | ||
// n = 2 // | ||
// a = amp param * n // | ||
// b = D + a.(S - D) // | ||
// D = invariant // | ||
// S = sum of balances but x,y = 0 since x and y are the only tokens // | ||
|
||
uint256 invariant = | ||
StableMath._computeLiquidityFromAdjustedBalances(reserve0, reserve1, 2 * amplificationParameter); | ||
|
||
uint256 a = (amplificationParameter * 2) / StableMath.A_PRECISION; | ||
uint256 b = (invariant * a) - invariant; | ||
uint256 axy2 = (a * 2 * reserve0).mulWad(reserve1); // n = 2 | ||
|
||
// dx = a.x.y.2 + a.y^2 - b.y | ||
uint256 derivativeX = axy2 + ((a * reserve1).mulWad(reserve1)) - (b.mulWad(reserve1)); | ||
|
||
// dy = a.x.y.2 + a.x^2 - b.x | ||
uint256 derivativeY = axy2 + ((a * reserve0).mulWad(reserve0)) - (b.mulWad(reserve0)); | ||
|
||
// 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 | ||
// result of zero is therefore only possible with zero balances, which are prevented via other means. | ||
spotPrice = derivativeX.divWadUp(derivativeY); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.10; | ||
|
||
import "forge-std/Test.sol"; | ||
import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; | ||
import { StableOracleMath, StableMath } from "src/libraries/StableOracleMath.sol"; | ||
import { Constants } from "src/Constants.sol"; | ||
import { StableOracleMathCanonical } from "test/__mocks/StableOracleMathCanonical.sol"; | ||
|
||
contract StableOracleMathTest is Test { | ||
using FixedPointMathLib for uint256; | ||
|
||
uint256 internal _defaultAmp = Constants.DEFAULT_AMP_COEFF * StableMath.A_PRECISION; | ||
|
||
// estimates the spot price by giving a very small input to simulate dx (an infinitesimally small x) | ||
function estimateSpotPrice( | ||
uint256 reserve0, | ||
uint256 reserve1, | ||
uint256 token0Multiplier, | ||
uint256 token1Multiplier, | ||
uint256 N_A | ||
) internal pure returns (uint256 rPrice) { | ||
uint256 lInputAmt = 1e8; // anything smaller than 1e7 the error becomes larger, as experimented | ||
uint256 lOut = StableMath._getAmountOut(lInputAmt, reserve0, reserve1, token0Multiplier, token1Multiplier, true, 0, N_A); | ||
rPrice = lOut.divWadUp(lInputAmt); | ||
} | ||
|
||
function testPrice_Token0MoreExpensive() external { | ||
// arrange | ||
uint256 lToken0Amt = 1_000_000e18; | ||
uint256 lToken1Amt = 2_000_000e18; | ||
|
||
// act | ||
uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); | ||
|
||
// assert | ||
assertEq(lPrice, 1_000_842_880_946_746_931); | ||
} | ||
|
||
function testPrice_Token1MoreExpensive() external { | ||
// arrange | ||
uint256 lToken0Amt = 2_000_000e18; | ||
uint256 lToken1Amt = 1_000_000e18; | ||
|
||
// act | ||
uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); | ||
|
||
// assert | ||
assertEq(lPrice, 999_157_828_903_224_444); | ||
} | ||
|
||
function testCalcSpotPrice_CanonicalVersion_VerySmallAmounts(uint256 aToken0Amt, uint256 aToken1Amt) external { | ||
// assume - if token amounts exceed these amounts then they will probably not revert | ||
uint256 lToken0Amt = bound(aToken0Amt, 1, 1e6); | ||
uint256 lToken1Amt = bound(aToken1Amt, 1, 6e6); | ||
|
||
// act & assert - reverts when the amount is very small | ||
vm.expectRevert(); | ||
StableOracleMathCanonical.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); | ||
} | ||
|
||
function testCalcSpotPrice_VerySmallAmounts(uint256 aToken0Amt, uint256 aToken1Amt) external { | ||
// assume | ||
uint256 lToken0Amt = bound(aToken0Amt, 1, 1e6); | ||
uint256 lToken1Amt = bound(aToken1Amt, 1, 6e6); | ||
|
||
// act - does not revert in this case, but instead just returns 1e18 | ||
uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); | ||
|
||
// assert | ||
assertEq(lPrice, 1e18); | ||
} | ||
|
||
function testCalculatedSpotPriceIsCloseToEstimated(uint256 aReserve0, uint256 aReserve1) external { | ||
// assume | ||
uint256 lReserve0 = bound(aReserve0, 1e18, 1000e18); | ||
uint256 lReserve1 = bound(aReserve1, 1e18, 1000e18); | ||
|
||
// act | ||
uint256 lSpotEstimated = estimateSpotPrice(lReserve0, lReserve1, 1, 1, _defaultAmp * 2); | ||
uint256 lSpotCalculated = StableOracleMath.calcSpotPrice(_defaultAmp, lReserve0, lReserve1); | ||
|
||
// assert | ||
assertApproxEqRel(lSpotEstimated, lSpotCalculated, 0.000001e18); // 1% of 1bp, or a 0.0001% error | ||
} | ||
} |