Logo
    📋

    Pledge shortfall specification

    Creator
    Alex North
    Created
    Mar 21, 2023 8:11 PM
    Project
    Crypto-economics

    Specification for an implementation of the 🏦Pledge Collateral Shortfall mechanism.

    Summary

    • At any point where an SP needs to lock new initial pledge, they can lock less than the notionally required amount: an incremental shortfall.
    • The maximum shortfall is determined by a pessimistic estimate of the expected rewards to an SP’s power associated over the commitment’s term (or the shortest term they have committed).
    • When rewards are earned (vested), a fraction are taken for repayments, and some burnt as fees. This reward fraction locked is constant or increasing until the entire shortfall is repaid.
    • The fee rate is dynamic. If shortfall is heavily utilised the fee for additional shortfall increases over time, pushing parties back towards sourcing tokens externally.

    Actor state

    Miner actor state

    A new initial_pledge_satisfied field tracks the amount of pledge requirement that is currently satisfied by the miner. At migration, set initial_pledge_satisfied equal to initial_pledge.

    A new shortfall_repayment_take field tracks the fraction of earned (vested) rewards to be redirected into pledge. At migration, set to zero.

    A miner has a shortfall of initial_pledge - initial_pledge_satisfied. Note that a miner may still have fee debt, independent of pledge shortfall.

    Note: A miner’s total pledge is considered to be locked_funds plus initial_pledge, i.e. vesting rewards plus sector pledges.

    💡
    Consider renaming initial_pledge to initial_pledge_requirement.

    Power actor state

    Two new fields track the total initial pledge requirement and satisfaction across all miners. At migration, set these to values calculated from the sum over corresponding miner actor fields.

    /// Sum of initial pledge *satisfied* plus vesting rewards across all miners.
    total_pledge_collateral: TokenAmount, // Existing
    
    /// Sum of initial pledge requirements of all miners.
    initial_pledge_requirement: TokenAmount, // New!
    
    /// Sum of initial pledge satisfied of all miners.
    initial_pledge_satisfied: TokenAmount, // New!

    Initial pledge is broken out from the existing total_pledge_collateral, which also includes vesting rewards. The total pledge collateral tracks the actually satisfied pledge, not the requirement. The total pledge collateral value continues to be used for all calculations that require this input, such as the circulating supply.

    The network overall has a shortfall of initial_pledge_requirement - initial_pledge_satisfied.

    Actor behaviour

    Miner actor

    An incremental shortfall is available at any time the storage provider is increasing their pledge. This means that whenever an SP is increasing their pledge (e.g. sector onboarding, SnapDeal, extension with multiplier), they can provide less than the notionally required amount. The maximum available incremental shortfall is equal to a maximum “repayment take” fraction of the expected rewards for the new commitment over its term. The maximum repayment take value comes from the power actor (see below).

    An SP’s total shortfall determines their repayment reward fraction. This fraction is held constant until shortfall is reduced to zero, or increased when additional onboarding increases the shortfall.

    Some supporting policy functions:

    Sector activation

    Sector activation includes the ProveCommit and ProveCommitAggregate methods. A new version of each of these methods adds an additional parameter for the amount of pledge to lock.

    The new sector’s pledge requirement is calculated as usual. The incremental shortfall allowed is calculated as the maximum repayment capability of the new power. The minimum pledge amount that the SP must provide is then the usual requirement less the incremental shortfall.

    The actual repayment take rate (fraction of earned rewards) that will subsequently be redirected into pledge is then calculated from the SP’s actual total shortfall (i.e. the incremental shortfall plus residual shortfall from earlier onboarding). If the new repayment take is greater than the rate the SP is already paying, their rate is updated. If less, the rate is not changed.

    Note that if an SP has a shortfall based on one duration, then commits a sector with shortfall and a shorter duration, the repayment take for both will be accelerated to the newer, shorter duration. An SP can commit shorter sectors with full collateral to avoid accelerating an existing repayment schedule.

    The power actor’s UpdatePledgeTotal method is expanded to include the initial pledge, and initial pledge satisfied.

    struct UpdatePledgeTotalParams {
        pledge_delta: TokenAmount, // The existing field
    		initial_pledge_delta: TokenAmount, // New!
        initial_pledge_satisifed_delta: TokenAmount, // New!
    }

    The miner sends pledge updates to the power actor corresponding to its state updates.

    Sector update

    Similar to sector activation, a new parameter lets the SP specify the amount of additional pledge to lock. This may be less than the computed requirement, resulting in a shortfall.

    Note that shortfall may only be taken against additional pledge requirements. If the pledge requirement does not increase, an SP cannot use this mechanism to release already-pledged tokens.

    Sector extension

    Similar to activation and update, an SP specifies the additional pledge to lock, and may take a shortfall. The duration for computing the maximum shortfall is the sectors new expiration epoch minus its old expiration epoch (i.e. the incremental duration addition).

    Note that initial pledge requirements at extension are only likely to change significantly with the introduction of some kind of duration multiplier.

    Sector expiration

    When a sector expires, its pledge requirement that is returned in proportion to the miner’s overall satisfaction of pledge requirements. The ratio of shortfall to pledge requirement thus remains constant.

    pledge_satisfaction = miner.initial_pledge_satisfied / miner.initial_pledge
    pledge_to_release = pledge_satisfaction * sector.initial_pledge

    The repayment fraction of rewards is updated to maintain a constant repayment amount despite the reduced rate of rewards from expired power.

    TODO: account for the fact that absolute shortfall has reduced (to keep fraction constant). This adjustment is only needed to adjust for different ratios of pledge:power for different sectors. The proposed spec assumes the ratio for the expired sector was zero, which is too harsh.

    miner.shortfall_repayment_take *= old_power / new_power

    Sector termination

    Similar to expiration (after paying termination fee).

    Receiving rewards—fees and repayment

    A miner pays fees from vested rewards, except where insufficient vested rewards are available in which case fees are paid from vesting rewards. Some of the earned rewards are also automatically locked to reduce the pledge shortfall.

    Repayments of the shortfall are made when tokens vest.

    vested_reward = unlock_vested_rewards(...)
    
    shortfall = miner.initial_pledge - miner.initial_pledge_satisfied
    repayment_amount = vested_reward * miner.shortfall_repayment_take
    
    if repayment_amount <= shortfall {
    	repayment_amount = shortfall
      // Reset repayment rate to zero
      miner.shortfall_repayment_take = 0 
    }
    miner.initial_pledge_satisfied += repayment_amount
    
    // Any amount not locked as pledge becomes available balance.

    Penalties and fee debt

    The current behaviour is maintained. A miner pays penalties from vesting rewards, then available balance, but cannot reduce their balance below the satisfied pledge amount. A miner goes into fee debt if they have no unlocked balance to pay an incremental fee. A penalty cannot be used to increase shortfall.

    Explicit shortfall repayment

    A new method allows a miner to lock funds to reduce their shortfall immediately.

    Dynamic fees

    This is a rough sketch, needs more detail.

    The power actor maintains the maximum repayment take and fee take (fractions of earned reward to take for repayment/fee) parameters for incremental shortfalls. The values sum to 1.0. The values adjust to increase the cost of taking a shortfall if it is heavily utilised by the network, and decrease to a floor if not utilised.

    The fee take is initially set to BaseShortfallFeeTake = 0.25, implying a repayment take of 0.75. The initial value 25% is chosen to match the existing parameter of amount of earned rewards that don’t vest, so can be immediately used by SPs. These values in turn imply a network maximum shortfall amount that can be repaid with the remaining emitted rewards.

    If the actual network shortfall is greater than TargetShortfallUtilisation then the fee take is increased by multiplying it by ShortfallFeeChangeVelocity per epoch (in cron). If the actual network shortfall is less than the target, the fee take is decreased, but only down to BaseShortfallFeeTake.

    Note that changes in the shortfall fee only affect SPs who add new, incremental shortfall. An SP that onboards only fully-pledged sectors will not experience any increase in their repayment take. An SP that does onboard a new sector with a shortfall will have the new repayment take apply to all their resulting shortfall.

    Design notes

    The incremental pledge shortfall fraction is the same as the miner’s max shortfall fraction. We need some part of the incremental pledge to be provided in order to avoid shocks, and maintain demand for tokens. We could set the incremental pledge shortfall fraction higher (e.g. 50%) than the max shortfall fraction, but this would advantage to larger miners. In effect, they would be using their already large income streams to pay down the new pledge. This would support greater network-wide use, but advantage established parties more.

    CryptoNet is a Protocol Labs initiative.

    /// Total rewards and added funds locked in vesting table.
    locked_funds: TokenAmount, // Existing
    
    /// Absolute value of debt this miner owes from unpaid fees.
    fee_debt: TokenAmount, // Existing
    
    /// Sum of initial pledge requirements of all active sectors.
    initial_pledge: TokenAmount, // Existing
    
    /// Amount of initial pledge requirement that is satisifed by tokens
    /// held on balance.
    initial_pledge_satisfied: TokenAmount, // New!
    
    /// Fraction of vested rewards to redirect into pledge 
    /// while there is a shortfall.
    /// Represented as some fixed point, e.g. 1/1000.
    shortfall_repayment_take: u32, // New!
    // A pessimistic projected reward for power over some period.
    fn expected_reward_for_power(network_reward, network_power, power, period) {
    	// The reward is projected from the current per-epoch reward for 
    	// the share of power, with a pessimistic decay
      // - REWARD_DECAY is the constant network reward decay rate
      // - BASELINE_GROWTH is the constant baseline function growth rate
      // This pessimistically assumes that the share of power decays as if
      // a miner stopped growing while the rest of the network grew at the
      // baseline function rate.
      // This can still temporarily under-estimate if:
      // - Reward decays faster, e.g. due to baseline crossing 
      //   (but then the network isn't growing at baseline rate), or
      // - The network grows even faster that baseline rate
    	decay = REWARD_DECAY + BASELINE_GROWTH
    	return sum_of_exponential_decay(period, decay) * network_reward * power / network_power
    }
    
    // The maximum shortfall amount permitted for a miner to maintain,
    // or incrementally add.
    fn max_shortfall(network_reward_estimate, network_power_estimate, power, period,
    		repayment_take) {
    	repayment_take * expected_reward_for_power(...)
    }
    
    // SUM[(1-r)^x] for x in 0..duration
    fn sum_over_exponential_decay(duration, decay) -> float:
        return (1 - pow(1 - decay, duration) + decay * pow(1 - decay, duration)) / decay
    struct ProveCommitSectorParams {
        sector_number: SectorNumber,
        proof: Vec<u8>,
    		// New: Amount of balance to lock as pledge.
    		// Any less than the required pledge amount is taken as a shortfall.
    		// A value greater than requirement is clamped to the actual requirement.
        // Zero means to lock exactly the minimum amount allowed (max shortfall).
    		// Value greater than zero but less than the minimum allowed are rejected.
    	  pledge: TokenAmount,
    }
    
    // Similar for ProveCommitAggregateParams...
    max_repayment_take = fetch_parameters_from_power_actor()
    pledge_requirement = initial_pledge_for_power(...)
    allowed_shortfall = max_shortfall(network_reward, network_power, 
    	sector_power, sector.duration, max_repayment_take)
    minimum_pledge_requirement = pledge_requirement - allowed_shortfall
    if params.pledge == 0 {
    	params.pledge = minimum_pledge_requirement
    } else if params.pledge > pledge_requirement {
    	params.pledge = pledge_requirement
    } else if params.pledge < minimum_pledge_requirement {
    	abort("pledge is less than minimum")
    }
    	
    // Update state
    miner.inital_pledge += pledge_requirement
    miner.initial_pledge_satisfied += params.pledge
    // After updating state
    if params.pledge < pledge_requirement {
    	current_shortfall = miner.inital_pledge - miner.initial_pledge_satisfied
    	expected_rewards = expected_reward_for_power(network_reward, network_power,
    		miner.power, sector.duration)
    	repayment_take = current_shortfall / expected_rewards
    	if repayment_take > max_repayment_take {
    	    abort("computed repayment reward fraction exceeds maximum")
    	}
    
    	// Ratchet up repayment take if necessary.
    	miner.shortfall_repayment_take = max(miner.shortfall_repayment_take, repayment_take)
    }
    incremental_power = new_power - old_power
    old_pledge_requirement = old_sector.initial_pledge
    new_pledge_requirement = initial_pledge_for_power(...)
    incremental_pledge = new_pledge_requirement - old_pledge_requirement
    if incremental_pledge > 0 {
    	duration = sector.expiration - current_epoch
    	allowed_shortfall = max_shortfall(network_reward, network_power, 
    	  incremental_power, duration)
    	minimum_pledge_requirement = new_pledge_requirement - allowed_shortfall
    	// Continue as for sector activation
    }
    max_fee_take = fetch_parameters_from_power_actor()
    immediate_reward = 0.25 * earned_reward // Existing behaviour
    vesting_reward = 0.75 * earned_reward // Existing behaviour
    
    max_shortfall = max_shortfall(network_reward, network_power,
    	miner_power, 5_YEARS) // 5_YEARS may as well be infinity given the decay rate
    actual_shortfall = miner.initial_pledge - miner.initial_pledge_satisfied
    shortfall_fraction = actual_shortfall / max_shortfall
    
    fee_take_rate = shortfall_fraction * max_fee_take
    fee_amount = earned_reward * miner.fee_take_rate
    burn(fee_amount)
    
    // Cater for parameters where fee amount can exceed the immediately 
    // available rewards.
    if fee_amount > immediate_reward {
    	vesting_reward -= (fee_amount - immediate_reward)
    }
    vest(vesting_reward)