Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

treasury: Delta Governor v1 #615

Merged
merged 32 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cc315d1
package.json: Update @openzeppelin libraries
victorges Jul 7, 2023
d42e92c
bonding: Create SortedArrays library
victorges Jul 7, 2023
2abf207
bonding: Create BondingCheckpoints contract
victorges Jul 7, 2023
a9dde9d
bonding: Checkpoint bonding state on changes
victorges Jul 7, 2023
078f348
test/bonding: Test BondingManager and Checkpoints
victorges Jul 7, 2023
22ee2db
bonding: Migrate to custom error types
victorges Jul 14, 2023
2e4157b
bonding: Allow querying unbonded+uncheckpointed accounts
victorges Jul 17, 2023
06b7100
treasury: Create treasury governance contracts
victorges Jul 17, 2023
0aa6cd0
test/treasury: Add unit test fro BondingCheckpointsVotes
victorges Jul 17, 2023
9f007ba
test/treasury: Test GovernorCountingOverridable
victorges Jul 18, 2023
2fd7abb
test/treasury: Test LivepeerGovernor
victorges Jul 20, 2023
34cb455
test/treasury: A couple additional Governor tests
victorges Jul 20, 2023
e970472
test/treasury: Rename Counting unit test mock
victorges Jul 20, 2023
a6db562
Apply suggestions from code review
victorges Jul 26, 2023
0ad3a8f
treasury: Fix storage layout situation
victorges Jul 27, 2023
bb9ff45
treasury: Move governor initial params to configs
victorges Aug 4, 2023
0c41f78
bonding: Make sure we checkpoint up to once per op
victorges Aug 12, 2023
d518cd6
bonding: Make bonding checkpoints implement IVotes
victorges Aug 13, 2023
ccc3ddf
bonding: Read votes from the end of the round
victorges Aug 15, 2023
d5062fe
bonding: Make checkpoint reading revert-free
victorges Aug 17, 2023
292f954
bonding: Address minor code review comments
victorges Aug 16, 2023
4f12ce9
Merge branch 'vg/feat/bonding-checkpoints' into vg/feat/onchain-governor
victorges Aug 17, 2023
e9d3339
treasury: Migrate to the new BondingVotes contract
victorges Aug 17, 2023
f0fb8ee
treasury: Address PR comments
victorges Aug 17, 2023
9dbb0ae
bonding: Move constructor to after modifiers
victorges Aug 17, 2023
b59a538
test/mocks: Remove mock functions that moved to other mock
victorges Aug 18, 2023
1692ec1
bonding: Implement ERC20 metadata on votes
victorges Aug 21, 2023
fecf5a2
bonding: Address PR comments
victorges Aug 22, 2023
4246474
bonding: Address BondingVotes review comments
victorges Aug 23, 2023
63d1d19
Merge branch 'vg/feat/bonding-checkpoints' into vg/feat/onchain-governor
victorges Aug 23, 2023
8c57a4d
treasury: Merge BondingCheckpoints and nits
victorges Aug 23, 2023
ddaf2bd
Merge branch 'delta' into vg/feat/onchain-governor
victorges Aug 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 93 additions & 38 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 "./IBondingVotes.sol";

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

Expand Down Expand Up @@ -123,6 +124,11 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
_;
}

modifier autoCheckpoint(address _account) {
_;
_checkpointBondingState(_account, delegators[_account], transcoders[_account]);
}

/**
* @notice BondingManager constructor. Only invokes constructor of base Manager contract with provided Controller address
* @dev This constructor will not initialize any state variables besides `controller`. The following setter functions
Expand Down Expand Up @@ -198,6 +204,15 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
rebondFromUnbondedWithHint(_to, _unbondingLockId, address(0), address(0));
}

/**
* @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.
* @param _account The account to make the checkpoint for
*/
function checkpointBondingState(address _account) external {
_checkpointBondingState(_account, delegators[_account], transcoders[_account]);
}

