Comments (8)
Spec
ETH = int(1e18)
class OracleContract:
LAST_PUSHED_EPOCH_ID = 0 # Need to be introduced. Absent currently.
gatheredEpochData = {}
# from beaconSpec. Production values. Frame = 1 day
epochsPerFrame = 225
secondsPerEpoch = 32 * 12
"""
If we use APR as a basic reference for increase, and expected range is between 1 and 10% APR.
So the granularity step for yearly APR is 1%
daily_granularity = 0.01 / 365 = 2.74e-05
Currently used basisPoints (1e-4) is unable to express such microscopic ratio.
So we should take another popular metric to express this - PPM or 1e-6
daily_granularity_in_ppm = 2.74e-05 / 1e-6 = 27 (pretty good)
with 100% buffer for future spikes it will be 50
"""
ALLOWED_BEACON_BALANCE_INCREASE = 50 # PPM
"""
When slashing happens, the balance may decrease at a much faster pace.
On the start the system will have about 100 validators and we can guess one of them can be slashed for any reason.
1/100 = 10000 PPM
"""
ALLOWED_BEACON_BALANCE_DECREASE = 10000 # PPM
"""
Based on this analytics https://etherscan.io/address/0xae7ab96520de3a18e5e111b5eaab095312d7fe84#analytics
The max number of daily deposited validators is about 2000 ETH (19 Dec)
Divide it by DEPOSIT_SIZE and get the number of daily activated validators 2000 / 32 = 62.5
And we leave 50-percent buffer for possible short-time bursts ~= 100
"""
ALLOWED_BEACON_VALIDATORS_DAILY_INCREASE = 100 # validators
def reportBeacon(self, _epochId, _beaconBalance, _beaconValidators):
if self._check_constraints(_epochId, _beaconBalance, _beaconValidators):
self.LAST_REPORTED_EPOCH_ID = _epochId
self.gatheredEpochData = (_beaconBalance, _beaconValidators)
return True # Push to lido
else:
return False
def setAllowedBeaconBalanceIncrease(self, _ppmPerDay):
pass
def setAllowedBeaconBalanceDecrease(self, _ppmPerDay):
pass
def setAllowedBeaconvalidatorsIncrease(self, _validatorsPerDay):
pass
def _check_constraints(self, _epochId, _beaconBalance, _beaconValidators):
"""Check the received numbers satisfy change boundaries"""
DAY = 60 * 60 * 24
epoch_delta = _epochId - self.LAST_PUSHED_EPOCH_ID
seconds_since_last_pushed_epoch = epoch_delta * self.secondsPerEpoch
last_pushed_beaconValidators = self.gatheredEpochData[self.LAST_PUSHED_EPOCH_ID][1]
validators_upper_boundary = last_pushed_beaconValidators + seconds_since_last_pushed_epoch * self.ALLOWED_BEACON_VALIDATORS_DAILY_INCREASE / DAY
if _beaconValidators > validators_upper_boundary:
return False # Revert with human-readable reason
last_pushed_beaconBalance = self.gatheredEpochData[self.LAST_PUSHED_EPOCH_ID][0]
beacon_balance_upper_boundary = last_pushed_beaconBalance + seconds_since_last_pushed_epoch * last_pushed_beaconBalance * self.ALLOWED_BEACON_BALANCE_INCREASE / DAY / 1e6
beacon_balance_lower_boundary = last_pushed_beaconBalance - seconds_since_last_pushed_epoch * last_pushed_beaconBalance * self.ALLOWED_BEACON_BALANCE_DECREASE / DAY / 1e6
if beacon_balance_lower_boundary <= _beaconBalance <= beacon_balance_upper_boundary:
return True
return False # Revert with human-readable reason
Tests
def test_report_beacon_balance_and_validators_unchanged():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 100000 * ETH, 100)
def test_report_beacon_balance_allowed_increase():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 100004 * ETH, 100) # one day later
def test_report_beacon_balance_too_much_increase():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 100006 * ETH, 100) == False # one day later
def test_report_beacon_balance_allowed_decrease():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 99000 * ETH, 100) # one day later
def test_report_beacon_balance_too_much_decrease():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 98000 * ETH, 100) == False # one day later
def test_report_beacon_validators_allowed_increase():
o = OracleContract()
o.gatheredEpochData[0] = (100000 * ETH, 100)
assert o.reportBeacon(225, 100004 * ETH, 199) # one day later
from lido-dao.
@vshvsh could you please check the spec for boundaries and default values for mainnet? Is it correct?
from lido-dao.
I think we APR limits should be expressed in yearly APR, not in daily APR (needs to look understandable in the code and in setters). Readability >> economy on calculations in that case.
We should ditch the maximum daily deposits completely (there's no upper limit for deposited validator per day), and the current number only reflects the current average influx of eth in lido, which is not indicative of what is the max allowed (mostly because there is no max deposits allowed, the more the better for lido).
from lido-dao.
-
@vshvsh No APR in the code, it's used above in comments to justify using PPMs instead of basisPoints and default value of ALLOWED_BEACON_BALANCE_INCREASE. Proposed interface
setAllowedBeaconBalanceIncrease(_ppmPerDay)
has a sane readability IMHO. Any ideas how to make it better? -
Reasonable, dropping
ALLOWED_BEACON_VALIDATORS_DAILY_INCREASE
,setAllowedBeaconvalidatorsIncrease(self, _validatorsPerDay)
and corresponding checks.
from lido-dao.
I don't agree that it has same readability. I think that setAllowedBeaconBalanceIncrease(_aprPerYear)
with internal conversion to a daily increase (immediately on setting or in each time on oracle report, not important) will be better, and I don't see reason to have limited resolution like PPMs do.
from lido-dao.
So the APR is calculated similarly to https://github.com/lidofinance/lido-oracle/blob/master/app/metrics.py#L119.
In particular,
DEPOSIT_SIZE = 32 ether
appeared_validators = current.beaconValidators - previous.beaconValidator
reward_base = appeared_validators * DEPOSIT_SIZE + previous.beaconBalance
reward = current.beaconBalance - reward_base
apr_ppm = 1e6 * reward / ( reward_base * 1 years / elapsedTime )
where elapsedTime
is the time in seconds between epochs of current
and previous
beacons,
and 1 years
is a Solidity built-in constant denoting the number of seconds in a year.
@vshvsh, please correct me here.
from lido-dao.
This works for the purpose of sanity checks (not the most correct way to calculate APR to check, that would be checking APR of active validators, but good enough and doesn't require additional data). We can use this.
We still need getters that would show a correct APR for stETH stakers (for them the principal would be appeared_validators * DEPOSIT_SIZE + previous.beaconBalance + transientValidators + bufferedBalance
, or, basically, total pooled ether)
from lido-dao.
We still need getters that would show a correct APR for stETH stakers
This is what I track in issue #289 for clarity. Going to update it today.
from lido-dao.
Related Issues (20)
- Add a view method in NOR to get a range of keys HOT 2
- Unproven keys can be used for the deposit if a node operator has stopped validators HOT 2
- Alternating transactions of oracle committee members HOT 5
- Volatile `gasLimit` for `_submit()` method HOT 3
- Tune up CI checks for readability HOT 1
- Make _submit() cheaper by removing _getTotalPooledEther() != 0 check HOT 3
- No check for report in submitReportData HOT 1
- Slippage control upon withdrawals HOT 7
- LegacyOracle.finalizeUpgrade_V4 can be called multiple times HOT 1
- Prevent possible `pauseStaking` and `resumeStaking` repetitive calls
- Steal of shares using `transferSharesFrom` due to math rounding issues HOT 2
- Potential withdrawal request griefing vector via `permit` front-running HOT 3
- Incorrect unstETH full name on Holesky
- Incompatible Solidity Version and Unresolved Import Issues with lido-dao Contract HOT 1
- Don't assign MANAGE_NODE_OPERATOR_ROLE to voting upon scratch deploy HOT 1
- requestWithdrawals!
- Mark Packed64x4.sol as Unsafe or Deprecated
- Improve precision for the `PositiveTokenRebaseLimiter` library
- Lido
- How to add new operator at the moment of passing the vote.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from lido-dao.