@0x/contracts-staking: Remove IStructs.CumulativeRewardInfo, etc.

`@0x/contracts-staking`: Convert all rewards to WETH.
`@0x/contracts-staking`: Style changes.
`@0x/contracts-staking`: Address misc. review comments.
`@0x/contracts-staking`: Make `LibFractions` scaling a separate step.
This commit is contained in:
Lawrence Forman
2019-09-21 03:42:38 -04:00
parent 14c4491b8c
commit 3ad7728a0e
31 changed files with 504 additions and 623 deletions

View File

@@ -23,8 +23,6 @@ import "./libs/LibProxy.sol";
contract ReadOnlyProxy is
MixinConstants,
Ownable,
MixinStorage
{
using LibProxy for address;

View File

@@ -37,17 +37,17 @@ contract Staking is
MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager,
MixinParams,
MixinScheduler,
MixinParams,
MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards,
MixinFinalizer,
MixinStakingPool,
MixinStake,
MixinExchangeFees,
MixinFinalizer
MixinExchangeFees
{
// this contract can receive ETH
// solhint-disable no-empty-blocks

View File

@@ -75,7 +75,7 @@ contract StakingProxy is
/// @dev Attach a staking contract; future calls will be delegated to the staking contract.
/// Note that this is callable only by this contract's owner.
/// @param _stakingContract Address of staking contract.
/// @param _stakingContract Address of staking contract.
/// @param _wethProxyAddress The address that can transfer WETH for fees.
/// Use address in storage if NIL_ADDRESS is passed in.
/// @param _ethVaultAddress Address of the EthVault contract.
@@ -209,6 +209,14 @@ contract StakingProxy is
));
}
// Minimum stake must be > 1
if (minimumStake < 2) {
LibRichErrors.rrevert(
LibStakingRichErrors.InvalidParamValueError(
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidMinimumPoolStake
));
}
// ERC20Proxy and Vault contract addresses must always be initialized
if (address(wethAssetProxy) == NIL_ADDRESS) {
LibRichErrors.rrevert(

View File

@@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
@@ -27,22 +28,11 @@ import "../libs/LibCobbDouglas.sol";
import "../immutable/MixinDeploymentConstants.sol";
import "../interfaces/IStructs.sol";
import "../stake/MixinStakeBalances.sol";
import "../sys/MixinAbstract.sol";
import "../sys/MixinFinalizer.sol";
import "../staking_pools/MixinStakingPool.sol";
import "./MixinExchangeManager.sol";
/// @dev This mixin contains the logic for 0x protocol fees.
/// Protocol fees are sent by 0x exchanges every time there is a trade.
/// If the maker has associated their address with a pool (see
/// MixinStakingPool.sol), then the fee will be attributed to their pool.
/// At the end of an epoch the maker and their pool will receive a rebate
/// that is proportional to (i) the fee volume attributed to their pool
/// over the epoch, and (ii) the amount of stake provided by the maker and
/// their delegators. Note that delegated stake (see MixinStake) is
/// weighted less than stake provided by directly by the maker; this is a
/// disincentive for market makers to monopolize a single pool that they
/// all delegate to.
contract MixinExchangeFees is
IStakingEvents,
MixinAbstract,
@@ -58,6 +48,7 @@ contract MixinExchangeFees is
MixinStakeBalances,
MixinCumulativeRewards,
MixinStakingPoolRewards,
MixinFinalizer,
MixinStakingPool
{
using LibSafeMath for uint256;
@@ -70,9 +61,7 @@ contract MixinExchangeFees is
/// @param protocolFeePaid The protocol fee that should be paid.
function payProtocolFee(
address makerAddress,
// solhint-disable-next-line
address payerAddress,
// solhint-disable-next-line
uint256 protocolFeePaid
)
external
@@ -108,9 +97,9 @@ contract MixinExchangeFees is
}
// Look up the pool for this epoch.
uint256 currentEpoch = currentEpoch;
uint256 currentEpoch_ = currentEpoch;
mapping (bytes32 => IStructs.ActivePool) storage activePoolsThisEpoch =
_getActivePoolsFromEpoch(currentEpoch);
_getActivePoolsFromEpoch(currentEpoch_);
IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId];
// If the pool was previously inactive in this epoch, initialize it.
@@ -128,32 +117,19 @@ contract MixinExchangeFees is
// Emit an event so keepers know what pools to pass into
// `finalize()`.
emit StakingPoolActivated(currentEpoch, poolId);
emit StakingPoolActivated(currentEpoch_, poolId);
}
// Credit the fees to the pool.
pool.feesCollected = pool.feesCollected.safeAdd(protocolFeePaid);
// Increase the total fees collected this epoch.
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(
protocolFeePaid
);
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(protocolFeePaid);
// Store the pool.
activePoolsThisEpoch[poolId] = pool;
}
/// @dev Returns the total amount of fees collected thus far, in the current
/// epoch.
/// @return _totalFeesCollectedThisEpoch Total fees collected this epoch.
function getTotalProtocolFeesThisEpoch()
external
view
returns (uint256 _totalFeesCollectedThisEpoch)
{
_totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch;
}
/// @dev Returns the total balance of this contract, including WETH.
/// @return totalBalance Total balance.
function getTotalBalance()
@@ -162,8 +138,9 @@ contract MixinExchangeFees is
returns (uint256 totalBalance)
{
totalBalance = address(this).balance.safeAdd(
IEtherToken(WETH_ADDRESS).balanceOf(address(this))
IEtherToken(_getWETHAddress()).balanceOf(address(this))
);
return totalBalance;
}
/// @dev Get information on an active staking pool in this epoch.
@@ -175,6 +152,7 @@ contract MixinExchangeFees is
returns (IStructs.ActivePool memory pool)
{
pool = _getActivePoolFromEpoch(currentEpoch, poolId);
return pool;
}
/// @dev Computes the members and weighted stake for a pool at the current
@@ -184,8 +162,8 @@ contract MixinExchangeFees is
/// @return membersStake Non-operator stake in the pool.
/// @return weightedStake Weighted stake of the pool.
function _computeMembersAndWeightedStake(
bytes32 poolId,
uint256 totalStake
bytes32 poolId,
uint256 totalStake
)
private
view
@@ -197,10 +175,13 @@ contract MixinExchangeFees is
).currentEpochBalance;
membersStake = totalStake.safeSub(operatorStake);
weightedStake = operatorStake.safeAdd(
membersStake
.safeMul(rewardDelegatedStakeWeight)
.safeDiv(PPM_DENOMINATOR)
LibMath.getPartialAmountFloor(
rewardDelegatedStakeWeight,
PPM_DENOMINATOR,
membersStake
)
);
return (membersStake, weightedStake);
}
/// @dev Checks that the protocol fee passed into `payProtocolFee()` is
@@ -211,21 +192,24 @@ contract MixinExchangeFees is
private
view
{
if (protocolFeePaid == 0 ||
(msg.value != protocolFeePaid && msg.value != 0)) {
LibRichErrors.rrevert(
LibStakingRichErrors.InvalidProtocolFeePaymentError(
protocolFeePaid == 0 ?
LibStakingRichErrors
.ProtocolFeePaymentErrorCodes
.ZeroProtocolFeePaid :
LibStakingRichErrors
.ProtocolFeePaymentErrorCodes
.MismatchedFeeAndPayment,
protocolFeePaid,
msg.value
)
);
if (protocolFeePaid != 0) {
return;
}
if (msg.value == protocolFeePaid || msg.value == 0) {
return;
}
LibRichErrors.rrevert(
LibStakingRichErrors.InvalidProtocolFeePaymentError(
protocolFeePaid == 0 ?
LibStakingRichErrors
.ProtocolFeePaymentErrorCodes
.ZeroProtocolFeePaid :
LibStakingRichErrors
.ProtocolFeePaymentErrorCodes
.MismatchedFeeAndPayment,
protocolFeePaid,
msg.value
)
);
}
}

