@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
IStaking,
IStakingEvents,
MixinAbstract,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager,
MixinParams,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards,
MixinStakingPool,
MixinStake,
MixinExchangeFees,
MixinExchangeFees
{
// this contract can receive ETH
// solhint-disable no-empty-blocks

View File

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

View File

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

View File

@@ -30,6 +30,8 @@ import "../immutable/MixinStorage.sol";
/// then it should be removed.
contract MixinExchangeManager is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage
{
/// @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 (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
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
/// in `payProtocolFee()`.
uint256 internal totalFeesCollectedThisEpoch;
uint256 public totalFeesCollectedThisEpoch;
/// @dev The total weighted stake in the current epoch, built up iteratively
/// in `payProtocolFee()`.
uint256 internal totalWeightedStakeThisEpoch;
uint256 public totalWeightedStakeThisEpoch;
/// @dev State information for each active pool in an epoch.
/// In practice, we only store state for `currentEpoch % 2`.
mapping(uint256 => mapping(bytes32 => IStructs.ActivePool))
internal
activePoolsByEpoch;
_activePoolsByEpoch;
/// @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
/// epoch). This is simply the balance of the contract at the end of
/// the epoch.
uint256 internal unfinalizedRewardsAvailable;
uint256 public unfinalizedRewardsAvailable;
/// @dev The number of active pools in the last epoch that have yet to be
/// finalized through `finalizePools()`.
uint256 internal unfinalizedPoolsRemaining;
uint256 public unfinalizedPoolsRemaining;
/// @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.
uint256 internal unfinalizedTotalWeightedStake;
uint256 public unfinalizedTotalWeightedStake;
/// @dev How many rewards were paid at the end of finalization.
uint256 totalRewardsPaidLastEpoch;

View File

@@ -42,13 +42,14 @@ interface IEthVault {
uint256 amount
);
/// @dev Deposit an `amount` of ETH from `owner` into the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param owner of ETH Tokens.
function depositFor(address owner)
external
payable;
/// @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;
/// @dev Withdraw an `amount` of ETH to `msg.sender` from the vault.
/// 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 poolId The ID of the pool.
event StakingPoolActivated(
uint256 epoch,
bytes32 poolId
uint256 indexed epoch,
bytes32 indexed poolId
);
/// @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 totalFeesCollected Total fees collected across all active pools.
event EpochEnded(
uint256 epoch,
uint256 indexed epoch,
uint256 numActivePools,
uint256 rewardsAvailable,
uint256 totalFeesCollected,
@@ -71,7 +71,7 @@ interface IStakingEvents {
/// @param rewardsPaid Total amount of rewards paid out.
/// @param rewardsRemaining Rewards left over.
event EpochFinalized(
uint256 epoch,
uint256 indexed epoch,
uint256 rewardsPaid,
uint256 rewardsRemaining
);
@@ -82,8 +82,8 @@ interface IStakingEvents {
/// @param operatorReward Amount of reward paid to pool operator.
/// @param membersReward Amount of reward paid to pool members.
event RewardsPaid(
uint256 epoch,
bytes32 poolId,
uint256 indexed epoch,
bytes32 indexed poolId,
uint256 operatorReward,
uint256 membersReward
);

View File

@@ -33,34 +33,35 @@ interface IStakingPoolRewardVault {
uint256 amount
);
/// @dev Emitted when a reward is transferred to the ETH vault.
/// @param amount The amount in ETH withdrawn.
/// @param member of the pool.
/// @param poolId The pool the reward was deposited for.
event PoolRewardTransferredToEthVault(
/// @dev Emitted when rewards are transferred out fo the vault.
/// @param poolId Unique Id of pool.
/// @param to Address to send funds to.
/// @param amount Amount of ETH to transfer.
event PoolRewardTransferred(
bytes32 indexed poolId,
address indexed member,
address to,
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.
/// Note that this is only callable by the staking contract.
/// @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.
/// @param poolId Unique Id of pool.
/// @param member of pool to transfer funds to.
/// @param amount Amount in ETH to transfer.
/// @param ethVaultAddress address of Eth Vault to send rewards to.
function transferToEthVault(
/// @param to Address to send funds to.
/// @param amount Amount of ETH to transfer.
function transfer(
bytes32 poolId,
address member,
uint256 amount,
address ethVaultAddress
address payable to,
uint256 amount
)
external;

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,12 @@ import "../stake/MixinStakeBalances.sol";
contract MixinCumulativeRewards is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances
{
using LibSafeMath for uint256;
@@ -34,7 +40,7 @@ contract MixinCumulativeRewards is
function _initializeCumulativeRewards(bytes32 poolId)
internal
{
// sets the default cumulative reward
// Sets the default cumulative reward
_forceSetCumulativeReward(
poolId,
currentEpoch,
@@ -51,31 +57,35 @@ contract MixinCumulativeRewards is
pure
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
// field to at least 1.
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 epoch of the cumulative reward.
/// @param epoch Epoch of the cumulative reward.
function _canUnsetCumulativeReward(bytes32 poolId, uint256 epoch)
internal
view
returns (bool)
{
return (
_isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch]) && // is there a value to unset
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] == 0 && // no references to this CR
_cumulativeRewardsByPoolLastStored[poolId] > epoch // this is *not* the most recent CR
// Is there a value to unset
_isCumulativeRewardSet(_cumulativeRewardsByPool[poolId][epoch]) &&
// 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`.
/// @param poolId Unique Id of pool.
/// @param epoch of cumulative reward.
/// @param value of cumulative reward.
/// @param epoch Epoch of cumulative reward.
/// @param value Value of cumulative reward.
function _trySetCumulativeReward(
bytes32 poolId,
uint256 epoch,
@@ -84,7 +94,7 @@ contract MixinCumulativeRewards is
internal
{
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;
}
_forceSetCumulativeReward(poolId, epoch, value);
@@ -93,8 +103,8 @@ contract MixinCumulativeRewards is
/// @dev Sets a cumulative reward for `poolId` at `epoch`.
/// This can be used to overwrite an existing value.
/// @param poolId Unique Id of pool.
/// @param epoch of cumulative reward.
/// @param value of cumulative reward.
/// @param epoch Epoch of cumulative reward.
/// @param value Value of cumulative reward.
function _forceSetCumulativeReward(
bytes32 poolId,
uint256 epoch,
@@ -108,7 +118,7 @@ contract MixinCumulativeRewards is
/// @dev Tries to unset the cumulative reward for `poolId` at `epoch`.
/// @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)
internal
{
@@ -120,11 +130,11 @@ contract MixinCumulativeRewards is
/// @dev Unsets the cumulative reward for `poolId` at `epoch`.
/// @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)
internal
{
_cumulativeRewardsByPool[poolId][epoch] = IStructs.Fraction({numerator: 0, denominator: 0});
delete _cumulativeRewardsByPool[poolId][epoch];
}
/// @dev Returns info on most recent cumulative reward.
@@ -133,31 +143,38 @@ contract MixinCumulativeRewards is
view
returns (IStructs.CumulativeRewardInfo memory)
{
// fetch the last epoch at which we stored a cumulative reward for this pool
uint256 cumulativeRewardsLastStored = _cumulativeRewardsByPoolLastStored[poolId];
// Fetch the last epoch at which we stored a cumulative reward for
// 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({
cumulativeReward: _cumulativeRewardsByPool[poolId][cumulativeRewardsLastStored],
cumulativeReward:
_cumulativeRewardsByPool[poolId][cumulativeRewardsLastStored],
cumulativeRewardEpoch: cumulativeRewardsLastStored
});
}
/// @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
/// most recent value.
/// The value will only be set if the input epoch is greater than the
/// current most recent value.
/// @param poolId Unique Id of pool.
/// @param epoch of the most recent cumulative reward.
function _trySetMostRecentCumulativeRewardEpoch(bytes32 poolId, uint256 epoch)
/// @param epoch Epoch of the most recent cumulative reward.
function _trySetMostRecentCumulativeRewardEpoch(
bytes32 poolId,
uint256 epoch
)
internal
{
// check if we should do any work
uint256 currentMostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId];
// Check if we should do any work
uint256 currentMostRecentEpoch =
_cumulativeRewardsByPoolLastStored[poolId];
if (epoch == currentMostRecentEpoch) {
return;
}
// update state to reflect the most recent cumulative reward
// Update state to reflect the most recent cumulative reward
_forceSetMostRecentCumulativeRewardEpoch(
poolId,
currentMostRecentEpoch,
@@ -167,8 +184,10 @@ contract MixinCumulativeRewards is
/// @dev Forcefully sets the epoch of the most recent cumulative reward.
/// @param poolId Unique Id of pool.
/// @param currentMostRecentEpoch of the most recent cumulative reward.
/// @param newMostRecentEpoch of the new most recent cumulative reward.
/// @param currentMostRecentEpoch Epoch of the most recent cumulative
/// reward.
/// @param newMostRecentEpoch Epoch of the new most recent cumulative
/// reward.
function _forceSetMostRecentCumulativeRewardEpoch(
bytes32 poolId,
uint256 currentMostRecentEpoch,
@@ -176,19 +195,21 @@ contract MixinCumulativeRewards is
)
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);
_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);
}
/// @dev Adds a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info for the most recent cumulative reward (value and epoch)
/// @param isDependent True iff there is a dependency on the cumulative reward for `poolId` at `epoch`
/// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info for the most recent
/// cumulative reward (value and epoch)
/// @param isDependent True iff there is a dependency on the cumulative
/// reward for `poolId` at `epoch`
function _addOrRemoveDependencyOnCumulativeReward(
bytes32 poolId,
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.
/// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative reward.
/// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative
/// reward.
function _addDependencyOnCumulativeReward(
bytes32 poolId,
uint256 epoch,
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo
)
internal
private
{
// add dependency by increasing the reference counter
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = _cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeAdd(1);
// Add dependency by increasing the reference counter
_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(
poolId,
epoch,
@@ -235,88 +335,20 @@ contract MixinCumulativeRewards is
/// @dev Removes a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool.
/// @param epoch to remove dependency from.
/// @param epoch Epoch to remove dependency from.
function _removeDependencyOnCumulativeReward(
bytes32 poolId,
uint256 epoch
)
internal
private
{
// remove dependency by decreasing reference counter
uint256 newReferenceCounter = _cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1);
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = newReferenceCounter;
// Remove dependency by decreasing reference counter
uint256 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);
}
/// @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
IStakingEvents,
MixinAbstract,
MixinConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers,
MixinScheduler,
MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards
{
using LibSafeMath for uint256;
@@ -113,17 +122,6 @@ contract MixinStakingPool is
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.
/// @param poolId Unique id of 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.
contract MixinStakingPoolMakers is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers
{

View File

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

View File

@@ -27,31 +27,53 @@ import "../sys/MixinAbstract.sol";
contract MixinStakingPoolRewards is
MixinCumulativeRewards,
MixinAbstract
IStakingEvents,
MixinAbstract,
MixinConstants,
Ownable,
MixinStorage,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances,
MixinCumulativeRewards
{
using LibSafeMath for uint256;
/// @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.
function computeRewardBalanceOfOperator(bytes32 poolId, address operator)
public
view
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.
function syncDelegatorRewards(bytes32 poolId)
external
{
address member = msg.sender;
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(_delegatedStakeToPoolByOwner[member][poolId]);
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner =
_loadAndSyncBalance(_delegatedStakeToPoolByOwner[member][poolId]);
_syncRewardsForDelegator(
poolId,
member,
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]), // initial balance
// Initial balance
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]),
finalDelegatedStakeToPoolByOwner
);
// update stored balance with synchronized version; this prevents redundant withdrawals.
_delegatedStakeToPoolByOwner[member][poolId] = finalDelegatedStakeToPoolByOwner;
// Update stored balance with synchronized version; this prevents
// redundant withdrawals.
_delegatedStakeToPoolByOwner[member][poolId] =
finalDelegatedStakeToPoolByOwner;
}
/// @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.
/// @return totalReward Balance in ETH.
function computeRewardBalanceOfDelegator(bytes32 poolId, address member)
public
external
view
returns (uint256 reward)
{
IStructs.PoolRewards memory unfinalizedPoolRewards =
IStructs.Pool memory pool = poolById[poolId];
// Get any unfinalized rewards.
(uint256 unfinalizedTotalRewards, uint256 unfinalizedMembersStake) =
_getUnfinalizedPoolRewards(poolId);
// Get the members' portion.
(, uint256 unfinalizedMembersReward) = _splitStakingPoolRewards(
pool.operatorShare,
unfinalizedTotalRewards,
unfinalizedMembersStake
);
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,
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]),
currentEpoch
currentEpoch,
unfinalizedMembersReward,
unfinalizedMembersStake
);
}
/// @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.
/// @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.
/// @param poolId Unique id of pool.
/// @param member of the pool.
/// @param initialDelegatedStakeToPoolByOwner The member's delegated balance at the beginning of this transaction.
/// @param finalDelegatedStakeToPoolByOwner The member's delegated balance at the end of this transaction.
/// @param initialDelegatedStakeToPoolByOwner The member's delegated
/// balance at the beginning of this transaction.
/// @param finalDelegatedStakeToPoolByOwner The member's delegated balance
/// at the end of this transaction.
function _syncRewardsForDelegator(
bytes32 poolId,
address member,
@@ -112,8 +121,9 @@ contract MixinStakingPoolRewards is
)
internal
{
// transfer 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.
// 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.
_transferDelegatorRewardsToEthVault(
poolId,
member,
@@ -121,14 +131,16 @@ contract MixinStakingPoolRewards is
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(
poolId,
finalDelegatedStakeToPoolByOwner,
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(
poolId,
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
/// the members' reward into the Staking Pool Vault. It also records the cumulative reward, which
/// is used to compute each delegator's portion of the members' reward.
/// @dev Handles a pool's reward at the current epoch.
/// This will compute the reward split and record the cumulative
/// 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 reward received by the pool.
/// @param amountOfDelegatedStake the amount of delegated stake that will split the reward.
/// @param epoch at which this was earned.
function _handleStakingPoolReward(
/// @param membersStake the amount of non-operator delegated stake that
/// will split the reward.
/// @return operatorReward
function _recordStakingPoolRewards(
bytes32 poolId,
uint256 reward,
uint256 amountOfDelegatedStake
uint256 membersStake
)
internal
returns (uint256 operatorReward, uint256 membersReward)
{
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).
uint256 operatorPortion = amountOfDelegatedStake == 0
? reward
: LibMath.getPartialAmountCeil(
uint256(pool.operatorShare),
PPM_DENOMINATOR,
reward
);
// Split the reward between operator and members
(operatorReward, membersReward) =
_splitStakingPoolRewards(pool.operatorShare, reward, membersStake);
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.
uint256 membersPortion = reward.safeSub(operatorPortion);
if (membersPortion == 0) {
return;
if (membersReward == 0) {
return (operatorReward, membersReward);
}
// 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.
mapping (uint256 => IStructs.Fraction) storage _cumulativeRewardsByPoolPtr = _cumulativeRewardsByPool[poolId];
// Cache a storage pointer to the cumulative rewards for `poolId`
// indexed by epoch.
mapping (uint256 => IStructs.Fraction)
storage
_cumulativeRewardsByPoolPtr = _cumulativeRewardsByPool[poolId];
// 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.
uint256 cumulativeRewardsLastStored = _cumulativeRewardsByPoolLastStored[poolId];
IStructs.Fraction memory mostRecentCumulativeRewards = _cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored];
uint256 cumulativeRewardsLastStored =
_cumulativeRewardsByPoolLastStored[poolId];
IStructs.Fraction memory mostRecentCumulativeRewards =
_cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored];
// Compute new cumulative reward
(uint256 numerator, uint256 denominator) = LibFractions.addFractions(
mostRecentCumulativeRewards.numerator,
mostRecentCumulativeRewards.denominator,
membersPortion,
amountOfDelegatedStake
);
// 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)
membersReward,
membersStake
);
// store cumulative rewards and set most recent
_forceSetCumulativeReward(
poolId,
epoch,
IStructs.Fraction({
numerator: numeratorNormalized,
denominator: denominatorNormalized
})
currentEpoch,
IStructs.Fraction(numerator, denominator)
);
}
/// @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.
/// @dev Deposit rewards into the eth vault and reward vault for pool
/// operators and members rewards, respectively. This should be called
/// 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 member The member of the pool.
/// @param unsyncedStake Unsynced stake of the delegator to the pool.
function _transferDelegatorRewardsToEthVault(
bytes32 poolId,
address member,
IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner,
IStructs.StoredBalance memory unsyncedStake,
uint256 currentEpoch
)
private
{
// compute balance owed to delegator
// Ensure the pool is finalized.
_finalizePool(poolId);
// Compute balance owed to delegator
uint256 balance = _computeRewardBalanceOfDelegator(
poolId,
unsyncedDelegatedStakeToPoolByOwner,
currentEpoch
unsyncedStake,
currentEpoch,
// No unfinalized values because we ensured the pool is already
// finalized.
0,
0
);
if (balance == 0) {
return;
}
// transfer from transient Reward Pool vault to ETH Vault
rewardVault.transferToEthVault(
// Transfer from transient Reward Pool vault to ETH Vault
ethVault.recordDepositFor(member, balance);
rewardVault.transfer(
poolId,
member,
balance,
address(ethVault)
address(uint160(address(ethVault))),
balance
);
}
/// @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.
/// @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 unfinalizedMembersReward Unfinalized total members reward
/// (if any).
/// @param unfinalizedMembersStake Unfinalized total members stake (if any).
/// @return totalReward Balance in ETH.
function _computeRewardBalanceOfDelegator(
bytes32 poolId,
IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner,
uint256 currentEpoch
IStructs.StoredBalance memory unsyncedStake,
uint256 currentEpoch,
uint256 unfinalizedMembersReward,
uint256 unfinalizedMembersStake
)
private
view
returns (uint256 totalReward)
returns (uint256 reward)
{
uint256 currentEpoch = getCurrentEpoch();
// There can be no rewards in epoch 0 because there is no delegated
// stake.
if (currentEpoch == 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
// equal to the current epoch, because all prior rewards, including
// rewards finalized this epoch have been claimed.
if (stake.currentEpoch == currentEpoch) {
if (unsyncedStake.currentEpoch == currentEpoch) {
return reward = 0;
}
// If there are unfinalized rewards this epoch, compute the member's
// share.
if (unfinalizedMembersReward != 0 && unfinalizedDelegatedStake != 0) {
if (unfinalizedMembersReward != 0 && unfinalizedMembersStake != 0) {
// Unfinalized rewards are always earned from stake in
// the prior epoch so we want the stake at `currentEpoch-1`.
uint256 _stake = stake.currentEpoch >= currentEpoch - 1 ?
stake.currentEpochBalance :
stake.nextEpochBalance;
uint256 _stake = unsyncedStake.currentEpoch >= currentEpoch - 1 ?
unsyncedStake.currentEpochBalance :
unsyncedStake.nextEpochBalance;
if (_stake != 0) {
reward = _stake
.safeMul(unfinalizedMembersReward)
.safeDiv(unfinalizedDelegatedStake);
.safeDiv(unfinalizedMembersStake);
}
}
// Get the last epoch where a reward was credited to this pool.
uint256 lastRewardEpoch = lastPoolRewardEpoch[poolId];
// Get the last epoch where a reward was credited to this pool, which
// 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,
// it has already been claimed.
if (stake.currentEpoch >= lastRewardEpoch) {
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
// From here we know: `stake.currentEpoch < currentEpoch > 0`.
// From here we know: `unsyncedStake.currentEpoch < currentEpoch > 0`.
if (stake.currentEpoch < lastRewardEpoch) {
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.currentEpochBalance,
unsyncedStake.currentEpoch,
unsyncedStake.currentEpoch + 1
)
);
if (unsyncedStake.currentEpoch + 1 < lastRewardEpoch) {
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
stake,
stake.currentEpoch,
stake.currentEpoch + 1
unsyncedStake.nextEpochBalance,
unsyncedStake.currentEpoch + 1,
lastRewardEpoch
)
);
if (stake.currentEpoch + 1 < lastRewardEpoch) {
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
stake,
stake.currentEpoch + 1,
lastRewardEpoch
)
);
}
}
}
/// @dev Adds or removes cumulative reward dependencies for a delegator.
/// A delegator always depends on the cumulative reward for the current epoch.
/// They will also depend on the previous epoch's reward, if they are already staked with the input pool.
/// A delegator always depends on the cumulative reward for the current
/// and next epoch, if they would still have stake in the next epoch.
/// @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.
function _setCumulativeRewardDependenciesForDelegator(
bytes32 poolId,
@@ -331,26 +391,34 @@ contract MixinStakingPoolRewards is
)
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) {
return;
}
// get the most recent cumulative reward, which will serve as a reference point when updating dependencies
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo = _getMostRecentCumulativeRewardInfo(poolId);
// Get the most recent cumulative reward, which will serve as a
// reference point when updating dependencies
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo =
_getMostRecentCumulativeRewardInfo(poolId);
// record dependency on `lastEpoch`
if (_delegatedStakeToPoolByOwner.currentEpoch > 0 && _delegatedStakeToPoolByOwner.currentEpochBalance != 0) {
// Record dependency on the next epoch
uint256 nextEpoch = currentEpoch.safeAdd(1);
if (_delegatedStakeToPoolByOwner.currentEpoch > 0
&& _delegatedStakeToPoolByOwner.nextEpochBalance != 0)
{
_addOrRemoveDependencyOnCumulativeReward(
poolId,
uint256(_delegatedStakeToPoolByOwner.currentEpoch).safeSub(1),
nextEpoch,
mostRecentCumulativeRewardInfo,
isDependent
);
}
// record dependency on current epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0 || _delegatedStakeToPoolByOwner.nextEpochBalance != 0) {
// Record dependency on current epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0
|| _delegatedStakeToPoolByOwner.nextEpochBalance != 0)
{
_addOrRemoveDependencyOnCumulativeReward(
poolId,
_delegatedStakeToPoolByOwner.currentEpoch,
@@ -358,9 +426,5 @@ contract MixinStakingPoolRewards is
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.
/// Does nothing if the pool is already finalized.
/// @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)
internal
view
returns (IStructs.PoolRewards memory rewards);
returns (
uint256 reward,
uint256 membersStake
);
/// @dev Get an active pool from an epoch by its ID.
/// @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
/// epoch, crediting it rewards and sending those rewards to the reward
/// vault. This can be called by internal functions that need to
/// finalize a pool immediately. Does nothing if the pool is already
/// and eth vault. This can be called by internal functions that need
/// 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.
/// @param poolId The pool ID to finalize.
/// @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.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _finalizePool(bytes32 poolId)
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 "../stake/MixinStakeBalances.sol";
import "../staking_pools/MixinStakingPool.sol";
import "../staking_pools/MixinStakingPoolRewardVault.sol";
import "./MixinScheduler.sol";
@@ -47,11 +46,10 @@ contract MixinFinalizer is
MixinDeploymentConstants,
Ownable,
MixinStorage,
MixinZrxVault,
MixinScheduler,
MixinStakingPoolRewardVault,
MixinStakeStorage,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards
{
using LibSafeMath for uint256;
@@ -67,7 +65,7 @@ contract MixinFinalizer is
external
returns (uint256 _unfinalizedPoolsRemaining)
{
uint256 closingEpoch = getCurrentEpoch();
uint256 closingEpoch = currentEpoch;
// Make sure the previous epoch has been fully finalized.
if (unfinalizedPoolsRemaining != 0) {
@@ -114,9 +112,9 @@ contract MixinFinalizer is
}
/// @dev Finalizes pools that were active in the previous epoch, paying out
/// rewards to the reward vault. Keepers should call this function
/// repeatedly until all active pools that were emitted in in a
/// `StakingPoolActivated` in the prior epoch have been finalized.
/// rewards to the reward and eth vault. Keepers should call this
/// function repeatedly until all active pools that were emitted in in
/// a `StakingPoolActivated` in the prior epoch have been finalized.
/// Pools that have already been finalized will be silently ignored.
/// We deliberately try not to revert here in case multiple parties
/// are finalizing pools.
@@ -126,7 +124,7 @@ contract MixinFinalizer is
external
returns (uint256 _unfinalizedPoolsRemaining)
{
uint256 epoch = getCurrentEpoch();
uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return _unfinalizedPoolsRemaining = 0;
@@ -144,8 +142,11 @@ contract MixinFinalizer is
_getActivePoolsFromEpoch(epoch - 1);
uint256 numPoolIds = poolIds.length;
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];
IStructs.ActivePool memory pool = activePools[poolId];
@@ -154,12 +155,17 @@ contract MixinFinalizer is
continue;
}
IStructs.PoolRewards memory poolRewards =
_creditRewardsToPool(epoch, poolId, pool);
(uint256 operatorReward, uint256 membersReward) =
_creditRewardsToPool(epoch, poolId, pool, rewardsPaid);
rewardsPaid = rewardsPaid.safeAdd(
poolRewards.operatorReward + poolRewards.membersReward
);
totalOperatorRewardsPaid =
totalOperatorRewardsPaid.safeAdd(operatorReward);
totalMembersRewardsPaid =
totalMembersRewardsPaid.safeAdd(membersReward);
rewardsPaid = rewardsPaid
.safeAdd(operatorReward)
.safeAdd(membersReward);
// Decrease the number of unfinalized pools left.
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) {
_depositIntoStakingPoolRewardVault(rewardsPaid);
_depositStakingPoolRewards(totalOperatorRewardsPaid, totalMembersRewardsPaid);
}
}
/// @dev Instantly finalizes a single pool that was active in the previous
/// epoch, crediting it rewards and sending those rewards to the reward
/// vault. This can be called by internal functions that need to
/// finalize a pool immediately. Does nothing if the pool is already
/// and eth vault. This can be called by internal functions that need
/// 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.
/// @param poolId The pool ID to finalize.
/// @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.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _finalizePool(bytes32 poolId)
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.
if (epoch == 0) {
return rewards;
return (operatorReward, membersReward, membersStake);
}
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
// Do nothing if the pool was not active (has no fees).
if (pool.feesCollected == 0) {
return rewards;
return (operatorReward, membersReward, membersStake);
}
rewards = _creditRewardsToPool(epoch, poolId, pool);
uint256 totalReward =
rewards.membersReward.safeAdd(rewards.operatorReward);
(operatorReward, membersReward) =
_creditRewardsToPool(epoch, poolId, pool, 0);
uint256 totalReward = operatorReward.safeAdd(membersReward);
if (totalReward > 0) {
totalRewardsPaidLastEpoch =
totalRewardsPaidLastEpoch.safeAdd(totalReward);
_depositIntoStakingPoolRewardVault(totalReward);
_depositStakingPoolRewards(operatorReward, membersReward);
}
// Decrease the number of unfinalized pools left.
@@ -238,26 +249,32 @@ contract MixinFinalizer is
unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
);
}
membersStake = pool.membersStake;
}
/// @dev Computes the reward owed to a pool during finalization.
/// Does nothing if the pool is already finalized.
/// @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)
internal
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.
if (epoch == 0) {
return rewards;
return (reward, membersStake);
}
rewards = _getUnfinalizedPoolRewards(
poolId,
_getActivePoolFromEpoch(epoch - 1, poolId)
);
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
reward = _getUnfinalizedPoolRewards(pool, 0);
membersStake = pool.membersStake;
}
/// @dev Get an active pool from an epoch by its ID.
@@ -287,11 +304,13 @@ contract MixinFinalizer is
view
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.
function _unwrapWETH() internal {
function _unwrapWETH()
internal
{
uint256 wethBalance = IEtherToken(WETH_ADDRESS)
.balanceOf(address(this));
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.
/// @param poolId The pool's ID.
/// @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(
bytes32 poolId,
IStructs.ActivePool memory pool
IStructs.ActivePool memory pool,
uint256 unpaidRewards
)
private
view
returns (IStructs.PoolRewards memory rewards)
returns (uint256 rewards)
{
// There can't be any rewards if the pool was active or if it has
// no stake.
if (pool.feesCollected == 0) {
return rewards;
return rewards = 0;
}
uint256 unfinalizedRewardsAvailable_ = unfinalizedRewardsAvailable;
// Use the cobb-douglas function to compute the total reward.
uint256 totalReward = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable,
rewards = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable_,
pool.feesCollected,
unfinalizedTotalFeesCollected,
pool.weightedStake,
@@ -370,17 +348,15 @@ contract MixinFinalizer is
cobbDouglasAlphaDenominator
);
// Split the reward between the operator and delegators.
if (pool.membersStake == 0) {
rewards.operatorReward = totalReward;
} else {
(rewards.operatorReward, rewards.membersReward) =
_splitRewardAmountBetweenOperatorAndMembers(
poolId,
totalReward
);
// Clip the reward to always be under
// `unfinalizedRewardsAvailable - totalRewardsPaid - unpaidRewards`,
// in case cobb-douglas overflows, which should be unlikely.
uint256 rewardsRemaining = unfinalizedRewardsAvailable_
.safeSub(totalRewardsPaidLastEpoch)
.safeSub(unpaidRewards);
if (rewardsRemaining < rewards) {
rewards = rewardsRemaining;
}
rewards.membersStake = pool.membersStake;
}
/// @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 poolId The pool ID to finalize.
/// @param pool The active pool to finalize.
/// @param unpaidRewards Rewards that have been credited but not finalized.
/// @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(
uint256 epoch,
bytes32 poolId,
IStructs.ActivePool memory pool
IStructs.ActivePool memory pool,
uint256 unpaidRewards
)
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
// some gas.
delete _getActivePoolsFromEpoch(epoch - 1)[poolId];
// Compute the rewards.
rewards = _getUnfinalizedPoolRewards(poolId, pool);
uint256 totalReward =
rewards.membersReward.safeAdd(rewards.operatorReward);
uint256 rewards = _getUnfinalizedPoolRewards(pool, unpaidRewards);
// Credit the pool the rewards in the RewardVault.
_recordDepositInRewardVaultFor(
// Credit the pool.
// Note that we credit at the CURRENT epoch even though these rewards
// were earned in the previous epoch.
(operatorReward, membersReward) = _recordStakingPoolRewards(
poolId,
totalReward,
// If no delegated stake, all rewards go to the operator.
pool.membersStake == 0
rewards,
pool.membersStake
);
// Sync delegator rewards.
if (rewards.membersReward != 0) {
_recordRewardForDelegators(
poolId,
rewards.membersReward,
pool.membersStake
);
}
// Emit an event.
emit RewardsPaid(
epoch,
poolId,
rewards.operatorReward,
rewards.membersReward
operatorReward,
membersReward
);
}
}

View File

@@ -30,6 +30,8 @@ import "../libs/LibStakingRichErrors.sol";
contract MixinParams is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage
{
/// @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.
contract MixinScheduler is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage
{
using LibSafeMath for uint256;

View File

@@ -26,6 +26,8 @@ import "./MixinVaultCore.sol";
/// @dev This vault manages ETH.
contract EthVault is
IEthVault,
IVaultCore,
Ownable,
MixinVaultCore
{
using LibSafeMath for uint256;
@@ -33,19 +35,21 @@ contract EthVault is
// mapping from Owner to ETH balance
mapping (address => uint256) internal _balances;
/// @dev Deposit an `amount` of ETH from `owner` into the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param owner of ETH Tokens.
function depositFor(address owner)
external
payable
{
// update balance
uint256 amount = msg.value;
_balances[owner] = _balances[owner].safeAdd(msg.value);
// solhint-disable no-empty-blocks
/// @dev Payable fallback for bulk-deposits.
function () payable external {}
// 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);
}

View File

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

View File

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

View File

@@ -20,35 +20,52 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/interfaces/IStructs.sol";
import "../src/interfaces/IStakingPoolRewardVault.sol";
import "../src/interfaces/IEthVault.sol";
import "./TestStaking.sol";
contract TestDelegatorRewards is
TestStaking
{
event Deposit(
event RecordDepositToEthVault(
address owner,
uint256 amount
);
event RecordDepositToRewardVault(
bytes32 poolId,
address member,
uint256 balance
uint256 membersReward
);
event FinalizePool(
bytes32 poolId,
uint256 reward,
uint256 stake
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
);
struct UnfinalizedMembersReward {
uint256 reward;
uint256 stake;
uint256 operatorReward;
uint256 membersReward;
uint256 membersStake;
}
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
unfinalizedMembersRewardByPoolByEpoch;
unfinalizedPoolRewardsByEpoch;
/// @dev Expose _finalizePool
function internalFinalizePool(bytes32 poolId) external {
@@ -58,15 +75,17 @@ contract TestDelegatorRewards is
/// @dev Set unfinalized members reward for a pool in the current epoch.
function setUnfinalizedMembersRewards(
bytes32 poolId,
uint256 operatorReward,
uint256 membersReward,
uint256 membersStake
)
external
{
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId] =
unfinalizedPoolRewardsByEpoch[currentEpoch][poolId] =
UnfinalizedMembersReward({
reward: membersReward,
stake: membersStake
operatorReward: operatorReward,
membersReward: membersReward,
membersStake: membersStake
});
}
@@ -85,13 +104,20 @@ contract TestDelegatorRewards is
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance memory initialStake =
_delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
_delegatedStakeToPoolByOwner[delegator][poolId];
_stake.isInitialized = true;
_stake.currentEpochBalance += 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
@@ -104,15 +130,22 @@ contract TestDelegatorRewards is
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance memory initialStake =
_delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
_delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance;
}
_stake.isInitialized = true;
_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
@@ -125,67 +158,115 @@ contract TestDelegatorRewards is
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance memory initialStake =
_delegatedStakeToPoolByOwner[delegator][poolId];
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
_delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance;
}
_stake.isInitialized = true;
_stake.nextEpochBalance -= uint96(stake);
_stake.currentEpoch = uint64(currentEpoch);
}
/// @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(
_stake.currentEpoch = uint32(currentEpoch);
_syncRewardsForDelegator(
poolId,
member,
balance
delegator,
initialStake,
_stake
);
}
/// @dev Overridden to realize unfinalizedMembersRewardByPoolByEpoch in
/// the current epoch and eit a event,
function _finalizePool(bytes32 poolId)
internal
returns (IStructs.PoolRewards memory rewards)
/// @dev `IEthVault.recordDepositFor()`,` overridden to just emit events.
function recordDepositFor(
address owner,
uint256 amount
)
external
{
UnfinalizedMembersReward memory reward =
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
delete unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
rewards.membersReward = reward.reward;
rewards.membersStake = reward.stake;
_recordRewardForDelegators(poolId, reward.reward, reward.stake);
emit FinalizePool(poolId, reward.reward, reward.stake);
emit RecordDepositToEthVault(
owner,
amount
);
}
/// @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)
internal
view
returns (IStructs.PoolRewards memory rewards)
returns (
uint256 totalReward,
uint256 membersStake
)
{
UnfinalizedMembersReward storage reward =
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
rewards.membersReward = reward.reward;
rewards.membersStake = reward.stake;
unfinalizedPoolRewardsByEpoch[currentEpoch][poolId];
totalReward = reward.operatorReward + reward.membersReward;
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
TestStaking
{
event RecordRewardForDelegatorsCall(
event RecordStakingPoolRewards(
bytes32 poolId,
uint256 membersReward,
uint256 membersStake
);
event RecordDepositInRewardVaultForCall(
bytes32 poolId,
uint256 totalReward,
bool operatorOnly
event DepositStakingPoolRewards(
uint256 operatorReward,
uint256 membersReward
);
event DepositIntoStakingPoolRewardVaultCall(
uint256 amount
);
address payable private _rewardReceiver;
address payable private _operatorRewardsReceiver;
address payable private _membersRewardsReceiver;
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.
constructor(address payable rewardReceiver) public {
_rewardReceiver = rewardReceiver;
init();
constructor(
address payable operatorRewardsReceiver,
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.
@@ -82,9 +89,15 @@ contract TestFinalizer is
/// @dev Expose `_finalizePool()`
function internalFinalizePool(bytes32 poolId)
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.
@@ -143,9 +156,12 @@ contract TestFinalizer is
function internalGetUnfinalizedPoolRewards(bytes32 poolId)
external
view
returns (IStructs.PoolRewards memory rewards)
returns (
uint256 totalReward,
uint256 membersStake
)
{
rewards = _getUnfinalizedPoolRewards(poolId);
(totalReward, membersStake) = _getUnfinalizedPoolRewards(poolId);
}
/// @dev Expose `_getActivePoolFromEpoch`.
@@ -157,68 +173,33 @@ contract TestFinalizer is
pool = _getActivePoolFromEpoch(epoch, poolId);
}
/// @dev Overridden to just store inputs.
function _recordRewardForDelegators(
/// @dev Overridden to log and do some basic math.
function _recordStakingPoolRewards(
bytes32 poolId,
uint256 membersReward,
uint256 reward,
uint256 membersStake
)
internal
returns (uint256 operatorReward, uint256 membersReward)
{
emit RecordRewardForDelegatorsCall(
(operatorReward, membersReward) = _splitReward(poolId, reward);
emit RecordStakingPoolRewards(
poolId,
membersReward,
reward,
membersStake
);
}
/// @dev Overridden to store inputs and do some really basic math.
function _depositIntoStakingPoolRewardVault(uint256 amount) internal {
emit DepositIntoStakingPoolRewardVaultCall(amount);
_rewardReceiver.transfer(amount);
}
/// @dev Overridden to store inputs and do some really basic math.
function _recordDepositInRewardVaultFor(
bytes32 poolId,
uint256 totalReward,
bool operatorOnly
/// @dev Overridden to log and transfer to receivers.
function _depositStakingPoolRewards(
uint256 operatorReward,
uint256 membersReward
)
internal
returns (
uint256 operatorPortion,
uint256 membersPortion
)
{
emit RecordDepositInRewardVaultForCall(
poolId,
totalReward,
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;
emit DepositStakingPoolRewards(operatorReward, membersReward);
address(_operatorRewardsReceiver).transfer(operatorReward);
address(_membersRewardsReceiver).transfer(operatorReward);
}
/// @dev Overriden to just increase the epoch counter.
@@ -229,4 +210,26 @@ contract TestFinalizer is
// solhint-disable no-empty-blocks
/// @dev Overridden to do nothing.
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
});
}
/// @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() }
slot := add(slot, 1)
if sub(activePoolsByEpoch_slot, slot) { revertIncorrectStorageSlot() }
if sub(_activePoolsByEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
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.
* -----------------------------------------------------------------------------
*/
import { ContractArtifact } from "ethereum-types";
import { ContractArtifact } from 'ethereum-types';
import * as EthVault from '../generated-artifacts/EthVault.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 TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.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 TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
@@ -103,6 +105,8 @@ export const artifacts = {
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
TestDelegatorRewards: TestDelegatorRewards as ContractArtifact,
TestFinalizer: TestFinalizer as ContractArtifact,
TestInitTarget: TestInitTarget as ContractArtifact,
TestLibFixedMath: TestLibFixedMath 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_cobb_douglas';
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_lib_fixed_math';
export * from '../generated-wrappers/test_lib_proxy';

View File

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

View File

@@ -9,8 +9,6 @@ library LibFractions {
/// @dev Maximum value for addition result components.
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`
/// @param n1 numerator of `1`
@@ -46,8 +44,10 @@ library LibFractions {
// If either the numerator or the denominator are > RESCALE_THRESHOLD,
// re-scale them to prevent overflows in future operations.
if (numerator > RESCALE_THRESHOLD || denominator > RESCALE_THRESHOLD) {
numerator = numerator.safeDiv(RESCALE_BASE);
denominator = denominator.safeDiv(RESCALE_BASE);
uint256 rescaleBase = numerator >= denominator ? numerator : denominator;
rescaleBase /= RESCALE_THRESHOLD;
numerator = numerator.safeDiv(rescaleBase);
denominator = denominator.safeDiv(rescaleBase);
}
}

View File

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