@0x/contracts-staking: Working on MBF v2

This commit is contained in:
Lawrence Forman
2019-09-13 04:11:59 -04:00
committed by Lawrence Forman
parent 712b2569e6
commit 06b4d241af
12 changed files with 381 additions and 137 deletions

View File

@@ -25,28 +25,35 @@ import "../libs/LibStakingRichErrors.sol";
import "../libs/LibCobbDouglas.sol";
import "../immutable/MixinDeploymentConstants.sol";
import "../interfaces/IStructs.sol";
import "../stake/MixinStakeBalances.sol";
import "../sys/MixinAbstract.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.
/// 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
MixinDeploymentConstants,
MixinExchangeManager,
MixinAbstract,
MixinStakeBalances,
MixinStakingPool
{
using LibSafeMath for uint256;
/// @dev Pays a protocol fee in ETH or WETH.
/// Only a known 0x exchange can call this method. See (MixinExchangeManager).
/// Only a known 0x exchange can call this method. See
/// (MixinExchangeManager).
/// @param makerAddress The address of the order's maker.
/// @param payerAddress The address of the protocol fee payer.
/// @param protocolFeePaid The protocol fee that should be paid.
@@ -80,18 +87,19 @@ contract MixinExchangeFees is
if (poolId == NIL_POOL_ID) {
return;
}
uint256 poolStake = getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
// Ignore pools with dust stake.
if (poolStake < minimumPoolStake) {
return;
}
// Look up the pool for this epoch. The epoch index is `currentEpoch % 2`
// because we only need to remember state in the current epoch and the
// epoch prior.
// Look up the pool for this epoch.
uint256 currentEpoch = getCurrentEpoch();
mapping (bytes32 => IStructs.ActivePool) storage activePoolsThisEpoch =
activePoolsByEpoch[currentEpoch % 2];
_getActivePoolsFromEpoch(currentEpoch);
IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId];
// If the pool was previously inactive in this epoch, initialize it.
if (pool.feesCollected == 0) {
// Compute weighted stake.
@@ -105,23 +113,30 @@ contract MixinExchangeFees is
.safeMul(rewardDelegatedStakeWeight)
.safeDiv(PPM_DENOMINATOR)
);
// Compute delegated (non-operator) stake.
pool.delegatedStake = poolStake.safeSub(operatorStake);
// Increase the total weighted stake.
totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch.safeAdd(
pool.weightedStake
);
// Increase the numberof active pools.
numActivePoolsThisEpoch += 1;
// Emit an event so keepers know what pools to pass into `finalize()`.
emit StakingPoolActivated(currentEpoch, poolId);
}
// Credit the fees to the pool.
pool.feesCollected = protocolFeePaid;
// Increase the total fees collected this epoch.
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(
protocolFeePaid
);
// Store the pool.
activePoolsThisEpoch[poolId] = pool;
}
@@ -147,7 +162,8 @@ contract MixinExchangeFees is
// Look up the pool for this epoch. The epoch index is `currentEpoch % 2`
// because we only need to remember state in the current epoch and the
// epoch prior.
IStructs.ActivePool memory pool = activePoolsByEpoch[getCurrentEpoch() % 2][poolId];
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(getCurrentEpoch(), poolId);
feesCollected = pool.feesCollected;
}

View File

@@ -55,19 +55,19 @@ contract MixinStorage is
// (access using _loadAndSyncBalance or _loadUnsyncedBalance)
mapping (address => IStructs.StoredBalance) internal _activeStakeByOwner;
// mapping from Owner to Amount of Inactive Stake
// Mapping from Owner to Amount of Inactive Stake
// (access using _loadAndSyncBalance or _loadUnsyncedBalance)
mapping (address => IStructs.StoredBalance) internal _inactiveStakeByOwner;
// mapping from Owner to Amount Delegated
// Mapping from Owner to Amount Delegated
// (access using _loadAndSyncBalance or _loadUnsyncedBalance)
mapping (address => IStructs.StoredBalance) internal _delegatedStakeByOwner;
// mapping from Owner to Pool Id to Amount Delegated
// Mapping from Owner to Pool Id to Amount Delegated
// (access using _loadAndSyncBalance or _loadUnsyncedBalance)
mapping (address => mapping (bytes32 => IStructs.StoredBalance)) internal _delegatedStakeToPoolByOwner;
// mapping from Pool Id to Amount Delegated
// Mapping from Pool Id to Amount Delegated
// (access using _loadAndSyncBalance or _loadUnsyncedBalance)
mapping (bytes32 => IStructs.StoredBalance) internal _delegatedStakeByPoolId;
@@ -149,7 +149,9 @@ 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 internal numActivePoolsThisEpoch;