View File

@@ -58,4 +58,15 @@ contract MixinDeploymentConstants {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidWethAssetDataError());
}
}
/// @dev An overridable way to access the deployed WETH address.
/// Must be view to allow overrides to access state.
/// @return wethAddress The address of the configured WETH contract.
function _getWETHAddress()
internal
view
returns (address wethAddress)
{
return WETH_ADDRESS;
}
}

View File

@@ -143,30 +143,13 @@ contract MixinStorage is
/// @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;
mapping (uint256 => mapping (bytes32 => IStructs.ActivePool)) internal _activePoolsByEpoch;
/// @dev Number of pools activated in the current epoch.
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 public unfinalizedRewardsAvailable;
/// @dev The number of active pools in the last epoch that have yet to be
/// finalized through `finalizePools()`.
uint256 public unfinalizedPoolsRemaining;
/// @dev The total fees collected for the epoch being finalized.
uint256 public unfinalizedTotalFeesCollected;
/// @dev The total fees collected for the epoch being finalized.
uint256 public unfinalizedTotalWeightedStake;
/// @dev How many rewards were paid at the end of finalization.
uint256 totalRewardsPaidLastEpoch;
/// @dev State for unfinalized rewards.
IStructs.UnfinalizedState public unfinalizedState;
/// @dev Adds owner as an authorized address.
constructor()

View File

