-
Notifications
You must be signed in to change notification settings - Fork 2
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: use snapshotting library in bounded union source adapter #12
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
*/ | ||
abstract contract SnapshotSource is DiamondRootOval { | ||
library SnapshotSourceLib { | ||
// Snapshot records the historical answer at a specific timestamp. | ||
struct Snapshot { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no reason really to snapshot round ID I guess? especially given any source that has the notion of a round ID also has history?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you run a before and after gas comparison?
import {IOSM} from "../../interfaces/makerdao/IOSM.sol"; | ||
|
||
/** | ||
* @title OSMSourceAdapter contract to read data from MakerDAO OSM and standardize it for Oval. | ||
*/ | ||
abstract contract OSMSourceAdapter is SnapshotSource { | ||
abstract contract OSMSourceAdapter is DiamondRootOval { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice. this is more consistent now.
IOSM public immutable osmSource; | ||
|
||
// MakerDAO performs decimal conversion in collateral adapter contracts, so all oracle prices are expected to have | ||
// 18 decimals and we can skip decimal conversion. | ||
uint8 public constant decimals = 18; | ||
|
||
SnapshotSourceLib.Snapshot[] public osmSnapshots; // Historical answer and timestamp snapshots. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also this works well I think. when you need the snapshot within the adapter you simple: a) import the lib b) have a local snapshots array.
@@ -0,0 +1,26 @@ | |||
// SPDX-License-Identifier: BUSL-1.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this showing up as new? was this not here before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we had them only in oval demo repo
// For Chronicle and Pyth, just pull the most recent prices and drop them if they don't satisfy the constraint. | ||
(int256 crAnswer, uint256 crTimestamp) = ChronicleMedianSourceAdapter.getLatestSourceData(); | ||
(int256 pyAnswer, uint256 pyTimestamp) = PythSourceAdapter.getLatestSourceData(); | ||
// For Chronicle and Pyth, tryLatestDataAt would attempt to get price from snapshots, but we can drop them if | ||
// they don't satisfy the timestamp constraint. | ||
(int256 crAnswer, uint256 crTimestamp) = ChronicleMedianSourceAdapter.tryLatestDataAt(timestamp, maxTraversal); | ||
(int256 pyAnswer, uint256 pyTimestamp) = PythSourceAdapter.tryLatestDataAt(timestamp, maxTraversal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is unrelated. why was it added? it does make sense but given we want the most recent data can we not continue to use those methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This attempted to fix the original bug. But now I realize this unnecessarily snapshots source data both in Pyth and Chronicle adapters. Instead, we should snapshot the aggregated union value and use that here - I just refactored this in the latest commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that makes a lot more sence.
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
(int256 crAnswer, uint256 crTimestamp) = ChronicleMedianSourceAdapter.getLatestSourceData(); | ||
(int256 pyAnswer, uint256 pyTimestamp) = PythSourceAdapter.getLatestSourceData(); | ||
// Chainlink has price history, so use tryLatestDataAt to pull the most recent price that satisfies the timestamp constraint. | ||
(data.clAnswer, data.clTimestamp) = ChainlinkSourceAdapter.tryLatestDataAt(timestamp, maxTraversal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could still be potentially optimized to avoid duplicate calls, but would require introducing version of tryLatestDataAt that accepts already fetched latest data as inputs.
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
if (data.crTimestamp > timestamp) data.crTimestamp = 0; | ||
if (data.pyTimestamp > timestamp) data.pyTimestamp = 0; | ||
|
||
// Bounded union prices could have been captured at snapshot that satisfies time constraint. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 great comments throughout this file
); | ||
|
||
// Return bounded data unless there is a newer snapshotted data that still satisfies time constraint. | ||
if (boundedTimestamp >= snapshot.timestamp || snapshot.timestamp > timestamp) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I've followed the logic correctly, should we have if (boundedTimestamp > snapshot.timestamp)
instead, so we give preference to the snapshot data in case there is one?
I understand that at this point (boundedAnswer, boundedTimestamp) and (snapshot.answer, snapshot.timestamp) could be different values if boundedTimestamp == snapshot.timestamp
, as the snapshot could have a more complete information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this makes sense, pushed the fix
(int256 clAnswer, uint256 clTimestamp,) = ChainlinkSourceAdapter.tryLatestDataAt(timestamp, maxTraversal); | ||
// In the happy path there have been no source updates since requested time, so we can return the latest data. | ||
AllSourceData memory data = _getAllLatestSourceData(); | ||
(int256 boundedAnswer, uint256 boundedTimestamp) = _selectBoundedPrice( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do _selectBoundedPrice(data)
and update _selectBoundedPrice like
function _selectBoundedPrice(AllSourceData memory data)
internal
view
returns (int256, uint256)
{
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great suggestion, pushing commit for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
I added one question and one suggestion. Overall, it looks like a solid solution 👍
|
||
// To "drop" Chronicle and Pyth, we set their timestamps to 0 (as old as possible) if they are too recent. | ||
// "Drop" Chronicle and/or Pyth by setting their timestamps to 0 (as old as possible) if they are too recent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the drop logic here I wanted to investigate a bit more:
consider chronicle or pyth have their second most recent values being newer than chainlink, but their newest value is too recent. the implementation below, due to setting these timestamps to zero, will result in this value not being returned, even though the snapshot for these sources might have a "better" value to return (it's newer than chainlink, but still old enough to not be filtered out by the lockWindow
).
I might be missing something here on how we want to compare these kinds of sources, but given we now have the ability to snapshot & query all parts of the BoundedUnion can we not consider them equally and look within all sources to find the newest source that is old enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we only drop the latest pyth and chronicle values if they are too recent. but we might still end up using any of these sources if they had been selected as part of snapshoted values.
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
…a point (#14) Signed-off-by: Matt Rice <matthewcrice32@gmail.com> Signed-off-by: chrismaree <christopher.maree@gmail.com> Co-authored-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
This test demonstrated that Oval prices can flip backwards when using BoundedUnionSourceAdapter. The root cause of the issue is that stacking multiple source adapters ended up using the same storage slot for snapshotting historical price data.
This PR addresses the issue by converting
SnapshotSource
to stateless library, so that any source adapters that need snapshotting would instead pass their own storage pointers. With this fixed it now became possible to snapshot individual source oracle data that did not support native historical data.Added test
testLookbackDoesNotFlipBackward
demonstrates that now its not possible to return earlier data than returned compared to a request made before.We might also want to fix this in raw UnionSourceAdapter
Fixes: https://linear.app/uma/issue/UMA-2183/consider-snapshotted-data-in-union-adapters