@0x/contracts-staking: Working on MBF v2
This commit is contained in:
committed by
Lawrence Forman
parent
712b2569e6
commit
06b4d241af
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
73
contracts/staking/contracts/src/sys/MixinAbstract.sol
Normal file
73
contracts/staking/contracts/src/sys/MixinAbstract.sol
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ contract StakingPoolRewardVault is
|
||||
function balanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
returns (uint256 balance)
|
||||
{
|
||||
return _balanceByPoolId[poolId];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user