@@ -42,29 +42,27 @@ interface IEthVault {
uint256 amount
);
/// @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.
/// @dev Deposit an `amount` of WETH for `owner` into the vault.
/// The staking contract should have granted the vault an allowance
/// because it will pull the WETH via `transferFrom()`.
/// Note that this is only callable by the staking contract.
/// @param owner Owner of the ETH.
/// @param owner Owner of the WETH.
/// @param amount Amount of deposit.
function recordDepositFor(address owner, uint256 amount)
function depositFor(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.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param amount of ETH to withdraw.
/// @dev Withdraw an `amount` of WETH to `msg.sender` from the vault.
/// @param amount of WETH to withdraw.
function withdraw(uint256 amount)
external;
/// @dev Withdraw ALL ETH to `msg.sender` from the vault.
/// @dev Withdraw ALL WETH to `msg.sender` from the vault.
function withdrawAll()
external
returns (uint256);
/// @dev Returns the balance in ETH of the `owner`
/// @return Balance in ETH.
/// @dev Returns the balance in WETH of the `owner`
/// @return Balance in WETH.
function balanceOf(address owner)
external
view

View File

@@ -23,40 +23,40 @@ pragma experimental ABIEncoderV2;
/// @dev This vault manages staking pool rewards.
interface IStakingPoolRewardVault {
/// @dev Emitted when Eth is deposited into the vault.
/// @dev Emitted when WETH is deposited into the vault.
/// @param sender Address of sender (`msg.sender`).
/// @param poolId that owns of Eth.
/// @param amount of Eth deposited.
/// @param poolId that owns of WETH.
/// @param amount of WETH deposited.
event EthDepositedIntoVault(
address indexed sender,
bytes32 indexed poolId,
uint256 amount
);
/// @dev Emitted when rewards are transferred out fo the vault.
/// @dev Emitted when rewards are transferred out of the vault.
/// @param poolId Unique Id of pool.
/// @param to Address to send funds to.
/// @param amount Amount of ETH to transfer.
/// @param amount Amount of WETH to transfer.
event PoolRewardTransferred(
bytes32 indexed poolId,
address to,
address indexed to,
uint256 amount
);
/// @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.
/// @dev Deposit an amount of WETH for `poolId` into the vault.
/// The staking contract should have granted the vault an allowance
/// because it will pull the WETH via `transferFrom()`.
/// Note that this is only callable by the staking contract.
/// @param poolId Pool that holds the ETH.
/// @param poolId Pool that holds the WETH.
/// @param amount Amount of deposit.
function recordDepositFor(bytes32 poolId, uint256 amount)
function depositFor(bytes32 poolId, uint256 amount)
external;
/// @dev Withdraw some amount in ETH from a pool.
/// @dev Withdraw some amount in WETH from a pool.
/// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool.
/// @param to Address to send funds to.
/// @param amount Amount of ETH to transfer.
/// @param amount Amount of WETH to transfer.
function transfer(
bytes32 poolId,
address payable to,
@@ -64,8 +64,8 @@ interface IStakingPoolRewardVault {
)
external;
/// @dev Returns the balance in ETH of `poolId`
/// @return Balance in ETH.
/// @dev Returns the balance in WETH of `poolId`
/// @return Balance in WETH.
function balanceOf(bytes32 poolId)
external
view

View File

@@ -32,6 +32,23 @@ interface IStructs {
uint256 membersStake;
}
/// @dev Holds state for unfinalized epoch rewards.
/// @param rewardsAvailable 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.
/// @param poolsRemaining The number of active pools in the last
/// epoch that have yet to be finalized through `finalizePools()`.
/// @param totalFeesCollected The total fees collected for the epoch being finalized.
/// @param totalWeightedStake The total fees collected for the epoch being finalized.
/// @param totalRewardsFinalized Amount of rewards that have been paid during finalization.
struct UnfinalizedState {
uint256 rewardsAvailable;
uint256 poolsRemaining;
uint256 totalFeesCollected;
uint256 totalWeightedStake;
uint256 totalRewardsFinalized;
}
/// @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`.
@@ -87,14 +104,6 @@ interface IStructs {
bool confirmed;
}
/// @dev Encapsulates the epoch and value of a cumulative reward.
/// @param cumulativeRewardEpoch Epoch of the reward.
/// @param cumulativeReward Value of the reward.
struct CumulativeRewardInfo {
uint256 cumulativeRewardEpoch;
IStructs.Fraction cumulativeReward;
}
/// @dev Holds the metadata for a staking pool.
/// @param initialized True iff the balance struct is initialized.
/// @param operator of the pool.

View File

@@ -41,7 +41,7 @@ library LibCobbDouglas {
/// @param alphaNumerator Numerator of `alpha` in the cobb-douglas function.
/// @param alphaDenominator Denominator of `alpha` in the cobb-douglas
/// function.
/// @return ownerRewards Rewards owned to the staking pool.
/// @return rewards Rewards owed to the staking pool.
function cobbDouglas(
uint256 totalRewards,
uint256 fees,
@@ -53,12 +53,12 @@ library LibCobbDouglas {
)
internal
pure
returns (uint256 ownerRewards)
returns (uint256 rewards)
{
int256 feeRatio = LibFixedMath.toFixed(fees, totalFees);
int256 stakeRatio = LibFixedMath.toFixed(stake, totalStake);
if (feeRatio == 0 || stakeRatio == 0) {
return ownerRewards = 0;
return rewards = 0;
}
// The cobb-doublas function has the form:
// `totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)`
@@ -93,6 +93,6 @@ library LibCobbDouglas {
LibFixedMath.mul(stakeRatio, n) :
LibFixedMath.div(stakeRatio, n);
// Multiply the above with totalRewards.
ownerRewards = LibFixedMath.uintMul(n, totalRewards);
rewards = LibFixedMath.uintMul(n, totalRewards);
}
}

View File

@@ -158,10 +158,8 @@ contract MixinStake is
: 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

View File

@@ -80,7 +80,7 @@ contract MixinCumulativeRewards is
if (_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] != 0) {
return false;
}
// Must not be the most recent CR.
// Must not be the most recently *stored* CR.
if (_cumulativeRewardsByPoolLastStored[poolId] == epoch) {
return false;
}
@@ -150,25 +150,6 @@ contract MixinCumulativeRewards is
delete _cumulativeRewardsByPool[poolId][epoch];
}
/// @dev Returns info on most recent cumulative reward.
function _getMostRecentCumulativeRewardInfo(bytes32 poolId)
internal
view
returns (IStructs.CumulativeRewardInfo memory)
{
// 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
return IStructs.CumulativeRewardInfo({
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.
@@ -219,14 +200,13 @@ contract MixinCumulativeRewards is
/// @dev Adds a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool.
/// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info for the most recent
/// cumulative reward (value and epoch)
/// @param mostRecentCumulativeReward The most recent cumulative reward.
/// @param isDependent True iff there is a dependency on the cumulative
/// reward for `poolId` at `epoch`
function _addOrRemoveDependencyOnCumulativeReward(
bytes32 poolId,
uint256 epoch,
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo,
IStructs.Fraction memory mostRecentCumulativeReward,
bool isDependent
)
internal
@@ -235,7 +215,7 @@ contract MixinCumulativeRewards is
_addDependencyOnCumulativeReward(
poolId,
epoch,
mostRecentCumulativeRewardInfo
mostRecentCumulativeReward
);
} else {
_removeDependencyOnCumulativeReward(
@@ -313,7 +293,7 @@ contract MixinCumulativeRewards is
}
// Compute reward
reward = LibFractions.scaleFractionalDifference(
reward = LibFractions.scaleDifference(
endReward.numerator,
endReward.denominator,
beginReward.numerator,
@@ -322,15 +302,26 @@ contract MixinCumulativeRewards is
);
}
/// @dev Fetch the most recent cumulative reward entry for a pool.
/// @param poolId Unique ID of pool.
/// @return cumulativeReward The most recent cumulative reward `poolId`.
function _getMostRecentCumulativeReward(bytes32 poolId)
internal
view
returns (IStructs.Fraction memory cumulativeReward)
{
uint256 lastStoredEpoch = _cumulativeRewardsByPoolLastStored[poolId];
return _cumulativeRewardsByPool[poolId][lastStoredEpoch];
}
/// @dev Adds a dependency on a cumulative reward for a given epoch.
/// @param poolId Unique Id of pool.
/// @param epoch Epoch to remove dependency from.
/// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative
/// reward.
/// @param mostRecentCumulativeReward The most recent cumulative reward.
function _addDependencyOnCumulativeReward(
bytes32 poolId,
uint256 epoch,
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo
IStructs.Fraction memory mostRecentCumulativeReward
)
private
{
@@ -342,7 +333,7 @@ contract MixinCumulativeRewards is
_trySetCumulativeReward(
poolId,
epoch,
mostRecentCumulativeRewardInfo.cumulativeReward
mostRecentCumulativeReward
);
}
@@ -356,10 +347,8 @@ contract MixinCumulativeRewards is
private
{
// Remove dependency by decreasing reference counter
uint256 newReferenceCounter =
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1);
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch] =
newReferenceCounter;
_cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1);
// Clear cumulative reward from state, if it is no longer needed
_tryUnsetCumulativeReward(poolId, epoch);

View File

@@ -84,11 +84,12 @@ contract MixinStakingPoolRewards is
(uint256 unfinalizedTotalRewards, uint256 unfinalizedMembersStake) =
_getUnfinalizedPoolRewards(poolId);
// Get the operators' portion.
(reward,) = _splitStakingPoolRewards(
(reward,) = _computeSplitStakingPoolRewards(
pool.operatorShare,
unfinalizedTotalRewards,
unfinalizedMembersStake
);
return reward;
}
/// @dev Computes the reward balance in ETH of a specific member of a pool.
@@ -106,12 +107,12 @@ contract MixinStakingPoolRewards is
(uint256 unfinalizedTotalRewards, uint256 unfinalizedMembersStake) =
_getUnfinalizedPoolRewards(poolId);
// Get the members' portion.
(, uint256 unfinalizedMembersReward) = _splitStakingPoolRewards(
(, uint256 unfinalizedMembersReward) = _computeSplitStakingPoolRewards(
pool.operatorShare,
unfinalizedTotalRewards,
unfinalizedMembersStake
);
reward = _computeRewardBalanceOfDelegator(
return _computeRewardBalanceOfDelegator(
poolId,
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]),
currentEpoch,
@@ -137,7 +138,7 @@ contract MixinStakingPoolRewards is
)
internal
{
// Rransfer any rewards from the transient pool vault to the eth vault;
// 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.
_transferDelegatorRewardsToEthVault(
@@ -165,17 +166,17 @@ contract MixinStakingPoolRewards is
}
/// @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()``.
/// This will split the reward between the operator and members,
/// depositing them into their respective vaults, and update the
/// accounting needed to allow members to withdraw their individual
/// rewards.
/// @param poolId Unique Id of pool.
/// @param reward received by the pool.
/// @param membersStake the amount of non-operator delegated stake that
/// will split the reward.
/// @return operatorReward
function _recordStakingPoolRewards(
/// @return operatorReward Portion of `reward` given to the pool operator.
/// @return membersReward Portion of `reward` given to the pool members.
function _depositStakingPoolRewards(
bytes32 poolId,
uint256 reward,
uint256 membersStake
@@ -186,71 +187,61 @@ contract MixinStakingPoolRewards is
IStructs.Pool memory pool = _poolById[poolId];
// Split the reward between operator and members
(operatorReward, membersReward) =
_splitStakingPoolRewards(pool.operatorShare, reward, membersStake);
// Record the operator's reward in the eth vault.
ethVault.recordDepositFor(pool.operator, operatorReward);
(operatorReward, membersReward) = _computeSplitStakingPoolRewards(
pool.operatorShare,
reward,
membersStake
);
// Deposit the operator's reward in the eth vault.
ethVault.depositFor(pool.operator, operatorReward);
if (membersReward == 0) {
return (operatorReward, membersReward);
return (0, 0);
}
// Record the members reward in the reward vault.
rewardVault.recordDepositFor(poolId, membersReward);
// Cache a storage pointer to the cumulative rewards for `poolId`
// indexed by epoch.
mapping (uint256 => IStructs.Fraction)
storage
_cumulativeRewardsByPoolPtr = _cumulativeRewardsByPool[poolId];
// Deposit the members' reward in the reward vault.
rewardVault.depositFor(poolId, membersReward);
// 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];
IStructs.Fraction memory mostRecentCumulativeReward =
_getMostRecentCumulativeReward(poolId);
// Compute new cumulative reward
(uint256 numerator, uint256 denominator) = LibFractions.addFractions(
mostRecentCumulativeRewards.numerator,
mostRecentCumulativeRewards.denominator,
membersReward,
membersStake
);
IStructs.Fraction memory cumulativeReward;
(cumulativeReward.numerator, cumulativeReward.denominator) =
LibFractions.add(
mostRecentCumulativeReward.numerator,
mostRecentCumulativeReward.denominator,
membersReward,
membersStake
);
// Normalize to prevent overflows.
(cumulativeReward.numerator, cumulativeReward.denominator) =
LibFractions.normalize(
cumulativeReward.numerator,
cumulativeReward.denominator
);
// store cumulative rewards and set most recent
// Store cumulative rewards for this epoch.
_forceSetCumulativeReward(
poolId,
currentEpoch,
IStructs.Fraction(numerator, denominator)
cumulativeReward
);
return (operatorReward, membersReward);
}
/// @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 Split a pool reward between the operator and members based on
/// the `operatorShare` and `membersStake`.
/// @dev Compute the split of 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(
/// @return operatorReward Portion of `totalReward` given to the pool operator.
/// @return membersReward Portion of `totalReward` given to the pool members.
function _computeSplitStakingPoolRewards(
uint32 operatorShare,
uint256 totalReward,
uint256 membersStake
@@ -269,6 +260,7 @@ contract MixinStakingPoolRewards is
);
membersReward = totalReward - operatorReward;
}
return (operatorReward, membersReward);
}
/// @dev Transfers a delegators accumulated rewards from the transient pool
@@ -286,7 +278,7 @@ contract MixinStakingPoolRewards is
private
{
// Ensure the pool is finalized.
_finalizePool(poolId);
finalizePool(poolId);
// Compute balance owed to delegator
uint256 balance = _computeRewardBalanceOfDelegator(
@@ -302,13 +294,10 @@ contract MixinStakingPoolRewards is
return;
}
// Transfer from transient Reward Pool vault to ETH Vault
ethVault.recordDepositFor(member, balance);
rewardVault.transfer(
poolId,
address(uint160(address(ethVault))),
balance
);
// Transfer from RewardVault to this contract.
rewardVault.transfer(poolId, address(uint160(address(this))), balance);
// Transfer to EthVault.
ethVault.depositFor(member, balance);
}
/// @dev Computes the reward balance in ETH of a specific member of a pool.
@@ -333,14 +322,14 @@ contract MixinStakingPoolRewards is
// There can be no rewards in epoch 0 because there is no delegated
// stake.
if (currentEpoch == 0) {
return reward = 0;
return 0;
}
// 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 (unsyncedStake.currentEpoch == currentEpoch) {
return reward = 0;
return 0;
}
// If there are unfinalized rewards this epoch, compute the member's
@@ -348,13 +337,15 @@ contract MixinStakingPoolRewards is
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 = unsyncedStake.currentEpoch >= currentEpoch - 1 ?
uint256 _stake = unsyncedStake.currentEpoch >= currentEpoch.safeSub(1) ?
unsyncedStake.currentEpochBalance :
unsyncedStake.nextEpochBalance;
if (_stake != 0) {
reward = _stake
.safeMul(unfinalizedMembersReward)
.safeDiv(unfinalizedMembersStake);
reward = LibMath.getPartialAmountFloor(
unfinalizedMembersReward,
unfinalizedMembersStake,
_stake
);
}
}
@@ -365,32 +356,30 @@ contract MixinStakingPoolRewards is
// If the stake has been touched since the last reward epoch,
// it has already been claimed.
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
return 0;
}
// From here we know: `unsyncedStake.currentEpoch < currentEpoch > 0`.
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
uint256 nextStakeEpoch = uint256(unsyncedStake.currentEpoch).safeAdd(1);
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.currentEpochBalance,
unsyncedStake.currentEpoch,
unsyncedStake.currentEpoch + 1
nextStakeEpoch
)
);
if (unsyncedStake.currentEpoch + 1 < lastRewardEpoch) {
if (nextStakeEpoch < lastRewardEpoch) {
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.nextEpochBalance,
unsyncedStake.currentEpoch + 1,
nextStakeEpoch,
lastRewardEpoch
)
);
}
return reward;
}
/// @dev Adds or removes cumulative reward dependencies for a delegator.
@@ -415,8 +404,8 @@ contract MixinStakingPoolRewards is
// Get the most recent cumulative reward, which will serve as a
// reference point when updating dependencies
IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo =
_getMostRecentCumulativeRewardInfo(poolId);
IStructs.Fraction memory mostRecentCumulativeReward =
_getMostRecentCumulativeReward(poolId);
// Record dependency on current epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0
@@ -425,7 +414,7 @@ contract MixinStakingPoolRewards is
_addOrRemoveDependencyOnCumulativeReward(
poolId,
_delegatedStakeToPoolByOwner.currentEpoch,
mostRecentCumulativeRewardInfo,
mostRecentCumulativeReward,
isDependent
);
}
@@ -435,7 +424,7 @@ contract MixinStakingPoolRewards is
_addOrRemoveDependencyOnCumulativeReward(
poolId,
uint256(_delegatedStakeToPoolByOwner.currentEpoch).safeAdd(1),
mostRecentCumulativeRewardInfo,
mostRecentCumulativeReward,
isDependent
);
}

