@0x/contracts-staking: It compiles!

This commit is contained in:
Lawrence Forman
2019-09-18 12:13:45 -04:00
committed by Lawrence Forman
parent 7fb5ed0b42
commit ac7f6aef9e
33 changed files with 930 additions and 684 deletions

View File

@@ -29,10 +29,24 @@ import "./fees/MixinExchangeFees.sol";
contract Staking is contract Staking is
IStaking, IStaking,
IStakingEvents,
MixinAbstract,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager,
MixinParams, MixinParams,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards,
MixinStakingPool, MixinStakingPool,
MixinStake, MixinStake,
MixinExchangeFees, MixinExchangeFees
{ {
// this contract can receive ETH // this contract can receive ETH
// solhint-disable no-empty-blocks // solhint-disable no-empty-blocks

View File

@@ -27,6 +27,8 @@ import "./interfaces/IStakingProxy.sol";
contract StakingProxy is contract StakingProxy is
IStakingProxy, IStakingProxy,
MixinConstants,
Ownable,
MixinStorage MixinStorage
{ {
using LibProxy for address; using LibProxy for address;

View File

@@ -44,10 +44,19 @@ import "./MixinExchangeManager.sol";
/// disincentive for market makers to monopolize a single pool that they /// disincentive for market makers to monopolize a single pool that they
/// all delegate to. /// all delegate to.
contract MixinExchangeFees is contract MixinExchangeFees is
MixinDeploymentConstants, IStakingEvents,
MixinExchangeManager,
MixinAbstract, MixinAbstract,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards, MixinStakingPoolRewards,
MixinStakingPool MixinStakingPool
{ {
@@ -99,7 +108,7 @@ contract MixinExchangeFees is
} }
// Look up the pool for this epoch. // Look up the pool for this epoch.
uint256 currentEpoch = getCurrentEpoch(); uint256 currentEpoch = currentEpoch;
mapping (bytes32 => IStructs.ActivePool) storage activePoolsThisEpoch = mapping (bytes32 => IStructs.ActivePool) storage activePoolsThisEpoch =
_getActivePoolsFromEpoch(currentEpoch); _getActivePoolsFromEpoch(currentEpoch);
IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId]; IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId];
@@ -169,7 +178,7 @@ contract MixinExchangeFees is
// because we only need to remember state in the current epoch and the // because we only need to remember state in the current epoch and the
// epoch prior. // epoch prior.
IStructs.ActivePool memory pool = IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(getCurrentEpoch(), poolId); _getActivePoolFromEpoch(currentEpoch, poolId);
feesCollected = pool.feesCollected; feesCollected = pool.feesCollected;
} }
@@ -188,7 +197,7 @@ contract MixinExchangeFees is
returns (uint256 membersStake, uint256 weightedStake) returns (uint256 membersStake, uint256 weightedStake)
{ {
uint256 operatorStake = getStakeDelegatedToPoolByOwner( uint256 operatorStake = getStakeDelegatedToPoolByOwner(
getPoolOperator(poolId), poolById[poolId].operator,
poolId poolId
).currentEpochBalance; ).currentEpochBalance;
membersStake = totalStake.safeSub(operatorStake); membersStake = totalStake.safeSub(operatorStake);

View File

@@ -30,6 +30,8 @@ import "../immutable/MixinStorage.sol";
/// then it should be removed. /// then it should be removed.
contract MixinExchangeManager is contract MixinExchangeManager is
IStakingEvents, IStakingEvents,
MixinConstants,
Ownable,
MixinStorage MixinStorage
{ {
/// @dev Asserts that the call is coming from a valid exchange. /// @dev Asserts that the call is coming from a valid exchange.

View File

@@ -74,12 +74,6 @@ contract MixinStorage is
// mapping from Owner to Amount of Withdrawable Stake // mapping from Owner to Amount of Withdrawable Stake
mapping (address => uint256) internal _withdrawableStakeByOwner; mapping (address => uint256) internal _withdrawableStakeByOwner;
// Mapping from Owner to Pool Id to epoch of the last rewards collected.
// This is the last reward epoch for a pool that a delegator collected
// rewards from. This is different from the epoch when the rewards were
// collected This will always be `<= currentEpoch`.
mapping (address => mapping (bytes32 => uint256)) internal lastCollectedRewardsEpochToPoolByOwner;
// tracking Pool Id // tracking Pool Id
bytes32 public nextPoolId = INITIAL_POOL_ID; bytes32 public nextPoolId = INITIAL_POOL_ID;
@@ -141,35 +135,35 @@ contract MixinStorage is
/// @dev The total fees collected in the current epoch, built up iteratively /// @dev The total fees collected in the current epoch, built up iteratively
/// in `payProtocolFee()`. /// in `payProtocolFee()`.
uint256 internal totalFeesCollectedThisEpoch; uint256 public totalFeesCollectedThisEpoch;
/// @dev The total weighted stake in the current epoch, built up iteratively /// @dev The total weighted stake in the current epoch, built up iteratively
/// in `payProtocolFee()`. /// in `payProtocolFee()`.
uint256 internal totalWeightedStakeThisEpoch; uint256 public totalWeightedStakeThisEpoch;
/// @dev State information for each active pool in an epoch. /// @dev State information for each active pool in an epoch.
/// In practice, we only store state for `currentEpoch % 2`. /// In practice, we only store state for `currentEpoch % 2`.
mapping(uint256 => mapping(bytes32 => IStructs.ActivePool)) mapping(uint256 => mapping(bytes32 => IStructs.ActivePool))
internal internal
activePoolsByEpoch; _activePoolsByEpoch;
/// @dev Number of pools activated in the current epoch. /// @dev Number of pools activated in the current epoch.
uint256 internal numActivePoolsThisEpoch; uint256 public numActivePoolsThisEpoch;
/// @dev Rewards (ETH) available to the epoch being finalized (the previous /// @dev Rewards (ETH) available to the epoch being finalized (the previous
/// epoch). This is simply the balance of the contract at the end of /// epoch). This is simply the balance of the contract at the end of
/// the epoch. /// the epoch.
uint256 internal unfinalizedRewardsAvailable; uint256 public unfinalizedRewardsAvailable;
/// @dev The number of active pools in the last epoch that have yet to be /// @dev The number of active pools in the last epoch that have yet to be
/// finalized through `finalizePools()`. /// finalized through `finalizePools()`.
uint256 internal unfinalizedPoolsRemaining; uint256 public unfinalizedPoolsRemaining;
/// @dev The total fees collected for the epoch being finalized. /// @dev The total fees collected for the epoch being finalized.
uint256 internal unfinalizedTotalFeesCollected; uint256 public unfinalizedTotalFeesCollected;
/// @dev The total fees collected for the epoch being finalized. /// @dev The total fees collected for the epoch being finalized.
uint256 internal unfinalizedTotalWeightedStake; uint256 public unfinalizedTotalWeightedStake;
/// @dev How many rewards were paid at the end of finalization. /// @dev How many rewards were paid at the end of finalization.
uint256 totalRewardsPaidLastEpoch; uint256 totalRewardsPaidLastEpoch;

View File

@@ -42,13 +42,14 @@ interface IEthVault {
uint256 amount uint256 amount
); );
/// @dev Deposit an `amount` of ETH from `owner` into the vault. /// @dev Record a deposit of an amount of ETH for `owner` into the vault.
/// Note that only the Staking contract can call this. /// The staking contract should pay this contract the ETH owed in the
/// Note that this can only be called when *not* in Catostrophic Failure mode. /// same transaction.
/// @param owner of ETH Tokens. /// Note that this is only callable by the staking contract.
function depositFor(address owner) /// @param owner Owner of the ETH.
external /// @param amount Amount of deposit.
payable; function recordDepositFor(address owner, uint256 amount)
external;
/// @dev Withdraw an `amount` of ETH to `msg.sender` from the vault. /// @dev Withdraw an `amount` of ETH to `msg.sender` from the vault.
/// Note that only the Staking contract can call this. /// Note that only the Staking contract can call this.

View File

@@ -48,8 +48,8 @@ interface IStakingEvents {
/// @param epoch The epoch in which the pool was activated. /// @param epoch The epoch in which the pool was activated.
/// @param poolId The ID of the pool. /// @param poolId The ID of the pool.
event StakingPoolActivated( event StakingPoolActivated(
uint256 epoch, uint256 indexed epoch,
bytes32 poolId bytes32 indexed poolId
); );
/// @dev Emitted by MixinFinalizer when an epoch has ended. /// @dev Emitted by MixinFinalizer when an epoch has ended.
@@ -59,7 +59,7 @@ interface IStakingEvents {
/// @param totalWeightedStake Total weighted stake across all active pools. /// @param totalWeightedStake Total weighted stake across all active pools.
/// @param totalFeesCollected Total fees collected across all active pools. /// @param totalFeesCollected Total fees collected across all active pools.
event EpochEnded( event EpochEnded(
uint256 epoch, uint256 indexed epoch,
uint256 numActivePools, uint256 numActivePools,
uint256 rewardsAvailable, uint256 rewardsAvailable,
uint256 totalFeesCollected, uint256 totalFeesCollected,
@@ -71,7 +71,7 @@ interface IStakingEvents {
/// @param rewardsPaid Total amount of rewards paid out. /// @param rewardsPaid Total amount of rewards paid out.
/// @param rewardsRemaining Rewards left over. /// @param rewardsRemaining Rewards left over.
event EpochFinalized( event EpochFinalized(
uint256 epoch, uint256 indexed epoch,
uint256 rewardsPaid, uint256 rewardsPaid,
uint256 rewardsRemaining uint256 rewardsRemaining
); );
@@ -82,8 +82,8 @@ interface IStakingEvents {
/// @param operatorReward Amount of reward paid to pool operator. /// @param operatorReward Amount of reward paid to pool operator.
/// @param membersReward Amount of reward paid to pool members. /// @param membersReward Amount of reward paid to pool members.
event RewardsPaid( event RewardsPaid(
uint256 epoch, uint256 indexed epoch,
bytes32 poolId, bytes32 indexed poolId,
uint256 operatorReward, uint256 operatorReward,
uint256 membersReward uint256 membersReward
); );

View File

@@ -33,34 +33,35 @@ interface IStakingPoolRewardVault {
uint256 amount uint256 amount
); );
/// @dev Emitted when a reward is transferred to the ETH vault. /// @dev Emitted when rewards are transferred out fo the vault.
/// @param amount The amount in ETH withdrawn. /// @param poolId Unique Id of pool.
/// @param member of the pool. /// @param to Address to send funds to.
/// @param poolId The pool the reward was deposited for. /// @param amount Amount of ETH to transfer.
event PoolRewardTransferredToEthVault( event PoolRewardTransferred(
bytes32 indexed poolId, bytes32 indexed poolId,
address indexed member, address to,
uint256 amount uint256 amount
); );
/// @dev Deposit an amount of ETH (`msg.value`) for `poolId` into the vault.
/// Note that this is only callable by the staking contract.
/// @param poolId that owns the ETH.
function depositFor(bytes32 poolId)
external
payable;
/// @dev Withdraw some amount in ETH of a pool member. /// @dev Record a deposit of an amount of ETH for `poolId` into the vault.
/// The staking contract should pay this contract the ETH owed in the
/// same transaction.
/// Note that this is only callable by the staking contract.
/// @param poolId Pool that holds the ETH.
/// @param amount Amount of deposit.
function recordDepositFor(bytes32 poolId, uint256 amount)
external;
/// @dev Withdraw some amount in ETH from a pool.
/// Note that this is only callable by the staking contract. /// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param member of pool to transfer funds to. /// @param to Address to send funds to.
/// @param amount Amount in ETH to transfer. /// @param amount Amount of ETH to transfer.
/// @param ethVaultAddress address of Eth Vault to send rewards to. function transfer(
function transferToEthVault(
bytes32 poolId, bytes32 poolId,
address member, address payable to,
uint256 amount, uint256 amount
address ethVaultAddress
) )
external; external;

View File

@@ -32,16 +32,6 @@ interface IStructs {
uint256 membersStake; uint256 membersStake;
} }
/// @dev Rewards credited to a pool during finalization.
/// @param operatorReward The amount of reward credited to the pool operator.
/// @param membersReward The amount of reward credited to the pool members.
/// @param membersStake The amount of members/delegated stake in the pool.
struct PoolRewards {
uint256 operatorReward;
uint256 membersReward;
uint256 membersStake;
}
/// @dev Encapsulates a balance for the current and next epochs. /// @dev Encapsulates a balance for the current and next epochs.
/// Note that these balances may be stale if the current epoch /// Note that these balances may be stale if the current epoch
/// is greater than `currentEpoch`. /// is greater than `currentEpoch`.

View File

@@ -27,16 +27,24 @@ import "../libs/LibStakingRichErrors.sol";
/// @dev This mixin contains logic for managing ZRX tokens and Stake. /// @dev This mixin contains logic for managing ZRX tokens and Stake.
contract MixinStake is contract MixinStake is
IStakingEvents,
MixinAbstract,
MixinConstants,
Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers, MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards, MixinStakingPoolRewards,
MixinStakingPool MixinStakingPool
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
/// @dev Stake ZRX tokens. Tokens are deposited into the ZRX Vault. Unstake to retrieve the ZRX. /// @dev Stake ZRX tokens. Tokens are deposited into the ZRX Vault.
/// Stake is in the 'Active' status. /// Unstake to retrieve the ZRX. Stake is in the 'Active' status.
/// @param amount of ZRX to stake. /// @param amount of ZRX to stake.
function stake(uint256 amount) function stake(uint256 amount)
external external
@@ -59,8 +67,9 @@ contract MixinStake is
); );
} }
/// @dev Unstake. Tokens are withdrawn from the ZRX Vault and returned to the owner. /// @dev Unstake. Tokens are withdrawn from the ZRX Vault and returned to
/// Stake must be in the 'inactive' status for at least one full epoch to unstake. /// the owner. Stake must be in the 'inactive' status for at least
/// one full epoch to unstake.
/// @param amount of ZRX to unstake. /// @param amount of ZRX to unstake.
function unstake(uint256 amount) function unstake(uint256 amount)
external external
@@ -85,7 +94,8 @@ contract MixinStake is
_decrementCurrentAndNextBalance(globalStakeByStatus[uint8(IStructs.StakeStatus.INACTIVE)], amount); _decrementCurrentAndNextBalance(globalStakeByStatus[uint8(IStructs.StakeStatus.INACTIVE)], amount);
// update withdrawable field // update withdrawable field
_withdrawableStakeByOwner[owner] = currentWithdrawableStake.safeSub(amount); _withdrawableStakeByOwner[owner] =
currentWithdrawableStake.safeSub(amount);
// withdraw equivalent amount of ZRX from vault // withdraw equivalent amount of ZRX from vault
zrxVault.withdrawFrom(owner, amount); zrxVault.withdrawFrom(owner, amount);
@@ -110,16 +120,20 @@ contract MixinStake is
external external
{ {
// sanity check - do nothing if moving stake between the same status // sanity check - do nothing if moving stake between the same status
if (from.status != IStructs.StakeStatus.DELEGATED && from.status == to.status) { if (from.status != IStructs.StakeStatus.DELEGATED
&& from.status == to.status)
{
return; return;
} else if (from.status == IStructs.StakeStatus.DELEGATED && from.poolId == to.poolId) { } else if (from.status == IStructs.StakeStatus.DELEGATED
&& from.poolId == to.poolId)
{
return; return;
} }
address payable owner = msg.sender; address payable owner = msg.sender;
// handle delegation; this must be done before moving stake as the current // handle delegation; this must be done before moving stake as the
// (out-of-sync) status is used during delegation. // current (out-of-sync) status is used during delegation.
if (from.status == IStructs.StakeStatus.DELEGATED) { if (from.status == IStructs.StakeStatus.DELEGATED) {
_undelegateStake( _undelegateStake(
from.poolId, from.poolId,
@@ -136,14 +150,18 @@ contract MixinStake is
); );
} }
// cache the current withdrawal amount, which may change if we're moving out of the inactive status. // cache the current withdrawal amount, which may change if we're
uint256 withdrawableStake = (from.status == IStructs.StakeStatus.INACTIVE) // moving out of the inactive status.
uint256 withdrawableStake =
(from.status == IStructs.StakeStatus.INACTIVE)
? getWithdrawableStake(owner) ? getWithdrawableStake(owner)
: 0; : 0;
// execute move // execute move
IStructs.StoredBalance storage fromPtr = _getBalancePtrFromStatus(owner, from.status); IStructs.StoredBalance storage fromPtr =
IStructs.StoredBalance storage toPtr = _getBalancePtrFromStatus(owner, to.status); _getBalancePtrFromStatus(owner, from.status);
IStructs.StoredBalance storage toPtr =
_getBalancePtrFromStatus(owner, to.status);
_moveStake(fromPtr, toPtr, amount); _moveStake(fromPtr, toPtr, amount);
// update global total of stake in the statuses being moved between // update global total of stake in the statuses being moved between
@@ -155,7 +173,8 @@ contract MixinStake is
// update withdrawable field, if necessary // update withdrawable field, if necessary
if (from.status == IStructs.StakeStatus.INACTIVE) { if (from.status == IStructs.StakeStatus.INACTIVE) {
_withdrawableStakeByOwner[owner] = _computeWithdrawableStake(owner, withdrawableStake); _withdrawableStakeByOwner[owner] =
_computeWithdrawableStake(owner, withdrawableStake);
} }
// notify // notify
@@ -171,8 +190,8 @@ contract MixinStake is
/// @dev Delegates a owners stake to a staking pool. /// @dev Delegates a owners stake to a staking pool.
/// @param poolId Id of pool to delegate to. /// @param poolId Id of pool to delegate to.
/// @param owner who wants to delegate. /// @param owner Owner who wants to delegate.
/// @param amount of stake to delegate. /// @param amount Amount of stake to delegate.
function _delegateStake( function _delegateStake(
bytes32 poolId, bytes32 poolId,
address payable owner, address payable owner,
@@ -180,27 +199,38 @@ contract MixinStake is
) )
private private
{ {
// sanity check the pool we're delegating to exists // Sanity check the pool we're delegating to exists.
_assertStakingPoolExists(poolId); _assertStakingPoolExists(poolId);
// cache amount delegated to pool by owner // Cache amount delegated to pool by owner.
IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]); IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner =
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]);
// increment how much stake the owner has delegated to the input pool // Increment how much stake the owner has delegated to the input pool.
_incrementNextBalance(_delegatedStakeToPoolByOwner[owner][poolId], amount); _incrementNextBalance(
_delegatedStakeToPoolByOwner[owner][poolId],
amount
);
// increment how much stake has been delegated to pool // Increment how much stake has been delegated to pool.
_incrementNextBalance(_delegatedStakeByPoolId[poolId], amount); _incrementNextBalance(_delegatedStakeByPoolId[poolId], amount);
// synchronizes reward state in the pool that the staker is delegating to // Synchronizes reward state in the pool that the staker is delegating
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(_delegatedStakeToPoolByOwner[owner][poolId]); // to.
_syncRewardsForDelegator(poolId, owner, initDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner); IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner =
_loadAndSyncBalance(_delegatedStakeToPoolByOwner[owner][poolId]);
_syncRewardsForDelegator(
poolId,
owner,
initDelegatedStakeToPoolByOwner,
finalDelegatedStakeToPoolByOwner
);
} }
/// @dev Un-Delegates a owners stake from a staking pool. /// @dev Un-Delegates a owners stake from a staking pool.
/// @param poolId Id of pool to un-delegate from. /// @param poolId Id of pool to un-delegate from.
/// @param owner who wants to un-delegate. /// @param owner Owner who wants to un-delegate.
/// @param amount of stake to un-delegate. /// @param amount Amount of stake to un-delegate.
function _undelegateStake( function _undelegateStake(
bytes32 poolId, bytes32 poolId,
address payable owner, address payable owner,
@@ -212,24 +242,38 @@ contract MixinStake is
_assertStakingPoolExists(poolId); _assertStakingPoolExists(poolId);
// cache amount delegated to pool by owner // cache amount delegated to pool by owner
IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]); IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner =
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]);
// decrement how much stake the owner has delegated to the input pool // decrement how much stake the owner has delegated to the input pool
_decrementNextBalance(_delegatedStakeToPoolByOwner[owner][poolId], amount); _decrementNextBalance(
_delegatedStakeToPoolByOwner[owner][poolId],
amount
);
// decrement how much stake has been delegated to pool // decrement how much stake has been delegated to pool
_decrementNextBalance(_delegatedStakeByPoolId[poolId], amount); _decrementNextBalance(_delegatedStakeByPoolId[poolId], amount);
// synchronizes reward state in the pool that the staker is undelegating from // synchronizes reward state in the pool that the staker is undelegating
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(_delegatedStakeToPoolByOwner[owner][poolId]); // from
_syncRewardsForDelegator(poolId, owner, initDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner); IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner =
_loadAndSyncBalance(_delegatedStakeToPoolByOwner[owner][poolId]);
_syncRewardsForDelegator(
poolId,
owner,
initDelegatedStakeToPoolByOwner,
finalDelegatedStakeToPoolByOwner
);
} }
/// @dev Returns a storage pointer to a user's stake in a given status. /// @dev Returns a storage pointer to a user's stake in a given status.
/// @param owner of stake to query. /// @param owner Owner of stake to query.
/// @param status of user's stake to lookup. /// @param status Status of user's stake to lookup.
/// @return a storage pointer to the corresponding stake stake /// @return storage A storage pointer to the corresponding stake stake
function _getBalancePtrFromStatus(address owner, IStructs.StakeStatus status) function _getBalancePtrFromStatus(
address owner,
IStructs.StakeStatus status
)
private private
view view
returns (IStructs.StoredBalance storage) returns (IStructs.StoredBalance storage)

