Documenting fees + rewards now use weighted stake in denominator of cobb douglas
This commit is contained in:
		@@ -31,6 +31,7 @@ contract StakingProxy is
 | 
			
		||||
    constructor(address _stakingContract)
 | 
			
		||||
        public
 | 
			
		||||
    {
 | 
			
		||||
        owner = msg.sender;
 | 
			
		||||
        stakingContract = _stakingContract;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,17 +18,17 @@
 | 
			
		||||
 | 
			
		||||
pragma solidity ^0.5.5;
 | 
			
		||||
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
 | 
			
		||||
import "../interfaces/IStakingEvents.sol";
 | 
			
		||||
import "../immutable/MixinConstants.sol";
 | 
			
		||||
import "../immutable/MixinStorage.sol";
 | 
			
		||||
import "./MixinOwnable.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract MixinExchangeManager is
 | 
			
		||||
    Authorizable,
 | 
			
		||||
    IStakingEvents,
 | 
			
		||||
    MixinConstants,
 | 
			
		||||
    MixinStorage
 | 
			
		||||
    MixinStorage,
 | 
			
		||||
    MixinOwnable
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /// @dev This mixin contains logic for managing exchanges.
 | 
			
		||||
@@ -73,8 +73,9 @@ contract MixinExchangeManager is
 | 
			
		||||
        emit ExchangeRemoved(addr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns true iff the address is a valid exchange
 | 
			
		||||
    /// @param addr Address of exchange contract
 | 
			
		||||
    /// @dev Returns true iff the address is a valid exchange.
 | 
			
		||||
    /// @param addr Address of exchange contract.
 | 
			
		||||
    /// @return True iff input address is a valid exchange.
 | 
			
		||||
    function isValidExchangeAddress(address addr)
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,19 @@ contract MixinFees is
 | 
			
		||||
 | 
			
		||||
    using LibSafeMath for uint256;
 | 
			
		||||
 | 
			
		||||
    /// @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 MixinPools.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.
 | 
			
		||||
 | 
			
		||||
    /// @dev Pays a protocol fee in ETH.
 | 
			
		||||
    ///      Only a known 0x exchange can call this method. See (MixinExchangeManager).
 | 
			
		||||
    /// @param makerAddress The address of the order's maker
 | 
			
		||||
    function payProtocolFee(address makerAddress)
 | 
			
		||||
        external
 | 
			
		||||
        payable
 | 
			
		||||
@@ -54,25 +67,37 @@ contract MixinFees is
 | 
			
		||||
        uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
        protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch._add(amount);
 | 
			
		||||
        if (_feesCollectedThisEpoch == 0) {
 | 
			
		||||
            activePoolIdsThisEpoch.push(poolId);
 | 
			
		||||
            activePoolsThisEpoch.push(poolId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @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.
 | 
			
		||||
    function finalizeFees()
 | 
			
		||||
        external
 | 
			
		||||
    {
 | 
			
		||||
        _payRebates();
 | 
			
		||||
        // payout rewards
 | 
			
		||||
        (uint256 totalActivePools,
 | 
			
		||||
        uint256 totalFeesCollected,
 | 
			
		||||
        uint256 totalWeightedStake,
 | 
			
		||||
        uint256 totalRewardsPaid,
 | 
			
		||||
        uint256 initialContractBalance,
 | 
			
		||||
        uint256 finalContractBalance) = _payMakerRewards();
 | 
			
		||||
        emit RewardsPaid(
 | 
			
		||||
            totalActivePools,
 | 
			
		||||
            totalFeesCollected,
 | 
			
		||||
            totalWeightedStake,
 | 
			
		||||
            totalRewardsPaid,
 | 
			
		||||
            initialContractBalance,
 | 
			
		||||
            finalContractBalance
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        _goToNextEpoch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getProtocolFeesThisEpochByPool(bytes32 poolId)
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256)
 | 
			
		||||
    {
 | 
			
		||||
        return protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the total amount of fees collected thus far, in the current epoch.
 | 
			
		||||
    /// @return Amount of fees.
 | 
			
		||||
    function getTotalProtocolFeesThisEpoch()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -81,78 +106,137 @@ contract MixinFees is
 | 
			
		||||
        return address(this).balance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _payRebates()
 | 
			
		||||
        internal
 | 
			
		||||
    /// @dev Returns the amount of fees attributed to the input pool.
 | 
			
		||||
    /// @param poolId Pool Id to query.
 | 
			
		||||
    /// @return Amount of fees.
 | 
			
		||||
    function getProtocolFeesThisEpochByPool(bytes32 poolId)
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256)
 | 
			
		||||
    {
 | 
			
		||||
        // Step 1 - compute total fees this epoch
 | 
			
		||||
        uint256 numberOfActivePoolIds = activePoolIdsThisEpoch.length;
 | 
			
		||||
        IStructs.ActivePool[] memory activePoolIds = new IStructs.ActivePool[](activePoolIdsThisEpoch.length);
 | 
			
		||||
        uint256 totalFees = 0;
 | 
			
		||||
        for (uint i = 0; i != numberOfActivePoolIds; i++) {
 | 
			
		||||
            activePoolIds[i].poolId = activePoolIdsThisEpoch[i];
 | 
			
		||||
            activePoolIds[i].feesCollected = protocolFeesThisEpochByPool[activePoolIds[i].poolId];
 | 
			
		||||
            totalFees = totalFees._add(activePoolIds[i].feesCollected);
 | 
			
		||||
        return protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
    }
 | 
			
		||||
        uint256 totalRewards = address(this).balance;
 | 
			
		||||
        uint256 totalStake = getActivatedStakeAcrossAllOwners();
 | 
			
		||||
 | 
			
		||||
        emit EpochFinalized(
 | 
			
		||||
            numberOfActivePoolIds,
 | 
			
		||||
            totalRewards,
 | 
			
		||||
            0
 | 
			
		||||
    /// @dev Pays rewards to market making pools that were active this epoch.
 | 
			
		||||
    /// Each pool receives a portion of the fees generated this epoch (see LibFeeMath) 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. Rebates are paid
 | 
			
		||||
    /// into the Reward Vault (see MixinRewardVault) where they can be withdraw by makers and
 | 
			
		||||
    /// the members of their pool. There will be a small amount of ETH leftover in this contract
 | 
			
		||||
    /// after paying out the rebates; at present, this rolls over into the next epoch. Eventually,
 | 
			
		||||
    /// we plan to deposit this leftover into a DAO managed by the 0x community.
 | 
			
		||||
    /// @return totalActivePools Total active pools this epoch.
 | 
			
		||||
    /// @return totalFeesCollected Total fees collected this epoch, across all active pools.
 | 
			
		||||
    /// @return totalWeightedStake Total weighted stake attributed to each pool. Delegated stake is weighted less.
 | 
			
		||||
    /// @return totalRewardsPaid Total rewards paid out across all active pools.
 | 
			
		||||
    /// @return initialContractBalance Balance of this contract before paying rewards.
 | 
			
		||||
    /// @return finalContractBalance Balance of this contract after paying rewards.
 | 
			
		||||
    function _payMakerRewards()
 | 
			
		||||
        private
 | 
			
		||||
        returns (
 | 
			
		||||
            uint256 totalActivePools,
 | 
			
		||||
            uint256 totalFeesCollected,
 | 
			
		||||
            uint256 totalWeightedStake,
 | 
			
		||||
            uint256 totalRewardsPaid,
 | 
			
		||||
            uint256 initialContractBalance,
 | 
			
		||||
            uint256 finalContractBalance
 | 
			
		||||
        )
 | 
			
		||||
    {
 | 
			
		||||
        // initialize return values
 | 
			
		||||
        totalActivePools = activePoolsThisEpoch.length;
 | 
			
		||||
        totalFeesCollected = 0;
 | 
			
		||||
        totalWeightedStake = 0;
 | 
			
		||||
        totalRewardsPaid = 0;
 | 
			
		||||
        initialContractBalance = address(this).balance;
 | 
			
		||||
        finalContractBalance = initialContractBalance;
 | 
			
		||||
 | 
			
		||||
        // sanity check - is there a balance to payout and were there any active pools?
 | 
			
		||||
        if (initialContractBalance == 0 || totalActivePools == 0) {
 | 
			
		||||
            return (
 | 
			
		||||
                totalActivePools,
 | 
			
		||||
                totalFeesCollected,
 | 
			
		||||
                totalWeightedStake,
 | 
			
		||||
                totalRewardsPaid,
 | 
			
		||||
                initialContractBalance,
 | 
			
		||||
                finalContractBalance
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        // no rebates available
 | 
			
		||||
        // note that there is a case in cobb-douglas where if we weigh either fees or stake at 100%,
 | 
			
		||||
        // then the other value doesn't matter. However, it's cheaper on gas to assume that there is some
 | 
			
		||||
        // non-zero split.
 | 
			
		||||
        if (totalRewards == 0 || totalFees == 0 || totalStake == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Step 2 - payout
 | 
			
		||||
        uint256 totalRewardsRecordedInVault = 0;
 | 
			
		||||
        for (uint i = 0; i != numberOfActivePoolIds; i++) {
 | 
			
		||||
            uint256 stakeDelegatedToPool = getStakeDelegatedToPool(activePoolIds[i].poolId);
 | 
			
		||||
            uint256 stakeHeldByPoolOperator = getActivatedAndUndelegatedStake(getPoolOperator(activePoolIds[i].poolId));
 | 
			
		||||
            uint256 scaledStake = stakeHeldByPoolOperator._add(
 | 
			
		||||
        // step 1/3 - compute stats for active maker pools
 | 
			
		||||
        IStructs.ActivePool[] memory activePools = new IStructs.ActivePool[](activePoolsThisEpoch.length);
 | 
			
		||||
        for (uint i = 0; i != totalActivePools; i++) {
 | 
			
		||||
            bytes32 poolId = activePoolsThisEpoch[i];
 | 
			
		||||
 | 
			
		||||
            // compute weighted stake
 | 
			
		||||
            uint256 stakeDelegatedToPool = getStakeDelegatedToPool(poolId);
 | 
			
		||||
            uint256 stakeHeldByPoolOperator = getActivatedAndUndelegatedStake(getPoolOperator(poolId));
 | 
			
		||||
            uint256 weightedStake = stakeHeldByPoolOperator._add(
 | 
			
		||||
                stakeDelegatedToPool
 | 
			
		||||
                ._mul(REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE)
 | 
			
		||||
                ._div(100)
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // store pool stats
 | 
			
		||||
            activePools[i].poolId = poolId;
 | 
			
		||||
            activePools[i].feesCollected = protocolFeesThisEpochByPool[poolId];
 | 
			
		||||
            activePools[i].weightedStake = weightedStake;
 | 
			
		||||
 | 
			
		||||
            // update cumulative amounts
 | 
			
		||||
            totalFeesCollected = totalFeesCollected._add(activePools[i].feesCollected);
 | 
			
		||||
            totalWeightedStake = totalWeightedStake._add(activePools[i].weightedStake);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // sanity check - this is a gas optimization that can be used because we assume a non-zero
 | 
			
		||||
        // split between stake and fees generated in the cobb-douglas computation (see below).
 | 
			
		||||
        if (totalFeesCollected == 0 || totalWeightedStake == 0) {
 | 
			
		||||
            return (
 | 
			
		||||
                totalActivePools,
 | 
			
		||||
                totalFeesCollected,
 | 
			
		||||
                totalWeightedStake,
 | 
			
		||||
                totalRewardsPaid,
 | 
			
		||||
                initialContractBalance,
 | 
			
		||||
                finalContractBalance
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // step 2/3 - record reward for each pool
 | 
			
		||||
        for (uint i = 0; i != totalActivePools; i++) {
 | 
			
		||||
            // compute reward using cobb-douglas formula
 | 
			
		||||
            uint256 reward = LibFeeMath._cobbDouglasSuperSimplified(
 | 
			
		||||
                totalRewards,
 | 
			
		||||
                activePoolIds[i].feesCollected,
 | 
			
		||||
                totalFees,
 | 
			
		||||
                scaledStake,
 | 
			
		||||
                totalStake
 | 
			
		||||
                initialContractBalance,
 | 
			
		||||
                activePools[i].feesCollected,
 | 
			
		||||
                totalFeesCollected,
 | 
			
		||||
                activePools[i].weightedStake,
 | 
			
		||||
                totalWeightedStake
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // record reward in vault
 | 
			
		||||
            _recordDepositInRewardVault(activePoolIds[i].poolId, reward);
 | 
			
		||||
            totalRewardsRecordedInVault = totalRewardsRecordedInVault._add(reward);
 | 
			
		||||
            _recordDepositInRewardVault(activePools[i].poolId, reward);
 | 
			
		||||
            totalRewardsPaid = totalRewardsPaid._add(reward);
 | 
			
		||||
 | 
			
		||||
            // clear state for refunds
 | 
			
		||||
            protocolFeesThisEpochByPool[activePoolIds[i].poolId] = 0;
 | 
			
		||||
            activePoolIdsThisEpoch[i] = 0;
 | 
			
		||||
            // clear state for gas refunds
 | 
			
		||||
            protocolFeesThisEpochByPool[activePools[i].poolId] = 0;
 | 
			
		||||
            activePoolsThisEpoch[i] = 0;
 | 
			
		||||
        }
 | 
			
		||||
        activePoolIdsThisEpoch.length = 0;
 | 
			
		||||
        activePoolsThisEpoch.length = 0;
 | 
			
		||||
 | 
			
		||||
        // Step 3 send total payout to vault
 | 
			
		||||
        // step 3/3 send total payout to vault
 | 
			
		||||
        require(
 | 
			
		||||
            totalRewardsRecordedInVault <= totalRewards,
 | 
			
		||||
            totalRewardsPaid <= initialContractBalance,
 | 
			
		||||
            "MISCALCULATED_REWARDS"
 | 
			
		||||
        );
 | 
			
		||||
        if (totalRewardsRecordedInVault > 0) {
 | 
			
		||||
            _depositIntoRewardVault(totalRewardsRecordedInVault);
 | 
			
		||||
        if (totalRewardsPaid > 0) {
 | 
			
		||||
            _depositIntoRewardVault(totalRewardsPaid);
 | 
			
		||||
        }
 | 
			
		||||
        finalContractBalance = address(this).balance;
 | 
			
		||||
 | 
			
		||||
        // Notify finalization
 | 
			
		||||
        emit EpochFinalized(
 | 
			
		||||
            numberOfActivePoolIds,
 | 
			
		||||
            totalRewards,
 | 
			
		||||
            totalRewardsRecordedInVault
 | 
			
		||||
        return (
 | 
			
		||||
            totalActivePools,
 | 
			
		||||
            totalFeesCollected,
 | 
			
		||||
            totalWeightedStake,
 | 
			
		||||
            totalRewardsPaid,
 | 
			
		||||
            initialContractBalance,
 | 
			
		||||
            finalContractBalance
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,11 @@ import "../libs/LibSafeMath64.sol";
 | 
			
		||||
import "../immutable/MixinConstants.sol";
 | 
			
		||||
import "../immutable/MixinStorage.sol";
 | 
			
		||||
import "../interfaces/IStructs.sol";
 | 
			
		||||
import "../interfaces/IStakingEvents.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract MixinScheduler is
 | 
			
		||||
    IStakingEvents,
 | 
			
		||||
    MixinConstants,
 | 
			
		||||
    MixinStorage,
 | 
			
		||||
    IMixinScheduler
 | 
			
		||||
@@ -43,6 +45,7 @@ contract MixinScheduler is
 | 
			
		||||
    /// and consistent scheduling metric than time. Timelocks, for example, are measured in epochs.
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the current epoch.
 | 
			
		||||
    /// @return Epoch.
 | 
			
		||||
    function getCurrentEpoch()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -53,6 +56,7 @@ contract MixinScheduler is
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the current epoch period, measured in seconds.
 | 
			
		||||
    ///      Epoch period = [startTimeInSeconds..endTimeInSeconds)
 | 
			
		||||
    /// @return Time in seconds.
 | 
			
		||||
    function getEpochPeriodInSeconds()
 | 
			
		||||
        public
 | 
			
		||||
        pure
 | 
			
		||||
@@ -63,6 +67,7 @@ contract MixinScheduler is
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the start time in seconds of the current epoch.
 | 
			
		||||
    ///      Epoch period = [startTimeInSeconds..endTimeInSeconds)
 | 
			
		||||
    /// @return Time in seconds.
 | 
			
		||||
    function getCurrentEpochStartTimeInSeconds()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -74,6 +79,7 @@ contract MixinScheduler is
 | 
			
		||||
    /// @dev Returns the earliest end time in seconds of this epoch.
 | 
			
		||||
    ///      The next epoch can begin once this time is reached.  
 | 
			
		||||
    ///      Epoch period = [startTimeInSeconds..endTimeInSeconds)
 | 
			
		||||
    /// @return Time in seconds.
 | 
			
		||||
    function getCurrentEpochEarliestEndTimeInSeconds()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -82,7 +88,8 @@ contract MixinScheduler is
 | 
			
		||||
        return getCurrentEpochStartTimeInSeconds()._add(getEpochPeriodInSeconds());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the current timelock period
 | 
			
		||||
    /// @dev Returns the current timelock period.
 | 
			
		||||
    /// @return Timelock period.
 | 
			
		||||
    function getCurrentTimelockPeriod()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -93,6 +100,7 @@ contract MixinScheduler is
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the length of a timelock period, measured in epochs.
 | 
			
		||||
    ///      Timelock period = [startEpoch..endEpoch)
 | 
			
		||||
    /// @return Timelock period end.
 | 
			
		||||
    function getTimelockPeriodInEpochs()
 | 
			
		||||
        public
 | 
			
		||||
        pure
 | 
			
		||||
@@ -103,6 +111,7 @@ contract MixinScheduler is
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the epoch that the current timelock period started at.
 | 
			
		||||
    ///      Timelock period = [startEpoch..endEpoch)
 | 
			
		||||
    /// @return Timelock period start.
 | 
			
		||||
    function getCurrentTimelockPeriodStartEpoch()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -113,6 +122,7 @@ contract MixinScheduler is
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns the epoch that the current timelock period will end.
 | 
			
		||||
    ///      Timelock period = [startEpoch..endEpoch)
 | 
			
		||||
    /// @return Timelock period.
 | 
			
		||||
    function getCurrentTimelockPeriodEndEpoch()
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
@@ -128,7 +138,6 @@ contract MixinScheduler is
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // get current timestamp
 | 
			
		||||
        // solium-disable security/no-block-members
 | 
			
		||||
        // solhint-disable-next-line not-rely-on-time
 | 
			
		||||
        uint64 currentBlockTimestamp = block.timestamp._downcastToUint64();
 | 
			
		||||
 | 
			
		||||
@@ -142,11 +151,27 @@ contract MixinScheduler is
 | 
			
		||||
        uint64 nextEpoch = currentEpoch._add(1);
 | 
			
		||||
        currentEpoch = nextEpoch;
 | 
			
		||||
        currentEpochStartTimeInSeconds = currentBlockTimestamp;
 | 
			
		||||
        uint64 earliestEndTimeInSeconds = currentEpochStartTimeInSeconds._add(getEpochPeriodInSeconds());
 | 
			
		||||
        
 | 
			
		||||
        // notify of epoch change
 | 
			
		||||
        emit EpochChanged(
 | 
			
		||||
            currentEpoch,
 | 
			
		||||
            currentEpochStartTimeInSeconds,
 | 
			
		||||
            earliestEndTimeInSeconds
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // increment timelock period, if needed
 | 
			
		||||
        if (getCurrentTimelockPeriodEndEpoch() <= nextEpoch) {
 | 
			
		||||
            currentTimelockPeriod = currentTimelockPeriod._add(1);
 | 
			
		||||
            currentTimelockPeriodStartEpoch = currentEpoch;
 | 
			
		||||
            uint64 endEpoch = currentEpoch._add(getTimelockPeriodInEpochs());
 | 
			
		||||
            
 | 
			
		||||
            // notify
 | 
			
		||||
            emit TimelockPeriodChanged(
 | 
			
		||||
                currentTimelockPeriod,
 | 
			
		||||
                currentTimelockPeriodStartEpoch,
 | 
			
		||||
                endEpoch
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ contract IMixinScheduler {
 | 
			
		||||
    /// Epochs serve as the basis for all other time intervals, which provides a more stable
 | 
			
		||||
    /// and consistent scheduling metric than time. Timelocks, for example, are measured in epochs.
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
    /// @dev Returns the current epoch.
 | 
			
		||||
    function getCurrentEpoch()
 | 
			
		||||
        public
 | 
			
		||||
@@ -82,4 +83,5 @@ contract IMixinScheduler {
 | 
			
		||||
        public
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint64);
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,16 +31,15 @@ contract MixinStorage is
 | 
			
		||||
 | 
			
		||||
    // @TODO Add notes about which Mixin manages which state
 | 
			
		||||
 | 
			
		||||
    // address of owner
 | 
			
		||||
    address internal owner;
 | 
			
		||||
 | 
			
		||||
    // address of staking contract
 | 
			
		||||
    address internal stakingContract;
 | 
			
		||||
 | 
			
		||||
    // mapping from Owner to Amount Staked
 | 
			
		||||
    mapping (address => uint256) internal stakeByOwner;
 | 
			
		||||
 | 
			
		||||
    // @TODO Think about merging these different states
 | 
			
		||||
    // It would be nice if the sum of the different states had to equal `stakeByOwner`
 | 
			
		||||
    // and it were all in a single variable (stakeByOwner in its own)
 | 
			
		||||
 | 
			
		||||
    // mapping from Owner to Amount of Instactive Stake
 | 
			
		||||
    mapping (address => uint256) internal activeStakeByOwner;
 | 
			
		||||
 | 
			
		||||
@@ -88,7 +87,7 @@ contract MixinStorage is
 | 
			
		||||
    mapping (bytes32 => uint256) internal protocolFeesThisEpochByPool;
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    bytes32[] internal activePoolIdsThisEpoch;
 | 
			
		||||
    bytes32[] internal activePoolsThisEpoch;
 | 
			
		||||
 | 
			
		||||
    // mapping from POol Id to Shadow Rewards
 | 
			
		||||
    mapping (bytes32 => uint256) internal shadowRewardsByPoolId;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,39 @@ interface IStakingEvents {
 | 
			
		||||
        address exchangeAddress
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    event EpochFinalized(
 | 
			
		||||
    /// @dev Emitted by MixinScheduler when the epoch is changed.
 | 
			
		||||
    /// @param epoch The epoch we changed to.
 | 
			
		||||
    /// @param startTimeInSeconds The start time of the epoch.
 | 
			
		||||
    /// @param earliestEndTimeInSeconds The earliest this epoch can end.
 | 
			
		||||
    event EpochChanged(
 | 
			
		||||
        uint64 epoch,
 | 
			
		||||
        uint64 startTimeInSeconds,
 | 
			
		||||
        uint64 earliestEndTimeInSeconds
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
     /// @dev Emitted by MixinScheduler when the timelock period is changed.
 | 
			
		||||
     /// @param timelockPeriod The timelock period we changed to.
 | 
			
		||||
     /// @param startEpoch The epoch this period started.
 | 
			
		||||
     /// @param endEpoch The epoch this period ends.
 | 
			
		||||
    event TimelockPeriodChanged(
 | 
			
		||||
        uint64 timelockPeriod,
 | 
			
		||||
        uint64 startEpoch,
 | 
			
		||||
        uint64 endEpoch
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted by MixinFees when rewards are paid out.
 | 
			
		||||
    /// @param totalActivePools Total active pools this epoch.
 | 
			
		||||
    /// @param totalFeesCollected Total fees collected this epoch, across all active pools.
 | 
			
		||||
    /// @param totalWeightedStake Total weighted stake attributed to each pool. Delegated stake is weighted less.
 | 
			
		||||
    /// @param totalRewardsPaid Total rewards paid out across all active pools.
 | 
			
		||||
    /// @param initialContractBalance Balance of this contract before paying rewards.
 | 
			
		||||
    /// @param finalContractBalance Balance of this contract after paying rewards.
 | 
			
		||||
    event RewardsPaid(
 | 
			
		||||
        uint256 totalActivePools,
 | 
			
		||||
        uint256 totalFees,
 | 
			
		||||
        uint256 totalRewards
 | 
			
		||||
        uint256 totalFeesCollected,
 | 
			
		||||
        uint256 totalWeightedStake,
 | 
			
		||||
        uint256 totalRewardsPaid,
 | 
			
		||||
        uint256 initialContractBalance,
 | 
			
		||||
        uint256 finalContractBalance
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -50,5 +50,6 @@ interface IStructs {
 | 
			
		||||
    struct ActivePool {
 | 
			
		||||
        bytes32 poolId;
 | 
			
		||||
        uint256 feesCollected;
 | 
			
		||||
        uint256 weightedStake;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -82,6 +82,7 @@ describe('Exchange Integrations', () => {
 | 
			
		||||
                stakingWrapper.removeExchangeAddressAsync(exchange),
 | 
			
		||||
                RevertReason.ExchangeAddressNotRegistered,
 | 
			
		||||
            );
 | 
			
		||||
            // @todo should not be able to add / remove an exchange if not contract owner.
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -118,13 +118,13 @@ describe('End-To-End Simulations', () => {
 | 
			
		||||
            /*
 | 
			
		||||
\           // the expected payouts were computed by hand
 | 
			
		||||
            // @TODO - get computations more accurate
 | 
			
		||||
                Pool | Total Fees  | Total Stake | Total Delegated Stake | Total Stake (Scaled)
 | 
			
		||||
                0    |  0.304958   | 42          | 0                     | 42
 | 
			
		||||
                1    | 15.323258   | 84          | 0                     | 84
 | 
			
		||||
                Pool | Total Fees  | Total Stake | Total Delegated Stake | Total Stake (Weighted) | Payout
 | 
			
		||||
                0    |  0.304958   | 42          | 0                     | 42                     | 3.0060373...
 | 
			
		||||
                1    | 15.323258   | 84          | 0                     | 84                     | 
 | 
			
		||||
                3    | 28.12222236 | 97          | 182                   | 260.8
 | 
			
		||||
                ...
 | 
			
		||||
                Cumulative Fees = 43.75043836
 | 
			
		||||
                Cumulative Stake = 405
 | 
			
		||||
                Cumulative Weighted Stake = 386.8
 | 
			
		||||
                Total Rewards = 43.75043836
 | 
			
		||||
            */
 | 
			
		||||
            const simulationParams = {
 | 
			
		||||
@@ -164,27 +164,27 @@ describe('End-To-End Simulations', () => {
 | 
			
		||||
                    StakingWrapper.toBaseUnitAmount(28.12222236),
 | 
			
		||||
                ],
 | 
			
		||||
                expectedPayoutByPool: [
 | 
			
		||||
                    new BigNumber('2.89303'), // 2.8930364057678784829875695710382241749912199174798475
 | 
			
		||||
                    new BigNumber('9.90218'), // 9.9021783083174087034787071054543342142019746753770943
 | 
			
		||||
                    new BigNumber('28.16463'), // 28.164631904035798614670299155719067954180760345463798
 | 
			
		||||
                    new BigNumber('3.00603'), // 3.006037310109530277237724562632303034914024715508955780682
 | 
			
		||||
                    new BigNumber('10.28895'), // 10.28895363598396754741643198605226143579652264694121578135
 | 
			
		||||
                    new BigNumber('29.26472'), // 29.26473180250053106672049765968527817034954761113582833460
 | 
			
		||||
                ],
 | 
			
		||||
                expectedPayoutByPoolOperator: [
 | 
			
		||||
                    new BigNumber('1.12828'), // 0.39 * 2.89303
 | 
			
		||||
                    new BigNumber('5.84228'), // 0.59 * 9.90218
 | 
			
		||||
                    new BigNumber('12.11079'), // 0.43 * 28.16463
 | 
			
		||||
                    new BigNumber('1.17235'), // 0.39 * 3.00603
 | 
			
		||||
                    new BigNumber('6.07048'), // 0.59 * 10.28895
 | 
			
		||||
                    new BigNumber('12.58383'), // 0.43 * 29.26472
 | 
			
		||||
                ],
 | 
			
		||||
                expectedMembersPayoutByPool: [
 | 
			
		||||
                    new BigNumber('1.76475'), // (1 - 0.39) * 2.89303
 | 
			
		||||
                    new BigNumber('4.05989'), // (1 - 0.59) * 9.90218
 | 
			
		||||
                    new BigNumber('16.05383'), // (1 - 0.43) * 28.16463
 | 
			
		||||
                    new BigNumber('1.83368'), // (1 - 0.39) * 3.00603
 | 
			
		||||
                    new BigNumber('4.21847'), // (1 - 0.59) * 10.28895
 | 
			
		||||
                    new BigNumber('16.68089'), // (1 - 0.43) * 29.26472
 | 
			
		||||
                ],
 | 
			
		||||
                expectedPayoutByDelegator: [
 | 
			
		||||
                    // note that the on-chain values may be slightly different due to rounding down on each entry
 | 
			
		||||
                    // there is a carry over between calls, which we account for here. the result is that delegators
 | 
			
		||||
                    // who withdraw later on will scoop up any rounding spillover from those who have already withdrawn.
 | 
			
		||||
                    new BigNumber('1.49953'), // (17 / 182) * 16.05383
 | 
			
		||||
                    new BigNumber('6.61559'), // (75 / 182) * 16.05383
 | 
			
		||||
                    new BigNumber('7.93871'), // (90 / 182) * 16.05383
 | 
			
		||||
                    new BigNumber('1.55810'), // (17 / 182) * 16.6809
 | 
			
		||||
                    new BigNumber('6.87399'), // (75 / 182) * 16.6809
 | 
			
		||||
                    new BigNumber('8.24879'), // (90 / 182) * 16.6809
 | 
			
		||||
                ],
 | 
			
		||||
                exchangeAddress: exchange,
 | 
			
		||||
            };
 | 
			
		||||
@@ -201,7 +201,7 @@ describe('End-To-End Simulations', () => {
 | 
			
		||||
                3    | 28.12222236 | 97          | 182                   | 260.8
 | 
			
		||||
                ...
 | 
			
		||||
                Cumulative Fees = 43.75043836
 | 
			
		||||
                Cumulative Stake = 405
 | 
			
		||||
                Cumulative Weighted Stake = 386.8
 | 
			
		||||
                Total Rewards = 43.75043836
 | 
			
		||||
 | 
			
		||||
                // In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
 | 
			
		||||
@@ -280,7 +280,7 @@ describe('End-To-End Simulations', () => {
 | 
			
		||||
                3    | 28.12222236 | 97          | 182                   | 260.8
 | 
			
		||||
                ...
 | 
			
		||||
                Cumulative Fees = 43.75043836
 | 
			
		||||
                Cumulative Stake = 405
 | 
			
		||||
                Cumulative Weighted Stake = 386.8
 | 
			
		||||
                Total Rewards = 43.75043836
 | 
			
		||||
 | 
			
		||||
                // In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ export class StakingWrapper {
 | 
			
		||||
    private readonly _web3Wrapper: Web3Wrapper;
 | 
			
		||||
    private readonly _provider: Provider;
 | 
			
		||||
    private readonly _logDecoder: LogDecoder;
 | 
			
		||||
    private readonly _ownerAddres: string;
 | 
			
		||||
    private readonly _ownerAddress: string;
 | 
			
		||||
    private readonly _erc20ProxyContract: ERC20ProxyContract;
 | 
			
		||||
    private readonly _zrxTokenContract: DummyERC20TokenContract;
 | 
			
		||||
    private readonly _accounts: string[];
 | 
			
		||||
@@ -73,7 +73,7 @@ export class StakingWrapper {
 | 
			
		||||
        this._provider = provider;
 | 
			
		||||
        const decoderArtifacts = _.merge(artifacts, erc20Artifacts);
 | 
			
		||||
        this._logDecoder = new LogDecoder(this._web3Wrapper, decoderArtifacts);
 | 
			
		||||
        this._ownerAddres = ownerAddres;
 | 
			
		||||
        this._ownerAddress = ownerAddres;
 | 
			
		||||
        this._erc20ProxyContract = erc20ProxyContract;
 | 
			
		||||
        this._zrxTokenContract = zrxTokenContract;
 | 
			
		||||
        this._accounts = accounts;
 | 
			
		||||
@@ -143,7 +143,7 @@ export class StakingWrapper {
 | 
			
		||||
            (this._zrxVaultContractIfExists).address,
 | 
			
		||||
        );
 | 
			
		||||
        const setZrxVaultTxData = {
 | 
			
		||||
            from: this._ownerAddres,
 | 
			
		||||
            from: this._ownerAddress,
 | 
			
		||||
            to: (this._stakingProxyContractIfExists).address,
 | 
			
		||||
            data: setZrxVaultCalldata,
 | 
			
		||||
        };
 | 
			
		||||
@@ -161,7 +161,7 @@ export class StakingWrapper {
 | 
			
		||||
            (this._rewardVaultContractIfExists).address,
 | 
			
		||||
        );
 | 
			
		||||
        const setRewardVaultTxData = {
 | 
			
		||||
            from: this._ownerAddres,
 | 
			
		||||
            from: this._ownerAddress,
 | 
			
		||||
            to: (this._stakingProxyContractIfExists).address,
 | 
			
		||||
            data: setRewardVaultCalldata,
 | 
			
		||||
        };
 | 
			
		||||
@@ -243,7 +243,7 @@ export class StakingWrapper {
 | 
			
		||||
    }
 | 
			
		||||
    public async forceTimelockSyncAsync(owner: string): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
        const calldata = this.getStakingContract().forceTimelockSync.getABIEncodedTransactionData(owner);
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres);
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddress);
 | 
			
		||||
        return txReceipt;
 | 
			
		||||
    }
 | 
			
		||||
    ///// STAKE BALANCES /////
 | 
			
		||||
@@ -411,6 +411,7 @@ export class StakingWrapper {
 | 
			
		||||
        logUtils.log(
 | 
			
		||||
            `Finalization costed ${txReceipt.gasUsed} gas`,
 | 
			
		||||
        );
 | 
			
		||||
        console.log(JSON.stringify(txReceipt.logs, null, 4));
 | 
			
		||||
        return txReceipt;
 | 
			
		||||
    }
 | 
			
		||||
    public async skipToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
@@ -511,14 +512,16 @@ export class StakingWrapper {
 | 
			
		||||
        const isValid = this.getStakingContract().isValidExchangeAddress.getABIDecodedReturnData(returnData);
 | 
			
		||||
        return isValid;
 | 
			
		||||
    }
 | 
			
		||||
    public async addExchangeAddressAsync(exchangeAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
    public async addExchangeAddressAsync(exchangeAddress: string, ownerAddressIfExists?: string): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
        const calldata = this.getStakingContract().addExchangeAddress.getABIEncodedTransactionData(exchangeAddress);
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres);
 | 
			
		||||
        const ownerAddress = ownerAddressIfExists !== undefined ? ownerAddressIfExists : this._ownerAddress;
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, ownerAddress);
 | 
			
		||||
        return txReceipt;
 | 
			
		||||
    }
 | 
			
		||||
    public async removeExchangeAddressAsync(exchangeAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
    public async removeExchangeAddressAsync(exchangeAddress: string, ownerAddressIfExists?: string): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
        const calldata = this.getStakingContract().removeExchangeAddress.getABIEncodedTransactionData(exchangeAddress);
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddres);
 | 
			
		||||
        const ownerAddress = ownerAddressIfExists !== undefined ? ownerAddressIfExists : this._ownerAddress;
 | 
			
		||||
        const txReceipt = await this._executeTransactionAsync(calldata, ownerAddress);
 | 
			
		||||
        return txReceipt;
 | 
			
		||||
    }
 | 
			
		||||
    ///// REWARDS /////
 | 
			
		||||
@@ -735,7 +738,7 @@ export class StakingWrapper {
 | 
			
		||||
        includeLogs?: boolean,
 | 
			
		||||
    ): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
        const txData = {
 | 
			
		||||
            from: from ? from : this._ownerAddres,
 | 
			
		||||
            from: from ? from : this._ownerAddress,
 | 
			
		||||
            to: this.getStakingProxyContract().address,
 | 
			
		||||
            data: calldata,
 | 
			
		||||
            gas: 3000000,
 | 
			
		||||
@@ -750,7 +753,7 @@ export class StakingWrapper {
 | 
			
		||||
    }
 | 
			
		||||
    private async _callAsync(calldata: string, from?: string): Promise<any> {
 | 
			
		||||
        const txData = {
 | 
			
		||||
            from: from ? from : this._ownerAddres,
 | 
			
		||||
            from: from ? from : this._ownerAddress,
 | 
			
		||||
            to: this.getStakingProxyContract().address,
 | 
			
		||||
            data: calldata,
 | 
			
		||||
            gas: 3000000,
 | 
			
		||||
 
 | 
			
		||||
@@ -873,7 +873,7 @@
 | 
			
		||||
 | 
			
		||||
"@0x/web3-wrapper@^4.0.1":
 | 
			
		||||
  version "4.0.2"
 | 
			
		||||
  resolved "https://registry.npmjs.org/@0x/web3-wrapper/-/web3-wrapper-4.0.2.tgz#d4e0a4fa1217155e1aed4cd91086654fd99f2959"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-4.0.2.tgz#d4e0a4fa1217155e1aed4cd91086654fd99f2959"
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@0x/assert" "^2.0.2"
 | 
			
		||||
    "@0x/json-schemas" "^3.0.2"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user