@0x/contracts-staking: Fully implement MBF (I hope).
				
					
				
			This commit is contained in:
		
				
					committed by
					
						
						Lawrence Forman
					
				
			
			
				
	
			
			
			
						parent
						
							94909f1a0f
						
					
				
				
					commit
					b57c0a2ebb
				
			@@ -62,19 +62,7 @@ contract MixinExchangeFees is
 | 
			
		||||
        payable
 | 
			
		||||
        onlyExchange
 | 
			
		||||
    {
 | 
			
		||||
        // If the protocol fee payment is invalid, revert with a rich error.
 | 
			
		||||
        if (
 | 
			
		||||
            protocolFeePaid == 0 ||
 | 
			
		||||
            (msg.value != protocolFeePaid && msg.value != 0)
 | 
			
		||||
        ) {
 | 
			
		||||
            LibRichErrors.rrevert(LibStakingRichErrors.InvalidProtocolFeePaymentError(
 | 
			
		||||
                protocolFeePaid == 0 ?
 | 
			
		||||
                    LibStakingRichErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid :
 | 
			
		||||
                    LibStakingRichErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
 | 
			
		||||
                protocolFeePaid,
 | 
			
		||||
                msg.value
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        _assertValidProtocolFee(protocolFeePaid);
 | 
			
		||||
 | 
			
		||||
        // Transfer the protocol fee to this address if it should be paid in WETH.
 | 
			
		||||
        if (msg.value == 0) {
 | 
			
		||||
@@ -88,25 +76,60 @@ contract MixinExchangeFees is
 | 
			
		||||
 | 
			
		||||
        // Get the pool id of the maker address.
 | 
			
		||||
        bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
 | 
			
		||||
 | 
			
		||||
        // Only attribute the protocol fee payment to a pool if the maker is registered to a pool.
 | 
			
		||||
        if (poolId != NIL_POOL_ID) {
 | 
			
		||||
            uint256 poolStake = getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
 | 
			
		||||
            // Ignore pools with dust stake.
 | 
			
		||||
            if (poolStake >= minimumPoolStake) {
 | 
			
		||||
                // Credit the pool.
 | 
			
		||||
                uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
                protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
 | 
			
		||||
                if (_feesCollectedThisEpoch == 0) {
 | 
			
		||||
                    activePoolsThisEpoch.push(poolId);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        // Only attribute the protocol fee payment to a pool if the maker is
 | 
			
		||||
        // registered to a pool.
 | 
			
		||||
        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.
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
        mapping (bytes32 => IStructs.ActivePool) activePoolsThisEpoch =
 | 
			
		||||
            activePoolsByEpoch[currentEpoch % 2];
 | 
			
		||||
        IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId]
 | 
			
		||||
        // If the pool was previously inactive in this epoch, initialize it.
 | 
			
		||||
        if (pool.feesCollected) {
 | 
			
		||||
            // Compute weighted stake.
 | 
			
		||||
            uint256 operatorStake = getStakeDelegatedToPoolByOwner(
 | 
			
		||||
                rewardVault.operatorOf(poolId),
 | 
			
		||||
                poolId
 | 
			
		||||
            ).currentEpochBalance;
 | 
			
		||||
            pool.weightedStake = operatorStake.safeAdd(
 | 
			
		||||
                totalStakeDelegatedToPool
 | 
			
		||||
                    .safeSub(operatorStake)
 | 
			
		||||
                    .safeMul(delegatedStakeWeight)
 | 
			
		||||
                    .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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Pays the rebates for to market making pool that was active this epoch,
 | 
			
		||||
    /// then updates the epoch and other time-based periods via the scheduler (see MixinScheduler).
 | 
			
		||||
    /// This is intentionally permissionless, and may be called by anyone.
 | 
			
		||||
    /// then updates the epoch and other time-based periods via the scheduler
 | 
			
		||||
    /// (see MixinScheduler). This is intentionally permissionless, and may be called by anyone.
 | 
			
		||||
    function finalizeFees()
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
@@ -140,15 +163,17 @@ contract MixinExchangeFees is
 | 
			
		||||
        return address(this).balance.safeAdd(wethBalance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Withdraws the entire WETH balance of the contract.
 | 
			
		||||
    function _unwrapWETH()
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));
 | 
			
		||||
 | 
			
		||||
        // Don't withdraw WETH if the WETH balance is zero as a gas optimization.
 | 
			
		||||
        if (wethBalance != 0) {
 | 
			
		||||
            IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
 | 
			
		||||
    /// @dev Checks that the protocol fee passed into `payProtocolFee()` is valid.
 | 
			
		||||
    /// @param protocolFeePaid The `protocolFeePaid` parameter to `payProtocolFee.`
 | 
			
		||||
    function _assertValidProtocolFee(uint256 protocolFeePaid) 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
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,12 +35,10 @@ import "./MixinExchangeManager.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/// @dev This mixin contains functions related to finalizing epochs.
 | 
			
		||||
///      Finalization occurs over multiple calls because we can only
 | 
			
		||||
///      discover the `totalRewardsPaid` to all pools by summing the
 | 
			
		||||
///      the reward function across all active pools at the end of an
 | 
			
		||||
///      epoch. Until this value is known for epoch `e`, we cannot finalize
 | 
			
		||||
///      epoch `e+1`, because the remaining balance (`balance - totalRewardsPaid`)
 | 
			
		||||
///      is the reward pool for finalizing the next epoch.
 | 
			
		||||
///      Finalization occurs AFTER the current epoch is ended/advanced and
 | 
			
		||||
///      over (potentially) multiple blocks/transactions. This pattern prevents
 | 
			
		||||
///      the contract from stalling while we finalize rewards for the previous
 | 
			
		||||
///      epoch.
 | 
			
		||||
contract MixinFinalizer is
 | 
			
		||||
    IStakingEvents,
 | 
			
		||||
    MixinConstants,
 | 
			
		||||
@@ -68,10 +66,12 @@ contract MixinFinalizer is
 | 
			
		||||
        // Make sure the previous epoch has been fully finalized.
 | 
			
		||||
        if (unfinalizedPoolsRemaining != 0) {
 | 
			
		||||
            LibRichErrors.rrevert(LibStakingRichErrors.PreviousEpochNotFinalized(
 | 
			
		||||
                closingEpoch.sub(1),
 | 
			
		||||
                closingEpoch.safeSub(1),
 | 
			
		||||
                unfinalizedPoolsRemaining
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        // Unrwap any WETH protocol fees.
 | 
			
		||||
        _unwrapWETH();
 | 
			
		||||
        // Populate finalization state.
 | 
			
		||||
        unfinalizedPoolsRemaining = numActivePoolsThisEpoch;
 | 
			
		||||
        unfinalizedRewardsAvailable = address(this).balance;
 | 
			
		||||
@@ -99,8 +99,8 @@ contract MixinFinalizer is
 | 
			
		||||
        return _unfinalizedPoolsRemaining = unfinalizedPoolsRemaining;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Finalizes a pool that was active in the previous epoch, paying out
 | 
			
		||||
    ///      its rewards to the reward vault. Keepers should call this function
 | 
			
		||||
    /// @dev Finalizes pools that were active in the previous epoch, paying out
 | 
			
		||||
    ///      rewards to the reward vault. Keepers should call this function
 | 
			
		||||
    ///      repeatedly until all active pools that were emitted in in a
 | 
			
		||||
    ///      `StakingPoolActivated` in the prior epoch have been finalized.
 | 
			
		||||
    ///      Pools that have already been finalized will be silently ignored.
 | 
			
		||||
@@ -113,7 +113,7 @@ contract MixinFinalizer is
 | 
			
		||||
        external
 | 
			
		||||
        returns (uint256 rewardsPaid, uint256 _unfinalizedPoolsRemaining)
 | 
			
		||||
    {
 | 
			
		||||
        uint256 epoch = getCurrentEpoch().sub(1);
 | 
			
		||||
        uint256 epoch = getCurrentEpoch().safeSub(1);
 | 
			
		||||
        uint256 poolsRemaining = unfinalizedPoolsRemaining;
 | 
			
		||||
        uint256 numPoolIds = poolIds.length;
 | 
			
		||||
        uint256 rewardsPaid = 0;
 | 
			
		||||
@@ -128,12 +128,12 @@ contract MixinFinalizer is
 | 
			
		||||
            if (pool.feesCollected != 0) {
 | 
			
		||||
                // Credit the pool with rewards.
 | 
			
		||||
                // We will transfer the total rewards to the vault at the end.
 | 
			
		||||
                rewardsPaid = rewardsPaid.add(_creditRewardsToPool(poolId, pool));
 | 
			
		||||
                rewardsPaid = rewardsPaid.safeAdd(_creditRewardsToPool(poolId, pool));
 | 
			
		||||
                // 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.sub(1);
 | 
			
		||||
                poolsRemaining = poolsRemaining.safeSub(1);
 | 
			
		||||
                // Emit an event.
 | 
			
		||||
                emit RewardsPaid(epoch, poolId, reward);
 | 
			
		||||
            }
 | 
			
		||||
@@ -141,7 +141,7 @@ contract MixinFinalizer is
 | 
			
		||||
        // Deposit all the rewards at once into the RewardVault.
 | 
			
		||||
        _depositIntoStakingPoolRewardVault(rewardsPaid);
 | 
			
		||||
        // Update finalization state.
 | 
			
		||||
        totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch.add(rewardsPaid);
 | 
			
		||||
        totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch.safeAdd(rewardsPaid);
 | 
			
		||||
        _unfinalizedPoolsRemaining = unfinalizedPoolsRemaining = poolsRemaining;
 | 
			
		||||
        // If there are no more unfinalized pools remaining, the epoch is
 | 
			
		||||
        // finalized.
 | 
			
		||||
@@ -149,7 +149,7 @@ contract MixinFinalizer is
 | 
			
		||||
            emit EpochFinalized(
 | 
			
		||||
                epoch,
 | 
			
		||||
                totalRewardsPaidLastEpoch,
 | 
			
		||||
                unfinalizedRewardsAvailable.sub(totalRewardsPaidLastEpoch)
 | 
			
		||||
                unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -190,12 +190,19 @@ contract MixinFinalizer is
 | 
			
		||||
            _recordRewardForDelegators(
 | 
			
		||||
                poolId,
 | 
			
		||||
                membersPortionOfReward,
 | 
			
		||||
                pool.delegatedStake,
 | 
			
		||||
                epoch
 | 
			
		||||
                pool.delegatedStake
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Converts the entire WETH balance of the contract into ETH.
 | 
			
		||||
    function _unwrapWETH() private {
 | 
			
		||||
        uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));
 | 
			
		||||
        if (wethBalance != 0) {
 | 
			
		||||
            IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @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
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,12 @@ contract MixinStorage is
 | 
			
		||||
    // mapping from Owner to Amount of Withdrawable Stake
 | 
			
		||||
    mapping (address => uint256) internal _withdrawableStakeByOwner;
 | 
			
		||||
 | 
			
		||||
    // Mapping from Owner to Pool Id to epoch of the last rewards collected.
 | 
			
		||||
    // This is the last reward epoch for a pool that a delegator collected
 | 
			
		||||
    // rewards from. This is different from the epoch when the rewards were
 | 
			
		||||
    // collected This will always be `<= currentEpoch`.
 | 
			
		||||
    mapping (address => mapping (bytes32 => uint256)) internal lastCollectedRewardsEpochToPoolByOwner;
 | 
			
		||||
 | 
			
		||||
    // tracking Pool Id
 | 
			
		||||
    bytes32 public nextPoolId = INITIAL_POOL_ID;
 | 
			
		||||
 | 
			
		||||
@@ -90,12 +96,6 @@ contract MixinStorage is
 | 
			
		||||
    // current epoch start time
 | 
			
		||||
    uint256 public currentEpochStartTimeInSeconds;
 | 
			
		||||
 | 
			
		||||
    // fees collected this epoch
 | 
			
		||||
    mapping (bytes32 => uint256) public protocolFeesThisEpochByPool;
 | 
			
		||||
 | 
			
		||||
    // pools that were active in the current epoch
 | 
			
		||||
    bytes32[] public activePoolsThisEpoch;
 | 
			
		||||
 | 
			
		||||
    // mapping from Pool Id to Epoch to Reward Ratio
 | 
			
		||||
    mapping (bytes32 => mapping (uint256 => IStructs.Fraction)) internal _cumulativeRewardsByPool;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,15 @@ interface IStakingEvents {
 | 
			
		||||
        address exchangeAddress
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted by MixinExchangeFees when a pool pays protocol fees
 | 
			
		||||
    ///      for the first time in an epoch.
 | 
			
		||||
    /// @param epoch The epoch in which the pool was activated.
 | 
			
		||||
    /// @param poolId The ID of the pool.
 | 
			
		||||
    event StakingPoolActivated(
 | 
			
		||||
        uint256 epoch,
 | 
			
		||||
        bytes32 poolId
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted by MixinFinalizer when an epoch has ended.
 | 
			
		||||
    /// @param epoch The closing epoch.
 | 
			
		||||
    /// @param numActivePools Number of active pools in the closing epoch.
 | 
			
		||||
 
 | 
			
		||||
@@ -39,12 +39,14 @@ interface IStructs {
 | 
			
		||||
    /// @param isInitialized
 | 
			
		||||
    /// @param currentEpoch the current epoch
 | 
			
		||||
    /// @param currentEpochBalance balance in the current epoch.
 | 
			
		||||
    /// @param nextEpochBalance balance in the next 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.
 | 
			
		||||
 
 | 
			
		||||
@@ -116,8 +116,7 @@ contract MixinStakingPoolRewards is
 | 
			
		||||
    function _handleStakingPoolReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 reward,
 | 
			
		||||
        uint256 amountOfDelegatedStake,
 | 
			
		||||
        uint256 epoch
 | 
			
		||||
        uint256 amountOfDelegatedStake
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user