View File

@@ -27,6 +27,11 @@ import "./MixinStakeStorage.sol";
/// @dev This mixin contains logic for querying stake balances. /// @dev This mixin contains logic for querying stake balances.
/// **** Read MixinStake before continuing **** /// **** Read MixinStake before continuing ****
contract MixinStakeBalances is contract MixinStakeBalances is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler,
MixinStakeStorage MixinStakeStorage
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;

View File

@@ -26,6 +26,10 @@ import "../sys/MixinScheduler.sol";
/// @dev This mixin contains logic for managing stake storage. /// @dev This mixin contains logic for managing stake storage.
contract MixinStakeStorage is contract MixinStakeStorage is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler MixinScheduler
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;

View File

@@ -25,6 +25,12 @@ import "../stake/MixinStakeBalances.sol";
contract MixinCumulativeRewards is contract MixinCumulativeRewards is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances MixinStakeBalances
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -34,7 +40,7 @@ contract MixinCumulativeRewards is
function _initializeCumulativeRewards(bytes32 poolId) function _initializeCumulativeRewards(bytes32 poolId)
internal internal
{ {
// sets the default cumulative reward // Sets the default cumulative reward
_forceSetCumulativeReward( _forceSetCumulativeReward(
poolId, poolId,
currentEpoch, currentEpoch,
@@ -51,31 +57,35 @@ contract MixinCumulativeRewards is
pure pure
returns (bool) returns (bool)
{ {
// we use the denominator as a proxy for whether the cumulative // We use the denominator as a proxy for whether the cumulative
// reward is set, as setting the cumulative reward always sets this // reward is set, as setting the cumulative reward always sets this
// field to at least 1. // field to at least 1.
return cumulativeReward.denominator != 0; return cumulativeReward.denominator != 0;
} }
/// Returns true iff the cumulative reward for `poolId` at `epoch` can be unset. /// @dev Returns true iff the cumulative reward for `poolId` at `epoch` can
/// be unset.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param epoch of the cumulative reward. /// @param epoch Epoch of the cumulative reward.
function _canUnsetCumulativeReward(bytes32 poolId, uint256 epoch) function _canUnsetCumulativeReward(bytes32 poolId, uint256 epoch)
internal internal
view view
returns (bool) returns (bool)
{ {
return ( return (
_isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch]) && // is there a value to unset // Is there a value to unset
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] == 0 && // no references to this CR _isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch]) &&
_cumulativeRewardsByPoolLastStored[poolId] > epoch // this is *not* the most recent CR // No references to this CR
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] == 0 &&
// This is *not* the most recent CR
_cumulativeRewardsByPoolLastStored[poolId] > epoch
); );
} }
/// @dev Tries to set a cumulative reward for `poolId` at `epoch`. /// @dev Tries to set a cumulative reward for `poolId` at `epoch`.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch of cumulative reward. /// @param epoch Epoch of cumulative reward.
/// @param value of cumulative reward. /// @param value Value of cumulative reward.
function _trySetCumulativeReward( function _trySetCumulativeReward(
bytes32 poolId, bytes32 poolId,
uint256 epoch, uint256 epoch,
@@ -84,7 +94,7 @@ contract MixinCumulativeRewards is
internal internal
{ {
if (_isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch])) { if (_isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch])) {
// do nothing; we don't want to override the current value // Do nothing; we don't want to override the current value
return; return;
} }
_forceSetCumulativeReward(poolId, epoch, value); _forceSetCumulativeReward(poolId, epoch, value);
@@ -93,8 +103,8 @@ contract MixinCumulativeRewards is
/// @dev Sets a cumulative reward for `poolId` at `epoch`. /// @dev Sets a cumulative reward for `poolId` at `epoch`.
/// This can be used to overwrite an existing value. /// This can be used to overwrite an existing value.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch of cumulative reward. /// @param epoch Epoch of cumulative reward.
/// @param value of cumulative reward. /// @param value Value of cumulative reward.
function _forceSetCumulativeReward( function _forceSetCumulativeReward(
bytes32 poolId, bytes32 poolId,
uint256 epoch, uint256 epoch,
@@ -108,7 +118,7 @@ contract MixinCumulativeRewards is
/// @dev Tries to unset the cumulative reward for `poolId` at `epoch`. /// @dev Tries to unset the cumulative reward for `poolId` at `epoch`.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param epoch of cumulative reward to unset. /// @param epoch Epoch of cumulative reward to unset.
function _tryUnsetCumulativeReward(bytes32 poolId, uint256 epoch) function _tryUnsetCumulativeReward(bytes32 poolId, uint256 epoch)
internal internal
{ {
@@ -120,11 +130,11 @@ contract MixinCumulativeRewards is
/// @dev Unsets the cumulative reward for `poolId` at `epoch`. /// @dev Unsets the cumulative reward for `poolId` at `epoch`.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param epoch of cumulative reward to unset. /// @param epoch Epoch of cumulative reward to unset.
function _forceUnsetCumulativeReward(bytes32 poolId, uint256 epoch) function _forceUnsetCumulativeReward(bytes32 poolId, uint256 epoch)
internal internal
{ {
_cumulativeRewardsByPool[poolId][epoch] = IStructs.Fraction({numerator: 0, denominator: 0}); delete _cumulativeRewardsByPool[poolId][epoch];
} }
/// @dev Returns info on most recent cumulative reward. /// @dev Returns info on most recent cumulative reward.
@@ -133,31 +143,38 @@ contract MixinCumulativeRewards is
view view
returns (IStructs.CumulativeRewardInfo memory) returns (IStructs.CumulativeRewardInfo memory)
{ {
// fetch the last epoch at which we stored a cumulative reward for this pool // Fetch the last epoch at which we stored a cumulative reward for
uint256 cumulativeRewardsLastStored = _cumulativeRewardsByPoolLastStored[poolId]; // this pool
uint256 cumulativeRewardsLastStored =
_cumulativeRewardsByPoolLastStored[poolId];
// query and return cumulative reward info for this pool // Query and return cumulative reward info for this pool
return IStructs.CumulativeRewardInfo({ return IStructs.CumulativeRewardInfo({
cumulativeReward: _cumulativeRewardsByPool[poolId][cumulativeRewardsLastStored], cumulativeReward:
_cumulativeRewardsByPool[poolId][cumulativeRewardsLastStored],
cumulativeRewardEpoch: cumulativeRewardsLastStored cumulativeRewardEpoch: cumulativeRewardsLastStored
}); });
} }
/// @dev Tries to set the epoch of the most recent cumulative reward. /// @dev Tries to set the epoch of the most recent cumulative reward.
/// The value will only be set if the input epoch is greater than the current /// The value will only be set if the input epoch is greater than the
/// most recent value. /// current most recent value.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch of the most recent cumulative reward. /// @param epoch Epoch of the most recent cumulative reward.
function _trySetMostRecentCumulativeRewardEpoch(bytes32 poolId, uint256 epoch) function _trySetMostRecentCumulativeRewardEpoch(
bytes32 poolId,
uint256 epoch
)
internal internal
{ {
// check if we should do any work // Check if we should do any work
uint256 currentMostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId]; uint256 currentMostRecentEpoch =
_cumulativeRewardsByPoolLastStored[poolId];
if (epoch == currentMostRecentEpoch) { if (epoch == currentMostRecentEpoch) {
return; return;
} }
// update state to reflect the most recent cumulative reward // Update state to reflect the most recent cumulative reward
_forceSetMostRecentCumulativeRewardEpoch( _forceSetMostRecentCumulativeRewardEpoch(
poolId, poolId,
currentMostRecentEpoch, currentMostRecentEpoch,
@@ -167,8 +184,10 @@ contract MixinCumulativeRewards is
/// @dev Forcefully sets the epoch of the most recent cumulative reward. /// @dev Forcefully sets the epoch of the most recent cumulative reward.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param currentMostRecentEpoch of the most recent cumulative reward. /// @param currentMostRecentEpoch Epoch of the most recent cumulative
/// @param newMostRecentEpoch of the new most recent cumulative reward. /// reward.
/// @param newMostRecentEpoch Epoch of the new most recent cumulative
/// reward.
function _forceSetMostRecentCumulativeRewardEpoch( function _forceSetMostRecentCumulativeRewardEpoch(
bytes32 poolId, bytes32 poolId,
uint256 currentMostRecentEpoch, uint256 currentMostRecentEpoch,
@@ -176,19 +195,21 @@ contract MixinCumulativeRewards is
) )
internal internal
{ {
// sanity check that we're not trying to go back in time // Sanity check that we're not trying to go back in time
assert(newMostRecentEpoch >= currentMostRecentEpoch); assert(newMostRecentEpoch >= currentMostRecentEpoch);
_cumulativeRewardsByPoolLastStored[poolId] = newMostRecentEpoch; _cumulativeRewardsByPoolLastStored[poolId] = newMostRecentEpoch;
// unset the previous most recent reward, if it is no longer needed // Unset the previous most recent reward, if it is no longer needed
_tryUnsetCumulativeReward(poolId, currentMostRecentEpoch); _tryUnsetCumulativeReward(poolId, currentMostRecentEpoch);
} }
/// @dev Adds a dependency on a cumulative reward for a given epoch. /// @dev Adds a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from. /// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info for the most recent cumulative reward (value and epoch) /// @param mostRecentCumulativeRewardInfo Info for the most recent
/// @param isDependent True iff there is a dependency on the cumulative reward for `poolId` at `epoch` /// cumulative reward (value and epoch)
/// @param isDependent True iff there is a dependency on the cumulative
/// reward for `poolId` at `epoch`
function _addOrRemoveDependencyOnCumulativeReward( function _addOrRemoveDependencyOnCumulativeReward(
bytes32 poolId, bytes32 poolId,
uint256 epoch, uint256 epoch,
@@ -211,21 +232,100 @@ contract MixinCumulativeRewards is
} }
} }
/// @dev Computes a member's reward over a given epoch interval.
/// @param poolId Uniqud Id of pool.
/// @param memberStakeOverInterval Stake delegated to pool by member over
/// the interval.
/// @param beginEpoch Beginning of interval.
/// @param endEpoch End of interval.
/// @return rewards Reward accumulated over interval [beginEpoch, endEpoch]
function _computeMemberRewardOverInterval(
bytes32 poolId,
uint256 memberStakeOverInterval,
uint256 beginEpoch,
uint256 endEpoch
)
internal
view
returns (uint256 reward)
{
if (memberStakeOverInterval == 0) {
return 0;
}
// Sanity check interval
if (beginEpoch > endEpoch) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors
.CumulativeRewardIntervalErrorCode
.BeginEpochMustBeLessThanEndEpoch,
poolId,
beginEpoch,
endEpoch
)
);
}
// Sanity check begin reward
IStructs.Fraction memory beginReward =
_cumulativeRewardsByPool[poolId][beginEpoch];
if (!_isCumulativeRewardSet(beginReward)) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors
.CumulativeRewardIntervalErrorCode
.BeginEpochDoesNotHaveReward,
poolId,
beginEpoch,
endEpoch
)
);
}
// Sanity check end reward
IStructs.Fraction memory endReward =
_cumulativeRewardsByPool[poolId][endEpoch];
if (!_isCumulativeRewardSet(endReward)) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors
.CumulativeRewardIntervalErrorCode
.EndEpochDoesNotHaveReward,
poolId,
beginEpoch,
endEpoch
)
);
}
// Compute reward
reward = LibFractions.scaleFractionalDifference(
endReward.numerator,
endReward.denominator,
beginReward.numerator,
beginReward.denominator,
memberStakeOverInterval
);
}
/// @dev Adds a dependency on a cumulative reward for a given epoch. /// @dev Adds a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from. /// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative reward. /// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative
/// reward.
function _addDependencyOnCumulativeReward( function _addDependencyOnCumulativeReward(
bytes32 poolId, bytes32 poolId,
uint256 epoch, uint256 epoch,
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo
) )
internal private
{ {
// add dependency by increasing the reference counter // Add dependency by increasing the reference counter
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = _cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeAdd(1); _cumulativeRewardsByPoolReferenceCounter[poolId][epoch] =
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeAdd(1);
// set CR to most recent reward (if it is not already set) // Set CR to most recent reward (if it is not already set)
_trySetCumulativeReward( _trySetCumulativeReward(
poolId, poolId,
epoch, epoch,
@@ -235,88 +335,20 @@ contract MixinCumulativeRewards is
/// @dev Removes a dependency on a cumulative reward for a given epoch. /// @dev Removes a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from. /// @param epoch Epoch to remove dependency from.
function _removeDependencyOnCumulativeReward( function _removeDependencyOnCumulativeReward(
bytes32 poolId, bytes32 poolId,
uint256 epoch uint256 epoch
) )
internal private
{ {
// remove dependency by decreasing reference counter // Remove dependency by decreasing reference counter
uint256 newReferenceCounter = _cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1); uint256 newReferenceCounter =
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = newReferenceCounter; _cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1);
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] =
newReferenceCounter;
// clear cumulative reward from state, if it is no longer needed // Clear cumulative reward from state, if it is no longer needed
_tryUnsetCumulativeReward(poolId, epoch); _tryUnsetCumulativeReward(poolId, epoch);
} }
/// @dev Computes a member's reward over a given epoch interval.
/// @param poolId Uniqud Id of pool.
/// @param memberStakeOverInterval Stake delegated to pool by member over the interval.
/// @param beginEpoch beginning of interval.
/// @param endEpoch end of interval.
/// @return rewards accumulated over interval [beginEpoch, endEpoch]
function _computeMemberRewardOverInterval(
bytes32 poolId,
uint256 memberStakeOverInterval,
uint256 beginEpoch,
uint256 endEpoch
)
internal
view
returns (uint256)
{
// sanity check inputs
if (memberStakeOverInterval == 0) {
return 0;
}
// sanity check interval
if (beginEpoch >= endEpoch) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.BeginEpochMustBeLessThanEndEpoch,
poolId,
beginEpoch,
endEpoch
)
);
}
// sanity check begin reward
IStructs.Fraction memory beginReward = _cumulativeRewardsByPool[poolId][beginEpoch];
if (!_isCumulativeRewardSet(beginReward)) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.BeginEpochDoesNotHaveReward,
poolId,
beginEpoch,
endEpoch
)
);
}
// sanity check end reward
IStructs.Fraction memory endReward = _cumulativeRewardsByPool[poolId][endEpoch];
if (!_isCumulativeRewardSet(endReward)) {
LibRichErrors.rrevert(
LibStakingRichErrors.CumulativeRewardIntervalError(
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.EndEpochDoesNotHaveReward,
poolId,
beginEpoch,
endEpoch
)
);
}
// compute reward
uint256 reward = LibFractions.scaleFractionalDifference(
endReward.numerator,
endReward.denominator,
beginReward.numerator,
beginReward.denominator,
memberStakeOverInterval
);
return reward;
}
} }

