Skip to content

Commit

Permalink
Merge pull request #135 from livepeer/yf/remove-redistribution
Browse files Browse the repository at this point in the history
Remove redistribution pool. Track claimable stake for token pools
  • Loading branch information
yondonfu authored Dec 29, 2017
2 parents 2e99899 + 2be8b86 commit b5cd4f3
Show file tree
Hide file tree
Showing 19 changed files with 366 additions and 186 deletions.
3 changes: 0 additions & 3 deletions contracts/Manager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ contract Manager is IManager {
// Controller that contract is registered with
IController public controller;

// Divisor used for representing percentages
uint256 public constant PERC_DIVISOR = 1000000;

// Check if sender is controller
modifier onlyController() {
require(msg.sender == address(controller));
Expand Down
83 changes: 39 additions & 44 deletions contracts/bonding/BondingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.4.17;
import "../ManagerProxyTarget.sol";
import "./IBondingManager.sol";
import "../libraries/SortedDoublyLL.sol";
import "../libraries/MathUtils.sol";
import "./libraries/TokenPools.sol";
import "../token/ILivepeerToken.sol";
import "../token/IMinter.sol";
Expand Down Expand Up @@ -165,9 +166,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Only callable if current round is not locked
require(!roundsManager().currentRoundLocked());
// Block reward cut must be a valid percentage
require(_blockRewardCut <= PERC_DIVISOR);
require(MathUtils.validPerc(_blockRewardCut));
// Fee share must be a valid percentage
require(_feeShare <= PERC_DIVISOR);
require(MathUtils.validPerc(_feeShare));

Transcoder storage t = transcoders[msg.sender];
t.pendingBlockRewardCut = _blockRewardCut;
Expand Down Expand Up @@ -449,12 +450,6 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
TokenPools.Data storage tokenPools = t.tokenPoolsPerRound[_round];
// Add fees to fee pool
tokenPools.feePool = tokenPools.feePool.add(_fees);
// Compute claimable and unclaimable fees
uint256 unclaimableFees = tokenPools.unclaimableFees(_fees);
// Add unclaimable fees to the redistribution pool
if (unclaimableFees > 0) {
minter().addToRedistributionPool(unclaimableFees);
}
}

/*
Expand All @@ -477,7 +472,7 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Transcoder must be valid
require(transcoderStatus(_transcoder) == TranscoderStatus.Registered);

uint256 penalty = delegators[_transcoder].bondedAmount.mul(_slashAmount).div(PERC_DIVISOR);
uint256 penalty = MathUtils.percOf(delegators[_transcoder].bondedAmount, _slashAmount);
if (penalty > del.bondedAmount) {
penalty = del.bondedAmount;
}
Expand All @@ -503,18 +498,20 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
// Remove transcoder from pools
transcoderPool.remove(_transcoder);

// Add slashed amount to the redistribution pool
if (penalty > 0) {
uint256 redistributedAmount = penalty;
// Award finder fee
if (penalty > 0 && _finder != address(0)) {
uint256 burnAmount = penalty;

if (_finder != address(0)) {
// Award finder fee
uint256 finderAmount = penalty.mul(_finderFee).div(PERC_DIVISOR);
redistributedAmount = redistributedAmount.sub(finderAmount);
uint256 finderAmount = MathUtils.percOf(penalty, _finderFee);
minter().transferTokens(_finder, finderAmount);

burnAmount = burnAmount.sub(finderAmount);
}

minter().addToRedistributionPool(redistributedAmount);
// Minter burns the remaining slashed funds
minter().burnTokens(burnAmount);
}

TranscoderSlashed(_transcoder, penalty);
Expand Down Expand Up @@ -591,9 +588,12 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {

for (uint256 i = del.lastClaimTokenPoolsSharesRound + 1; i <= currentRound; i++) {
TokenPools.Data storage tokenPools = transcoders[del.delegateAddress].tokenPoolsPerRound[i];

bool isTranscoder = _delegator == del.delegateAddress;
// Calculate and add reward pool share from this round
currentBondedAmount = currentBondedAmount.add(tokenPools.rewardPoolShare(currentBondedAmount, isTranscoder));
if (tokenPools.hasClaimableShares()) {
// Calculate and add reward pool share from this round
currentBondedAmount = currentBondedAmount.add(tokenPools.rewardPoolShare(currentBondedAmount, isTranscoder));
}
}

return currentBondedAmount;
Expand All @@ -618,12 +618,14 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
for (uint256 i = del.lastClaimTokenPoolsSharesRound + 1; i <= currentRound; i++) {
TokenPools.Data storage tokenPools = transcoders[del.delegateAddress].tokenPoolsPerRound[i];

bool isTranscoder = _delegator == del.delegateAddress;
// Calculate and add fee pool share from this round
currentUnbondedAmount = currentUnbondedAmount.add(tokenPools.feePoolShare(currentBondedAmount, isTranscoder));
// Calculate new bonded amount with rewards from this round. Updated bonded amount used
// to calculate fee pool share in next round
currentBondedAmount = currentBondedAmount.add(tokenPools.rewardPoolShare(currentBondedAmount, isTranscoder));
if (tokenPools.hasClaimableShares()) {
bool isTranscoder = _delegator == del.delegateAddress;
// Calculate and add fee pool share from this round
currentUnbondedAmount = currentUnbondedAmount.add(tokenPools.feePoolShare(currentBondedAmount, isTranscoder));
// Calculate new bonded amount with rewards from this round. Updated bonded amount used
// to calculate fee pool share in next round
currentBondedAmount = currentBondedAmount.add(tokenPools.rewardPoolShare(currentBondedAmount, isTranscoder));
}
}

return currentUnbondedAmount;
Expand Down Expand Up @@ -722,14 +724,14 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
)
public
view
returns (uint256 rewardPool, uint256 feePool, uint256 totalStake, uint256 usedStake)
returns (uint256 rewardPool, uint256 feePool, uint256 totalStake, uint256 claimableStake)
{
TokenPools.Data storage tokenPools = transcoders[_transcoder].tokenPoolsPerRound[_round];

rewardPool = tokenPools.rewardPool;
feePool = tokenPools.feePool;
totalStake = tokenPools.totalStake;
usedStake = tokenPools.usedStake;
claimableStake = tokenPools.claimableStake;
}

/*
Expand Down Expand Up @@ -813,20 +815,13 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
TokenPools.Data storage tokenPools = t.tokenPoolsPerRound[_round];
// Add rewards to reward pool
tokenPools.rewardPool = tokenPools.rewardPool.add(_rewards);
// Compute claimable and unclaimable rewards
uint256 unclaimableRewards = tokenPools.unclaimableRewards(_rewards);
uint256 claimableRewards = _rewards.sub(unclaimableRewards);
// Update transcoder's delegated amount with claimable rewards
del.delegatedAmount = del.delegatedAmount.add(claimableRewards);
// Update transcoder's total stake with claimable rewards
uint256 newStake = transcoderPool.getKey(_transcoder).add(claimableRewards);
// Update transcoder's delegated amount with rewards
del.delegatedAmount = del.delegatedAmount.add(_rewards);
// Update transcoder's total stake with rewards
uint256 newStake = transcoderPool.getKey(_transcoder).add(_rewards);
transcoderPool.updateKey(_transcoder, newStake, address(0), address(0));
// Update total bonded tokens with claimable rewards
totalBonded = totalBonded.add(claimableRewards);
// Add unclaimable rewards to the redistribution pool
if (unclaimableRewards > 0) {
minter().addToRedistributionPool(unclaimableRewards);
}
totalBonded = totalBonded.add(_rewards);
}

/*
Expand All @@ -844,15 +839,15 @@ contract BondingManager is ManagerProxyTarget, IBondingManager {
for (uint256 i = del.lastClaimTokenPoolsSharesRound + 1; i <= _endRound; i++) {
TokenPools.Data storage tokenPools = transcoders[del.delegateAddress].tokenPoolsPerRound[i];

bool isTranscoder = _delegator == del.delegateAddress;
uint256 fees = tokenPools.feePoolShare(currentBondedAmount, isTranscoder);
uint256 rewards = tokenPools.rewardPoolShare(currentBondedAmount, isTranscoder);

// Update used stake for token pools for the round
tokenPools.usedStake = tokenPools.usedStake.add(currentBondedAmount);
if (tokenPools.hasClaimableShares()) {
bool isTranscoder = _delegator == del.delegateAddress;

currentUnbondedAmount = currentUnbondedAmount.add(fees);
currentBondedAmount = currentBondedAmount.add(rewards);
var (fees, rewards) = tokenPools.claimShare(currentBondedAmount, isTranscoder);

currentUnbondedAmount = currentUnbondedAmount.add(fees);
currentBondedAmount = currentBondedAmount.add(rewards);
}
}

// Rewards are bonded by default
Expand Down
58 changes: 34 additions & 24 deletions contracts/bonding/libraries/TokenPools.sol
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
pragma solidity ^0.4.17;

import "../../libraries/MathUtils.sol";

import "zeppelin-solidity/contracts/math/SafeMath.sol";


library TokenPools {
using SafeMath for uint256;

uint256 public constant PERC_DIVISOR = 1000000;

// Represents rewards and fees to be distributed to delegators
struct Data {
uint256 rewardPool; // Reward tokens in the pool. (totalStake - stakeUsed) / totalStake = % of claimable rewards in the pool
uint256 feePool; // Fee tokens in the pool. (totalStake - stakeUsed) / totalStake = % of claimable fees in the pool
uint256 rewardPool; // Rewards in the pool
uint256 feePool; // Fees in the pool
uint256 totalStake; // Transcoder's total stake during the pool's round
uint256 usedStake; // Staked used to claim from fee and reward pools
uint256 claimableStake; // Stake that can be used to claim portions of the fee and reward pool
uint256 transcoderBlockRewardCut; // Block reward cut for the reward pool
uint256 transcoderFeeShare; // Fee share for the fee pool
}

function init(TokenPools.Data storage tokenPools, uint256 _stake, uint256 _blockRewardCut, uint256 _feeShare) internal {
tokenPools.totalStake = _stake;
tokenPools.claimableStake = _stake;
tokenPools.transcoderBlockRewardCut = _blockRewardCut;
tokenPools.transcoderFeeShare = _feeShare;
}

function unclaimableFees(TokenPools.Data storage tokenPools, uint256 _fees) internal view returns (uint256) {
if (tokenPools.totalStake == 0) {
return 0;
} else {
uint256 delegatorsFeeShare = _fees.mul(tokenPools.transcoderFeeShare).div(PERC_DIVISOR);
return delegatorsFeeShare.mul(tokenPools.usedStake).div(tokenPools.totalStake);
}
function hasClaimableShares(TokenPools.Data storage tokenPools) internal view returns (bool) {
return tokenPools.claimableStake > 0;
}

function unclaimableRewards(TokenPools.Data storage tokenPools, uint256 _rewards) internal view returns (uint256) {
if (tokenPools.totalStake == 0) {
return 0;
} else {
uint256 delegatorsRewardShare = _rewards.mul(PERC_DIVISOR.sub(tokenPools.transcoderBlockRewardCut)).div(PERC_DIVISOR);
return delegatorsRewardShare.mul(tokenPools.usedStake).div(tokenPools.totalStake);
function claimShare(TokenPools.Data storage tokenPools, uint256 _stake, bool _isTranscoder) internal returns (uint256, uint256) {
uint256 fees = 0;
uint256 rewards = 0;

if (tokenPools.feePool > 0) {
// Compute fee share
fees = feePoolShare(tokenPools, _stake, _isTranscoder);
tokenPools.feePool = tokenPools.feePool.sub(fees);
}

if (tokenPools.rewardPool > 0) {
// Compute reward share
rewards = rewardPoolShare(tokenPools, _stake, _isTranscoder);
tokenPools.rewardPool = tokenPools.rewardPool.sub(rewards);
}

// Update remaning claimable stake for token pools
tokenPools.claimableStake = tokenPools.claimableStake.sub(_stake);

return (fees, rewards);
}

function feePoolShare(TokenPools.Data storage tokenPools, uint256 _stake, bool _isTranscoder) internal view returns (uint256) {
uint256 transcoderFees = 0;
uint256 delegatorFees = 0;

if (tokenPools.totalStake > 0) {
transcoderFees = tokenPools.feePool.mul(PERC_DIVISOR.sub(tokenPools.transcoderFeeShare)).div(PERC_DIVISOR);
delegatorFees = tokenPools.feePool.sub(transcoderFees).mul(_stake).div(tokenPools.totalStake);
if (tokenPools.claimableStake > 0) {
uint256 delegatorsFees = MathUtils.percOf(tokenPools.feePool, tokenPools.transcoderFeeShare);
transcoderFees = tokenPools.feePool.sub(delegatorsFees);
delegatorFees = MathUtils.percOf(delegatorsFees, _stake, tokenPools.claimableStake);
}

if (_isTranscoder) {
Expand All @@ -62,9 +72,9 @@ library TokenPools {
uint256 transcoderRewards = 0;
uint256 delegatorRewards = 0;

if (tokenPools.totalStake > 0) {
transcoderRewards = tokenPools.rewardPool.mul(tokenPools.transcoderBlockRewardCut).div(PERC_DIVISOR);
delegatorRewards = tokenPools.rewardPool.sub(transcoderRewards).mul(_stake).div(tokenPools.totalStake);
if (tokenPools.claimableStake > 0) {
transcoderRewards = MathUtils.percOf(tokenPools.rewardPool, tokenPools.transcoderBlockRewardCut);
delegatorRewards = MathUtils.percOf(tokenPools.rewardPool.sub(transcoderRewards), _stake, tokenPools.claimableStake);
}

if (_isTranscoder) {
Expand Down
9 changes: 5 additions & 4 deletions contracts/jobs/JobsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "../bonding/IBondingManager.sol";
import "../rounds/IRoundsManager.sol";
import "../verification/IVerifiable.sol";
import "../verification/IVerifier.sol";
import "../libraries/MathUtils.sol";

import "zeppelin-solidity/contracts/math/SafeMath.sol";

Expand Down Expand Up @@ -187,7 +188,7 @@ contract JobsManager is ManagerProxyTarget, IVerifiable, IJobsManager {
*/
function setFailedVerificationSlashAmount(uint256 _failedVerificationSlashAmount) external onlyControllerOwner {
// Must be a valid percentage
require(_failedVerificationSlashAmount <= PERC_DIVISOR);
require(MathUtils.validPerc(_failedVerificationSlashAmount));

failedVerificationSlashAmount = _failedVerificationSlashAmount;

Expand All @@ -200,7 +201,7 @@ contract JobsManager is ManagerProxyTarget, IVerifiable, IJobsManager {
*/
function setMissedVerificationSlashAmount(uint256 _missedVerificationSlashAmount) external onlyControllerOwner {
// Must be a valid percentage
require(_missedVerificationSlashAmount <= PERC_DIVISOR);
require(MathUtils.validPerc(_missedVerificationSlashAmount));

missedVerificationSlashAmount = _missedVerificationSlashAmount;

Expand All @@ -213,7 +214,7 @@ contract JobsManager is ManagerProxyTarget, IVerifiable, IJobsManager {
*/
function setDoubleClaimSegmentSlashAmount(uint256 _doubleClaimSegmentSlashAmount) external onlyControllerOwner {
// Must be a valid percentage
require(_doubleClaimSegmentSlashAmount <= PERC_DIVISOR);
require(MathUtils.validPerc(_doubleClaimSegmentSlashAmount));

doubleClaimSegmentSlashAmount = _doubleClaimSegmentSlashAmount;

Expand All @@ -226,7 +227,7 @@ contract JobsManager is ManagerProxyTarget, IVerifiable, IJobsManager {
*/
function setFinderFee(uint256 _finderFee) external onlyControllerOwner {
// Must be a valid percentage
require(_finderFee <= PERC_DIVISOR);
require(MathUtils.validPerc(_finderFee));

finderFee = _finderFee;
}
Expand Down
47 changes: 47 additions & 0 deletions contracts/libraries/MathUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pragma solidity ^0.4.17;

import "zeppelin-solidity/contracts/math/SafeMath.sol";


library MathUtils {
using SafeMath for uint256;

// Divisor used for representing percentages
uint256 public constant PERC_DIVISOR = 1000000;

/*
* @dev Returns whether an amount is a valid percentage out of PERC_DIVISOR
* @param _amount Amount that is supposed to be a percentage
*/
function validPerc(uint256 _amount) internal view returns (bool) {
return _amount <= PERC_DIVISOR;
}

/*
* @dev Compute percentage of a value with the percentage represented by a fraction
* @param _amount Amount to take the percentage of
* @param _fracNum Numerator of fraction representing the percentage
* @param _fracDenom Denominator of fraction representing the percentage
*/
function percOf(uint256 _amount, uint256 _fracNum, uint256 _fracDenom) internal view returns (uint256) {
return _amount.mul(percPoints(_fracNum, _fracDenom)).div(PERC_DIVISOR);
}

/*
* @dev Compute percentage of a value with the percentage represented by a fraction over PERC_DIVISOR
* @param _amount Amount to take the percentage of
* @param _fracNum Numerator of fraction representing the percentage with PERC_DIVISOR as the denominator
*/
function percOf(uint256 _amount, uint256 _fracNum) internal view returns (uint256) {
return _amount.mul(_fracNum).div(PERC_DIVISOR);
}

/*
* @dev Compute percentage representation of a fraction
* @param _fracNum Numerator of fraction represeting the percentage
* @param _fracDenom Denominator of fraction represeting the percentage
*/
function percPoints(uint256 _fracNum, uint256 _fracDenom) internal view returns (uint256) {
return _fracNum.mul(PERC_DIVISOR).div(_fracDenom);
}
}
6 changes: 4 additions & 2 deletions contracts/rounds/RoundsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "../ManagerProxyTarget.sol";
import "./IRoundsManager.sol";
import "../bonding/IBondingManager.sol";
import "../token/IMinter.sol";
import "../libraries/MathUtils.sol";

import "zeppelin-solidity/contracts/math/SafeMath.sol";

Expand Down Expand Up @@ -31,7 +32,7 @@ contract RoundsManager is ManagerProxyTarget, IRoundsManager {
*/
function setParameters(uint256 _roundLength, uint256 _roundLockAmount) external onlyControllerOwner {
// Must be a valid percentage
require(_roundLockAmount <= PERC_DIVISOR);
require(MathUtils.validPerc(_roundLockAmount));

roundLength = _roundLength;
roundLockAmount = _roundLockAmount;
Expand Down Expand Up @@ -103,7 +104,8 @@ contract RoundsManager is ManagerProxyTarget, IRoundsManager {
* @dev Check if we are in the lock period of the current round
*/
function currentRoundLocked() public view returns (bool) {
return blockNum().sub(currentRoundStartBlock()) >= roundLength.sub(roundLength.mul(roundLockAmount).div(PERC_DIVISOR));
uint256 lockedBlocks = MathUtils.percOf(roundLength, roundLockAmount);
return blockNum().sub(currentRoundStartBlock()) >= roundLength.sub(lockedBlocks);
}

/*
Expand Down
Loading

0 comments on commit b5cd4f3

Please sign in to comment.