|
4 | 4 | - [Deployed contract](https://etherscan.io/address/0x442af784A788A5bd6F42A01Ebe9F287a871243fb)
|
5 | 5 |
|
6 | 6 | :::warning
|
7 |
| -LegacyOracle is used to be a previous oracle contract for Lido. |
8 |
| -It's left currently for compatibility reasons only and might be deprecated completely in the future releases. |
| 7 | +`LegacyOracle` will be maintained till the end of 2023. |
| 8 | +Afterwards, it will be discontinued and external integrations should rely on [`AccountingOracle`](/contracts/accounting-oracle). |
9 | 9 | :::
|
10 | 10 |
|
11 |
| -Superseded with [AccountingOracle](/contracts/accounting-oracle). |
| 11 | +## What is LegacyOracle? |
| 12 | + |
| 13 | +`LegacyOracle` is an Aragon app previously known as `LidoOracle`, used to track changes on the Beacon Chain. |
| 14 | +Following the Lido V2 upgrade, this was replaced by the [`AccountingOracle`](/contracts/accounting-oracle) |
| 15 | +and the oracle workflow was redesigned to deliver synchronized historical data chunks for the same reference slot |
| 16 | +both for the Consensus and Execution Layer parts. |
| 17 | + |
| 18 | +## Key changes |
| 19 | + |
| 20 | +In Lido V2, `LegacyOracle` only supports a subset of view functions and events. |
| 21 | +`AccountingOracle` interacts with it to push data changes on each report. |
| 22 | + |
| 23 | +### How does LegacyOracle receive the AccountingOracle reports anyway (flow) |
| 24 | + |
| 25 | +The `LegacyOracle` contract receives the data changes on each `AccountingOracle` report using two stages |
| 26 | +(still within the same transaction): |
| 27 | + |
| 28 | +1. Invoke [`handleConsensusLayerReport`](/contracts/legacy-oracle#handleConsensusLayerReport) |
| 29 | +providing the reference slot and validators data from `AccountingOracle` itself. |
| 30 | +1. Invoke [`handlePostTokenRebase`](/contracts/legacy-oracle#handlePostTokenRebase) |
| 31 | +from [`Lido`](/contracts/lido). |
| 32 | + |
| 33 | +```mermaid |
| 34 | +graph LR; |
| 35 | + A[/ \]--submitReportData-->AccountingOracle--handleConsensusLayerReport--->LegacyOracle; |
| 36 | + AccountingOracle--handleOracleReport-->Lido--handlePostTokenRebase-->LegacyOracle |
| 37 | +``` |
| 38 | + |
| 39 | +### Rebase and APR |
| 40 | + |
| 41 | +To calculate the protocol's daily rebase and APR projections one would use the old `LidoOracle` APIs for a while. |
| 42 | +Although the old way of calculating the APR would still result in relevant numbers, the math might be off in case of significant withdrawals. |
| 43 | + |
| 44 | +#### How it was with LidoOracle |
| 45 | + |
| 46 | +:::note |
| 47 | +The formula is outdated and inaccurate since the [Lido V2 upgrade](https://blog.lido.fi/lido-v2-launch/) happened. |
| 48 | +::: |
| 49 | + |
| 50 | +```javascript |
| 51 | +protocolAPR = (postTotalPooledEther - preTotalPooledEther) * secondsInYear / (preTotalPooledEther * timeElapsed) |
| 52 | +lidoFeeAsFraction = lidoFee / basisPoint |
| 53 | +userAPR = protocolAPR * (1 - lidoFeeAsFraction) |
| 54 | +``` |
| 55 | + |
| 56 | +#### What's new from Lido V2 |
| 57 | + |
| 58 | +See the new Lido API docs with regards to [APR](/integrations/api#LidoAPR). |
| 59 | + |
| 60 | +```js |
| 61 | +// Emits when token rebased (total supply and/or total shares were changed) |
| 62 | +event TokenRebased( |
| 63 | + uint256 indexed reportTimestamp, |
| 64 | + uint256 timeElapsed, |
| 65 | + uint256 preTotalShares, |
| 66 | + uint256 preTotalEther, /* preTotalPooledEther */ |
| 67 | + uint256 postTotalShares, |
| 68 | + uint256 postTotalEther, /* postTotalPooledEther */ |
| 69 | + uint256 sharesMintedAsFees /* fee part included in `postTotalShares` */ |
| 70 | +); |
| 71 | + |
| 72 | +preShareRate = preTotalEther * 1e27 / preTotalShares |
| 73 | +postShareRate = postTotalEther * 1e27 / postTotalShares |
| 74 | + |
| 75 | +userAPR = |
| 76 | + secondsInYear * ( |
| 77 | + (postShareRate - preShareRate) / preShareRate |
| 78 | + ) / timeElapsed |
| 79 | +``` |
| 80 | + |
| 81 | +In short, the new formula takes into account both `preTotalShares` and `postTotalShares` values, while, |
| 82 | +in contrast, the old formula didn't use them. The new formula also doesn't require to calculate `lidoFee` |
| 83 | +at all (because the fee distribution works by changing the total shares amount under the hood). |
| 84 | + |
| 85 | +#### Why does it matter |
| 86 | + |
| 87 | +When Lido V2 protocol finalizes withdrawal requests, the `Lido` contract sends ether to `WithdrawalQueue` (excluding these funds from `totalPooledEther`, i.e., decreasing TVL) and assigns to burn underlying locked requests' `stETH` shares in return. |
| 88 | + |
| 89 | +In other words, withdrawal finalization decreases both TVL and total shares. |
| 90 | + |
| 91 | +Old formula isn't suitable anymore because it catches TVL changes, but skips total shares changes. |
| 92 | + |
| 93 | +Illustrative example (using smallish numbers far from the real ones for simplicity): |
| 94 | + |
| 95 | +```javascript |
| 96 | +preTotalEther = 1000 ETH |
| 97 | +preTotalShares = 1000 * 10^18 // 1 share : 1 wei |
| 98 | + |
| 99 | +postTotalEther = 999 ETH |
| 100 | +postTotalShares = 990 * 10^18 |
| 101 | + |
| 102 | +timeElapsed = 24 * 60 * 60 // 1 day, or 86400 seconds |
| 103 | + |
| 104 | +//!!! using the old (imprecise) method |
| 105 | + |
| 106 | +// protocolAPR = (postTotalPooledEther - preTotalPooledEther) * secondsInYear / (preTotalPooledEther * timeElapsed) |
| 107 | +protocolAPR = (999ETH - 1000ETH) * 31557600 / (1000ETH * 86400) = -0.36525 |
| 108 | +//lidoFeeAsFraction = lidoFee / basisPoint = 0.1 |
| 109 | +//userAPR = protocolAPR * (1 - lidoFeeAsFraction) = protocolAPR * (1 - 0.1) |
| 110 | + |
| 111 | +userAPR = -0.36525 * (1 - 0.1) = -0.328725 |
| 112 | + |
| 113 | +//!!! i.e, userAPR now is ~minus 32.9% |
| 114 | + |
| 115 | +//!!! using the updated (proper) method |
| 116 | + |
| 117 | +preShareRate = 1000 ETH * 1e27 / 1000 * 10^18 = 1e27 |
| 118 | +postShareRate = 999 ETH * 1e27 / 990 * 10^18 = 1.009090909090909e+27 |
| 119 | +userAPR = 31557600 * ((postShareRate - preShareRate) / preShareRate) / 86400 = 3.320454545454529 |
| 120 | + |
| 121 | +//!!! i.e., userAPR now is ~plus 332% |
| 122 | +``` |
| 123 | + |
| 124 | +## View Methods |
| 125 | + |
| 126 | +### getLido() |
| 127 | + |
| 128 | +Returns the `Lido` contract address. |
| 129 | + |
| 130 | +```sol |
| 131 | +function getLido() returns (address) |
| 132 | +``` |
| 133 | + |
| 134 | +:::note |
| 135 | +Always returns the `Lido` address stated in the [deployed addresses](/deployed-contracts) list. |
| 136 | +::: |
| 137 | + |
| 138 | +### getAccountingOracle() |
| 139 | + |
| 140 | +Returns the `AccountingOracle` contract address. |
| 141 | + |
| 142 | +```sol |
| 143 | +function getAccountingOracle() returns (address) |
| 144 | +``` |
| 145 | + |
| 146 | +:::note |
| 147 | +Always returns the `AccountingOracle` address stated in the [deployed addresses](/deployed-contracts) list. |
| 148 | +::: |
| 149 | + |
| 150 | +### getContractVersion() |
| 151 | + |
| 152 | +Returns the current contract version. |
| 153 | + |
| 154 | +```sol |
| 155 | +function getContractVersion() returns (uint256) |
| 156 | +``` |
| 157 | + |
| 158 | +:::note |
| 159 | +Always returns `4`. |
| 160 | +::: |
| 161 | + |
| 162 | +### getVersion() |
| 163 | + |
| 164 | +Returns the current contract version (compatibility method). |
| 165 | + |
| 166 | +```sol |
| 167 | +function getVersion() returns (uint256) |
| 168 | +``` |
| 169 | + |
| 170 | +:::note |
| 171 | +Always returns `4`, calls `getContractVersion()` internally. |
| 172 | +::: |
| 173 | + |
| 174 | +### getBeaconSpec() |
| 175 | + |
| 176 | +Returns the `AccountingOracle` frame period together with Ethereum Beacon Chain specification constants. |
| 177 | + |
| 178 | +```sol |
| 179 | +function getBeaconSpec() returns ( |
| 180 | + uint64 epochsPerFrame, |
| 181 | + uint64 slotsPerEpoch, |
| 182 | + uint64 secondsPerSlot, |
| 183 | + uint64 genesisTime |
| 184 | +) |
| 185 | +``` |
| 186 | + |
| 187 | +:::note |
| 188 | +Always returns (225, 32, 12, 1606824023) for Mainnet and (225, 32, 12, 1616508000) for Görli. |
| 189 | +::: |
| 190 | + |
| 191 | +#### Returns |
| 192 | + |
| 193 | +| Name | Type | Description | |
| 194 | +| ---------------- | -------- | -------------------------------------------------------------- | |
| 195 | +| `epochsPerFrame` | `uint64` | Beacon Chain epochs per single `AccountingOracle` report frame | |
| 196 | +| `slotsPerEpoch` | `uint64` | Beacon Chain slots per single Beacon Chain epoch | |
| 197 | +| `secondsPerSlot` | `uint64` | Seconds per single Beacon Chain slot | |
| 198 | +| `genesisTime` | `uint64` | Beacon Chain genesis timestamp | |
| 199 | + |
| 200 | +### getCurrentEpochId() |
| 201 | + |
| 202 | +Returns the Beacon Chain epoch id calculated from the current timestamp using the [beacon chain spec](/contracts/legacy-oracle#getBeaconSpec). |
| 203 | + |
| 204 | +```sol |
| 205 | +function getCurrentEpochId() returns (uint256) |
| 206 | +``` |
| 207 | + |
| 208 | +### getCurrentFrame() |
| 209 | + |
| 210 | +Returns the first epoch of the current `AccountingOracle` reporting frame as well as its start and end times in seconds. |
| 211 | + |
| 212 | +```sol |
| 213 | +function getCurrentFrame() returns ( |
| 214 | + uint256 frameEpochId, |
| 215 | + uint256 frameStartTime, |
| 216 | + uint256 frameEndTime |
| 217 | +) |
| 218 | +``` |
| 219 | + |
| 220 | +#### Returns |
| 221 | + |
| 222 | +| Name | Type | Description | |
| 223 | +| ----------------- | ---------- | ----------------------------------------------------------------- | |
| 224 | +| `frameEpochId` | `uint256` | The first epoch of the current `AccountingOracle` reporting frame | |
| 225 | +| `frameStartTime` | `uint256` | The start timestamp of the current reporting frame | |
| 226 | +| `frameEndTime` | `uint256` | The end timestamp of the current reporting frame | |
| 227 | + |
| 228 | +### getLastCompletedEpochId() |
| 229 | + |
| 230 | +Returns the starting epoch of the last frame in which the last `AccountingOracle` report was received and applied. |
| 231 | + |
| 232 | +```sol |
| 233 | +function getLastCompletedEpochId() returns (uint256) |
| 234 | +``` |
| 235 | + |
| 236 | +### getLastCompletedReportDelta() |
| 237 | + |
| 238 | +Returns the total supply change ocurred with the last completed `AccountingOracle` report. |
| 239 | + |
| 240 | +```sol |
| 241 | +function getLastCompletedReportDelta() returns ( |
| 242 | + uint256 postTotalPooledEther, |
| 243 | + uint256 preTotalPooledEther, |
| 244 | + uint256 timeElapsed |
| 245 | +) |
| 246 | +``` |
| 247 | + |
| 248 | +#### Returns |
| 249 | + |
| 250 | +| Name | Type | Description | |
| 251 | +| ------------------------- | ---------- | ------------------------------------------------------------- | |
| 252 | +| `postTotalPooledEther` | `uint256` | Post-report `stETH`` total pooled ether (i.e., total supply) | |
| 253 | +| `preTotalPooledEther` | `uint256` | Pre-report `stETH` total pooled ether (i.e., total supply) | |
| 254 | +| `timeElapsed` | `uint256` | Time elapsed since the previously completed report, seconds | |
| 255 | + |
| 256 | +## Methods |
| 257 | + |
| 258 | +### handlePostTokenRebase() |
| 259 | + |
| 260 | +Handles a `stETH` token rebase incurred by the succeeded `AccountingOracle` report storing |
| 261 | +the total ether and time elapsed stats. |
| 262 | + |
| 263 | +Emits [`PostTotalShares`](/contracts/legacy-oracle#PostTotalShares) |
| 264 | + |
| 265 | +```sol |
| 266 | +function handlePostTokenRebase( |
| 267 | + uint256 reportTimestamp, |
| 268 | + uint256 timeElapsed, |
| 269 | + uint256 preTotalShares, |
| 270 | + uint256 preTotalEther, |
| 271 | + uint256 postTotalShares, |
| 272 | + uint256 postTotalEther, |
| 273 | + uint256 totalSharesMintedAsFees |
| 274 | +) |
| 275 | +``` |
| 276 | + |
| 277 | +:::note |
| 278 | +The caller must be `Lido`. |
| 279 | +::: |
| 280 | + |
| 281 | +#### Parameters |
| 282 | + |
| 283 | +| Name | Type | Description | |
| 284 | +| ------------------------- | ---------- | ------------------------------------------------------------------------------------- | |
| 285 | +| `reportTimestamp` | `uint256` | The reference timestamp corresponding to the moment of the oracle report calculation | |
| 286 | +| `timeElapsed` | `uint256` | Time elapsed since the previously completed report, seconds | |
| 287 | +| `preTotalShares` | `uint256` | Pre-report `stETH` total shares | |
| 288 | +| `preTotalEther` | `uint256` | Pre-report `stETH` total pooled ether (i.e., total supply) | |
| 289 | +| `postTotalShares` | `uint256` | Post-report `stETH` total shares | |
| 290 | +| `postTotalEther` | `uint256` | Post-report `stETH` total pooled ether (i.e., total supply) | |
| 291 | +| `totalSharesMintedAsFees` | `uint256` | Total shares amount minted as the protocol fees on top of the accrued rewards | |
| 292 | + |
| 293 | +### handleConsensusLayerReport() |
| 294 | + |
| 295 | +Handles a new completed `AccountingOracle` report storing the corresponding Beacon Chain epoch id. |
| 296 | + |
| 297 | +Emits [`Completed`](/contracts/legacy-oracle#Completed). |
| 298 | + |
| 299 | +```sol |
| 300 | +function handleConsensusLayerReport( |
| 301 | + uint256 _refSlot, |
| 302 | + uint256 _clBalance, |
| 303 | + uint256 _clValidators |
| 304 | +) |
| 305 | +``` |
| 306 | + |
| 307 | +:::note |
| 308 | +The caller must be `AccountingOracle`. |
| 309 | +::: |
| 310 | + |
| 311 | +#### Parameters |
| 312 | + |
| 313 | +| Name | Type | Description | |
| 314 | +| ---------------- | ---------- | -------------------------------------------------------------------------------- | |
| 315 | +| `_refSlot` | `uint256` | The reference slot corresponding to the moment of the oracle report calculation | |
| 316 | +| `_clBalance` | `uint256` | Lido-participating validators balance on the Beacon Chain side | |
| 317 | +| `_clValidators` | `uint256` | Number of the Lido-participating validators on the Beacon Chain side | |
| 318 | + |
| 319 | +## Events |
| 320 | + |
| 321 | +### Completed() |
| 322 | + |
| 323 | +Emits whenever the `AccountingOracle` report landed. |
| 324 | + |
| 325 | +This event is still emitted after oracle committee reaches consensus on a report, but only for compatibility purposes. |
| 326 | +The values in this event are not enough to calculate APR or TVL anymore due to withdrawals, Execution Layer rewards, and Consensus Layer rewards skimming. |
| 327 | + |
| 328 | +```solidity |
| 329 | +event Completed( |
| 330 | + uint256 epochId, |
| 331 | + uint128 beaconBalance, |
| 332 | + uint128 beaconValidators |
| 333 | +); |
| 334 | +``` |
| 335 | + |
| 336 | +:::note |
| 337 | +Emits inside the [`handleConsensusLayerReport`](/contracts/legacy-oracle#handleConsensusLayerReport) methods. |
| 338 | +::: |
| 339 | + |
| 340 | +#### Parameters |
| 341 | + |
| 342 | +| Name | Type | Description | |
| 343 | +| ------------------ | --------- | ---------------------------------------------------------------------------- | |
| 344 | +| `epochId` | `uint256` | Report reference epoch identifier | |
| 345 | +| `beaconBalance` | `uint128` | The balance of the Lido-participating validators on the Consensus Layer side | |
| 346 | +| `beaconValidators` | `uint128` | The number of the ever appeared Lido-participating validators | |
| 347 | + |
| 348 | +### PostTotalShares() |
| 349 | + |
| 350 | +Emits whenever the `AccountingOracle` report landed. |
| 351 | + |
| 352 | +This event is still emitted after each rebase but only for compatibility purposes. The values in this event are not enough to correctly calculate the rebase APR since a rebase can result from shares burning without changing total ETH held by the protocol. |
| 353 | + |
| 354 | +```solidity |
| 355 | +event PostTotalShares( |
| 356 | + uint256 postTotalPooledEther, |
| 357 | + uint256 preTotalPooledEther, |
| 358 | + uint256 timeElapsed, |
| 359 | + uint256 totalShares |
| 360 | +) |
| 361 | +``` |
| 362 | + |
| 363 | +:::note |
| 364 | +The new [`TokenRebased`](/contracts/lido#TokenRebased) event emitted from the main Lido contract should be used instead because it provides the pre-report total shares amount as well which is essential to properly estimate a token rebase and its projected APR. |
| 365 | +::: |
| 366 | + |
| 367 | +#### Parameters |
| 368 | + |
| 369 | +| Name | Type | Description | |
| 370 | +| ---------------------- | --------- | ----------------------------------------------- | |
| 371 | +| `postTotalPooledEther` | `uint256` | Post-report total pooled ether | |
| 372 | +| `preTotalPooledEther` | `uint256` | Pre-report total pooled ether | |
| 373 | +| `timeElapsed` | `uint256` | Time elapsed since the previous report, seconds | |
| 374 | +| `totalShares` | `uint256` | Post-report total shares | |
0 commit comments