View File

@@ -28,8 +28,17 @@ import "./MixinStakingPoolMakers.sol";
contract MixinStakingPool is contract MixinStakingPool is
IStakingEvents,
MixinAbstract,
MixinConstants,
Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers, MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards MixinStakingPoolRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -113,17 +122,6 @@ contract MixinStakingPool is
return poolById[poolId]; return poolById[poolId];
} }
/// @dev Look up the operator of a pool.
/// @param poolId The ID of the pool.
/// @return operatorAddress The pool operator.
function getPoolOperator(bytes32 poolId)
public
view
returns (address operatorAddress)
{
return rewardVault.operatorOf(poolId);
}
/// @dev Computes the unique id that comes after the input pool id. /// @dev Computes the unique id that comes after the input pool id.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @return Next pool id after input pool. /// @return Next pool id after input pool.

View File

@@ -31,6 +31,8 @@ import "./MixinStakingPoolModifiers.sol";
/// @dev This mixin contains logic for staking pools. /// @dev This mixin contains logic for staking pools.
contract MixinStakingPoolMakers is contract MixinStakingPoolMakers is
IStakingEvents, IStakingEvents,
MixinConstants,
Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers MixinStakingPoolModifiers
{ {

View File

@@ -23,6 +23,8 @@ import "../immutable/MixinStorage.sol";
contract MixinStakingPoolModifiers is contract MixinStakingPoolModifiers is
MixinConstants,
Ownable,
MixinStorage MixinStorage
{ {

View File

@@ -27,31 +27,53 @@ import "../sys/MixinAbstract.sol";
contract MixinStakingPoolRewards is contract MixinStakingPoolRewards is
MixinCumulativeRewards, IStakingEvents,
MixinAbstract MixinAbstract,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances,
MixinCumulativeRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
/// @dev Syncs rewards for a delegator. This includes transferring rewards from function computeRewardBalanceOfOperator(bytes32 poolId, address operator)
/// the Reward Vault to the Eth Vault, and adding/removing dependencies on cumulative rewards. public
/// This is used by a delegator when they want to sync their rewards without delegating/undelegating. view
/// It's effectively the same as delegating zero stake. returns (uint256 reward)
{
// TODO.
// unfinalizedStake +
}
/// @dev Syncs rewards for a delegator. This includes transferring rewards
/// from the Reward Vault to the Eth Vault, and adding/removing
/// dependencies on cumulative rewards.
/// This is used by a delegator when they want to sync their rewards
/// without delegating/undelegating. It's effectively the same as
/// delegating zero stake.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
function syncDelegatorRewards(bytes32 poolId) function syncDelegatorRewards(bytes32 poolId)
external external
{ {
address member = msg.sender; address member = msg.sender;
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(_delegatedStakeToPoolByOwner[member][poolId]); IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner =
_loadAndSyncBalance(_delegatedStakeToPoolByOwner[member][poolId]);
_syncRewardsForDelegator( _syncRewardsForDelegator(
poolId, poolId,
member, member,
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]), // initial balance // Initial balance
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]),
finalDelegatedStakeToPoolByOwner finalDelegatedStakeToPoolByOwner
); );
// update stored balance with synchronized version; this prevents redundant withdrawals. // Update stored balance with synchronized version; this prevents
_delegatedStakeToPoolByOwner[member][poolId] = finalDelegatedStakeToPoolByOwner; // redundant withdrawals.
_delegatedStakeToPoolByOwner[member][poolId] =
finalDelegatedStakeToPoolByOwner;
} }
/// @dev Computes the reward balance in ETH of a specific member of a pool. /// @dev Computes the reward balance in ETH of a specific member of a pool.
@@ -59,51 +81,38 @@ contract MixinStakingPoolRewards is
/// @param member The member of the pool. /// @param member The member of the pool.
/// @return totalReward Balance in ETH. /// @return totalReward Balance in ETH.
function computeRewardBalanceOfDelegator(bytes32 poolId, address member) function computeRewardBalanceOfDelegator(bytes32 poolId, address member)
public external
view view
returns (uint256 reward) returns (uint256 reward)
{ {
IStructs.PoolRewards memory unfinalizedPoolRewards = IStructs.Pool memory pool = poolById[poolId];
// Get any unfinalized rewards.
(uint256 unfinalizedTotalRewards, uint256 unfinalizedMembersStake) =
_getUnfinalizedPoolRewards(poolId); _getUnfinalizedPoolRewards(poolId);
// Get the members' portion.
(, uint256 unfinalizedMembersReward) = _splitStakingPoolRewards(
pool.operatorShare,
unfinalizedTotalRewards,
unfinalizedMembersStake
);
reward = _computeRewardBalanceOfDelegator( reward = _computeRewardBalanceOfDelegator(
poolId,
member,
unfinalizedPoolRewards.membersReward,
unfinalizedPoolRewards.membersStake
);
}
/// @dev Computes the reward balance in ETH of a specific member of a pool.
/// @param poolId Unique id of pool.
/// @param member The member of the pool.
/// @param unfinalizedMembersReward Unfinalized memeber reward for
/// this pool in the current epoch.
/// @param unfinalizedDelegatedStake Unfinalized total delegated stake for
/// this pool in the current epoch.
/// @return totalReward Balance in ETH.
function _computeRewardBalanceOfDelegator(
bytes32 poolId,
address member,
uint256 unfinalizedMembersReward,
uint256 unfinalizedDelegatedStake
)
internal
view
returns (uint256 reward)
{
return _computeRewardBalanceOfDelegator(
poolId, poolId,
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]), _loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]),
currentEpoch currentEpoch,
unfinalizedMembersReward,
unfinalizedMembersStake
); );
} }
/// @dev Syncs rewards for a delegator. This includes transferring rewards from /// @dev Syncs rewards for a delegator. This includes transferring rewards
/// the Reward Vault to the Eth Vault, and adding/removing dependencies on cumulative rewards. /// from the Reward Vault to the Eth Vault, and adding/removing
/// dependencies on cumulative rewards.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param member of the pool. /// @param member of the pool.
/// @param initialDelegatedStakeToPoolByOwner The member's delegated balance at the beginning of this transaction. /// @param initialDelegatedStakeToPoolByOwner The member's delegated
/// @param finalDelegatedStakeToPoolByOwner The member's delegated balance at the end of this transaction. /// balance at the beginning of this transaction.
/// @param finalDelegatedStakeToPoolByOwner The member's delegated balance
/// at the end of this transaction.
function _syncRewardsForDelegator( function _syncRewardsForDelegator(
bytes32 poolId, bytes32 poolId,
address member, address member,
@@ -112,8 +121,9 @@ contract MixinStakingPoolRewards is
) )
internal internal
{ {
// transfer any rewards from the transient pool vault to the eth vault; // Rransfer any rewards from the transient pool vault to the eth vault;
// this must be done before we can modify the owner's portion of the delegator pool. // this must be done before we can modify the owner's portion of the
// delegator pool.
_transferDelegatorRewardsToEthVault( _transferDelegatorRewardsToEthVault(
poolId, poolId,
member, member,
@@ -121,14 +131,16 @@ contract MixinStakingPoolRewards is
currentEpoch currentEpoch
); );
// add dependencies on cumulative rewards for this epoch and the previous epoch, if necessary. // Add dependencies on cumulative rewards for this epoch and the next
// epoch, if necessary.
_setCumulativeRewardDependenciesForDelegator( _setCumulativeRewardDependenciesForDelegator(
poolId, poolId,
finalDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner,
true true
); );
// remove dependencies on previous cumulative rewards, if they are no longer needed. // Remove dependencies on previous cumulative rewards, if they are no
// longer needed.
_setCumulativeRewardDependenciesForDelegator( _setCumulativeRewardDependenciesForDelegator(
poolId, poolId,
initialDelegatedStakeToPoolByOwner, initialDelegatedStakeToPoolByOwner,
@@ -136,193 +148,241 @@ contract MixinStakingPoolRewards is
); );
} }
/// @dev Handles a pool's reward. This will deposit the operator's reward into the Eth Vault and /// @dev Handles a pool's reward at the current epoch.
/// the members' reward into the Staking Pool Vault. It also records the cumulative reward, which /// This will compute the reward split and record the cumulative
/// is used to compute each delegator's portion of the members' reward. /// reward, which is used to compute each delegator's portion of the
/// members' reward. It will NOT make any transfers to the eth or
/// reward vaults. That should be done with a separate call to
/// `_depositStakingPoolRewards()``.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param reward received by the pool. /// @param reward received by the pool.
/// @param amountOfDelegatedStake the amount of delegated stake that will split the reward. /// @param membersStake the amount of non-operator delegated stake that
/// @param epoch at which this was earned. /// will split the reward.
function _handleStakingPoolReward( /// @return operatorReward
function _recordStakingPoolRewards(
bytes32 poolId, bytes32 poolId,
uint256 reward, uint256 reward,
uint256 amountOfDelegatedStake uint256 membersStake
) )
internal internal
returns (uint256 operatorReward, uint256 membersReward)
{ {
IStructs.Pool memory pool = poolById[poolId]; IStructs.Pool memory pool = poolById[poolId];
// compute the operator's portion of the reward and transfer it to the ETH vault (we round in favor of the operator). // Split the reward between operator and members
uint256 operatorPortion = amountOfDelegatedStake == 0 (operatorReward, membersReward) =
? reward _splitStakingPoolRewards(pool.operatorShare, reward, membersStake);
: LibMath.getPartialAmountCeil(
uint256(pool.operatorShare),
PPM_DENOMINATOR,
reward
);
ethVault.depositFor.value(operatorPortion)(pool.operator); // Record the operator's reward in the eth vault.
ethVault.recordDepositFor(pool.operator, operatorReward);
// compute the reward portion for the pool members and transfer it to the Reward Vault. if (membersReward == 0) {
uint256 membersPortion = reward.safeSub(operatorPortion); return (operatorReward, membersReward);
if (membersPortion == 0) {
return;
} }
// Record the members reward in the reward vault.
rewardVault.recordDepositFor(poolId, membersReward);
rewardVault.depositFor.value(membersPortion)(poolId); // Cache a storage pointer to the cumulative rewards for `poolId`
// indexed by epoch.
// cache a storage pointer to the cumulative rewards for `poolId` indexed by epoch. mapping (uint256 => IStructs.Fraction)
mapping (uint256 => IStructs.Fraction) storage _cumulativeRewardsByPoolPtr = _cumulativeRewardsByPool[poolId]; storage
_cumulativeRewardsByPoolPtr = _cumulativeRewardsByPool[poolId];
// Fetch the last epoch at which we stored an entry for this pool; // Fetch the last epoch at which we stored an entry for this pool;
// this is the most up-to-date cumulative rewards for this pool. // this is the most up-to-date cumulative rewards for this pool.
uint256 cumulativeRewardsLastStored = _cumulativeRewardsByPoolLastStored[poolId]; uint256 cumulativeRewardsLastStored =
IStructs.Fraction memory mostRecentCumulativeRewards = _cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored]; _cumulativeRewardsByPoolLastStored[poolId];
IStructs.Fraction memory mostRecentCumulativeRewards =
_cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored];
// Compute new cumulative reward // Compute new cumulative reward
(uint256 numerator, uint256 denominator) = LibFractions.addFractions( (uint256 numerator, uint256 denominator) = LibFractions.addFractions(
mostRecentCumulativeRewards.numerator, mostRecentCumulativeRewards.numerator,
mostRecentCumulativeRewards.denominator, mostRecentCumulativeRewards.denominator,
membersPortion, membersReward,
amountOfDelegatedStake membersStake
);
// Normalize fraction components by dividing by the minimum denominator.
uint256 minDenominator =
mostRecentCumulativeRewards.denominator <= amountOfDelegatedStake ?
mostRecentCumulativeRewards.denominator :
amountOfDelegatedStake;
minDenominator = minDenominator == 0 ? 1 : minDenominator;
(uint256 numeratorNormalized, uint256 denominatorNormalized) = (
numerator.safeDiv(minDenominator),
denominator.safeDiv(minDenominator)
); );
// store cumulative rewards and set most recent // store cumulative rewards and set most recent
_forceSetCumulativeReward( _forceSetCumulativeReward(
poolId, poolId,
epoch, currentEpoch,
IStructs.Fraction({ IStructs.Fraction(numerator, denominator)
numerator: numeratorNormalized,
denominator: denominatorNormalized
})
); );
} }
/// @dev Transfers a delegators accumulated rewards from the transient pool Reward Pool vault /// @dev Deposit rewards into the eth vault and reward vault for pool
/// to the Eth Vault. This is required before the member's stake in the pool can be /// operators and members rewards, respectively. This should be called
/// modified. /// in tandem with `_recordStakingPoolRewards()`. We separate them
/// so we can bath deposits, because ETH transfers are expensive.
/// @param operatorReward Operator rewards.
/// @param membersReward Operator rewards.
function _depositStakingPoolRewards(
uint256 operatorReward,
uint256 membersReward
)
internal
{
address(uint160(address(ethVault))).transfer(operatorReward);
address(uint160(address(rewardVault))).transfer(membersReward);
}
/// @dev Transfers a delegators accumulated rewards from the transient pool
/// Reward Pool vault to the Eth Vault. This is required before the
/// member's stake in the pool can be modified.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param member The member of the pool. /// @param member The member of the pool.
/// @param unsyncedStake Unsynced stake of the delegator to the pool.
function _transferDelegatorRewardsToEthVault( function _transferDelegatorRewardsToEthVault(
bytes32 poolId, bytes32 poolId,
address member, address member,
IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner, IStructs.StoredBalance memory unsyncedStake,
uint256 currentEpoch uint256 currentEpoch
) )
private private
{ {
// compute balance owed to delegator // Ensure the pool is finalized.
_finalizePool(poolId);
// Compute balance owed to delegator
uint256 balance = _computeRewardBalanceOfDelegator( uint256 balance = _computeRewardBalanceOfDelegator(
poolId, poolId,
unsyncedDelegatedStakeToPoolByOwner, unsyncedStake,
currentEpoch currentEpoch,
// No unfinalized values because we ensured the pool is already
// finalized.
0,
0
); );
if (balance == 0) { if (balance == 0) {
return; return;
} }
// transfer from transient Reward Pool vault to ETH Vault // Transfer from transient Reward Pool vault to ETH Vault
rewardVault.transferToEthVault( ethVault.recordDepositFor(member, balance);
rewardVault.transfer(
poolId, poolId,
member, address(uint160(address(ethVault))),
balance, balance
address(ethVault)
); );
} }
/// @dev Split a pool reward between the operator and members based on
/// the `operatorShare` and `membersStake`.
/// @param operatorShare The fraction of rewards owed to the operator,
/// in PPM.
/// @param totalReward The pool reward.
/// @param membersStake The amount of member (non-operator) stake delegated
/// to the pool in the epoch the rewards were earned.
function _splitStakingPoolRewards(
uint32 operatorShare,
uint256 totalReward,
uint256 membersStake
)
internal
pure
returns (uint256 operatorReward, uint256 membersReward)
{
if (membersStake == 0) {
operatorReward = totalReward;
} else {
operatorReward = LibMath.getPartialAmountCeil(
uint256(operatorShare),
PPM_DENOMINATOR,
totalReward
);
membersReward = totalReward - operatorReward;
}
}
/// @dev Computes the reward balance in ETH of a specific member of a pool. /// @dev Computes the reward balance in ETH of a specific member of a pool.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param unsyncedDelegatedStakeToPoolByOwner Unsynced delegated stake to pool by owner /// @param unsyncedStake Unsynced delegated stake to pool by owner
/// @param currentEpoch The epoch in which this call is executing /// @param currentEpoch The epoch in which this call is executing
/// @param unfinalizedMembersReward Unfinalized total members reward
/// (if any).
/// @param unfinalizedMembersStake Unfinalized total members stake (if any).
/// @return totalReward Balance in ETH. /// @return totalReward Balance in ETH.
function _computeRewardBalanceOfDelegator( function _computeRewardBalanceOfDelegator(
bytes32 poolId, bytes32 poolId,
IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner, IStructs.StoredBalance memory unsyncedStake,
uint256 currentEpoch uint256 currentEpoch,
uint256 unfinalizedMembersReward,
uint256 unfinalizedMembersStake
) )
private private
view view
returns (uint256 totalReward) returns (uint256 reward)
{ {
uint256 currentEpoch = getCurrentEpoch();
// There can be no rewards in epoch 0 because there is no delegated // There can be no rewards in epoch 0 because there is no delegated
// stake. // stake.
if (currentEpoch == 0) { if (currentEpoch == 0) {
return reward = 0; return reward = 0;
} }
IStructs.StoredBalance memory stake =
_loadUnsyncedBalance(delegatedStakeToPoolByOwner[member][poolId]);
// There can be no rewards if the last epoch when stake was synced is // There can be no rewards if the last epoch when stake was synced is
// equal to the current epoch, because all prior rewards, including // equal to the current epoch, because all prior rewards, including
// rewards finalized this epoch have been claimed. // rewards finalized this epoch have been claimed.
if (stake.currentEpoch == currentEpoch) { if (unsyncedStake.currentEpoch == currentEpoch) {
return reward = 0; return reward = 0;
} }
// If there are unfinalized rewards this epoch, compute the member's // If there are unfinalized rewards this epoch, compute the member's
// share. // share.
if (unfinalizedMembersReward != 0 && unfinalizedDelegatedStake != 0) { if (unfinalizedMembersReward != 0 && unfinalizedMembersStake != 0) {
// Unfinalized rewards are always earned from stake in // Unfinalized rewards are always earned from stake in
// the prior epoch so we want the stake at `currentEpoch-1`. // the prior epoch so we want the stake at `currentEpoch-1`.
uint256 _stake = stake.currentEpoch >= currentEpoch - 1 ? uint256 _stake = unsyncedStake.currentEpoch >= currentEpoch - 1 ?
stake.currentEpochBalance : unsyncedStake.currentEpochBalance :
stake.nextEpochBalance; unsyncedStake.nextEpochBalance;
if (_stake != 0) { if (_stake != 0) {
reward = _stake reward = _stake
.safeMul(unfinalizedMembersReward) .safeMul(unfinalizedMembersReward)
.safeDiv(unfinalizedDelegatedStake); .safeDiv(unfinalizedMembersStake);
} }
} }
// Get the last epoch where a reward was credited to this pool. // Get the last epoch where a reward was credited to this pool, which
uint256 lastRewardEpoch = lastPoolRewardEpoch[poolId]; // also happens to be when we last created a cumulative reward entry.
uint256 lastRewardEpoch = _cumulativeRewardsByPoolLastStored[poolId];
// If the stake has been touched since the last reward epoch, // If the stake has been touched since the last reward epoch,
// it has already been claimed. // it has already been claimed.
if (stake.currentEpoch >= lastRewardEpoch) { if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
// From here we know: `unsyncedStake.currentEpoch < currentEpoch > 0`.
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward; return reward;
} }
// From here we know: `stake.currentEpoch < currentEpoch > 0`.
if (stake.currentEpoch < lastRewardEpoch) {
reward = reward.safeAdd( reward = reward.safeAdd(
_computeMemberRewardOverInterval( _computeMemberRewardOverInterval(
poolId, poolId,
stake, unsyncedStake.currentEpochBalance,
stake.currentEpoch, unsyncedStake.currentEpoch,
stake.currentEpoch + 1 unsyncedStake.currentEpoch + 1
) )
); );
if (stake.currentEpoch + 1 < lastRewardEpoch) { if (unsyncedStake.currentEpoch + 1 < lastRewardEpoch) {
reward = reward.safeAdd( reward = reward.safeAdd(
_computeMemberRewardOverInterval( _computeMemberRewardOverInterval(
poolId, poolId,
stake, unsyncedStake.nextEpochBalance,
stake.currentEpoch + 1, unsyncedStake.currentEpoch + 1,
lastRewardEpoch lastRewardEpoch
) )
); );
} }
} }
}
/// @dev Adds or removes cumulative reward dependencies for a delegator. /// @dev Adds or removes cumulative reward dependencies for a delegator.
/// A delegator always depends on the cumulative reward for the current epoch. /// A delegator always depends on the cumulative reward for the current
/// They will also depend on the previous epoch's reward, if they are already staked with the input pool. /// and next epoch, if they would still have stake in the next epoch.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param _delegatedStakeToPoolByOwner Amount of stake the member has delegated to the pool. /// @param _delegatedStakeToPoolByOwner Amount of stake the member has
/// delegated to the pool.
/// @param isDependent is true iff adding a dependency. False, otherwise. /// @param isDependent is true iff adding a dependency. False, otherwise.
function _setCumulativeRewardDependenciesForDelegator( function _setCumulativeRewardDependenciesForDelegator(
bytes32 poolId, bytes32 poolId,
@@ -331,26 +391,34 @@ contract MixinStakingPoolRewards is
) )
private private
{ {
// if this delegator is not yet initialized then there's no dependency to unset. // If this delegator is not yet initialized then there's no dependency
// to unset.
if (!isDependent && !_delegatedStakeToPoolByOwner.isInitialized) { if (!isDependent && !_delegatedStakeToPoolByOwner.isInitialized) {
return; return;
} }
// get the most recent cumulative reward, which will serve as a reference point when updating dependencies // Get the most recent cumulative reward, which will serve as a
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo = _getMostRecentCumulativeRewardInfo(poolId); // reference point when updating dependencies
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo =
_getMostRecentCumulativeRewardInfo(poolId);
// record dependency on `lastEpoch` // Record dependency on the next epoch
if (_delegatedStakeToPoolByOwner.currentEpoch > 0 && _delegatedStakeToPoolByOwner.currentEpochBalance != 0) { uint256 nextEpoch = currentEpoch.safeAdd(1);
if (_delegatedStakeToPoolByOwner.currentEpoch > 0
&& _delegatedStakeToPoolByOwner.nextEpochBalance != 0)
{
_addOrRemoveDependencyOnCumulativeReward( _addOrRemoveDependencyOnCumulativeReward(
poolId, poolId,
uint256(_delegatedStakeToPoolByOwner.currentEpoch).safeSub(1), nextEpoch,
mostRecentCumulativeRewardInfo, mostRecentCumulativeRewardInfo,
isDependent isDependent
); );
} }
// record dependency on current epoch. // Record dependency on current epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0 || _delegatedStakeToPoolByOwner.nextEpochBalance != 0) { if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0
|| _delegatedStakeToPoolByOwner.nextEpochBalance != 0)
{
_addOrRemoveDependencyOnCumulativeReward( _addOrRemoveDependencyOnCumulativeReward(
poolId, poolId,
_delegatedStakeToPoolByOwner.currentEpoch, _delegatedStakeToPoolByOwner.currentEpoch,
@@ -358,9 +426,5 @@ contract MixinStakingPoolRewards is
isDependent isDependent
); );
} }
uint256 nextEpoch = epoch.safeAdd(1);
if (!_isCumulativeRewardSet(cumulativeRewardsByPoolPtr[nextEpoch])) {
cumulativeRewardsByPoolPtr[nextEpoch] = mostRecentCumulativeRewards;
}
} }
} }