View File

@@ -32,6 +32,16 @@ interface IStructs {
uint256 delegatedStake;
}
/// @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`.
@@ -40,13 +50,11 @@ interface IStructs {
/// @param currentEpoch the current epoch
/// @param currentEpochBalance balance in the current epoch.
/// @param nextEpochBalance balance in `currentEpoch+1`.
/// @param prevEpochBalance balance in `currentEpoch-1`.
struct StoredBalance {
bool isInitialized;
uint32 currentEpoch;
uint96 currentEpochBalance;
uint96 nextEpochBalance;
uint96 prevEpochBalance;
}
/// @dev Balance struct for stake.

View File

@@ -162,7 +162,8 @@ contract MixinStakeBalances is
});
}
/// @dev Returns the total stake delegated to a specific staking pool, across all members.
/// @dev Returns the total stake delegated to a specific staking pool,
/// across all members.
/// @param poolId Unique Id of pool.
/// @return Total stake delegated to pool.
function getTotalStakeDelegatedToPool(bytes32 poolId)
@@ -179,9 +180,13 @@ contract MixinStakeBalances is
/// @dev Returns the stake that can be withdrawn for a given owner.
/// @param owner to query.
/// @param lastStoredWithdrawableStake The amount of withdrawable stake that was last stored.
/// @param lastStoredWithdrawableStake The amount of withdrawable stake
/// that was last stored.
/// @return Withdrawable stake for owner.
function _computeWithdrawableStake(address owner, uint256 lastStoredWithdrawableStake)
function _computeWithdrawableStake(
address owner,
uint256 lastStoredWithdrawableStake
)
internal
view
returns (uint256)
@@ -190,9 +195,15 @@ contract MixinStakeBalances is
// so the upper bound of withdrawable stake is always limited by the value of `next`.
IStructs.StoredBalance memory storedBalance = _loadUnsyncedBalance(_inactiveStakeByOwner[owner]);
if (storedBalance.currentEpoch == currentEpoch) {
return LibSafeMath.min256(storedBalance.nextEpochBalance, lastStoredWithdrawableStake);
return LibSafeMath.min256(
storedBalance.nextEpochBalance,
lastStoredWithdrawableStake
);
} else if (uint256(storedBalance.currentEpoch).safeAdd(1) == currentEpoch) {
return LibSafeMath.min256(storedBalance.nextEpochBalance, storedBalance.currentEpochBalance);
return LibSafeMath.min256(
storedBalance.nextEpochBalance,
storedBalance.currentEpochBalance
);
} else {
return storedBalance.nextEpochBalance;
}

View File

