Skip to content

Commit

Permalink
bonding: Checkpoint bonding state on changes
Browse files Browse the repository at this point in the history
  • Loading branch information
victorges committed Jul 7, 2023
1 parent 2abf207 commit a9dde9d
Showing 1 changed file with 114 additions and 19 deletions.
133 changes: 114 additions & 19 deletions contracts/bonding/BondingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "../token/ILivepeerToken.sol";
import "../token/IMinter.sol";
import "../rounds/IRoundsManager.sol";
import "../snapshots/IMerkleSnapshot.sol";
import "./IBondingCheckpoints.sol";

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

Expand Down Expand Up @@ -348,6 +349,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
uint256 _slashAmount,
uint256 _finderFee
) external whenSystemNotPaused onlyVerifier {
_autoClaimEarnings(_transcoder);

Delegator storage del = delegators[_transcoder];

if (del.bondedAmount > 0) {
Expand All @@ -361,6 +364,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Decrease bonded stake
del.bondedAmount = del.bondedAmount.sub(penalty);

checkpointBondingState(_transcoder, del, transcoders[_transcoder]);

// If still bonded decrease delegate's delegated amount
if (delegatorStatus(_transcoder) == DelegatorStatus.Bonded) {
delegators[del.delegateAddress].delegatedAmount = delegators[del.delegateAddress].delegatedAmount.sub(
Expand Down Expand Up @@ -407,6 +412,11 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
*/
function setCurrentRoundTotalActiveStake() external onlyRoundsManager {
currentRoundTotalActiveStake = nextRoundTotalActiveStake;

IBondingCheckpoints checkpoints = bondingCheckpoints();
if (address(checkpoints) != address(0)) {
checkpoints.checkpointTotalActiveStake(currentRoundTotalActiveStake, roundsManager().currentRound());
}
}

/**
Expand Down Expand Up @@ -524,6 +534,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
if (currPool.cumulativeRewardFactor == 0) {
currPool.cumulativeRewardFactor = cumulativeFactorsPool(newDelegate, newDelegate.lastRewardRound)
.cumulativeRewardFactor;
if (currPool.cumulativeRewardFactor == 0) {
currPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}
}
if (currPool.cumulativeFeeFactor == 0) {
currPool.cumulativeFeeFactor = cumulativeFactorsPool(newDelegate, newDelegate.lastFeeRound)
Expand All @@ -538,6 +551,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Update bonded amount
del.bondedAmount = currentBondedAmount.add(_amount);

checkpointBondingState(_owner, del, transcoders[_owner]);

increaseTotalStake(_to, delegationAmount, _currDelegateNewPosPrev, _currDelegateNewPosNext);

if (_amount > 0) {
Expand All @@ -548,6 +563,40 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
emit Bond(_to, currentDelegate, _owner, _amount, del.bondedAmount);
}

/**
* @notice Checkpoints a delegator state after changes, to be used for historical voting power calculations in
* on-chain governor logic.
*/
function checkpointBondingState(
address _owner,
Delegator storage _delegator,
Transcoder storage _transcoder
) internal {
// start round refers to the round where the checkpointed stake will be active. The actual `startRound` value
// in the delegators doesn't get updated on bond or claim earnings though, so we use currentRound() + 1
// which is the only guaranteed round where the currently stored stake will be active.
uint256 startRound = roundsManager().currentRound() + 1;
bondingCheckpoints().checkpointBondingState(
_owner,
startRound,
_delegator.bondedAmount,
_delegator.delegateAddress,
_delegator.delegatedAmount,
_delegator.lastClaimRound,
_transcoder.lastRewardRound
);
}

/**
* @notice Checkpoints the bonding state for a given account.
* @dev This is to allow checkpointing an account that has an inconsistent checkpoint with its current state.
* Implemented as a deploy utility to checkpoint the existing state when deploying the BondingCheckpoints contract.
* @param _account The account to initialize the bonding checkpoint for
*/
function checkpointBondingState(address _account) external {
checkpointBondingState(_account, delegators[_account], transcoders[_account]);
}

/**
* @notice Delegates stake towards a specific address and updates the transcoder pool using optional list hints if needed
* @dev If the caller is decreasing the stake of its old delegate in the transcoder pool, the caller can provide an optional hint
Expand Down Expand Up @@ -700,6 +749,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
}
}

// No problem that startRound may have been cleared above, checkpoints are always made for currentRound()+1
checkpointBondingState(msg.sender, del, transcoders[msg.sender]);

// If msg.sender was resigned this statement will only decrease delegators[currentDelegate].delegatedAmount
decreaseTotalStake(currentDelegate, _amount, _newPosPrev, _newPosNext);

Expand Down Expand Up @@ -799,6 +851,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Set last round that transcoder called reward
t.lastRewardRound = currentRound;

checkpointBondingState(msg.sender, delegators[msg.sender], t);

emit Reward(msg.sender, rewardTokens);
}

Expand Down Expand Up @@ -1120,7 +1174,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
* @notice Return a delegator's cumulative stake and fees using the LIP-36 earnings claiming algorithm
* @param _transcoder Storage pointer to a transcoder struct for a delegator's delegate
* @param _startRound The round for the start cumulative factors
* @param _endRound The round for the end cumulative factors
* @param _endRound The round for the end cumulative factors. Normally this is the current round as historical
* lookup is only supported through BondingCheckpoints
* @param _stake The delegator's initial stake before including earned rewards
* @param _fees The delegator's initial fees before including earned fees
* @return cStake , cFees where cStake is the delegator's cumulative stake including earned rewards and cFees is the delegator's cumulative fees including earned fees
Expand All @@ -1134,29 +1189,49 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
) internal view returns (uint256 cStake, uint256 cFees) {
// Fetch start cumulative factors
EarningsPool.Data memory startPool = cumulativeFactorsPool(_transcoder, _startRound);
// Fetch end cumulative factors
EarningsPool.Data memory endPool = latestCumulativeFactorsPool(_transcoder, _endRound);

return delegatorCumulativeStakeAndFees(startPool, endPool, _stake, _fees);
}

/**
* @notice Calculates a delegator's cumulative stake and fees using the LIP-36 earnings claiming algorithm.
* @dev This is a mostly a memroy-only function to be called from other contracts. Created for historical stake
* calculations with BondingCheckpoints.
* @param _startPool The earning pool from the start round for the start cumulative factors. Normally this is the
* earning pool from the {Delegator-lastclaimRound}+1 round, as the round where `bondedAmount` was measured.
* @param _endPool The earning pool from the end round for the end cumulative factors
* @param _stake The delegator initial stake before including earned rewards. Normally the {Delegator-bondedAmount}
* @param _fees The delegator's initial fees before including earned fees
* @return cStake , cFees where cStake is the delegator's cumulative stake including earned rewards and cFees is the
* delegator's cumulative fees including earned fees
*/
function delegatorCumulativeStakeAndFees(
EarningsPool.Data memory _startPool,
EarningsPool.Data memory _endPool,
uint256 _stake,
uint256 _fees
) public pure returns (uint256 cStake, uint256 cFees) {
// If the start cumulativeRewardFactor is 0 set the default value to PreciseMathUtils.percPoints(1, 1)
if (startPool.cumulativeRewardFactor == 0) {
startPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
if (_startPool.cumulativeRewardFactor == 0) {
_startPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}

// Fetch end cumulative factors
EarningsPool.Data memory endPool = latestCumulativeFactorsPool(_transcoder, _endRound);

// If the end cumulativeRewardFactor is 0 set the default value to PreciseMathUtils.percPoints(1, 1)
if (endPool.cumulativeRewardFactor == 0) {
endPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
if (_endPool.cumulativeRewardFactor == 0) {
_endPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}

cFees = _fees.add(
PreciseMathUtils.percOf(
_stake,
endPool.cumulativeFeeFactor.sub(startPool.cumulativeFeeFactor),
startPool.cumulativeRewardFactor
_endPool.cumulativeFeeFactor.sub(_startPool.cumulativeFeeFactor),
_startPool.cumulativeRewardFactor
)
);

cStake = PreciseMathUtils.percOf(_stake, endPool.cumulativeRewardFactor, startPool.cumulativeRewardFactor);
cStake = PreciseMathUtils.percOf(_stake, _endPool.cumulativeRewardFactor, _startPool.cumulativeRewardFactor);

return (cStake, cFees);
}
Expand Down Expand Up @@ -1208,17 +1283,19 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
address _newPosPrev,
address _newPosNext
) internal {
Transcoder storage t = transcoders[_delegate];

uint256 currStake = transcoderTotalStake(_delegate);
uint256 newStake = currStake.add(_amount);

if (isRegisteredTranscoder(_delegate)) {
uint256 currStake = transcoderTotalStake(_delegate);
uint256 newStake = currStake.add(_amount);
uint256 currRound = roundsManager().currentRound();
uint256 nextRound = currRound.add(1);

// If the transcoder is already in the active set update its stake and return
if (transcoderPool.contains(_delegate)) {
transcoderPool.updateKey(_delegate, newStake, _newPosPrev, _newPosNext);
nextRoundTotalActiveStake = nextRoundTotalActiveStake.add(_amount);
Transcoder storage t = transcoders[_delegate];

// currStake (the transcoder's delegatedAmount field) will reflect the transcoder's stake from lastActiveStakeUpdateRound
// because it is updated every time lastActiveStakeUpdateRound is updated
Expand All @@ -1236,8 +1313,12 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
}
}

Delegator storage del = delegators[_delegate];

// Increase delegate's delegated amount
delegators[_delegate].delegatedAmount = delegators[_delegate].delegatedAmount.add(_amount);
del.delegatedAmount = newStake;

checkpointBondingState(_delegate, del, t);
}

/**
Expand All @@ -1251,15 +1332,17 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
address _newPosPrev,
address _newPosNext
) internal {
Transcoder storage t = transcoders[_delegate];

uint256 currStake = transcoderTotalStake(_delegate);
uint256 newStake = currStake.sub(_amount);

if (transcoderPool.contains(_delegate)) {
uint256 currStake = transcoderTotalStake(_delegate);
uint256 newStake = currStake.sub(_amount);
uint256 currRound = roundsManager().currentRound();
uint256 nextRound = currRound.add(1);

transcoderPool.updateKey(_delegate, newStake, _newPosPrev, _newPosNext);
nextRoundTotalActiveStake = nextRoundTotalActiveStake.sub(_amount);
Transcoder storage t = transcoders[_delegate];

// currStake (the transcoder's delegatedAmount field) will reflect the transcoder's stake from lastActiveStakeUpdateRound
// because it is updated every time lastActiveStakeUpdateRound is updated
Expand All @@ -1273,8 +1356,12 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
t.earningsPoolPerRound[nextRound].setStake(newStake);
}

Delegator storage del = delegators[_delegate];

// Decrease old delegate's delegated amount
delegators[_delegate].delegatedAmount = delegators[_delegate].delegatedAmount.sub(_amount);
del.delegatedAmount = newStake;

checkpointBondingState(_delegate, del, t);
}

/**
Expand Down Expand Up @@ -1441,6 +1528,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Rewards are bonded by default
del.bondedAmount = currentBondedAmount;
del.fees = currentFees;

checkpointBondingState(_delegator, del, transcoders[_delegator]);
}

/**
Expand All @@ -1466,6 +1555,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Increase delegator's bonded amount
del.bondedAmount = del.bondedAmount.add(amount);

checkpointBondingState(_delegator, del, transcoders[_delegator]);

// Delete lock
delete del.unbondingLocks[_unbondingLockId];

Expand Down Expand Up @@ -1506,6 +1597,10 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
return IRoundsManager(controller.getContract(keccak256("RoundsManager")));
}

function bondingCheckpoints() internal view returns (IBondingCheckpoints) {
return IBondingCheckpoints(controller.getContract(keccak256("BondingCheckpoints")));
}

function _onlyTicketBroker() internal view {
require(msg.sender == controller.getContract(keccak256("TicketBroker")), "caller must be TicketBroker");
}
Expand Down

0 comments on commit a9dde9d

Please sign in to comment.