View File

@@ -29,11 +29,16 @@ contract MixinAbstract {
/// @dev Computes the reward owed to a pool during finalization. /// @dev Computes the reward owed to a pool during finalization.
/// Does nothing if the pool is already finalized. /// Does nothing if the pool is already finalized.
/// @param poolId The pool's ID. /// @param poolId The pool's ID.
/// @return rewards Amount of rewards for this pool. /// @return operatorReward The reward owed to the pool operator.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _getUnfinalizedPoolRewards(bytes32 poolId) function _getUnfinalizedPoolRewards(bytes32 poolId)
internal internal
view view
returns (IStructs.PoolRewards memory rewards); returns (
uint256 reward,
uint256 membersStake
);
/// @dev Get an active pool from an epoch by its ID. /// @dev Get an active pool from an epoch by its ID.
/// @param epoch The epoch the pool was/will be active in. /// @param epoch The epoch the pool was/will be active in.
@@ -61,13 +66,20 @@ contract MixinAbstract {
/// @dev Instantly finalizes a single pool that was active in the previous /// @dev Instantly finalizes a single pool that was active in the previous
/// epoch, crediting it rewards and sending those rewards to the reward /// epoch, crediting it rewards and sending those rewards to the reward
/// vault. This can be called by internal functions that need to /// and eth vault. This can be called by internal functions that need
/// finalize a pool immediately. Does nothing if the pool is already /// to finalize a pool immediately. Does nothing if the pool is already
/// finalized. Does nothing if the pool was not active or was already
/// finalized. /// finalized.
/// @param poolId The pool ID to finalize. /// @param poolId The pool ID to finalize.
/// @return rewards Rewards. /// @return operatorReward The reward credited to the pool operator.
/// @return rewards The rewards credited to the pool. /// @return membersReward The reward credited to the pool members.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _finalizePool(bytes32 poolId) function _finalizePool(bytes32 poolId)
internal internal
returns (IStructs.PoolRewards memory rewards); returns (
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
);
} }

