Skip to content

Commit

Permalink
fix: revert back to writing instant prices for all updates
Browse files Browse the repository at this point in the history
  • Loading branch information
xenide committed Mar 16, 2024
1 parent cef2075 commit 91aaff4
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 57 deletions.
8 changes: 4 additions & 4 deletions script/optimized-deployer-meta
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"constant_product_hash": "0x77c5c3ef9b580effb26c04af13bb63d1e97351d2a8689539040ae96463e2abd0",
"factory_hash": "0x25b913ad2532eb731a94c0d5fb82e2e83cbb3cd143c78839da1f6438d0cebe03",
"oracle_caller_hash": "0x66e028dd05cd4a19797a499ee9a8bc992775e66648091b986049496b25735717",
"stable_hash": "0x30daa056ce5458164ec4385a9ccbe708da7312e79e2df6add034a2bbe57c8edf"
"constant_product_hash": "0x2e1855a9c7c865ffebad4f447ecfab47e1d6ab831296df75001daaf007e5267b",
"factory_hash": "0xb2ef8e3586799b92cdb144a1b3c54b4d45e0c009c3f4cf5df1f2a1c215f60678",
"oracle_caller_hash": "0x52cab92c606b06d97c858917be569d00b837961e6b3ae9ab691f51ca4112485d",
"stable_hash": "0xd3f00ad4687a40bec25b7db2093104770e2038cab61df9513a446bcd025c4835"
}
8 changes: 4 additions & 4 deletions src/ReservoirDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ contract ReservoirDeployer {
uint256 public step = 0;

// Bytecode hashes.
bytes32 public constant FACTORY_HASH = bytes32(0x25b913ad2532eb731a94c0d5fb82e2e83cbb3cd143c78839da1f6438d0cebe03);
bytes32 public constant FACTORY_HASH = bytes32(0xb2ef8e3586799b92cdb144a1b3c54b4d45e0c009c3f4cf5df1f2a1c215f60678);
bytes32 public constant CONSTANT_PRODUCT_HASH =
bytes32(0x77c5c3ef9b580effb26c04af13bb63d1e97351d2a8689539040ae96463e2abd0);
bytes32 public constant STABLE_HASH = bytes32(0x30daa056ce5458164ec4385a9ccbe708da7312e79e2df6add034a2bbe57c8edf);
bytes32(0x2e1855a9c7c865ffebad4f447ecfab47e1d6ab831296df75001daaf007e5267b);
bytes32 public constant STABLE_HASH = bytes32(0xd3f00ad4687a40bec25b7db2093104770e2038cab61df9513a446bcd025c4835);
bytes32 public constant ORACLE_CALLER_HASH =
bytes32(0x66e028dd05cd4a19797a499ee9a8bc992775e66648091b986049496b25735717);
bytes32(0x52cab92c606b06d97c858917be569d00b837961e6b3ae9ab691f51ca4112485d);

// Deployment addresses.
GenericFactory public factory;
Expand Down
50 changes: 37 additions & 13 deletions src/ReservoirPair.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 {

/// @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 We write an oracle sample even at the time of pair creation. The price is not updated on subsequent swaps as manipulating
/// @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.
function _updateAndUnlock(
Slot0 storage sSlot0,
Expand All @@ -158,21 +158,36 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 {
lTimeElapsed = lBlockTimestamp - aBlockTimestampLast;

// both balance should never be zero, but necessary to check so we don't pass 0 values into arithmetic operations
// shortcut to calculate lTimeElapsed > 0 && aBalance0 > 0 && aBalance1 > 0
if (lTimeElapsed * aBalance0 * aBalance1 > 0) {
// shortcut to calculate aBalance0 > 0 && aBalance1 > 0
if (aBalance0 * aBalance1 > 0) {
Observation storage lPrevious = _observations[sSlot0.index];
(uint256 lInstantRawPrice, int256 lLogInstantRawPrice) = _calcSpotAndLogPrice(aBalance0, aBalance1);

(, int256 lLogInstantClampedPrice) = _calcClampedPrice(
lInstantRawPrice,
lLogInstantRawPrice,
LogCompression.fromLowResLog(lPrevious.logInstantClampedPrice),
lTimeElapsed,
aBlockTimestampLast // assert: aBlockTimestampLast == lPrevious.timestamp
);
_updateOracleNewSample(
sSlot0, lPrevious, lLogInstantRawPrice, lLogInstantClampedPrice, lTimeElapsed, lBlockTimestamp
);
if (lTimeElapsed * aReserve0 * aReserve1 > 0) {
(, int256 lLogInstantClampedPrice) = _calcClampedPrice(
lInstantRawPrice,
lLogInstantRawPrice,
LogCompression.fromLowResLog(lPrevious.logInstantClampedPrice),
lTimeElapsed,
aBlockTimestampLast // assert: aBlockTimestampLast == lPrevious.timestamp
);
_updateOracleNewSample(
sSlot0, lPrevious, lLogInstantRawPrice, lLogInstantClampedPrice, lTimeElapsed, lBlockTimestamp
);
} else {
// for instant price updates in the same timestamp, we use the time difference from the previous oracle observation as the time elapsed
lTimeElapsed = lBlockTimestamp - lPrevious.timestamp;

(, int256 lLogInstantClampedPrice) = _calcClampedPrice(
lInstantRawPrice,
lLogInstantRawPrice,
LogCompression.fromLowResLog(lPrevious.logInstantClampedPrice),
lTimeElapsed,
lPrevious.timestamp
);

_updateOracleInstantPrices(lPrevious, lLogInstantRawPrice, lLogInstantClampedPrice);
}
}
}

Expand Down Expand Up @@ -584,6 +599,15 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 {
}
}

function _updateOracleInstantPrices(
Observation storage aPrevious,
int256 aLogInstantRawPrice,
int256 aLogInstantClampedPrice
) internal {
aPrevious.logInstantRawPrice = int24(aLogInstantRawPrice);
aPrevious.logInstantClampedPrice = int24(aLogInstantClampedPrice);
}

/// @param aBalance0 The balance of token0 in its native precision.
/// @param aBalance1 The balance of token1 in its native precision.
/// @return spotPrice Expressed as 1e18 == 1.
Expand Down
72 changes: 36 additions & 36 deletions test/unit/OracleWriter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,34 +63,34 @@ contract OracleWriterTest is BaseTest {

// sanity
(,,, uint16 lIndex) = _pair.getReserves();
assertEq(lIndex, 2);
assertEq(lIndex, 1);

Observation memory lObs = _oracleCaller.observation(_pair, 1);
Observation memory lObs = _oracleCaller.observation(_pair, 0);
assertEq(lObs.logAccRawPrice, 0);
assertEq(lObs.logAccClampedPrice, 0);
assertNotEq(lObs.logInstantRawPrice, 0);
assertNotEq(lObs.logInstantClampedPrice, 0);
assertNotEq(lObs.timestamp, 0);

lObs = _oracleCaller.observation(_pair, 2);
lObs = _oracleCaller.observation(_pair, 1);
assertNotEq(lObs.logAccRawPrice, 0);
assertNotEq(lObs.logAccClampedPrice, 0);
assertNotEq(lObs.logInstantRawPrice, 0);
assertNotEq(lObs.logInstantClampedPrice, 0);
assertNotEq(lObs.timestamp, 0);

// act
_writeObservation(_pair, 1, int24(123), int24(-456), int88(789), int56(-1011), uint32(666));
_writeObservation(_pair, 0, int24(123), int24(-456), int88(789), int56(-1011), uint32(666));

// assert
lObs = _oracleCaller.observation(_pair, 1);
lObs = _oracleCaller.observation(_pair, 0);
assertEq(lObs.logInstantRawPrice, int24(123));
assertEq(lObs.logInstantClampedPrice, int24(-456));
assertEq(lObs.logAccRawPrice, int88(789));
assertEq(lObs.logAccClampedPrice, int88(-1011));
assertEq(lObs.timestamp, uint32(666));

lObs = _oracleCaller.observation(_pair, 2);
lObs = _oracleCaller.observation(_pair, 1);
assertNotEq(lObs.logAccRawPrice, 0);
assertNotEq(lObs.logAccClampedPrice, 0);
assertNotEq(lObs.timestamp, 0);
Expand Down Expand Up @@ -184,17 +184,17 @@ contract OracleWriterTest is BaseTest {
assertEq(lFinalIndex, lInitialIndex);
}

// instant price should not update even if multiple activities happen in the same block
function testUpdateOracle_CreatePairThenSwapSameBlock() external allPairs {
// instant price should update when multiple activities happen in the same block
function testUpdateOracle_MintThenSwapSameBlock() external allPairs {
// arrange
uint256 lOriginalPrice = 1e18;
uint256 lSwapAmt = 10e18;

// sanity
(,,, uint16 lIndex) = _pair.getReserves();
Observation memory lObs = _oracleCaller.observation(_pair, lIndex);
assertEq(lIndex, 0);
assertEq(LogCompression.fromLowResLog(lObs.logInstantRawPrice), lOriginalPrice);
assertEq(lIndex, type(uint16).max);
assertEq(LogCompression.fromLowResLog(lObs.logInstantRawPrice), lOriginalPrice, "instant 1");

// act
_tokenA.mint(address(_pair), lSwapAmt);
Expand All @@ -204,16 +204,16 @@ contract OracleWriterTest is BaseTest {
(,,, lIndex) = _pair.getReserves();
lObs = _oracleCaller.observation(_pair, lIndex);

assertEq(lIndex, 0);
assertEq(LogCompression.fromLowResLog(lObs.logInstantRawPrice), lOriginalPrice);
assertEq(LogCompression.fromLowResLog(lObs.logInstantClampedPrice), lOriginalPrice);
assertEq(lIndex, type(uint16).max);
assertNotEq(LogCompression.fromLowResLog(lObs.logInstantRawPrice), lOriginalPrice);
assertNotEq(LogCompression.fromLowResLog(lObs.logInstantClampedPrice), lOriginalPrice);

(, int256 lLogSpotPrice) = _calcPriceForCurve(_pair);
assertNotEq(lObs.logInstantRawPrice, lLogSpotPrice);
assertEq(lObs.logInstantRawPrice, lLogSpotPrice);
assertEq(lObs.logInstantClampedPrice, lLogSpotPrice);
}

// accumulators and instant prices should not update even though many of them
// take place in the same block
// accumulator should not update but instant prices should update
function testUpdateOracle_MultipleSwapsSameBlock() external allPairs {
// arrange
_stepTime(5);
Expand All @@ -226,30 +226,30 @@ contract OracleWriterTest is BaseTest {

// sanity - observation after first swap
(,,, uint16 lIndex) = _pair.getReserves();
assertEq(lIndex, 1);
Observation memory lObs1 = _oracleCaller.observation(_pair, lIndex);
assertEq(lIndex, 0);
Observation memory lObs0 = _oracleCaller.observation(_pair, lIndex);

// second swap
_tokenA.mint(address(_pair), lSwapAmt);
_pair.swap(int256(lSwapAmt), true, address(this), "");

Observation memory lObs2 = _oracleCaller.observation(_pair, lIndex);
Observation memory lObs1 = _oracleCaller.observation(_pair, lIndex);

// third swap
_tokenA.mint(address(_pair), lSwapAmt);
_pair.swap(int256(lSwapAmt), true, address(this), "");

Observation memory lObs3 = _oracleCaller.observation(_pair, lIndex);
Observation memory lObs2 = _oracleCaller.observation(_pair, lIndex);

// assert
assertEq(lObs0.timestamp, lObs1.timestamp);
assertEq(lObs1.timestamp, lObs2.timestamp);
assertEq(lObs2.timestamp, lObs3.timestamp);
assertEq(lObs0.logAccRawPrice, lObs1.logAccRawPrice);
assertEq(lObs1.logAccRawPrice, lObs2.logAccRawPrice);
assertEq(lObs2.logAccRawPrice, lObs3.logAccRawPrice);
assertEq(lObs0.logAccClampedPrice, lObs1.logAccClampedPrice);
assertEq(lObs1.logAccClampedPrice, lObs2.logAccClampedPrice);
assertEq(lObs2.logAccClampedPrice, lObs3.logAccClampedPrice);
assertEq(lObs1.logInstantRawPrice, lObs2.logInstantRawPrice);
assertEq(lObs2.logInstantRawPrice, lObs3.logInstantRawPrice);
assertNotEq(lObs0.logInstantRawPrice, lObs1.logInstantRawPrice);
assertNotEq(lObs1.logInstantRawPrice, lObs2.logInstantRawPrice);
}

function testUpdateOracle_AccumulateOldPricesNotNew() external allPairs {
Expand Down Expand Up @@ -348,19 +348,19 @@ contract OracleWriterTest is BaseTest {

// sanity - ensure that two oracle observations have been written at slots 1 and 2
(,,, uint16 lIndex) = lCP.getReserves();
assertEq(lIndex, 2);
assertEq(lIndex, 1);
(,,, lIndex) = lSP.getReserves();
assertEq(lIndex, 2);
assertEq(lIndex, 1);

// assert
Observation memory lObs0CP = _oracleCaller.observation(lCP, 0);
Observation memory lObs1CP = _oracleCaller.observation(lCP, 1);
Observation memory lObs2CP = _oracleCaller.observation(lCP, 2);
Observation memory lObs0SP = _oracleCaller.observation(lSP, 0);
Observation memory lObs1SP = _oracleCaller.observation(lSP, 1);
Observation memory lObs2SP = _oracleCaller.observation(lSP, 2);
uint256 lUncompressedPriceCP =
LogCompression.fromLowResLog((lObs2CP.logAccRawPrice - lObs1CP.logAccRawPrice) / 12);
LogCompression.fromLowResLog((lObs1CP.logAccRawPrice - lObs0CP.logAccRawPrice) / 12);
uint256 lUncompressedPriceSP =
LogCompression.fromLowResLog((lObs2SP.logAccRawPrice - lObs1SP.logAccRawPrice) / 12);
LogCompression.fromLowResLog((lObs1SP.logAccRawPrice - lObs0SP.logAccRawPrice) / 12);
assertEq(lUncompressedPriceCP, lUncompressedPriceSP);
}

Expand All @@ -385,14 +385,14 @@ contract OracleWriterTest is BaseTest {
_stepTime(12);
lCP.sync();
lSP.sync();
Observation memory lObsCP0 = _oracleCaller.observation(lCP, 0);
Observation memory lObsCP1 = _oracleCaller.observation(lCP, 1);
Observation memory lObsCP2 = _oracleCaller.observation(lCP, 2);
Observation memory lObsSP0 = _oracleCaller.observation(lSP, 0);
Observation memory lObsSP1 = _oracleCaller.observation(lSP, 1);
Observation memory lObsSP2 = _oracleCaller.observation(lSP, 2);
uint256 lUncompressedPriceCP =
LogCompression.fromLowResLog((lObsCP2.logAccRawPrice - lObsCP1.logAccRawPrice) / 12);
LogCompression.fromLowResLog((lObsCP1.logAccRawPrice - lObsCP0.logAccRawPrice) / 12);
uint256 lUncompressedPriceSP =
LogCompression.fromLowResLog((lObsSP2.logAccRawPrice - lObsSP1.logAccRawPrice) / 12);
LogCompression.fromLowResLog((lObsSP1.logAccRawPrice - lObsSP0.logAccRawPrice) / 12);
assertEq(lUncompressedPriceCP, lUncompressedPriceSP);
}

Expand All @@ -410,7 +410,7 @@ contract OracleWriterTest is BaseTest {

// assert
(,,, uint16 lIndex) = _pair.getReserves();
assertEq(lIndex, 4);
assertEq(lIndex, 3);
}

function testOracle_OverflowAccPrice(uint32 aNewStartTime) public randomizeStartTime(aNewStartTime) allPairs {
Expand Down

0 comments on commit 91aaff4

Please sign in to comment.