Skip to content

Commit

Permalink
feat: add maxAge parameter to limit the staleness of a historical dat…
Browse files Browse the repository at this point in the history
…a point

Signed-off-by: Matt Rice <[email protected]>
  • Loading branch information
mrice32 committed May 20, 2024
1 parent 8a21cce commit 4655cc0
Show file tree
Hide file tree
Showing 17 changed files with 63 additions and 12 deletions.
7 changes: 5 additions & 2 deletions src/DiamondRootOval.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ abstract contract DiamondRootOval is IBaseController, IOval, IBaseOracleAdapter
* @notice Time window that bounds how long the permissioned actor has to call the unlockLatestValue function after
* a new source update is posted. If the permissioned actor does not call unlockLatestValue within this window of a
* new source price, the latest value will be made available to everyone without going through an MEV-Share auction.
* @return lockWindow time in seconds.
*/
function lockWindow() public view virtual returns (uint256);

/**
* @notice Max number of historical source updates to traverse when looking for a historic value in the past.
* @return maxTraversal max number of historical source updates to traverse.
*/
function maxTraversal() public view virtual returns (uint256);

/**
* @notice Max age of a historical price that can be used instead of the current price.
*/
function maxAge() public view virtual returns (uint256);
}
4 changes: 3 additions & 1 deletion src/adapters/source-adapters/ChainlinkSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ abstract contract ChainlinkSourceAdapter is DiamondRootOval {
_searchRoundDataAt(timestamp, roundId, maxTraversal);

// Validate returned data. If it is uninitialized we fallback to returning the current latest round data.
if (historicalUpdatedAt > 0) return (historicalAnswer, historicalUpdatedAt, historicalRoundId);
if (historicalUpdatedAt > block.timestamp - maxAge()) {
return (historicalAnswer, historicalUpdatedAt, historicalRoundId);
}
return (answer, updatedAt, roundId);
}

Expand Down
4 changes: 2 additions & 2 deletions src/adapters/source-adapters/SnapshotSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ abstract contract SnapshotSource is DiamondRootOval {
// Attempt traversing historical snapshot data. This might still be newer or uninitialized.
Snapshot memory historicalData = _searchSnapshotAt(timestamp, maxTraversal);

// Validate returned data. If it is uninitialized we fallback to returning the current latest round data.
if (historicalData.timestamp > 0) return historicalData;
// Validate returned data. If it is uninitialized or too old we fallback to returning the current latest round data.
if (historicalData.timestamp > block.timestamp - maxAge()) return historicalData;
return latestData;
}