View File

@@ -31,7 +31,6 @@ import "../interfaces/IStakingEvents.sol";
import "../interfaces/IStructs.sol"; import "../interfaces/IStructs.sol";
import "../stake/MixinStakeBalances.sol"; import "../stake/MixinStakeBalances.sol";
import "../staking_pools/MixinStakingPool.sol"; import "../staking_pools/MixinStakingPool.sol";
import "../staking_pools/MixinStakingPoolRewardVault.sol";
import "./MixinScheduler.sol"; import "./MixinScheduler.sol";
@@ -47,11 +46,10 @@ contract MixinFinalizer is
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable, Ownable,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinScheduler, MixinScheduler,
MixinStakingPoolRewardVault,
MixinStakeStorage, MixinStakeStorage,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards MixinStakingPoolRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -67,7 +65,7 @@ contract MixinFinalizer is
external external
returns (uint256 _unfinalizedPoolsRemaining) returns (uint256 _unfinalizedPoolsRemaining)
{ {
uint256 closingEpoch = getCurrentEpoch(); uint256 closingEpoch = currentEpoch;
// Make sure the previous epoch has been fully finalized. // Make sure the previous epoch has been fully finalized.
if (unfinalizedPoolsRemaining != 0) { if (unfinalizedPoolsRemaining != 0) {
@@ -114,9 +112,9 @@ contract MixinFinalizer is
} }
/// @dev Finalizes pools that were active in the previous epoch, paying out /// @dev Finalizes pools that were active in the previous epoch, paying out
/// rewards to the reward vault. Keepers should call this function /// rewards to the reward and eth vault. Keepers should call this
/// repeatedly until all active pools that were emitted in in a /// function repeatedly until all active pools that were emitted in in
/// `StakingPoolActivated` in the prior epoch have been finalized. /// a `StakingPoolActivated` in the prior epoch have been finalized.
/// Pools that have already been finalized will be silently ignored. /// Pools that have already been finalized will be silently ignored.
/// We deliberately try not to revert here in case multiple parties /// We deliberately try not to revert here in case multiple parties
/// are finalizing pools. /// are finalizing pools.
@@ -126,7 +124,7 @@ contract MixinFinalizer is
external external
returns (uint256 _unfinalizedPoolsRemaining) returns (uint256 _unfinalizedPoolsRemaining)
{ {
uint256 epoch = getCurrentEpoch(); uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0. // There are no pools to finalize at epoch 0.
if (epoch == 0) { if (epoch == 0) {
return _unfinalizedPoolsRemaining = 0; return _unfinalizedPoolsRemaining = 0;
@@ -144,8 +142,11 @@ contract MixinFinalizer is
_getActivePoolsFromEpoch(epoch - 1); _getActivePoolsFromEpoch(epoch - 1);
uint256 numPoolIds = poolIds.length; uint256 numPoolIds = poolIds.length;
uint256 rewardsPaid = 0; uint256 rewardsPaid = 0;
uint256 totalOperatorRewardsPaid = 0;
uint256 totalMembersRewardsPaid = 0;
for (uint256 i = 0; i != numPoolIds && poolsRemaining != 0; ++i) { for (uint256 i = 0; i != numPoolIds && poolsRemaining != 0; ++i)
{
bytes32 poolId = poolIds[i]; bytes32 poolId = poolIds[i];
IStructs.ActivePool memory pool = activePools[poolId]; IStructs.ActivePool memory pool = activePools[poolId];
@@ -154,12 +155,17 @@ contract MixinFinalizer is
continue; continue;
} }
IStructs.PoolRewards memory poolRewards = (uint256 operatorReward, uint256 membersReward) =
_creditRewardsToPool(epoch, poolId, pool); _creditRewardsToPool(epoch, poolId, pool, rewardsPaid);
rewardsPaid = rewardsPaid.safeAdd( totalOperatorRewardsPaid =
poolRewards.operatorReward + poolRewards.membersReward totalOperatorRewardsPaid.safeAdd(operatorReward);
); totalMembersRewardsPaid =
totalMembersRewardsPaid.safeAdd(membersReward);
rewardsPaid = rewardsPaid
.safeAdd(operatorReward)
.safeAdd(membersReward);
// Decrease the number of unfinalized pools left. // Decrease the number of unfinalized pools left.
poolsRemaining = poolsRemaining.safeSub(1); poolsRemaining = poolsRemaining.safeSub(1);
@@ -182,47 +188,52 @@ contract MixinFinalizer is
); );
} }
// Deposit all the rewards at once into the RewardVault. // Deposit all the rewards at once.
if (rewardsPaid != 0) { if (rewardsPaid != 0) {
_depositIntoStakingPoolRewardVault(rewardsPaid); _depositStakingPoolRewards(totalOperatorRewardsPaid, totalMembersRewardsPaid);
} }
} }
/// @dev Instantly finalizes a single pool that was active in the previous /// @dev Instantly finalizes a single pool that was active in the previous
/// epoch, crediting it rewards and sending those rewards to the reward /// epoch, crediting it rewards and sending those rewards to the reward
/// vault. This can be called by internal functions that need to /// and eth vault. This can be called by internal functions that need
/// finalize a pool immediately. Does nothing if the pool is already /// to finalize a pool immediately. Does nothing if the pool is already
/// finalized. Does nothing if the pool was not active or was already /// finalized. Does nothing if the pool was not active or was already
/// finalized. /// finalized.
/// @param poolId The pool ID to finalize. /// @param poolId The pool ID to finalize.
/// @return rewards Rewards. /// @return operatorReward The reward credited to the pool operator.
/// @return rewards The rewards credited to the pool. /// @return membersReward The reward credited to the pool members.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _finalizePool(bytes32 poolId) function _finalizePool(bytes32 poolId)
internal internal
returns (IStructs.PoolRewards memory rewards) returns (
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
)
{ {
uint256 epoch = getCurrentEpoch(); uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0. // There are no pools to finalize at epoch 0.
if (epoch == 0) { if (epoch == 0) {
return rewards; return (operatorReward, membersReward, membersStake);
} }
IStructs.ActivePool memory pool = IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId); _getActivePoolFromEpoch(epoch - 1, poolId);
// Do nothing if the pool was not active (has no fees). // Do nothing if the pool was not active (has no fees).
if (pool.feesCollected == 0) { if (pool.feesCollected == 0) {
return rewards; return (operatorReward, membersReward, membersStake);
} }
rewards = _creditRewardsToPool(epoch, poolId, pool); (operatorReward, membersReward) =
uint256 totalReward = _creditRewardsToPool(epoch, poolId, pool, 0);
rewards.membersReward.safeAdd(rewards.operatorReward); uint256 totalReward = operatorReward.safeAdd(membersReward);
if (totalReward > 0) { if (totalReward > 0) {
totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch =
totalRewardsPaidLastEpoch.safeAdd(totalReward); totalRewardsPaidLastEpoch.safeAdd(totalReward);
_depositIntoStakingPoolRewardVault(totalReward); _depositStakingPoolRewards(operatorReward, membersReward);
} }
// Decrease the number of unfinalized pools left. // Decrease the number of unfinalized pools left.
@@ -238,26 +249,32 @@ contract MixinFinalizer is
unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch) unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
); );
} }
membersStake = pool.membersStake;
} }
/// @dev Computes the reward owed to a pool during finalization. /// @dev Computes the reward owed to a pool during finalization.
/// Does nothing if the pool is already finalized. /// Does nothing if the pool is already finalized.
/// @param poolId The pool's ID. /// @param poolId The pool's ID.
/// @return rewards Amount of rewards for this pool. /// @return operatorReward The reward owed to the pool operator.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _getUnfinalizedPoolRewards(bytes32 poolId) function _getUnfinalizedPoolRewards(bytes32 poolId)
internal internal
view view
returns (IStructs.PoolRewards memory rewards) returns (
uint256 reward,
uint256 membersStake
)
{ {
uint256 epoch = getCurrentEpoch(); uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0. // There are no pools to finalize at epoch 0.
if (epoch == 0) { if (epoch == 0) {
return rewards; return (reward, membersStake);
} }
rewards = _getUnfinalizedPoolRewards( IStructs.ActivePool memory pool =
poolId, _getActivePoolFromEpoch(epoch - 1, poolId);
_getActivePoolFromEpoch(epoch - 1, poolId) reward = _getUnfinalizedPoolRewards(pool, 0);
); membersStake = pool.membersStake;
} }
/// @dev Get an active pool from an epoch by its ID. /// @dev Get an active pool from an epoch by its ID.
@@ -287,11 +304,13 @@ contract MixinFinalizer is
view view
returns (mapping (bytes32 => IStructs.ActivePool) storage activePools) returns (mapping (bytes32 => IStructs.ActivePool) storage activePools)
{ {
activePools = activePoolsByEpoch[epoch % 2]; activePools = _activePoolsByEpoch[epoch % 2];
} }
/// @dev Converts the entire WETH balance of the contract into ETH. /// @dev Converts the entire WETH balance of the contract into ETH.
function _unwrapWETH() internal { function _unwrapWETH()
internal
{
uint256 wethBalance = IEtherToken(WETH_ADDRESS) uint256 wethBalance = IEtherToken(WETH_ADDRESS)
.balanceOf(address(this)); .balanceOf(address(this));
if (wethBalance != 0) { if (wethBalance != 0) {
@@ -299,69 +318,28 @@ contract MixinFinalizer is
} }
} }
/// @dev Splits an amount between the pool operator and members of the
/// pool based on the pool operator's share.
/// @param poolId The ID of the pool.
/// @param amount Amount to to split.
/// @return operatorPortion Portion of `amount` attributed to the operator.
/// @return membersPortion Portion of `amount` attributed to the pool.
function _splitRewardAmountBetweenOperatorAndMembers(
bytes32 poolId,
uint256 amount
)
internal
view
returns (uint256 operatorReward, uint256 membersReward)
{
(operatorReward, membersReward) =
rewardVault.splitAmountBetweenOperatorAndMembers(poolId, amount);
}
/// @dev Record a deposit for a pool in the RewardVault.
/// @param poolId ID of the pool.
/// @param amount Amount in ETH to record.
/// @param operatorOnly Only attribute amount to operator.
/// @return operatorPortion Portion of `amount` attributed to the operator.
/// @return membersPortion Portion of `amount` attributed to the pool.
function _recordDepositInRewardVaultFor(
bytes32 poolId,
uint256 amount,
bool operatorOnly
)
internal
returns (
uint256 operatorPortion,
uint256 membersPortion
)
{
(operatorPortion, membersPortion) = rewardVault.recordDepositFor(
poolId,
amount,
operatorOnly
);
}
/// @dev Computes the reward owed to a pool during finalization. /// @dev Computes the reward owed to a pool during finalization.
/// @param poolId The pool's ID.
/// @param pool The active pool. /// @param pool The active pool.
/// @return rewards Amount of rewards for this pool. /// @param unpaidRewards Rewards that have been credited but not finalized.
/// @return rewards Unfinalized rewards for this pool.
function _getUnfinalizedPoolRewards( function _getUnfinalizedPoolRewards(
bytes32 poolId, IStructs.ActivePool memory pool,
IStructs.ActivePool memory pool uint256 unpaidRewards
) )
private private
view view
returns (IStructs.PoolRewards memory rewards) returns (uint256 rewards)
{ {
// There can't be any rewards if the pool was active or if it has // There can't be any rewards if the pool was active or if it has
// no stake. // no stake.
if (pool.feesCollected == 0) { if (pool.feesCollected == 0) {
return rewards; return rewards = 0;
} }
uint256 unfinalizedRewardsAvailable_ = unfinalizedRewardsAvailable;
// Use the cobb-douglas function to compute the total reward. // Use the cobb-douglas function to compute the total reward.
uint256 totalReward = LibCobbDouglas._cobbDouglas( rewards = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable, unfinalizedRewardsAvailable_,
pool.feesCollected, pool.feesCollected,
unfinalizedTotalFeesCollected, unfinalizedTotalFeesCollected,
pool.weightedStake, pool.weightedStake,
@@ -370,17 +348,15 @@ contract MixinFinalizer is
cobbDouglasAlphaDenominator cobbDouglasAlphaDenominator
); );
// Split the reward between the operator and delegators. // Clip the reward to always be under
if (pool.membersStake == 0) { // `unfinalizedRewardsAvailable - totalRewardsPaid - unpaidRewards`,
rewards.operatorReward = totalReward; // in case cobb-douglas overflows, which should be unlikely.
} else { uint256 rewardsRemaining = unfinalizedRewardsAvailable_
(rewards.operatorReward, rewards.membersReward) = .safeSub(totalRewardsPaidLastEpoch)
_splitRewardAmountBetweenOperatorAndMembers( .safeSub(unpaidRewards);
poolId, if (rewardsRemaining < rewards) {
totalReward rewards = rewardsRemaining;
);
} }
rewards.membersStake = pool.membersStake;
} }
/// @dev Credits finalization rewards to a pool that was active in the /// @dev Credits finalization rewards to a pool that was active in the
@@ -388,48 +364,41 @@ contract MixinFinalizer is
/// @param epoch The current epoch. /// @param epoch The current epoch.
/// @param poolId The pool ID to finalize. /// @param poolId The pool ID to finalize.
/// @param pool The active pool to finalize. /// @param pool The active pool to finalize.
/// @param unpaidRewards Rewards that have been credited but not finalized.
/// @return rewards Rewards. /// @return rewards Rewards.
/// @return rewards The rewards credited to the pool. /// @return operatorReward The reward credited to the pool operator.
/// @return membersReward The reward credited to the pool members.
function _creditRewardsToPool( function _creditRewardsToPool(
uint256 epoch, uint256 epoch,
bytes32 poolId, bytes32 poolId,
IStructs.ActivePool memory pool IStructs.ActivePool memory pool,
uint256 unpaidRewards
) )
private private
returns (IStructs.PoolRewards memory rewards) returns (uint256 operatorReward, uint256 membersReward)
{ {
// Clear the pool state so we don't finalize it again, and to recoup // Clear the pool state so we don't finalize it again, and to recoup
// some gas. // some gas.
delete _getActivePoolsFromEpoch(epoch - 1)[poolId]; delete _getActivePoolsFromEpoch(epoch - 1)[poolId];
// Compute the rewards. // Compute the rewards.
rewards = _getUnfinalizedPoolRewards(poolId, pool); uint256 rewards = _getUnfinalizedPoolRewards(pool, unpaidRewards);
uint256 totalReward =
rewards.membersReward.safeAdd(rewards.operatorReward);
// Credit the pool the rewards in the RewardVault. // Credit the pool.
_recordDepositInRewardVaultFor( // Note that we credit at the CURRENT epoch even though these rewards
// were earned in the previous epoch.
(operatorReward, membersReward) = _recordStakingPoolRewards(
poolId, poolId,
totalReward, rewards,
// If no delegated stake, all rewards go to the operator.
pool.membersStake == 0
);
// Sync delegator rewards.
if (rewards.membersReward != 0) {
_recordRewardForDelegators(
poolId,
rewards.membersReward,
pool.membersStake pool.membersStake
); );
}
// Emit an event. // Emit an event.
emit RewardsPaid( emit RewardsPaid(
epoch, epoch,
poolId, poolId,
rewards.operatorReward, operatorReward,
rewards.membersReward membersReward
); );
} }
} }