@@ -23,10 +23,12 @@ import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/LibFractions.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "./MixinCumulativeRewards.sol";
import "../sys/MixinAbstract.sol";
contract MixinStakingPoolRewards is
MixinCumulativeRewards
MixinCumulativeRewards,
MixinAbstract
{
using LibSafeMath for uint256;
@@ -59,7 +61,35 @@ contract MixinStakingPoolRewards is
function computeRewardBalanceOfDelegator(bytes32 poolId, address member)
public
view
returns (uint256 totalReward)
returns (uint256 reward)
{
IStructs.PoolRewards memory unfinalizedPoolReward =
_getUnfinalizedPoolReward(poolId);
reward = _computeRewardBalanceOfDelegator(
poolId,
member,
unfinalizedPoolReward.membersReward,
unfinalizedPoolReward.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,
@@ -144,12 +174,12 @@ contract MixinStakingPoolRewards is
// 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;
// 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];
// compute new cumulative reward
// Compute new cumulative reward
(uint256 numerator, uint256 denominator) = LibFractions.addFractions(
mostRecentCumulativeRewards.numerator,
mostRecentCumulativeRewards.denominator,
@@ -157,7 +187,8 @@ contract MixinStakingPoolRewards is
amountOfDelegatedStake
);
// normalize fraction components by dividing by the min token value (10^18)
// Normalize fraction components by dividing by the min token value
// (10^18)
(uint256 numeratorNormalized, uint256 denominatorNormalized) = (
numerator.safeDiv(MIN_TOKEN_VALUE),
denominator.safeDiv(MIN_TOKEN_VALUE)

View File

@@ -0,0 +1,73 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../interfaces/IStructs.sol";
/// @dev Exposes some internal functions from various contracts to avoid
/// cyclical dependencies.
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.
function _getUnfinalizedPoolReward(bytes32 poolId)
internal
view
returns (IStructs.PoolRewards memory rewards);
/// @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
/// vault. This can be called by internal functions that need to
/// finalize a pool immediately. Does nothing if the pool is already
/// finalized.
/// @param poolId The pool ID to finalize.
/// @return rewards Rewards.
/// @return rewards The rewards credited to the pool.
function _finalizePool(bytes32 poolIds)
internal
returns (IStructs.PoolRewards memory rewards);
}

View File

@@ -22,8 +22,8 @@ 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/LibCobbDouglas.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibFixedMath.sol";
import "../immutable/MixinStorage.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinDeploymentConstants.sol";
@@ -44,7 +44,6 @@ contract MixinFinalizer is
IStakingEvents,
MixinConstants,
MixinDeploymentConstants,
Ownable,
MixinStorage,
MixinStakingPoolRewardVault,
MixinScheduler,
@@ -65,21 +64,28 @@ contract MixinFinalizer is
returns (uint256 _unfinalizedPoolsRemaining)
{
uint256 closingEpoch = getCurrentEpoch();
// Make sure the previous epoch has been fully finalized.
if (unfinalizedPoolsRemaining != 0) {
LibRichErrors.rrevert(LibStakingRichErrors.PreviousEpochNotFinalizedError(
closingEpoch.safeSub(1),
unfinalizedPoolsRemaining
));
LibRichErrors.rrevert(
LibStakingRichErrors.PreviousEpochNotFinalizedError(
closingEpoch.safeSub(1),
unfinalizedPoolsRemaining
)
);
}
// Unrwap any WETH protocol fees.
_unwrapWETH();
// Populate finalization state.
unfinalizedPoolsRemaining = numActivePoolsThisEpoch;
unfinalizedPoolsRemaining =
_unfinalizedPoolsRemaining = numActivePoolsThisEpoch;
unfinalizedRewardsAvailable = address(this).balance;
unfinalizedTotalFeesCollected = totalFeesCollectedThisEpoch;
unfinalizedTotalWeightedStake = totalWeightedStakeThisEpoch;
totalRewardsPaidLastEpoch = 0;
// Emit an event.
emit EpochEnded(
closingEpoch,
@@ -88,17 +94,19 @@ contract MixinFinalizer is
unfinalizedTotalFeesCollected,
unfinalizedTotalWeightedStake
);
// Reset current epoch state.
totalFeesCollectedThisEpoch = 0;
totalWeightedStakeThisEpoch = 0;
numActivePoolsThisEpoch = 0;
// Advance the epoch. This will revert if not enough time has passed.
_goToNextEpoch();
// If there were no active pools, the epoch is already finalized.
if (unfinalizedPoolsRemaining == 0) {
emit EpochFinalized(closingEpoch, 0, unfinalizedRewardsAvailable);
}
return _unfinalizedPoolsRemaining = unfinalizedPoolsRemaining;
}
/// @dev Finalizes pools that were active in the previous epoch, paying out
@@ -109,44 +117,121 @@ contract MixinFinalizer is
/// We deliberately try not to revert here in case multiple parties
/// are finalizing pools.
/// @param poolIds List of active pool IDs to finalize.
/// @return rewardsPaid Total rewards paid to the pools passed in.
/// @return _unfinalizedPoolsRemaining The number of unfinalized pools left.
function finalizePools(bytes32[] calldata poolIds)
external
returns (uint256 rewardsPaid, uint256 _unfinalizedPoolsRemaining)
returns (_unfinalizedPoolsRemaining)
{
uint256 epoch = getCurrentEpoch();
uint256 priorEpoch = epoch.safeSub(1);
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return;
}
uint256 poolsRemaining = unfinalizedPoolsRemaining;
// If there are no more unfinalized pools remaining, there's nothing
// to do.
if (poolsRemaining == 0) {
return;
}
// 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;
// Pointer to the active pools in the last epoch.
// We use `(currentEpoch - 1) % 2` as the index to reuse state.
mapping(bytes32 => IStructs.ActivePool) storage activePools =
activePoolsByEpoch[priorEpoch % 2];
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];
// Ignore pools that aren't active.
if (pool.feesCollected != 0) {
// Credit the pool with rewards.
// We will transfer the total rewards to the vault at the end.
uint256 reward = _creditRewardToPool(poolId, pool);
rewardsPaid = rewardsPaid.safeAdd(reward);
// Clear the pool state so we don't finalize it again,
// and to recoup some gas.
activePools[poolId] = IStructs.ActivePool(0, 0, 0);
// Decrease the number of unfinalized pools left.
poolsRemaining = poolsRemaining.safeSub(1);
// Emit an event.
emit RewardsPaid(epoch, poolId, reward);
if (pool.feesCollected == 0) {
continue;
}
// Clear the pool state so we don't finalize it again, and to
// recoup some gas.
delete activePools[poolId];
// Credit the pool with rewards.
// We will transfer the total rewards to the vault at the end.
uint256 reward = _creditRewardToPool(poolId, pool);
rewardsPaid = rewardsPaid.safeAdd(reward);
// Decrease the number of unfinalized pools left.
poolsRemaining = poolsRemaining.safeSub(1);
// Emit an event.
emit RewardsPaid(epoch, poolId, reward);
}
// Deposit all the rewards at once into the RewardVault.
_depositIntoStakingPoolRewardVault(rewardsPaid);
// Update finalization state.
totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch.safeAdd(rewardsPaid);
_unfinalizedPoolsRemaining = unfinalizedPoolsRemaining = poolsRemaining;
// Update finalization states.
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)
);
}
}
/// @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
/// finalized.
/// @param poolId The pool ID to finalize.
/// @return rewards Rewards.
/// @return rewards The rewards credited to the pool.
function _finalizePool(bytes32 poolId)
internal
returns (IStructs.PoolRewards memory rewards)
{
uint256 epoch = getCurrentEpoch();
// There are no pools to finalize at epoch 0.
if (epoch == 0) {
return;
}
// Get the active pool.
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
// Ignore pools that weren't active.
if (pool.feesCollected == 0) {
return;
}
// Clear the pool state so we don't finalize it again, and to recoup
// some gas.
delete activePools[poolId];
// Credit the pool with rewards.
// We will transfer the total rewards to the vault at the end.
rewards = _creditRewardToPool(poolId, pool);
totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch.safeAdd(reward);
// Decrease the number of unfinalized pools left.
uint256 poolsRemaining =
unfinalizedPoolsRemaining =
unfinalizedPoolsRemaining.safeSub(1);
// Emit an event.
emit RewardsPaid(epoch, poolId, reward);
// Deposit all the rewards at once into the RewardVault.
_depositIntoStakingPoolRewardVault(rewardsPaid);
// If there are no more unfinalized pools remaining, the epoch is
// finalized.
if (poolsRemaining == 0) {
@@ -158,69 +243,72 @@ contract MixinFinalizer is
}
}
/// @dev The cobb-douglas function used to compute fee-based rewards for
/// staking pools in a given epoch. Note that in this function there
/// is no limitation on alpha; we tend to get better rounding on the
/// simplified versions below.
/// @param totalRewards collected over an epoch.
/// @param ownerFees Fees attributed to the owner of the staking pool.
/// @param totalFees collected across all active staking pools in the epoch.
/// @param ownerStake Stake attributed to the owner of the staking pool.
/// @param totalStake collected across all active staking pools in the epoch.
/// @param alphaNumerator Numerator of `alpha` in the cobb-dougles function.
/// @param alphaDenominator Denominator of `alpha` in the cobb-douglas function.
/// @return ownerRewards Rewards for the owner.
function _cobbDouglas(
uint256 totalRewards,
uint256 ownerFees,
uint256 totalFees,
uint256 ownerStake,
uint256 totalStake,
uint256 alphaNumerator,
uint256 alphaDenominator
/// @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
pure
returns (uint256 ownerRewards)
view
returns (IStructs.ActivePool memory pool)
{
int256 feeRatio = LibFixedMath._toFixed(ownerFees, totalFees);
int256 stakeRatio = LibFixedMath._toFixed(ownerStake, totalStake);
if (feeRatio == 0 || stakeRatio == 0) {
return ownerRewards = 0;
}
// The cobb-doublas function has the form:
// `totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)`
// This is equivalent to:
// `totalRewards * stakeRatio * e^(alpha * (ln(feeRatio / stakeRatio)))`
// However, because `ln(x)` has the domain of `0 < x < 1`
// and `exp(x)` has the domain of `x < 0`,
// and fixed-point math easily overflows with multiplication,
// we will choose the following if `stakeRatio > feeRatio`:
// `totalRewards * stakeRatio / e^(alpha * (ln(stakeRatio / feeRatio)))`
pool = _getActivePoolFromEpoch(epoch)[poolId];
}
// Compute
// `e^(alpha * (ln(feeRatio/stakeRatio)))` if feeRatio <= stakeRatio
// or
// `e^(ln(stakeRatio/feeRatio))` if feeRatio > stakeRatio
int256 n = feeRatio <= stakeRatio ?
LibFixedMath._div(feeRatio, stakeRatio) :
LibFixedMath._div(stakeRatio, feeRatio);
n = LibFixedMath._exp(
LibFixedMath._mulDiv(
LibFixedMath._ln(n),
int256(alphaNumerator),
int256(alphaDenominator)
)
/// @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)
{
activePools = activePoolsByEpoch[epoch % 2];
}
/// @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.
function _getUnfinalizedPoolReward(bytes32 poolId)
internal
view
returns (IStructs.PoolRewards memory rewards)
{
uint256 epoch = getCurrentEpoch();
// There can't be any rewards in the first epoch.
if (epoch == 0) {
return;
}
IStructs.ActivePool memory pool =
_getActivePoolFromEpoch(epoch - 1, poolId);
// Use the cobb-douglas function to compute the total reward.
totalReward = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable,
pool.feesCollected,
unfinalizedTotalFeesCollected,
pool.weightedStake,
unfinalizedTotalWeightedStake,
cobbDouglasAlphaNumerator,
cobbDouglasAlphaDenomintor
);
// Compute
// `totalRewards * n` if feeRatio <= stakeRatio
// or
// `totalRewards / n` if stakeRatio > feeRatio
n = feeRatio <= stakeRatio ?
LibFixedMath._mul(stakeRatio, n) :
LibFixedMath._div(stakeRatio, n);
// Multiply the above with totalRewards.
ownerRewards = LibFixedMath._uintMul(n, totalRewards);
// Split the reward between the operator and delegators.
(rewards.operatorReward, rewards.membersReward) =
rewardVault.splitAmountBetweenOperatorAndMembers(
poolId,
totalReward
);
rewards.delegatedStake = pool.delegatedStake;
}
/// @dev Computes the reward owed to a pool during finalization and
@@ -233,10 +321,10 @@ contract MixinFinalizer is
IStructs.ActivePool memory pool
)
private
returns (uint256 reward)
returns (PoolRewards memory rewards)
{
// Use the cobb-douglas function to compute the reward.
reward = _cobbDouglas(
// Use the cobb-douglas function to compute the total reward.
totalReward = LibCobbDouglas._cobbDouglas(
unfinalizedRewardsAvailable,
pool.feesCollected,
unfinalizedTotalFeesCollected,
@@ -245,13 +333,17 @@ contract MixinFinalizer is
cobbDouglasAlphaNumerator,
cobbDouglasAlphaDenomintor
);
// Credit the pool the reward in the RewardVault.
(, uint256 membersPortionOfReward) = rewardVault.recordDepositFor(
poolId,
reward,
// If no delegated stake, all rewards go to the operator.
pool.delegatedStake == 0
);
(rewards.operatorReward, rewards.membersReward) =
rewardVault.recordDepositFor(
poolId,
reward,
// If no delegated stake, all rewards go to the operator.
pool.delegatedStake == 0
);
rewards.delegatedStake = pool.delegatedStake;
// Sync delegator rewards.
if (membersPortionOfReward != 0) {
_recordRewardForDelegators(
@@ -264,10 +356,10 @@ contract MixinFinalizer is
/// @dev Converts the entire WETH balance of the contract into ETH.
function _unwrapWETH() private {
uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));
uint256 wethBalance = IEtherToken(WETH_ADDRESS)
.balanceOf(address(this));
if (wethBalance != 0) {
IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
}
}
}

View File

@@ -81,7 +81,7 @@ contract StakingPoolRewardVault is
function balanceOf(bytes32 poolId)
external
view
returns (uint256)
returns (uint256 balance)
{
return _balanceByPoolId[poolId];
}

View File

@@ -22,6 +22,7 @@ import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRich
import * as LibProxy from '../generated-artifacts/LibProxy.json';
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
import * as MixinAbstract from '../generated-artifacts/MixinAbstract.json';
import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
import * as MixinCumulativeRewards from '../generated-artifacts/MixinCumulativeRewards.json';
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
@@ -64,7 +65,6 @@ export const artifacts = {
StakingProxy: StakingProxy as ContractArtifact,
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
MixinFinalizer: MixinFinalizer as ContractArtifact,
MixinConstants: MixinConstants as ContractArtifact,
MixinDeploymentConstants: MixinDeploymentConstants as ContractArtifact,
MixinStorage: MixinStorage as ContractArtifact,
@@ -92,6 +92,8 @@ export const artifacts = {
MixinStakingPoolMakers: MixinStakingPoolMakers as ContractArtifact,
MixinStakingPoolModifiers: MixinStakingPoolModifiers as ContractArtifact,
MixinStakingPoolRewards: MixinStakingPoolRewards as ContractArtifact,
MixinAbstract: MixinAbstract as ContractArtifact,
MixinFinalizer: MixinFinalizer as ContractArtifact,
MixinParams: MixinParams as ContractArtifact,
MixinScheduler: MixinScheduler as ContractArtifact,
EthVault: EthVault as ContractArtifact,

View File

@@ -20,6 +20,7 @@ export * from '../generated-wrappers/lib_fixed_math_rich_errors';
export * from '../generated-wrappers/lib_proxy';
export * from '../generated-wrappers/lib_safe_downcast';
export * from '../generated-wrappers/lib_staking_rich_errors';
export * from '../generated-wrappers/mixin_abstract';
export * from '../generated-wrappers/mixin_constants';
export * from '../generated-wrappers/mixin_cumulative_rewards';
export * from '../generated-wrappers/mixin_deployment_constants';

View File

@@ -20,6 +20,7 @@
"generated-artifacts/LibProxy.json",
"generated-artifacts/LibSafeDowncast.json",
"generated-artifacts/LibStakingRichErrors.json",
"generated-artifacts/MixinAbstract.json",
"generated-artifacts/MixinConstants.json",
"generated-artifacts/MixinCumulativeRewards.json",
"generated-artifacts/MixinDeploymentConstants.json",

View File

@@ -27,12 +27,16 @@ library LibFractions {
uint256 denominator
)
{
if (n1 == 0) {
return (numerator = n2, denominator = d2);
}
if (n2 == 0) {
return (numerator = n1, denominator = d1);
}
numerator = n1
.safeMul(d2)
.safeAdd(n2.safeMul(d1));
denominator = d1.safeMul(d2);
return (numerator, denominator);
}
/// @dev Safely scales the difference between two fractions.
@@ -53,14 +57,17 @@ library LibFractions {
pure
returns (uint256 result)
{
if (n2 == 0) {
return result = s
.safeMul(n1)
.safeDiv(d1);
}
uint256 numerator = n1
.safeMul(d2)
.safeSub(n2.safeMul(d1));
uint256 tmp = numerator.safeDiv(d2);
result = s
.safeMul(tmp)
.safeDiv(d1);
return result;
}
}