Giter VIP home page Giter VIP logo

Comments (8)

ongrid avatar ongrid commented on August 10, 2024

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.

ongrid avatar ongrid commented on August 10, 2024

@vshvsh could you please check the spec for boundaries and default values for mainnet? Is it correct?

from lido-dao.

vshvsh avatar vshvsh commented on August 10, 2024

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.

ongrid avatar ongrid commented on August 10, 2024
  1. @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?

  2. Reasonable, dropping ALLOWED_BEACON_VALIDATORS_DAILY_INCREASE, setAllowedBeaconvalidatorsIncrease(self, _validatorsPerDay) and corresponding checks.

from lido-dao.

vshvsh avatar vshvsh commented on August 10, 2024

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.

dechjo avatar dechjo commented on August 10, 2024

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.

vshvsh avatar vshvsh commented on August 10, 2024

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.

dechjo avatar dechjo commented on August 10, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.