View File

@@ -30,6 +30,8 @@ import "../libs/LibStakingRichErrors.sol";
contract MixinParams is contract MixinParams is
IStakingEvents, IStakingEvents,
MixinConstants,
Ownable,
MixinStorage MixinStorage
{ {
/// @dev Set all configurable parameters at once. /// @dev Set all configurable parameters at once.

View File

@@ -33,6 +33,8 @@ import "../interfaces/IStakingEvents.sol";
/// and consistent scheduling metric than time. TimeLocks, for example, are measured in epochs. /// and consistent scheduling metric than time. TimeLocks, for example, are measured in epochs.
contract MixinScheduler is contract MixinScheduler is
IStakingEvents, IStakingEvents,
MixinConstants,
Ownable,
MixinStorage MixinStorage
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;

View File

@@ -26,6 +26,8 @@ import "./MixinVaultCore.sol";
/// @dev This vault manages ETH. /// @dev This vault manages ETH.
contract EthVault is contract EthVault is
IEthVault, IEthVault,
IVaultCore,
Ownable,
MixinVaultCore MixinVaultCore
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -33,19 +35,21 @@ contract EthVault is
// mapping from Owner to ETH balance // mapping from Owner to ETH balance
mapping (address => uint256) internal _balances; mapping (address => uint256) internal _balances;
/// @dev Deposit an `amount` of ETH from `owner` into the vault. // solhint-disable no-empty-blocks
/// Note that only the Staking contract can call this. /// @dev Payable fallback for bulk-deposits.
/// Note that this can only be called when *not* in Catostrophic Failure mode. function () payable external {}
/// @param owner of ETH Tokens.
function depositFor(address owner)
external
payable
{
// update balance
uint256 amount = msg.value;
_balances[owner] = _balances[owner].safeAdd(msg.value);
// notify /// @dev Record a deposit of an amount of ETH for `owner` into the vault.
/// The staking contract should pay this contract the ETH owed in the
/// same transaction.
/// Note that this is only callable by the staking contract.
/// @param owner Owner of the ETH.
/// @param amount Amount of deposit.
function recordDepositFor(address owner, uint256 amount)
external
onlyStakingProxy
{
_balances[owner] = _balances[owner].safeAdd(amount);
emit EthDepositedIntoVault(msg.sender, owner, amount); emit EthDepositedIntoVault(msg.sender, owner, amount);
} }

View File

@@ -25,14 +25,15 @@ import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSafeDowncast.sol"; import "../libs/LibSafeDowncast.sol";
import "./MixinVaultCore.sol"; import "./MixinVaultCore.sol";
import "../interfaces/IStakingPoolRewardVault.sol"; import "../interfaces/IStakingPoolRewardVault.sol";
import "../interfaces/IEthVault.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
/// @dev This vault manages staking pool rewards. /// @dev This vault manages staking pool rewards.
contract StakingPoolRewardVault is contract StakingPoolRewardVault is
IStakingPoolRewardVault, IStakingPoolRewardVault,
IVaultCore,
MixinConstants, MixinConstants,
Ownable,
MixinVaultCore MixinVaultCore
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@@ -40,38 +41,42 @@ contract StakingPoolRewardVault is
// mapping from poolId to Pool metadata // mapping from poolId to Pool metadata
mapping (bytes32 => uint256) internal _balanceByPoolId; mapping (bytes32 => uint256) internal _balanceByPoolId;
/// @dev Deposit an amount of ETH (`msg.value`) for `poolId` into the vault. // solhint-disable no-empty-blocks
/// @dev Payable fallback for bulk-deposits.
function () payable external {}
/// @dev Record a deposit of an amount of ETH for `poolId` into the vault.
/// The staking contract should pay this contract the ETH owed in the
/// same transaction.
/// Note that this is only callable by the staking contract. /// Note that this is only callable by the staking contract.
/// @param poolId that holds the ETH. /// @param poolId Pool that holds the ETH.
function depositFor(bytes32 poolId) /// @param amount Amount of deposit.
function recordDepositFor(bytes32 poolId, uint256 amount)
external external
payable
onlyStakingProxy onlyStakingProxy
{ {
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeAdd(msg.value); _balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeAdd(amount);
emit EthDepositedIntoVault(msg.sender, poolId, msg.value); emit EthDepositedIntoVault(msg.sender, poolId, amount);
} }
/// @dev Withdraw some amount in ETH of a pool member. /// @dev Withdraw some amount in ETH from a pool.
/// Note that this is only callable by the staking contract. /// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param member of pool to transfer funds to. /// @param to Address to send funds to.
/// @param amount Amount in ETH to transfer. /// @param amount Amount of ETH to transfer.
/// @param ethVaultAddress address of Eth Vault to send rewards to. function transfer(
function transferToEthVault(
bytes32 poolId, bytes32 poolId,
address member, address payable to,
uint256 amount, uint256 amount
address ethVaultAddress
) )
external external
onlyStakingProxy onlyStakingProxy
{ {
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeSub(amount); _balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeSub(amount);
IEthVault(ethVaultAddress).depositFor.value(amount)(member); to.transfer(amount);
emit PoolRewardTransferredToEthVault( emit PoolRewardTransferred(
poolId, poolId,
member, to,
amount amount
); );
} }

View File

@@ -34,7 +34,9 @@ import "./MixinVaultCore.sol";
/// failure mode, it cannot be returned to normal mode; this prevents /// failure mode, it cannot be returned to normal mode; this prevents
/// corruption of related state in the staking contract. /// corruption of related state in the staking contract.
contract ZrxVault is contract ZrxVault is
IVaultCore,
IZrxVault, IZrxVault,
Ownable,
MixinVaultCore MixinVaultCore
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;

View File

@@ -20,35 +20,52 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "../src/interfaces/IStructs.sol"; import "../src/interfaces/IStructs.sol";
import "../src/interfaces/IStakingPoolRewardVault.sol";
import "../src/interfaces/IEthVault.sol";
import "./TestStaking.sol"; import "./TestStaking.sol";
contract TestDelegatorRewards is contract TestDelegatorRewards is
TestStaking TestStaking
{ {
event Deposit( event RecordDepositToEthVault(
address owner,
uint256 amount
);
event RecordDepositToRewardVault(
bytes32 poolId, bytes32 poolId,
address member, uint256 membersReward
uint256 balance
); );
event FinalizePool( event FinalizePool(
bytes32 poolId, bytes32 poolId,
uint256 reward, uint256 operatorReward,
uint256 stake uint256 membersReward,
uint256 membersStake
); );
struct UnfinalizedMembersReward { struct UnfinalizedMembersReward {
uint256 reward; uint256 operatorReward;
uint256 stake; uint256 membersReward;
uint256 membersStake;
} }
constructor() public { constructor() public {
init(); init(
address(1),
address(1),
address(1),
address(1)
);
// Set this contract up as the eth and reward vault to intercept
// deposits.
ethVault = IEthVault(address(this));
rewardVault = IStakingPoolRewardVault(address(this));
} }
mapping (uint256 => mapping (bytes32 => UnfinalizedMembersReward)) private mapping (uint256 => mapping (bytes32 => UnfinalizedMembersReward)) private
unfinalizedMembersRewardByPoolByEpoch; unfinalizedPoolRewardsByEpoch;
/// @dev Expose _finalizePool /// @dev Expose _finalizePool
function internalFinalizePool(bytes32 poolId) external { function internalFinalizePool(bytes32 poolId) external {
@@ -58,15 +75,17 @@ contract TestDelegatorRewards is
/// @dev Set unfinalized members reward for a pool in the current epoch. /// @dev Set unfinalized members reward for a pool in the current epoch.
function setUnfinalizedMembersRewards( function setUnfinalizedMembersRewards(
bytes32 poolId, bytes32 poolId,
uint256 operatorReward,
uint256 membersReward, uint256 membersReward,
uint256 membersStake uint256 membersStake
) )
external external
{ {
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId] = unfinalizedPoolRewardsByEpoch[currentEpoch][poolId] =
UnfinalizedMembersReward({ UnfinalizedMembersReward({
reward: membersReward, operatorReward: operatorReward,
stake: membersStake membersReward: membersReward,
membersStake: membersStake
}); });
} }
@@ -85,13 +104,20 @@ contract TestDelegatorRewards is
) )
external external
{ {
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator); IStructs.StoredBalance memory initialStake =
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch); _delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake = IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId]; _delegatedStakeToPoolByOwner[delegator][poolId];
_stake.isInitialized = true;
_stake.currentEpochBalance += uint96(stake); _stake.currentEpochBalance += uint96(stake);
_stake.nextEpochBalance += uint96(stake); _stake.nextEpochBalance += uint96(stake);
_stake.currentEpoch = uint64(currentEpoch); _stake.currentEpoch = uint32(currentEpoch);
_syncRewardsForDelegator(
poolId,
delegator,
initialStake,
_stake
);
} }
/// @dev Create and delegate stake that will occur in the next epoch /// @dev Create and delegate stake that will occur in the next epoch
@@ -104,15 +130,22 @@ contract TestDelegatorRewards is
) )
external external
{ {
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator); IStructs.StoredBalance memory initialStake =
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch); _delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake = IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId]; _delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) { if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance; _stake.currentEpochBalance = _stake.nextEpochBalance;
} }
_stake.isInitialized = true;
_stake.nextEpochBalance += uint96(stake); _stake.nextEpochBalance += uint96(stake);
_stake.currentEpoch = uint64(currentEpoch); _stake.currentEpoch = uint32(currentEpoch);
_syncRewardsForDelegator(
poolId,
delegator,
initialStake,
_stake
);
} }
/// @dev Clear stake that will occur in the next epoch /// @dev Clear stake that will occur in the next epoch
@@ -125,67 +158,115 @@ contract TestDelegatorRewards is
) )
external external
{ {
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator); IStructs.StoredBalance memory initialStake =
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch); _delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake = IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId]; _delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) { if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance; _stake.currentEpochBalance = _stake.nextEpochBalance;
} }
_stake.isInitialized = true;
_stake.nextEpochBalance -= uint96(stake); _stake.nextEpochBalance -= uint96(stake);
_stake.currentEpoch = uint64(currentEpoch); _stake.currentEpoch = uint32(currentEpoch);
} _syncRewardsForDelegator(
/// @dev Expose `_recordDepositInRewardVaultFor`.
function recordRewardForDelegators(
bytes32 poolId,
uint256 reward,
uint256 amountOfDelegatedStake
)
external
{
_recordRewardForDelegators(poolId, reward, amountOfDelegatedStake);
}
/// @dev Overridden to just emit events.
function _transferMemberBalanceToEthVault(
bytes32 poolId,
address member,
uint256 balance
)
internal
{
emit Deposit(
poolId, poolId,
member, delegator,
balance initialStake,
_stake
); );
} }
/// @dev Overridden to realize unfinalizedMembersRewardByPoolByEpoch in /// @dev `IEthVault.recordDepositFor()`,` overridden to just emit events.
/// the current epoch and eit a event, function recordDepositFor(
function _finalizePool(bytes32 poolId) address owner,
internal uint256 amount
returns (IStructs.PoolRewards memory rewards) )
external
{ {
UnfinalizedMembersReward memory reward = emit RecordDepositToEthVault(
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId]; owner,
delete unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId]; amount
rewards.membersReward = reward.reward; );
rewards.membersStake = reward.stake;
_recordRewardForDelegators(poolId, reward.reward, reward.stake);
emit FinalizePool(poolId, reward.reward, reward.stake);
} }
/// @dev Overridden to use unfinalizedMembersRewardByPoolByEpoch. /// @dev `IStakingPoolRewardVault.recordDepositFor()`,`
/// overridden to just emit events.
function recordDepositFor(
bytes32 poolId,
uint256 membersReward
)
external
{
emit RecordDepositToRewardVault(
poolId,
membersReward
);
}
/// @dev Expose `_recordStakingPoolRewards`.
function recordStakingPoolRewards(
bytes32 poolId,
uint256 operatorReward,
uint256 membersReward,
uint256 rewards,
uint256 amountOfDelegatedStake
)
public
{
_setOperatorShare(poolId, operatorReward, membersReward);
_recordStakingPoolRewards(poolId, rewards, amountOfDelegatedStake);
}
/// @dev Overridden to realize `unfinalizedPoolRewardsByEpoch` in
/// the current epoch and emit a event,
function _finalizePool(bytes32 poolId)
internal
returns (
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
)
{
UnfinalizedMembersReward memory reward =
unfinalizedPoolRewardsByEpoch[currentEpoch][poolId];
delete unfinalizedPoolRewardsByEpoch[currentEpoch][poolId];
_setOperatorShare(poolId, operatorReward, membersReward);
uint256 totalRewards = reward.operatorReward + reward.membersReward;
membersStake = reward.membersStake;
(operatorReward, membersReward) =
_recordStakingPoolRewards(poolId, totalRewards, membersStake);
emit FinalizePool(poolId, operatorReward, membersReward, membersStake);
}
/// @dev Overridden to use unfinalizedPoolRewardsByEpoch.
function _getUnfinalizedPoolRewards(bytes32 poolId) function _getUnfinalizedPoolRewards(bytes32 poolId)
internal internal
view view
returns (IStructs.PoolRewards memory rewards) returns (
uint256 totalReward,
uint256 membersStake
)
{ {
UnfinalizedMembersReward storage reward = UnfinalizedMembersReward storage reward =
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId]; unfinalizedPoolRewardsByEpoch[currentEpoch][poolId];
rewards.membersReward = reward.reward; totalReward = reward.operatorReward + reward.membersReward;
rewards.membersStake = reward.stake; membersStake = reward.membersStake;
} }
/// @dev Set the operator share of a pool based on reward ratios.
function _setOperatorShare(
bytes32 poolId,
uint256 operatorReward,
uint256 membersReward
)
private
{
uint32 operatorShare = uint32(
operatorReward * PPM_DENOMINATOR / (operatorReward + membersReward)
);
poolById[poolId].operatorShare = operatorShare;
}
} }