View File

@@ -29,54 +29,29 @@ 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 operatorReward The reward owed to the pool operator.
/// @return totalReward The total reward owed to a pool.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _getUnfinalizedPoolRewards(bytes32 poolId)
internal
view
returns (
uint256 reward,
uint256 totalReward,
uint256 membersStake
);
/// @dev Get an active pool from an epoch by its ID.
/// @param epoch The epoch the pool was/will be active in.
/// @param poolId The ID of the pool.
/// @return pool The pool with ID `poolId` that was active in `epoch`.
function _getActivePoolFromEpoch(
uint256 epoch,
bytes32 poolId
)
internal
view
returns (IStructs.ActivePool memory pool);
/// @dev Get a mapping of active pools from an epoch.
/// This uses the formula `epoch % 2` as the epoch index in order
/// to reuse state, because we only need to remember, at most, two
/// epochs at once.
/// @return activePools The pools that were active in `epoch`.
function _getActivePoolsFromEpoch(
uint256 epoch
)
internal
view
returns (mapping (bytes32 => IStructs.ActivePool) storage activePools);
/// @dev Instantly finalizes a single pool that was active in the previous
/// epoch, crediting it rewards and sending those rewards to the reward
/// 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.
/// finalized or was not active in the previous epoch.
/// @param poolId The pool ID to finalize.
/// @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
function finalizePool(bytes32 poolId)
public
returns (
uint256 operatorReward,
uint256 membersReward,

View File

@@ -58,43 +58,41 @@ contract MixinFinalizer is
/// Throws if not enough time has passed between epochs or if the
/// previous epoch was not fully finalized.
/// If there were no active pools in the closing epoch, the epoch
/// will be instantly finalized here. Otherwise, `finalizePools()`
/// will be instantly finalized here. Otherwise, `finalizePool()`
/// should be called on each active pool afterwards.
/// @return _unfinalizedPoolsRemaining The number of unfinalized pools.
/// @return poolsRemaining The number of unfinalized pools.
function endEpoch()
external
returns (uint256 _unfinalizedPoolsRemaining)
returns (uint256 poolsRemaining)
{
uint256 closingEpoch = currentEpoch;
// Make sure the previous epoch has been fully finalized.
if (unfinalizedPoolsRemaining != 0) {
if (poolsRemaining != 0) {
LibRichErrors.rrevert(
LibStakingRichErrors.PreviousEpochNotFinalizedError(
closingEpoch.safeSub(1),
unfinalizedPoolsRemaining
poolsRemaining
)
);
}
// Unrwap any WETH protocol fees.
_unwrapWETH();
// Populate finalization state.
unfinalizedPoolsRemaining =
_unfinalizedPoolsRemaining = numActivePoolsThisEpoch;
unfinalizedRewardsAvailable = address(this).balance;
unfinalizedTotalFeesCollected = totalFeesCollectedThisEpoch;
unfinalizedTotalWeightedStake = totalWeightedStakeThisEpoch;
totalRewardsPaidLastEpoch = 0;
// Set up unfinalized state.
IStructs.UnfinalizedState memory state;
state.rewardsAvailable = _wrapBalanceToWETHAndGetBalance();
state.poolsRemaining = poolsRemaining = numActivePoolsThisEpoch;
state.totalFeesCollected = totalFeesCollectedThisEpoch;
state.totalWeightedStake = totalWeightedStakeThisEpoch;
state.totalRewardsFinalized = 0;
unfinalizedState = state;
// Emit an event.
emit EpochEnded(
closingEpoch,
unfinalizedPoolsRemaining,
unfinalizedRewardsAvailable,
unfinalizedTotalFeesCollected,
unfinalizedTotalWeightedStake
state.poolsRemaining,
state.rewardsAvailable,
state.totalFeesCollected,
state.totalWeightedStake
);
// Reset current epoch state.
@@ -106,91 +104,8 @@ contract MixinFinalizer is
_goToNextEpoch();
// If there were no active pools, the epoch is already finalized.
if (unfinalizedPoolsRemaining == 0) {
emit EpochFinalized(closingEpoch, 0, unfinalizedRewardsAvailable);
}
}
/// @dev Finalizes pools that were active in the previous epoch, paying out
/// 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.
/// @param poolIds List of active pool IDs to finalize.
/// @return _unfinalizedPoolsRemaining The number of unfinalized pools left.
function finalizePools(bytes32[] calldata poolIds)
external
returns (uint256 _unfinalizedPoolsRemaining)
{
uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return _unfinalizedPoolsRemaining = 0;
}
uint256 poolsRemaining = unfinalizedPoolsRemaining;
// If there are no more unfinalized pools remaining, there's nothing
// to do.
if (poolsRemaining == 0) {
return _unfinalizedPoolsRemaining = 0;
}
// Pointer to the active pools in the last epoch.
mapping(bytes32 => IStructs.ActivePool) storage activePools =
_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)
{
bytes32 poolId = poolIds[i];
IStructs.ActivePool memory pool = activePools[poolId];
// Ignore pools that aren't active.
if (pool.feesCollected == 0) {
continue;
}
(uint256 operatorReward, uint256 membersReward) =
_creditRewardsToPool(epoch, poolId, pool, rewardsPaid);
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);
}
// Update finalization states.
if (rewardsPaid != 0) {
totalRewardsPaidLastEpoch =
totalRewardsPaidLastEpoch.safeAdd(rewardsPaid);
}
unfinalizedPoolsRemaining = _unfinalizedPoolsRemaining = poolsRemaining;
// If there are no more unfinalized pools remaining, the epoch is
// finalized.
if (poolsRemaining == 0) {
emit EpochFinalized(
epoch - 1,
totalRewardsPaidLastEpoch,
unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
);
}
// Deposit all the rewards at once.
if (rewardsPaid != 0) {
_depositStakingPoolRewards(totalOperatorRewardsPaid, totalMembersRewardsPaid);
emit EpochFinalized(closingEpoch, 0, state.rewardsAvailable);
}
}
@@ -198,15 +113,14 @@ contract MixinFinalizer is
/// epoch, crediting it rewards and sending those rewards to the reward
/// 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.
/// finalized or was not active in the previous epoch.
/// @param poolId The pool ID to finalize.
/// @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
function finalizePool(bytes32 poolId)
public
returns (
uint256 operatorReward,
uint256 membersReward,
@@ -216,46 +130,62 @@ contract MixinFinalizer is
uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return (operatorReward, membersReward, membersStake);
return (0, 0, 0);
}
uint256 prevEpoch = epoch - 1;
// Load the finalization state into memory.
IStructs.UnfinalizedState memory state = unfinalizedState;
// If there are no more unfinalized pools remaining, there's nothing
// to do.
if (state.poolsRemaining == 0) {
return (0, 0, 0);
}
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
// Do nothing if the pool was not active (has no fees).
IStructs.ActivePool memory pool = _getActivePoolFromEpoch(prevEpoch, poolId);
// Do nothing if the pool was not active or already finalized (has no fees).
if (pool.feesCollected == 0) {
return (operatorReward, membersReward, membersStake);
}
(operatorReward, membersReward) =
_creditRewardsToPool(epoch, poolId, pool, 0);
(operatorReward, membersReward) = _creditRewardsToPool(
epoch,
poolId,
pool,
state
);
uint256 totalReward = operatorReward.safeAdd(membersReward);
if (totalReward > 0) {
totalRewardsPaidLastEpoch =
totalRewardsPaidLastEpoch.safeAdd(totalReward);
_depositStakingPoolRewards(operatorReward, membersReward);
// Increase `totalRewardsFinalized`.
unfinalizedState.totalRewardsFinalized =
state.totalRewardsFinalized =
state.totalRewardsFinalized.safeAdd(totalReward);
}
// Decrease the number of unfinalized pools left.
uint256 poolsRemaining = unfinalizedPoolsRemaining;
unfinalizedPoolsRemaining = poolsRemaining = poolsRemaining.safeSub(1);
unfinalizedState.poolsRemaining =
state.poolsRemaining =
state.poolsRemaining.safeSub(1);
// If there are no more unfinalized pools remaining, the epoch is
// finalized.
if (poolsRemaining == 0) {
if (state.poolsRemaining == 0) {
emit EpochFinalized(
epoch - 1,
totalRewardsPaidLastEpoch,
unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
prevEpoch,
state.totalRewardsFinalized,
state.rewardsAvailable.safeSub(state.totalRewardsFinalized)
);
}
membersStake = pool.membersStake;
return (operatorReward, membersReward, 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 operatorReward The reward owed to the pool operator.
/// @return totalReward The total reward owed to a pool.
/// @return membersStake The total stake for all non-operator members in
/// this pool.
function _getUnfinalizedPoolRewards(bytes32 poolId)
@@ -269,11 +199,10 @@ contract MixinFinalizer is
uint256 epoch = currentEpoch;
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return (reward, membersStake);
return (0, 0);
}
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
reward = _getUnfinalizedPoolRewards(pool, 0);
IStructs.ActivePool memory pool = _getActivePoolFromEpoch(epoch - 1, poolId);
reward = _getUnfinalizedPoolRewards(pool, unfinalizedState);
membersStake = pool.membersStake;
}
@@ -290,6 +219,7 @@ contract MixinFinalizer is
returns (IStructs.ActivePool memory pool)
{
pool = _getActivePoolsFromEpoch(epoch)[poolId];
return pool;
}
/// @dev Get a mapping of active pools from an epoch.
@@ -305,26 +235,32 @@ contract MixinFinalizer is
returns (mapping (bytes32 => IStructs.ActivePool) storage activePools)
{
activePools = _activePoolsByEpoch[epoch % 2];
return activePools;
}
/// @dev Converts the entire WETH balance of the contract into ETH.
function _unwrapWETH()
/// @dev Converts the entire ETH balance of the contract into WETH and
/// returns the total WETH balance of this contract.
/// @return The WETH balance of this contract.
function _wrapBalanceToWETHAndGetBalance()
internal
returns (uint256 balance)
{
uint256 wethBalance = IEtherToken(WETH_ADDRESS)
.balanceOf(address(this));
if (wethBalance != 0) {
IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
IEtherToken weth = IEtherToken(_getWETHAddress());
uint256 ethBalance = address(this).balance;
if (ethBalance != 0) {
weth.deposit.value((address(this).balance));
}
balance = weth.balanceOf(address(this));
return balance;
}
/// @dev Computes the reward owed to a pool during finalization.
/// @param pool The active pool.
/// @param unpaidRewards Rewards that have been credited but not finalized.
/// @param state The current state of finalization.
/// @return rewards Unfinalized rewards for this pool.
function _getUnfinalizedPoolRewards(
IStructs.ActivePool memory pool,
uint256 unpaidRewards
IStructs.UnfinalizedState memory state
)
private
view
@@ -333,27 +269,25 @@ contract MixinFinalizer is
// There can't be any rewards if the pool was active or if it has
// no stake.
if (pool.feesCollected == 0) {
return rewards = 0;
return rewards;
}
uint256 unfinalizedRewardsAvailable_ = unfinalizedRewardsAvailable;
// Use the cobb-douglas function to compute the total reward.
rewards = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable_,
rewards = LibCobbDouglas.cobbDouglas(
state.rewardsAvailable,
pool.feesCollected,
unfinalizedTotalFeesCollected,
state.totalFeesCollected,
pool.weightedStake,
unfinalizedTotalWeightedStake,
state.totalWeightedStake,
cobbDouglasAlphaNumerator,
cobbDouglasAlphaDenominator
);
// Clip the reward to always be under
// `unfinalizedRewardsAvailable - totalRewardsPaid - unpaidRewards`,
// `rewardsAvailable - totalRewardsPaid`,
// in case cobb-douglas overflows, which should be unlikely.
uint256 rewardsRemaining = unfinalizedRewardsAvailable_
.safeSub(totalRewardsPaidLastEpoch)
.safeSub(unpaidRewards);
uint256 rewardsRemaining =
state.rewardsAvailable.safeSub(state.totalRewardsFinalized);
if (rewardsRemaining < rewards) {
rewards = rewardsRemaining;
}
@@ -364,30 +298,29 @@ 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.
/// @param state The current state of finalization.
/// @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,
uint256 unpaidRewards
IStructs.UnfinalizedState memory state
)
private
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];
delete _getActivePoolsFromEpoch(epoch.safeSub(1))[poolId];
// Compute the rewards.
uint256 rewards = _getUnfinalizedPoolRewards(pool, unpaidRewards);
uint256 rewards = _getUnfinalizedPoolRewards(pool, state);
// Credit the pool.
// Pay the pool.
// Note that we credit at the CURRENT epoch even though these rewards
// were earned in the previous epoch.
(operatorReward, membersReward) = _recordStakingPoolRewards(
(operatorReward, membersReward) = _depositStakingPoolRewards(
poolId,
rewards,
pool.membersStake

View File

@@ -18,9 +18,11 @@
pragma solidity ^0.5.9;
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
import "../immutable/MixinStorage.sol";
import "../immutable/MixinDeploymentConstants.sol";
import "../interfaces/IStakingEvents.sol";
import "../interfaces/IEthVault.sol";
import "../interfaces/IStakingPoolRewardVault.sol";
@@ -31,6 +33,7 @@ import "../libs/LibStakingRichErrors.sol";
contract MixinParams is
IStakingEvents,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinStorage
{
@@ -194,6 +197,11 @@ contract MixinParams is
)
private
{
_transferWETHAllownces(
[address(ethVault), address(rewardVault)],
[_ethVaultAddress, _rewardVaultAddress]
);
epochDurationInSeconds = _epochDurationInSeconds;
rewardDelegatedStakeWeight = _rewardDelegatedStakeWeight;
minimumPoolStake = _minimumPoolStake;
@@ -218,4 +226,24 @@ contract MixinParams is
_zrxVaultAddress
);
}
/// @dev Rescind the WETH allowance for `oldSpenders` and grant `newSpenders`
/// an unlimited allowance.
/// @param oldSpenders Addresses to remove allowance from.
/// @param newSpenders Addresses to grant allowance to.
function _transferWETHAllownces(
address[2] memory oldSpenders,
address[2] memory newSpenders
)
private
{
IEtherToken weth = IEtherToken(_getWETHAddress());
// Grant new allowances.
for (uint256 i = 0; i < oldSpenders.length; i++) {
// Rescind old allowance.
weth.approve(oldSpenders[i], 0);
// Grant new allowance.
weth.approve(newSpenders[i], uint256(-1));
}
}
}

