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.
/// 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 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.
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:
// 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
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.
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...
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.
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
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.
// 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)
}
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.
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
}
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.
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)
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.