View File

@@ -27,30 +27,37 @@ import "./TestStaking.sol";
contract TestFinalizer is contract TestFinalizer is
TestStaking TestStaking
{ {
event RecordRewardForDelegatorsCall( event RecordStakingPoolRewards(
bytes32 poolId, bytes32 poolId,
uint256 membersReward, uint256 membersReward,
uint256 membersStake uint256 membersStake
); );
event RecordDepositInRewardVaultForCall( event DepositStakingPoolRewards(
bytes32 poolId, uint256 operatorReward,
uint256 totalReward, uint256 membersReward
bool operatorOnly
); );
event DepositIntoStakingPoolRewardVaultCall( address payable private _operatorRewardsReceiver;
uint256 amount address payable private _membersRewardsReceiver;
);
address payable private _rewardReceiver;
mapping (bytes32 => uint32) private _operatorSharesByPool; mapping (bytes32 => uint32) private _operatorSharesByPool;
/// @param rewardReceiver The address to transfer rewards into when /// @param operatorRewardsReceiver The address to transfer rewards into when
/// a pool is finalized. /// a pool is finalized.
constructor(address payable rewardReceiver) public { constructor(
_rewardReceiver = rewardReceiver; address payable operatorRewardsReceiver,
init(); address payable membersRewardsReceiver
)
public
{
init(
address(1),
address(1),
address(1),
address(1)
);
_operatorRewardsReceiver = operatorRewardsReceiver;
_membersRewardsReceiver = membersRewardsReceiver;
} }
/// @dev Activate a pool in the current epoch. /// @dev Activate a pool in the current epoch.
@@ -82,9 +89,15 @@ contract TestFinalizer is
/// @dev Expose `_finalizePool()` /// @dev Expose `_finalizePool()`
function internalFinalizePool(bytes32 poolId) function internalFinalizePool(bytes32 poolId)
external external
returns (IStructs.PoolRewards memory rewards) returns (
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
)
{ {
rewards = _finalizePool(poolId); (operatorReward,
membersReward,
membersStake) = _finalizePool(poolId);
} }
/// @dev Get finalization-related state variables. /// @dev Get finalization-related state variables.
@@ -143,9 +156,12 @@ contract TestFinalizer is
function internalGetUnfinalizedPoolRewards(bytes32 poolId) function internalGetUnfinalizedPoolRewards(bytes32 poolId)
external external
view view
returns (IStructs.PoolRewards memory rewards) returns (
uint256 totalReward,
uint256 membersStake
)
{ {
rewards = _getUnfinalizedPoolRewards(poolId); (totalReward, membersStake) = _getUnfinalizedPoolRewards(poolId);
} }
/// @dev Expose `_getActivePoolFromEpoch`. /// @dev Expose `_getActivePoolFromEpoch`.
@@ -157,68 +173,33 @@ contract TestFinalizer is
pool = _getActivePoolFromEpoch(epoch, poolId); pool = _getActivePoolFromEpoch(epoch, poolId);
} }
/// @dev Overridden to just store inputs. /// @dev Overridden to log and do some basic math.
function _recordRewardForDelegators( function _recordStakingPoolRewards(
bytes32 poolId, bytes32 poolId,
uint256 membersReward, uint256 reward,
uint256 membersStake uint256 membersStake
) )
internal internal
returns (uint256 operatorReward, uint256 membersReward)
{ {
emit RecordRewardForDelegatorsCall( (operatorReward, membersReward) = _splitReward(poolId, reward);
emit RecordStakingPoolRewards(
poolId, poolId,
membersReward, reward,
membersStake membersStake
); );
} }
/// @dev Overridden to store inputs and do some really basic math. /// @dev Overridden to log and transfer to receivers.
function _depositIntoStakingPoolRewardVault(uint256 amount) internal { function _depositStakingPoolRewards(
emit DepositIntoStakingPoolRewardVaultCall(amount); uint256 operatorReward,
_rewardReceiver.transfer(amount); uint256 membersReward
}
/// @dev Overridden to store inputs and do some really basic math.
function _recordDepositInRewardVaultFor(
bytes32 poolId,
uint256 totalReward,
bool operatorOnly
) )
internal internal
returns (
uint256 operatorPortion,
uint256 membersPortion
)
{ {
emit RecordDepositInRewardVaultForCall( emit DepositStakingPoolRewards(operatorReward, membersReward);
poolId, address(_operatorRewardsReceiver).transfer(operatorReward);
totalReward, address(_membersRewardsReceiver).transfer(operatorReward);
operatorOnly
);
if (operatorOnly) {
operatorPortion = totalReward;
} else {
(operatorPortion, membersPortion) =
_splitRewardAmountBetweenOperatorAndMembers(
poolId,
totalReward
);
}
}
/// @dev Overridden to do some really basic math.
function _splitRewardAmountBetweenOperatorAndMembers(
bytes32 poolId,
uint256 amount
)
internal
view
returns (uint256 operatorPortion, uint256 membersPortion)
{
uint32 operatorShare = _operatorSharesByPool[poolId];
operatorPortion = operatorShare * amount / PPM_DENOMINATOR;
membersPortion = amount - operatorPortion;
} }
/// @dev Overriden to just increase the epoch counter. /// @dev Overriden to just increase the epoch counter.
@@ -229,4 +210,26 @@ contract TestFinalizer is
// solhint-disable no-empty-blocks // solhint-disable no-empty-blocks
/// @dev Overridden to do nothing. /// @dev Overridden to do nothing.
function _unwrapWETH() internal {} function _unwrapWETH() internal {}
/// @dev Split a pool's total reward between the operator and members.
function _splitReward(
bytes32 poolId,
uint256 amount
)
private
view
returns (uint256 operatorReward, uint256 membersReward)
{
IStructs.ActivePool memory pool = _getActivePoolFromEpoch(
currentEpoch - 1,
poolId
);
uint32 operatorShare = _operatorSharesByPool[poolId];
(operatorReward, membersReward) = _splitStakingPoolRewards(
operatorShare,
amount,
pool.membersStake
);
}
} }