View File

@@ -18,52 +18,52 @@
pragma solidity ^0.5.9;
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../interfaces/IEthVault.sol";
import "../immutable/MixinDeploymentConstants.sol";
import "./MixinVaultCore.sol";
/// @dev This vault manages ETH.
/// @dev This vault manages WETH.
contract EthVault is
IEthVault,
IVaultCore,
MixinDeploymentConstants,
Ownable,
MixinVaultCore
{
using LibSafeMath for uint256;
// mapping from Owner to ETH balance
// mapping from Owner to WETH balance
mapping (address => uint256) internal _balances;
// solhint-disable no-empty-blocks
/// @dev Payable fallback for bulk-deposits.
function () 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.
/// @dev Deposit an `amount` of WETH for `owner` into the vault.
/// The staking contract should have granted the vault an allowance
/// because it will pull the WETH via `transferFrom()`.
/// Note that this is only callable by the staking contract.
/// @param owner Owner of the ETH.
/// @param owner Owner of the WETH.
/// @param amount Amount of deposit.
function recordDepositFor(address owner, uint256 amount)
function depositFor(address owner, uint256 amount)
external
onlyStakingProxy
{
// Transfer WETH from the staking contract into this contract.
IEtherToken(_getWETHAddress()).transferFrom(msg.sender, address(this), amount);
// Credit the owner.
_balances[owner] = _balances[owner].safeAdd(amount);
emit EthDepositedIntoVault(msg.sender, owner, amount);
}
/// @dev Withdraw an `amount` of ETH to `msg.sender` from 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 amount of ETH to withdraw.
/// @dev Withdraw an `amount` of WETH to `msg.sender` from the vault.
/// @param amount of WETH to withdraw.
function withdraw(uint256 amount)
external
{
_withdrawFrom(msg.sender, amount);
}
/// @dev Withdraw ALL ETH to `msg.sender` from the vault.
/// @dev Withdraw ALL WETH to `msg.sender` from the vault.
function withdrawAll()
external
returns (uint256 totalBalance)
@@ -72,13 +72,13 @@ contract EthVault is
address payable owner = msg.sender;
totalBalance = _balances[owner];
// withdraw ETH to owner
// withdraw WETH to owner
_withdrawFrom(owner, totalBalance);
return totalBalance;
}
/// @dev Returns the balance in ETH of the `owner`
/// @return Balance in ETH.
/// @dev Returns the balance in WETH of the `owner`
/// @return Balance in WETH.
function balanceOf(address owner)
external
view
@@ -87,21 +87,19 @@ contract EthVault is
return _balances[owner];
}
/// @dev Withdraw an `amount` of ETH to `owner` from the vault.
/// @param owner of ETH.
/// @param amount of ETH to withdraw.
/// @dev Withdraw an `amount` of WETH to `owner` from the vault.
/// @param owner of WETH.
/// @param amount of WETH to withdraw.
function _withdrawFrom(address payable owner, uint256 amount)
internal
{
// update balance
// note that this call will revert if trying to withdraw more
// than the current balance
//Uupdate balance.
_balances[owner] = _balances[owner].safeSub(amount);
// withdraw WETH to owner
IEtherToken(_getWETHAddress()).transfer(msg.sender, amount);
// notify
emit EthWithdrawnFromVault(msg.sender, owner, amount);
// withdraw ETH to owner
owner.transfer(amount);
}
}

