-
Notifications
You must be signed in to change notification settings - Fork 28
Minimum Viable TWAP #736
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
Merged
Merged
Minimum Viable TWAP #736
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
22c58d3
draft impl
dapp-whisperer 1ff046d
add logging
dapp-whisperer 6e0f87d
twap=spot, existing EToFoundry
dapp-whisperer 88a93b9
more sync twap fixes
dapp-whisperer 3f9e440
fix failing tests and add event in _setValue()
rayeaster e7c3f30
add simulation for twap observe
rayeaster ead8985
add twap accumulator foundry test
rayeaster ef9cdf4
restore skew for weighted observe value
rayeaster e4f7961
fix failing hardhat tests after restore skew weighted observe()
rayeaster c2d82ba
fix failed CdpManagerTest
rayeaster e3c58ae
remove test only
rayeaster 23002b2
relax the tolerance for comparing redemption fee
rayeaster 6fcdb82
add foudnry test to show manipulation attack mitigated by twap
rayeaster c81fa1e
fix: `getRealValue` is redundant
GalloDaSballo 7f00214
fix: comment `setValue`
GalloDaSballo 7085607
fix: early return on 0
GalloDaSballo d6ef2dd
chore: comment
GalloDaSballo c7b7f12
chore: prettier
GalloDaSballo 0d95dd1
rename twap packed data for better clarity
rayeaster afbb362
chore: readability of names
GalloDaSballo 50032ac
chore: natspec
GalloDaSballo 0a7c1e3
fix: interface
GalloDaSballo 9b8a19c
fix: compilation / renaming
GalloDaSballo 1a6787c
Merge branch 'release-0.6' into feat/minimal-twap
c38ed96
fix failing foundry test by sync twap
rayeaster e160ef1
Merge remote-tracking branch 'ebtc/release-0.6' into feat/minimal-twap
rayeaster File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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
139 changes: 139 additions & 0 deletions
139
packages/contracts/contracts/Dependencies/TwapWeightedObserver.sol
This file contains hidden or 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,139 @@ | ||
// SPDX-License Identifier: MIT | ||
pragma solidity 0.8.17; | ||
import {ITwapWeightedObserver} from "../Interfaces/ITwapWeightedObserver.sol"; | ||
|
||
/// @title TwapWeightedObserver | ||
/// @notice Given a value, applies a time-weighted TWAP that smooths out changes over a 7 days period | ||
/// @dev Used to get the lowest value of total supply to prevent underpaying redemptions | ||
contract TwapWeightedObserver is ITwapWeightedObserver { | ||
PackedData public data; | ||
uint128 public valueToTrack; | ||
|
||
constructor(uint128 initialValue) { | ||
PackedData memory cachedData = PackedData({ | ||
observerCumuVal: initialValue, | ||
accumulator: initialValue, | ||
lastObserved: uint64(block.timestamp), | ||
lastAccrued: uint64(block.timestamp), | ||
lastObservedAverage: initialValue | ||
}); | ||
|
||
valueToTrack = initialValue; | ||
data = cachedData; | ||
} | ||
|
||
/// TWAP /// | ||
event NewTrackValue(uint256 _oldValue, uint256 _newValue, uint256 _ts, uint256 _newAcc); | ||
|
||
// Set to new value, sync accumulator to now with old value | ||
// Changes in same block have no impact, as no time has expired | ||
// Effectively we use the previous block value, and we magnify it by weight | ||
function _setValue(uint128 newValue) internal { | ||
uint128 _newAcc = _updateAcc(valueToTrack); | ||
|
||
data.lastAccrued = uint64(block.timestamp); | ||
emit NewTrackValue(valueToTrack, newValue, block.timestamp, _newAcc); | ||
valueToTrack = newValue; | ||
} | ||
|
||
// Update the accumulator based on time passed | ||
function _updateAcc(uint128 oldValue) internal returns (uint128) { | ||
uint128 _newAcc = data.accumulator + oldValue * (timeToAccrue()); | ||
data.accumulator = _newAcc; | ||
return _newAcc; | ||
} | ||
|
||
/// @notice Returns the time since the last update | ||
/// @return Duration since last update | ||
/// @dev Safe from overflow for tens of thousands of years | ||
function timeToAccrue() public view returns (uint64) { | ||
return uint64(block.timestamp) - data.lastAccrued; | ||
} | ||
|
||
/// @notice Returns the accumulator value, adjusted according to the current value and block timestamp | ||
// Return the update value to now | ||
function _syncToNow() internal view returns (uint128) { | ||
return data.accumulator + (valueToTrack * (timeToAccrue())); | ||
} | ||
|
||
// == Getters == // | ||
|
||
/// @notice Returns the accumulator value, adjusted according to the current value and block timestamp | ||
function getLatestAccumulator() public view returns (uint128) { | ||
return _syncToNow(); | ||
} | ||
|
||
/// END TWAP /// | ||
|
||
/// TWAP WEIGHTED OBSERVER /// | ||
|
||
// Hardcoded TWAP Period of 7 days | ||
uint256 public constant PERIOD = 7 days; | ||
|
||
// Look at last | ||
// Linear interpolate (or prob TWAP already does that for you) | ||
|
||
/// @notice Returns the current value, adjusted according to the current value and block timestamp | ||
function observe() external returns (uint256) { | ||
// Here, we need to apply the new accumulator to skew the price in some way | ||
// The weight of the skew should be proportional to the time passed | ||
uint256 futureWeight = block.timestamp - data.lastObserved; | ||
|
||
if (futureWeight == 0) { | ||
return data.lastObservedAverage; | ||
} | ||
|
||
// A reference period is 7 days | ||
// For each second passed after update | ||
// Let's virtally sync TWAP | ||
// With a weight, that is higher, the more time has passed | ||
(uint128 virtualAvgValue, uint128 obsAcc) = _calcUpdatedAvg(); | ||
|
||
if (_checkUpdatePeriod()) { | ||
_update(virtualAvgValue, obsAcc); // May as well update | ||
// Return virtual | ||
return virtualAvgValue; | ||
} | ||
|
||
uint256 weightedAvg = data.lastObservedAverage * (PERIOD - futureWeight); | ||
uint256 weightedVirtual = virtualAvgValue * (futureWeight); | ||
|
||
uint256 weightedMean = (weightedAvg + weightedVirtual) / PERIOD; | ||
|
||
return weightedMean; | ||
} | ||
|
||
/// @dev Usual Accumulator Math, (newAcc - acc0) / (now - t0) | ||
function _calcUpdatedAvg() internal view returns (uint128, uint128) { | ||
uint128 latestAcc = getLatestAccumulator(); | ||
uint128 avgValue = (latestAcc - data.observerCumuVal) / | ||
(uint64(block.timestamp) - data.lastObserved); | ||
return (avgValue, latestAcc); | ||
} | ||
|
||
/// @dev Utility to update internal data | ||
function _update(uint128 avgValue, uint128 obsAcc) internal { | ||
data.lastObservedAverage = avgValue; | ||
data.observerCumuVal = obsAcc; | ||
data.lastObserved = uint64(block.timestamp); | ||
} | ||
|
||
/// @dev Should we update in observe? | ||
function _checkUpdatePeriod() internal returns (bool) { | ||
return block.timestamp >= (data.lastObserved + PERIOD); | ||
} | ||
|
||
/// @dev update time-weighted Observer | ||
function update() public { | ||
if (_checkUpdatePeriod()) { | ||
(uint128 avgValue, uint128 latestAcc) = _calcUpdatedAvg(); | ||
_update(avgValue, latestAcc); | ||
} | ||
} | ||
|
||
function getData() external view returns (PackedData memory) { | ||
return data; | ||
} | ||
|
||
/// END TWAP WEIGHTED OBSERVER /// | ||
} |
This file contains hidden or 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
27 changes: 27 additions & 0 deletions
27
packages/contracts/contracts/Interfaces/IBaseTwapWeightedObserver.sol
This file contains hidden or 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,27 @@ | ||
// SPDX-License Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
interface IBaseTwapWeightedObserver { | ||
// NOTE: Packing manually is cheaper, but this is simpler to understand and follow | ||
struct PackedData { | ||
// Slot 0 | ||
// Seconds in a year: 3.154e+7 | ||
/// @dev Accumulator value recorded for TWAP Observer until last update | ||
uint128 observerCumuVal; // 3.154e+7 * 80 * 100e27 = 2.5232e+38 | log_2(100e27 * 3.154e+7 * 80) = 127.568522171 | ||
/// @dev Accumulator for TWAP globally | ||
uint128 accumulator; // 3.154e+7 * 80 * 100e27 = 2.5232e+38 | log_2(100e27 * 3.154e+7 * 80) = 127.568522171 | ||
// NOTE: We can further compress this slot but we will not be able to use only one (see u72 impl) | ||
/// So what's the point of making the code more complex? | ||
|
||
// Slot 1 | ||
/// @dev last update timestamp for TWAP Observer | ||
uint64 lastObserved; // Thousands of Years, if we use relative time we can use u32 | Relative to deploy time (as immutable) | ||
/// @dev last update timestamp for TWAP global track(spot) value | ||
uint64 lastAccrued; // Thousands of years | ||
// Expect eBTC debt to never surpass 100e27, which is 100 BILLION eBTC | ||
// log_2(100e27) = 96.3359147517 | log_2(100e27 / 1e18) = 36.5412090438 | ||
// We could use a u64 | ||
/// @dev average value since last observe | ||
uint128 lastObservedAverage; | ||
} | ||
} |
This file contains hidden or 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
17 changes: 17 additions & 0 deletions
17
packages/contracts/contracts/Interfaces/ITwapWeightedObserver.sol
This file contains hidden or 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,17 @@ | ||
// SPDX-License Identifier: MIT | ||
pragma solidity 0.8.17; | ||
import {IBaseTwapWeightedObserver} from "./IBaseTwapWeightedObserver.sol"; | ||
|
||
interface ITwapWeightedObserver is IBaseTwapWeightedObserver { | ||
function PERIOD() external view returns (uint256); | ||
|
||
function valueToTrack() external view returns (uint128); | ||
GalloDaSballo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function timeToAccrue() external view returns (uint64); | ||
|
||
function getLatestAccumulator() external view returns (uint128); | ||
|
||
function observe() external returns (uint256); | ||
|
||
function update() external; | ||
} |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.