View File

@@ -110,14 +110,4 @@ contract TestProtocolFees is
nextEpochBalance: pool.operatorStake nextEpochBalance: pool.operatorStake
}); });
} }
/// @dev Overridden to use test pools.
function getPoolOperator(bytes32)
public
view
returns (address operatorAddress)
{
// Just return nil, we won't use it.
return address(0);
}
} }

View File

@@ -129,7 +129,7 @@ contract TestStorageLayout is
if sub(totalWeightedStakeThisEpoch_slot, slot) { revertIncorrectStorageSlot() } if sub(totalWeightedStakeThisEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1) slot := add(slot, 1)
if sub(activePoolsByEpoch_slot, slot) { revertIncorrectStorageSlot() } if sub(_activePoolsByEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1) slot := add(slot, 1)
if sub(numActivePoolsThisEpoch_slot, slot) { revertIncorrectStorageSlot() } if sub(numActivePoolsThisEpoch_slot, slot) { revertIncorrectStorageSlot() }

View File

@@ -3,7 +3,7 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
import { ContractArtifact } from "ethereum-types"; import { ContractArtifact } from 'ethereum-types';
import * as EthVault from '../generated-artifacts/EthVault.json'; import * as EthVault from '../generated-artifacts/EthVault.json';
import * as IEthVault from '../generated-artifacts/IEthVault.json'; import * as IEthVault from '../generated-artifacts/IEthVault.json';
@@ -47,6 +47,8 @@ import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
import * as TestAssertStorageParams from '../generated-artifacts/TestAssertStorageParams.json'; import * as TestAssertStorageParams from '../generated-artifacts/TestAssertStorageParams.json';
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json'; import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json';
import * as TestCumulativeRewardTracking from '../generated-artifacts/TestCumulativeRewardTracking.json'; import * as TestCumulativeRewardTracking from '../generated-artifacts/TestCumulativeRewardTracking.json';
import * as TestDelegatorRewards from '../generated-artifacts/TestDelegatorRewards.json';
import * as TestFinalizer from '../generated-artifacts/TestFinalizer.json';
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json'; import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json'; import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json'; import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
@@ -103,6 +105,8 @@ export const artifacts = {
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact, TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
TestCobbDouglas: TestCobbDouglas as ContractArtifact, TestCobbDouglas: TestCobbDouglas as ContractArtifact,
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact, TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
TestDelegatorRewards: TestDelegatorRewards as ContractArtifact,
TestFinalizer: TestFinalizer as ContractArtifact,
TestInitTarget: TestInitTarget as ContractArtifact, TestInitTarget: TestInitTarget as ContractArtifact,
TestLibFixedMath: TestLibFixedMath as ContractArtifact, TestLibFixedMath: TestLibFixedMath as ContractArtifact,
TestLibProxy: TestLibProxy as ContractArtifact, TestLibProxy: TestLibProxy as ContractArtifact,

View File

@@ -45,6 +45,8 @@ export * from '../generated-wrappers/staking_proxy';
export * from '../generated-wrappers/test_assert_storage_params'; export * from '../generated-wrappers/test_assert_storage_params';
export * from '../generated-wrappers/test_cobb_douglas'; export * from '../generated-wrappers/test_cobb_douglas';
export * from '../generated-wrappers/test_cumulative_reward_tracking'; export * from '../generated-wrappers/test_cumulative_reward_tracking';
export * from '../generated-wrappers/test_delegator_rewards';
export * from '../generated-wrappers/test_finalizer';
export * from '../generated-wrappers/test_init_target'; export * from '../generated-wrappers/test_init_target';
export * from '../generated-wrappers/test_lib_fixed_math'; export * from '../generated-wrappers/test_lib_fixed_math';
export * from '../generated-wrappers/test_lib_proxy'; export * from '../generated-wrappers/test_lib_proxy';

View File

@@ -45,6 +45,8 @@
"generated-artifacts/TestAssertStorageParams.json", "generated-artifacts/TestAssertStorageParams.json",
"generated-artifacts/TestCobbDouglas.json", "generated-artifacts/TestCobbDouglas.json",
"generated-artifacts/TestCumulativeRewardTracking.json", "generated-artifacts/TestCumulativeRewardTracking.json",
"generated-artifacts/TestDelegatorRewards.json",
"generated-artifacts/TestFinalizer.json",
"generated-artifacts/TestInitTarget.json", "generated-artifacts/TestInitTarget.json",
"generated-artifacts/TestLibFixedMath.json", "generated-artifacts/TestLibFixedMath.json",
"generated-artifacts/TestLibProxy.json", "generated-artifacts/TestLibProxy.json",

View File

@@ -9,8 +9,6 @@ library LibFractions {
/// @dev Maximum value for addition result components. /// @dev Maximum value for addition result components.
uint256 constant internal RESCALE_THRESHOLD = 10 ** 27; uint256 constant internal RESCALE_THRESHOLD = 10 ** 27;
/// @dev Rescale factor for addition.
uint256 constant internal RESCALE_BASE = 10 ** 9;
/// @dev Safely adds two fractions `n1/d1 + n2/d2` /// @dev Safely adds two fractions `n1/d1 + n2/d2`
/// @param n1 numerator of `1` /// @param n1 numerator of `1`
@@ -46,8 +44,10 @@ library LibFractions {
// If either the numerator or the denominator are > RESCALE_THRESHOLD, // If either the numerator or the denominator are > RESCALE_THRESHOLD,
// re-scale them to prevent overflows in future operations. // re-scale them to prevent overflows in future operations.
if (numerator > RESCALE_THRESHOLD || denominator > RESCALE_THRESHOLD) { if (numerator > RESCALE_THRESHOLD || denominator > RESCALE_THRESHOLD) {
numerator = numerator.safeDiv(RESCALE_BASE); uint256 rescaleBase = numerator >= denominator ? numerator : denominator;
denominator = denominator.safeDiv(RESCALE_BASE); rescaleBase /= RESCALE_THRESHOLD;
numerator = numerator.safeDiv(rescaleBase);
denominator = denominator.safeDiv(rescaleBase);
} }
} }

View File

@@ -29,6 +29,10 @@ export enum InvalidParamValueErrorCode {
InvalidZrxVaultAddress, InvalidZrxVaultAddress,
InvalidEpochDuration, InvalidEpochDuration,
InvalidMinimumPoolStake, InvalidMinimumPoolStake,
InvalidWethProxyAddress,
InvalidEthVaultAddress,
InvalidRewardVaultAddress,
InvalidZrxVaultAddress,
} }
export enum InitializationErrorCode { export enum InitializationErrorCode {