View File

@@ -19,20 +19,21 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSafeDowncast.sol";
import "./MixinVaultCore.sol";
import "../interfaces/IStakingPoolRewardVault.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinDeploymentConstants.sol";
/// @dev This vault manages staking pool rewards.
contract StakingPoolRewardVault is
IStakingPoolRewardVault,
IVaultCore,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinVaultCore
{
@@ -41,29 +42,28 @@ contract StakingPoolRewardVault is
// mapping from poolId to Pool metadata
mapping (bytes32 => uint256) internal _balanceByPoolId;
// solhint-disable no-empty-blocks
/// @dev Payable fallback for bulk-deposits.
function () external payable {}
/// @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.
/// @dev Deposit an amount of WETH for `poolId` into the vault.
/// The staking contract should have granted the vault an allowance
/// because it will pull the WETH via `transferFrom()`.
/// Note that this is only callable by the staking contract.
/// @param poolId Pool that holds the ETH.
/// @param poolId Pool that holds the WETH.
/// @param amount Amount of deposit.
function recordDepositFor(bytes32 poolId, uint256 amount)
function depositFor(bytes32 poolId, uint256 amount)
external
onlyStakingProxy
{
// Transfer WETH from the staking contract into this contract.
IEtherToken(_getWETHAddress()).transferFrom(msg.sender, address(this), amount);
// Credit the pool.
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeAdd(amount);
emit EthDepositedIntoVault(msg.sender, poolId, amount);
}
/// @dev Withdraw some amount in ETH from a pool.
/// @dev Withdraw some amount in WETH from a pool.
/// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool.
/// @param to Address to send funds to.
/// @param amount Amount of ETH to transfer.
/// @param amount Amount of WETH to transfer.
function transfer(
bytes32 poolId,
address payable to,
@@ -73,7 +73,7 @@ contract StakingPoolRewardVault is
onlyStakingProxy
{
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeSub(amount);
to.transfer(amount);
IEtherToken(_getWETHAddress()).transfer(to, amount);
emit PoolRewardTransferred(
poolId,
to,
@@ -81,8 +81,8 @@ contract StakingPoolRewardVault is
);
}
/// @dev Returns the balance in ETH of `poolId`
/// @return Balance in ETH.
/// @dev Returns the balance in WETH of `poolId`
/// @return Balance in WETH.
function balanceOf(bytes32 poolId)
external
view

View File

@@ -94,8 +94,8 @@ contract TestDelegatorRewards is
_initGenesisCumulativeRewards(poolId);
}
/// @dev Expose/wrap `_recordStakingPoolRewards`.
function recordStakingPoolRewards(
/// @dev Expose/wrap `_depositStakingPoolRewards`.
function depositStakingPoolRewards(
bytes32 poolId,
address payable operatorAddress,
uint256 operatorReward,
@@ -109,7 +109,7 @@ contract TestDelegatorRewards is
_setOperatorShare(poolId, operatorReward, membersReward);
_initGenesisCumulativeRewards(poolId);
_recordStakingPoolRewards(
_depositStakingPoolRewards(
poolId,
operatorReward + membersReward,
membersStake
@@ -206,8 +206,8 @@ contract TestDelegatorRewards is
);
}
/// @dev `IEthVault.recordDepositFor()`,` overridden to just emit events.
function recordDepositFor(
/// @dev `IEthVault.depositFor()`,` overridden to just emit events.
function depositFor(
address owner,
uint256 amount
)
@@ -219,9 +219,9 @@ contract TestDelegatorRewards is
);
}
/// @dev `IStakingPoolRewardVault.recordDepositFor()`,`
/// @dev `IStakingPoolRewardVault.depositFor()`,`
/// overridden to just emit events.
function recordDepositFor(
function depositFor(
bytes32 poolId,
uint256 membersReward
)
@@ -252,7 +252,7 @@ contract TestDelegatorRewards is
uint256 totalRewards = reward.operatorReward + reward.membersReward;
membersStake = reward.membersStake;
(operatorReward, membersReward) =
_recordStakingPoolRewards(poolId, totalRewards, membersStake);
_depositStakingPoolRewards(poolId, totalRewards, membersStake);
emit FinalizePool(poolId, operatorReward, membersReward, membersStake);
}

View File

@@ -97,16 +97,6 @@ contract TestFinalizer is
numActivePoolsThisEpoch += 1;
}
/// @dev Expose `_finalizePool()`
function finalizePool(bytes32 poolId)
external
returns (FinalizedPoolRewards memory reward)
{
(reward.operatorReward,
reward.membersReward,
reward.membersStake) = _finalizePool(poolId);
}
/// @dev Drain the balance of this contract.
function drainBalance()
external
@@ -114,35 +104,6 @@ contract TestFinalizer is
address(0).transfer(address(this).balance);
}
/// @dev Get finalization-related state variables.
function getFinalizationState()
external
view
returns (
uint256 _balance,
uint256 _currentEpoch,
uint256 _closingEpoch,
uint256 _numActivePoolsThisEpoch,
uint256 _totalFeesCollectedThisEpoch,
uint256 _totalWeightedStakeThisEpoch,
uint256 _unfinalizedPoolsRemaining,
uint256 _unfinalizedRewardsAvailable,
uint256 _unfinalizedTotalFeesCollected,
uint256 _unfinalizedTotalWeightedStake
)
{
_balance = address(this).balance;
_currentEpoch = currentEpoch;
_closingEpoch = currentEpoch - 1;
_numActivePoolsThisEpoch = numActivePoolsThisEpoch;
_totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch;
_totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch;
_unfinalizedPoolsRemaining = unfinalizedPoolsRemaining;
_unfinalizedRewardsAvailable = unfinalizedRewardsAvailable;
_unfinalizedTotalFeesCollected = unfinalizedTotalFeesCollected;
_unfinalizedTotalWeightedStake = unfinalizedTotalWeightedStake;
}
/// @dev Compute Cobb-Douglas.
function cobbDouglas(
uint256 totalRewards,
@@ -155,7 +116,7 @@ contract TestFinalizer is
view
returns (uint256 ownerRewards)
{
ownerRewards = LibCobbDouglas._cobbDouglas(
ownerRewards = LibCobbDouglas.cobbDouglas(
totalRewards,
ownerFees,
totalFees,
@@ -185,8 +146,8 @@ contract TestFinalizer is
pool = _getActivePoolFromEpoch(epoch, poolId);
}
/// @dev Overridden to log and do some basic math.
function _recordStakingPoolRewards(
/// @dev Overridden to log and transfer to receivers.
function _depositStakingPoolRewards(
bytes32 poolId,
uint256 reward,
uint256 membersStake
@@ -194,52 +155,16 @@ contract TestFinalizer is
internal
returns (uint256 operatorReward, uint256 membersReward)
{
uint32 operatorShare = _operatorSharesByPool[poolId];
(operatorReward, membersReward) =
_splitReward(poolId, reward, membersStake);
emit RecordStakingPoolRewards(
poolId,
reward,
membersStake
);
}
/// @dev Overridden to log and transfer to receivers.
function _depositStakingPoolRewards(
uint256 operatorReward,
uint256 membersReward
)
internal
{
emit DepositStakingPoolRewards(operatorReward, membersReward);
_computeSplitStakingPoolRewards(operatorShare, reward, membersStake);
address(_operatorRewardsReceiver).transfer(operatorReward);
address(_membersRewardsReceiver).transfer(membersReward);
emit DepositStakingPoolRewards(operatorReward, membersReward);
}
/// @dev Overriden to just increase the epoch counter.
function _goToNextEpoch() internal {
currentEpoch += 1;
}
// 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,
uint256 membersStake
)
private
view
returns (uint256 operatorReward, uint256 membersReward)
{
uint32 operatorShare = _operatorSharesByPool[poolId];
(operatorReward, membersReward) = _splitStakingPoolRewards(
operatorShare,
amount,
membersStake
);
}
}

View File

@@ -25,6 +25,12 @@ import "../src/Staking.sol";
contract TestStaking is
Staking
{
address internal _wethAddress;
constructor(address wethAddress) public {
_wethAddress = wethAddress;
}
/// @dev Overridden to avoid hard-coded WETH.
function getTotalBalance()
external
@@ -34,9 +40,8 @@ contract TestStaking is
totalBalance = address(this).balance;
}
// Stub out `_unwrapWETH` to prevent the calls to `finalizeFees` from failing in tests
// that do not relate to protocol fee payments in WETH.
function _unwrapWETH()
internal
{} // solhint-disable-line no-empty-blocks
/// @dev Overridden to use _wethAddress;
function _getWETHAddress() internal view returns (address) {
return _wethAddress;
}
}

View File

@@ -135,19 +135,7 @@ contract TestStorageLayout is
if sub(numActivePoolsThisEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(unfinalizedRewardsAvailable_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(unfinalizedPoolsRemaining_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(unfinalizedTotalFeesCollected_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(unfinalizedTotalWeightedStake_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(totalRewardsPaidLastEpoch_slot, slot) { revertIncorrectStorageSlot() }
if sub(unfinalizedState_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
}
}