Expand Down
18 changes: 18 additions & 0 deletions src/controllers/BaseController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ abstract contract BaseController is Ownable, Oval {
// these don't need to be public since they can be accessed via the accessor functions below.
uint256 private lockWindow_ = 60; // The lockWindow in seconds.
uint256 private maxTraversal_ = 10; // The maximum number of rounds to traverse when looking for historical data.
uint256 private maxAge_ = 86400; // Default 1 day.

mapping(address => bool) public unlockers;

Expand Down Expand Up @@ -66,6 +67,16 @@ abstract contract BaseController is Ownable, Oval {
emit MaxTraversalSet(newMaxTraversal);
}

/**
* @notice Enables the owner to set the maxAge.
* @param newMaxAge The maxAge to set
*/
function setMaxAge(uint256 newMaxAge) public onlyOwner {
maxAge_ = newMaxAge;

emit MaxAgeSet(newMaxAge);
}

/**
* @notice Time window that bounds how long the permissioned actor has to call the unlockLatestValue function after
* a new source update is posted. If the permissioned actor does not call unlockLatestValue within this window of a
Expand All @@ -83,4 +94,11 @@ abstract contract BaseController is Ownable, Oval {
function maxTraversal() public view override returns (uint256) {
return maxTraversal_;
}

/**
* @notice Max age of a historical price that can be used instead of the current price.
*/
function maxAge() public view override returns (uint256) {
return maxTraversal_;
}
}
12 changes: 11 additions & 1 deletion src/controllers/ImmutableController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import {Oval} from "../Oval.sol";
abstract contract ImmutableController is Oval {
uint256 private immutable LOCK_WINDOW; // The lockWindow in seconds.
uint256 private immutable MAX_TRAVERSAL; // The maximum number of rounds to traverse when looking for historical data.
uint256 private immutable MAX_AGE;

mapping(address => bool) public unlockers;

constructor(uint256 _lockWindow, uint256 _maxTraversal, address[] memory _unlockers) {
constructor(uint256 _lockWindow, uint256 _maxTraversal, address[] memory _unlockers, uint256 _maxAge) {
LOCK_WINDOW = _lockWindow;
MAX_TRAVERSAL = _maxTraversal;
MAX_AGE = _maxAge;
for (uint256 i = 0; i < _unlockers.length; i++) {
unlockers[_unlockers[i]] = true;

Expand All @@ -27,6 +29,7 @@ abstract contract ImmutableController is Oval {

emit LockWindowSet(_lockWindow);
emit MaxTraversalSet(_maxTraversal);
emit MaxAgeSet(_maxAge);
}

/**
Expand Down Expand Up @@ -57,4 +60,11 @@ abstract contract ImmutableController is Oval {
function maxTraversal() public view override returns (uint256) {
return MAX_TRAVERSAL;
}

/**
* @notice Max age of a historical price that can be used instead of the current price.
*/
function maxAge() public view override returns (uint256) {
return MAX_AGE;
}
}
1 change: 1 addition & 0 deletions src/interfaces/IBaseController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface IBaseController {
event LockWindowSet(uint256 indexed lockWindow);
event MaxTraversalSet(uint256 indexed maxTraversal);
event UnlockerSet(address indexed unlocker, bool indexed allowed);
event MaxAgeSet(uint256 indexed newMaxAge);

function canUnlock(address caller, uint256 cachedLatestTimestamp) external view returns (bool);
}
2 changes: 1 addition & 1 deletion test/fork/aave/AaveV2.Liquidation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface Usdc is IERC20 {
contract TestedOval is ImmutableController, ChainlinkSourceAdapter, ChainlinkDestinationAdapter {
constructor(IAggregatorV3Source source, uint8 decimals, address[] memory unlockers)
ChainlinkSourceAdapter(source)
ImmutableController(60, 10, unlockers)
ImmutableController(60, 10, unlockers, 86400)
ChainlinkDestinationAdapter(decimals)
{}
}
Expand Down
2 changes: 1 addition & 1 deletion test/fork/aave/AaveV3.Liquidation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface Usdc is IERC20 {
contract TestedOval is ImmutableController, ChainlinkSourceAdapter, ChainlinkDestinationAdapter {
constructor(IAggregatorV3Source source, uint8 decimals, address[] memory unlockers)
ChainlinkSourceAdapter(source)
ImmutableController(60, 10, unlockers)
ImmutableController(60, 10, unlockers, 86400)
ChainlinkDestinationAdapter(decimals)
{}
}
Expand Down
2 changes: 2 additions & 0 deletions test/fork/adapters/ChainlinkSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ contract TestedSourceAdapter is ChainlinkSourceAdapter {
function lockWindow() public view virtual override returns (uint256) {}

function maxTraversal() public view virtual override returns (uint256) {}

function maxAge() public view virtual override returns (uint256) {}
}

contract ChainlinkSourceAdapterTest is CommonTest {
Expand Down
1 change: 1 addition & 0 deletions test/fork/adapters/ChronicleMedianSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ contract TestedSourceAdapter is ChronicleMedianSourceAdapter {
function canUnlock(address caller, uint256 cachedLatestTimestamp) public view virtual override returns (bool) {}
function lockWindow() public view virtual override returns (uint256) {}
function maxTraversal() public view virtual override returns (uint256) {}
function maxAge() public view virtual override returns (uint256) {}
}

contract ChronicleMedianSourceAdapterTest is CommonTest {
Expand Down
1 change: 1 addition & 0 deletions test/fork/adapters/OSMSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contract TestedSourceAdapter is OSMSourceAdapter {
function canUnlock(address caller, uint256 cachedLatestTimestamp) public view virtual override returns (bool) {}
function lockWindow() public view virtual override returns (uint256) {}
function maxTraversal() public view virtual override returns (uint256) {}
function maxAge() public view virtual override returns (uint256) {}
}

contract OSMSourceAdapterTest is CommonTest {
Expand Down
2 changes: 2 additions & 0 deletions test/fork/adapters/PythSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ contract TestedSourceAdapter is PythSourceAdapter {
function lockWindow() public view virtual override returns (uint256) {}

function maxTraversal() public view virtual override returns (uint256) {}

function maxAge() public view virtual override returns (uint256) {}
}

contract PythSourceAdapterTest is CommonTest {
Expand Down
2 changes: 2 additions & 0 deletions test/fork/adapters/UnionSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ contract TestedSourceAdapter is UnionSourceAdapter {
function lockWindow() public view virtual override returns (uint256) {}

function maxTraversal() public view virtual override returns (uint256) {}

function maxAge() public view virtual override returns (uint256) {}
}

contract UnionSourceAdapterTest is CommonTest {
Expand Down
1 change: 1 addition & 0 deletions test/fork/adapters/UniswapAnchoredViewSourceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ contract TestedSourceAdapter is UniswapAnchoredViewSourceAdapter {
function canUnlock(address caller, uint256 cachedLatestTimestamp) public view virtual override returns (bool) {}
function lockWindow() public view virtual override returns (uint256) {}
function maxTraversal() public view virtual override returns (uint256) {}
function maxAge() public view virtual override returns (uint256) {}
}

contract UniswapAnchoredViewSourceAdapterTest is CommonTest {
Expand Down
2 changes: 1 addition & 1 deletion test/fork/compound/CompoundV2.Liquidation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface Usdc is IERC20 {
contract TestedOval is ImmutableController, UniswapAnchoredViewSourceAdapter, BaseDestinationAdapter {
constructor(IUniswapAnchoredView source, address cToken, address[] memory unlockers)
UniswapAnchoredViewSourceAdapter(source, cToken)
ImmutableController(60, 10, unlockers)
ImmutableController(60, 10, unlockers, 86400)
BaseDestinationAdapter()
{}
}
Expand Down
13 changes: 10 additions & 3 deletions test/unit/ImmutableController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ import {BaseDestinationAdapter} from "../../src/adapters/destination-adapters/Ba
import {MockSourceAdapter} from "../mocks/MockSourceAdapter.sol";

contract TestImmutableController is ImmutableController, MockSourceAdapter, BaseDestinationAdapter {
constructor(uint8 decimals, uint256 _lockWindow, uint256 _maxTraversal, address[] memory _unlockers)
constructor(
uint8 decimals,
uint256 _lockWindow,
uint256 _maxTraversal,
address[] memory _unlockers,
uint256 _maxAge
)
MockSourceAdapter(decimals)
ImmutableController(_lockWindow, _maxTraversal, _unlockers)
ImmutableController(_lockWindow, _maxTraversal, _unlockers, _maxAge)
BaseDestinationAdapter()
{}
}
Expand All @@ -19,6 +25,7 @@ contract OvalUnlockLatestValue is CommonTest {
uint256 lockWindow = 60;
uint256 maxTraversal = 10;
address[] unlockers;
uint256 maxAge = 86400;

uint256 lastUnlockTime = 1690000000;

Expand All @@ -28,7 +35,7 @@ contract OvalUnlockLatestValue is CommonTest {
unlockers.push(permissionedUnlocker);

vm.startPrank(owner);
immutableController = new TestImmutableController(decimals, lockWindow, maxTraversal, unlockers);
immutableController = new TestImmutableController(decimals, lockWindow, maxTraversal, unlockers, maxAge);
vm.stopPrank();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ contract TestBoundedUnionSource is BoundedUnionSourceAdapter {

function lockWindow() public view virtual override returns (uint256) {}
function maxTraversal() public view virtual override returns (uint256) {}
function maxAge() public view virtual override returns (uint256) {}
}

contract MinimalChainlinkAdapter {
Expand Down

0 comments on commit 4655cc0

Please sign in to comment.