-
Notifications
You must be signed in to change notification settings - Fork 15
/
Oracle.sol
130 lines (109 loc) · 5.93 KB
/
Oracle.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
import "@equilibria/root/attribute/Instance.sol";
import "@equilibria/perennial-v2/contracts/interfaces/IOracleProviderFactory.sol";
import "./interfaces/IOracle.sol";
/// @title Oracle
/// @notice The top-level oracle contract that implements an oracle provider interface.
/// @dev Manages swapping between different underlying oracle provider interfaces over time.
contract Oracle is IOracle, Instance {
/// @notice A historical mapping of underlying oracle providers
mapping(uint256 => Epoch) public oracles;
/// @notice The global state of the oracle
Global public global;
/// @notice Initializes the contract state
/// @param initialProvider The initial oracle provider
function initialize(IOracleProvider initialProvider) external initializer(1) {
__Instance__initialize();
_updateCurrent(initialProvider);
_updateLatest(initialProvider.latest());
}
/// @notice Updates the current oracle provider
/// @dev Both the current and new oracle provider must have the same current
/// @param newProvider The new oracle provider
function update(IOracleProvider newProvider) external {
if (msg.sender != address(factory())) revert OracleNotFactoryError();
_updateCurrent(newProvider);
_updateLatest(newProvider.latest());
}
/// @notice Requests a new version at the current timestamp
/// @param account Original sender to optionally use for callbacks
function request(address account) external onlyAuthorized {
(OracleVersion memory latestVersion, uint256 currentTimestamp) = oracles[global.current].provider.status();
oracles[
(currentTimestamp > oracles[global.latest].timestamp) ? global.current : global.latest
].provider.request(account);
oracles[global.current].timestamp = uint96(currentTimestamp);
_updateLatest(latestVersion);
}
/// @notice Returns the latest committed version as well as the current timestamp
/// @return latestVersion The latest committed version
/// @return currentTimestamp The current timestamp
function status() external view returns (OracleVersion memory latestVersion, uint256 currentTimestamp) {
(latestVersion, currentTimestamp) = oracles[global.current].provider.status();
latestVersion = _handleLatest(latestVersion);
}
/// @notice Returns the latest committed version
function latest() public view returns (OracleVersion memory) {
return _handleLatest(oracles[global.current].provider.latest());
}
/// @notice Returns the current value
function current() public view returns (uint256) {
return oracles[global.current].provider.current();
}
/// @notice Returns the oracle version at a given timestamp
/// @param timestamp The timestamp to query
/// @return atVersion The oracle version at the given timestamp
function at(uint256 timestamp) public view returns (OracleVersion memory atVersion) {
if (timestamp == 0) return atVersion;
IOracleProvider provider = oracles[global.current].provider;
for (uint256 i = global.current - 1; i > 0; i--) {
if (timestamp > uint256(oracles[i].timestamp)) break;
provider = oracles[i].provider;
}
return provider.at(timestamp);
}
/// @notice Handles update the oracle to the new provider
/// @param newProvider The new oracle provider
function _updateCurrent(IOracleProvider newProvider) private {
if (global.current != global.latest) revert OracleOutOfSyncError();
oracles[uint256(++global.current)] = Epoch(newProvider, uint96(newProvider.current()));
emit OracleUpdated(newProvider);
}
/// @notice Handles updating the latest oracle to the current if it is ready
/// @param currentOracleLatestVersion The latest version from the current oracle
function _updateLatest(OracleVersion memory currentOracleLatestVersion) private {
if (_latestStale(currentOracleLatestVersion)) global.latest = global.current;
}
/// @notice Handles overriding the latest version
/// @dev Applicable if we haven't yet switched over to the current oracle from the latest oracle
/// @param currentOracleLatestVersion The latest version from the current oracle
/// @return latestVersion The latest version
function _handleLatest(
OracleVersion memory currentOracleLatestVersion
) private view returns (OracleVersion memory latestVersion) {
if (global.current == global.latest) return currentOracleLatestVersion;
bool isLatestStale = _latestStale(currentOracleLatestVersion);
latestVersion = isLatestStale ? currentOracleLatestVersion : oracles[global.latest].provider.latest();
uint256 latestOracleTimestamp =
uint256(isLatestStale ? oracles[global.current].timestamp : oracles[global.latest].timestamp);
if (!isLatestStale && latestVersion.timestamp > latestOracleTimestamp)
return at(latestOracleTimestamp);
}
/// @notice Returns whether the latest oracle is ready to be updated
/// @param currentOracleLatestVersion The latest version from the current oracle
/// @return Whether the latest oracle is ready to be updated
function _latestStale(OracleVersion memory currentOracleLatestVersion) private view returns (bool) {
if (global.current == global.latest) return false;
if (global.latest == 0) return true;
if (uint256(oracles[global.latest].timestamp) > oracles[global.latest].provider.latest().timestamp) return false;
if (uint256(oracles[global.latest].timestamp) >= currentOracleLatestVersion.timestamp) return false;
return true;
}
/// @dev Only if the caller is authorized by the factory
modifier onlyAuthorized {
if (!IOracleProviderFactory(address(factory())).authorized(msg.sender))
revert OracleProviderUnauthorizedError();
_;
}
}