/**
* @notice Withdraws tokens for an unbonding lock that has existed through an unbonding period
* @param _unbondingLockId ID of unbonding lock to withdraw with
Expand Down Expand Up @@ -347,7 +362,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
address _finder,
uint256 _slashAmount,
uint256 _finderFee
) external whenSystemNotPaused onlyVerifier {
) external whenSystemNotPaused onlyVerifier autoClaimEarnings(_transcoder) autoCheckpoint(_transcoder) {
Delegator storage del = delegators[_transcoder];

if (del.bondedAmount > 0) {
Expand Down Expand Up @@ -395,7 +410,12 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
* @notice Claim token pools shares for a delegator from its lastClaimRound through the end round
* @param _endRound The last round for which to claim token pools shares for a delegator
*/
function claimEarnings(uint256 _endRound) external whenSystemNotPaused currentRoundInitialized {
function claimEarnings(uint256 _endRound)
external
whenSystemNotPaused
currentRoundInitialized
autoCheckpoint(msg.sender)
{
// Silence unused param compiler warning
_endRound;

Expand All @@ -407,6 +427,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
*/
function setCurrentRoundTotalActiveStake() external onlyRoundsManager {
currentRoundTotalActiveStake = nextRoundTotalActiveStake;

bondingVotes().checkpointTotalActiveStake(currentRoundTotalActiveStake, roundsManager().currentRound());
}

/**
Expand Down Expand Up @@ -546,6 +568,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
}

emit Bond(_to, currentDelegate, _owner, _amount, del.bondedAmount);

// the `autoCheckpoint` modifier has been replaced with its internal function as a `Stack too deep` error work-around
_checkpointBondingState(_owner, del, transcoders[_owner]);
}

/**
Expand Down Expand Up @@ -669,7 +694,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
uint256 _amount,
address _newPosPrev,
address _newPosNext
) public whenSystemNotPaused currentRoundInitialized autoClaimEarnings(msg.sender) {
) public whenSystemNotPaused currentRoundInitialized autoClaimEarnings(msg.sender) autoCheckpoint(msg.sender) {
require(delegatorStatus(msg.sender) == DelegatorStatus.Bonded, "caller must be bonded");

Delegator storage del = delegators[msg.sender];
Expand Down Expand Up @@ -766,6 +791,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
public
whenSystemNotPaused
currentRoundInitialized
autoCheckpoint(msg.sender)
{
uint256 currentRound = roundsManager().currentRound();

Expand Down Expand Up @@ -1120,7 +1146,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 BondingVotes
* @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,31 +1161,10 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
) internal view returns (uint256 cStake, uint256 cFees) {
// Fetch start cumulative factors
EarningsPool.Data memory startPool = cumulativeFactorsPool(_transcoder, _startRound);

// 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);
}

// 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);
}

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

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

return (cStake, cFees);
return EarningsPoolLIP36.delegatorCumulativeStakeAndFees(startPool, endPool, _stake, _fees);
}

/**
Expand Down Expand Up @@ -1207,18 +1213,33 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
uint256 _amount,
address _newPosPrev,
address _newPosNext
) internal autoCheckpoint(_delegate) {
return increaseTotalStakeUncheckpointed(_delegate, _amount, _newPosPrev, _newPosNext);
}

/**
* @dev Implementation of increaseTotalStake that does not checkpoint the caller, to be used by functions that
* guarantee the checkpointing themselves.
*/
function increaseTotalStakeUncheckpointed(
address _delegate,
uint256 _amount,
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 @@ -1237,7 +1258,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
}

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

/**
Expand All @@ -1250,16 +1271,18 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
uint256 _amount,
address _newPosPrev,
address _newPosNext
) internal {
) internal autoCheckpoint(_delegate) {
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 @@ -1274,7 +1297,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
}

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

/**
Expand Down Expand Up @@ -1342,7 +1365,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {

/**
* @dev Update a transcoder with rewards and update the transcoder pool with an optional list hint if needed.
* See SortedDoublyLL.sol for details on list hints
* See SortedDoublyLL.sol for details on list hints. This function updates the transcoder state but does not
* checkpoint it as it assumes the caller will ensure that.
* @param _transcoder Address of transcoder
* @param _rewards Amount of rewards
* @param _round Round that transcoder is updated
Expand Down Expand Up @@ -1378,11 +1402,14 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// the earnings claiming algorithm and instead that amount is accounted for in the transcoder's cumulativeRewards field
earningsPool.updateCumulativeRewardFactor(prevEarningsPool, delegatorsRewards);
// Update transcoder's total stake with rewards
increaseTotalStake(_transcoder, _rewards, _newPosPrev, _newPosNext);
increaseTotalStakeUncheckpointed(_transcoder, _rewards, _newPosPrev, _newPosNext);
}

/**
* @dev Update a delegator with token pools shares from its lastClaimRound through a given round
*
* Notice that this function updates the delegator storage but does not checkpoint its state. Since it is internal
* it assumes the top-level caller will checkpoint it instead.
* @param _delegator Delegator address
* @param _endRound The last round for which to update a delegator's stake with earnings pool shares
* @param _lastClaimRound The round for which a delegator has last claimed earnings
Expand Down Expand Up @@ -1456,7 +1483,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
uint256 _unbondingLockId,
address _newPosPrev,
address _newPosNext
) internal {
) internal autoCheckpoint(_delegator) {
Delegator storage del = delegators[_delegator];
UnbondingLock storage lock = del.unbondingLocks[_unbondingLockId];

Expand All @@ -1474,6 +1501,30 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
emit Rebond(del.delegateAddress, _delegator, _unbondingLockId, amount);
}

/**
* @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;
bondingVotes().checkpointBondingState(
_owner,
startRound,
_delegator.bondedAmount,
_delegator.delegateAddress,
_delegator.delegatedAmount,
_delegator.lastClaimRound,
_transcoder.lastRewardRound
);
}

/**
* @dev Return LivepeerToken interface
* @return Livepeer token contract registered with Controller
Expand Down Expand Up @@ -1506,6 +1557,10 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
return IRoundsManager(controller.getContract(keccak256("RoundsManager")));
}

function bondingVotes() internal view returns (IBondingVotes) {
return IBondingVotes(controller.getContract(keccak256("BondingVotes")));
}

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