Reference counting for rewards, plus unit tests for cumulative rewards.
This commit is contained in:
		@@ -95,6 +95,9 @@ contract MixinStorage is
 | 
			
		||||
    // mapping from Pool Id to Epoch to Reward Ratio
 | 
			
		||||
    mapping (bytes32 => mapping (uint256 => IStructs.Fraction)) internal cumulativeRewardsByPool;
 | 
			
		||||
 | 
			
		||||
    // mapping from Pool Id to Epoch to Cumulative Rewards Reference Counter
 | 
			
		||||
    mapping (bytes32 => mapping (uint256 => uint256)) internal cumulativeRewardsByPoolReferenceCounter;
 | 
			
		||||
 | 
			
		||||
    // mapping from Pool Id to Epoch
 | 
			
		||||
    mapping (bytes32 => uint256) internal cumulativeRewardsByPoolLastStored;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ interface IStakingEvents {
 | 
			
		||||
        uint8 fromStatus,
 | 
			
		||||
        bytes32 indexed fromPool,
 | 
			
		||||
        uint8 toStatus,
 | 
			
		||||
        bytes32 indexed toProol
 | 
			
		||||
        bytes32 indexed toPool
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /// @dev Emitted by MixinExchangeManager when an exchange is added.
 | 
			
		||||
 
 | 
			
		||||
@@ -37,11 +37,13 @@ interface IStructs {
 | 
			
		||||
    /// Note that these balances may be stale if the current epoch
 | 
			
		||||
    /// is greater than `currentEpoch`.
 | 
			
		||||
    /// Always load this struct using _loadAndSyncBalance or _loadUnsyncedBalance.
 | 
			
		||||
    /// @param isInitialized
 | 
			
		||||
    /// @param currentEpoch the current epoch
 | 
			
		||||
    /// @param currentEpochBalance balance in the current epoch.
 | 
			
		||||
    /// @param nextEpochBalance balance in the next epoch.
 | 
			
		||||
    struct StoredBalance {
 | 
			
		||||
        uint64 currentEpoch;
 | 
			
		||||
        bool isInitialized;
 | 
			
		||||
        uint32 currentEpoch;
 | 
			
		||||
        uint96 currentEpochBalance;
 | 
			
		||||
        uint96 nextEpochBalance;
 | 
			
		||||
    }
 | 
			
		||||
@@ -85,4 +87,12 @@ interface IStructs {
 | 
			
		||||
        bytes32 poolId;
 | 
			
		||||
        bool confirmed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Encapsulates the epoch and value of a cumulative reward.
 | 
			
		||||
    /// @param cumulativeRewardEpoch Epoch of the reward.
 | 
			
		||||
    /// @param cumulativeReward Value of the reward.
 | 
			
		||||
    struct CumulativeRewardInfo {
 | 
			
		||||
        uint256 cumulativeRewardEpoch;
 | 
			
		||||
        IStructs.Fraction cumulativeReward;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,4 +57,21 @@ library LibSafeDowncast {
 | 
			
		||||
        }
 | 
			
		||||
        return b;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Safely downcasts to a uint32
 | 
			
		||||
    /// Note that this reverts if the input value is too large.
 | 
			
		||||
    function downcastToUint32(uint256 a)
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (uint32 b)
 | 
			
		||||
    {
 | 
			
		||||
        b = uint32(a);
 | 
			
		||||
        if (uint256(b) != a) {
 | 
			
		||||
            LibRichErrors.rrevert(LibSafeMathRichErrors.Uint256DowncastError(
 | 
			
		||||
                LibSafeMathRichErrors.DowncastErrorCodes.VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT32,
 | 
			
		||||
                a
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return b;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -181,18 +181,18 @@ contract MixinStake is
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        // transfer any rewards from the transient pool vault to the eth vault;
 | 
			
		||||
        // this must be done before we can modify the owner's portion of the delegator pool.
 | 
			
		||||
        _transferDelegatorsAccumulatedRewardsToEthVault(poolId, owner);
 | 
			
		||||
 | 
			
		||||
        // sync cumulative rewards that we'll need for future computations
 | 
			
		||||
        _syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
 | 
			
		||||
        // cache amount delegated to pool by owner
 | 
			
		||||
        IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(delegatedStakeToPoolByOwner[owner][poolId]);
 | 
			
		||||
 | 
			
		||||
        // increment how much stake the owner has delegated to the input pool
 | 
			
		||||
        _incrementNextBalance(delegatedStakeToPoolByOwner[owner][poolId], amount);
 | 
			
		||||
 | 
			
		||||
        // increment how much stake has been delegated to pool
 | 
			
		||||
        _incrementNextBalance(delegatedStakeByPoolId[poolId], amount);
 | 
			
		||||
 | 
			
		||||
        // synchronizes reward state in the pool that the staker is undelegating from
 | 
			
		||||
        IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(delegatedStakeToPoolByOwner[owner][poolId]);
 | 
			
		||||
        _syncRewardsForDelegator(poolId, owner, initDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Un-Delegates a owners stake from a staking pool.
 | 
			
		||||
@@ -206,18 +206,18 @@ contract MixinStake is
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        // transfer any rewards from the transient pool vault to the eth vault;
 | 
			
		||||
        // this must be done before we can modify the owner's portion of the delegator pool.
 | 
			
		||||
        _transferDelegatorsAccumulatedRewardsToEthVault(poolId, owner);
 | 
			
		||||
 | 
			
		||||
        // sync cumulative rewards that we'll need for future computations
 | 
			
		||||
        _syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
 | 
			
		||||
        // cache amount delegated to pool by owner
 | 
			
		||||
        IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(delegatedStakeToPoolByOwner[owner][poolId]);
 | 
			
		||||
 | 
			
		||||
        // decrement how much stake the owner has delegated to the input pool
 | 
			
		||||
        _decrementNextBalance(delegatedStakeToPoolByOwner[owner][poolId], amount);
 | 
			
		||||
 | 
			
		||||
        // decrement how much stake has been delegated to pool
 | 
			
		||||
        _decrementNextBalance(delegatedStakeByPoolId[poolId], amount);
 | 
			
		||||
 | 
			
		||||
        // synchronizes reward state in the pool that the staker is undelegating from
 | 
			
		||||
        IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(delegatedStakeToPoolByOwner[owner][poolId]);
 | 
			
		||||
        _syncRewardsForDelegator(poolId, owner, initDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns a storage pointer to a user's stake in a given status.
 | 
			
		||||
 
 | 
			
		||||
@@ -95,7 +95,7 @@ contract MixinStakeStorage is
 | 
			
		||||
        // sync
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
        if (currentEpoch > balance.currentEpoch) {
 | 
			
		||||
            balance.currentEpoch = currentEpoch.downcastToUint64();
 | 
			
		||||
            balance.currentEpoch = currentEpoch.downcastToUint32();
 | 
			
		||||
            balance.currentEpochBalance = balance.nextEpochBalance;
 | 
			
		||||
        }
 | 
			
		||||
        return balance;
 | 
			
		||||
@@ -185,6 +185,7 @@ contract MixinStakeStorage is
 | 
			
		||||
    {
 | 
			
		||||
        // note - this compresses into a single `sstore` when optimizations are enabled,
 | 
			
		||||
        // since the StakeBalance struct occupies a single word of storage.
 | 
			
		||||
        balancePtr.isInitialized = true;
 | 
			
		||||
        balancePtr.currentEpoch = balance.currentEpoch;
 | 
			
		||||
        balancePtr.nextEpochBalance = balance.nextEpochBalance;
 | 
			
		||||
        balancePtr.currentEpochBalance = balance.currentEpochBalance;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,230 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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 "@0x/contracts-utils/contracts/src/LibFractions.sol";
 | 
			
		||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
 | 
			
		||||
import "../immutable/MixinStorage.sol";
 | 
			
		||||
import "../immutable/MixinConstants.sol";
 | 
			
		||||
import "../stake/MixinStakeBalances.sol";
 | 
			
		||||
import "./MixinStakingPoolRewardVault.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract MixinCumulativeRewards is
 | 
			
		||||
    IStakingEvents,
 | 
			
		||||
    MixinConstants,
 | 
			
		||||
    Ownable,
 | 
			
		||||
    MixinStorage,
 | 
			
		||||
    MixinZrxVault,
 | 
			
		||||
    MixinStakingPoolRewardVault,
 | 
			
		||||
    MixinScheduler,
 | 
			
		||||
    MixinStakeStorage,
 | 
			
		||||
    MixinStakeBalances
 | 
			
		||||
{
 | 
			
		||||
    using LibSafeMath for uint256;
 | 
			
		||||
 | 
			
		||||
    /// @dev Initializes Cumulative Rewards for a given pool.
 | 
			
		||||
    function _initializeCumulativeRewards(bytes32 poolId)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
        _setCumulativeReward(poolId, currentEpoch, IStructs.Fraction({numerator: 0, denominator: MIN_TOKEN_VALUE}));
 | 
			
		||||
        _setMostRecentCumulativeReward(poolId, currentEpoch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev returns true iff Cumulative Rewards are set
 | 
			
		||||
    function _isCumulativeRewardSet(IStructs.Fraction memory cumulativeReward)
 | 
			
		||||
        internal
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bool)
 | 
			
		||||
    {
 | 
			
		||||
        // we use the denominator as a proxy for whether the cumulative
 | 
			
		||||
        // reward is set, as setting the cumulative reward always sets this
 | 
			
		||||
        // field to at least 1.
 | 
			
		||||
        return cumulativeReward.denominator != 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Sets a cumulative reward for `poolId` at `epoch`.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch of cumulative reward.
 | 
			
		||||
    /// @param value of cumulative reward.
 | 
			
		||||
    function _setCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch,
 | 
			
		||||
        IStructs.Fraction memory value
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        cumulativeRewardsByPool[poolId][epoch] = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Unsets the cumulative reward for `poolId` at `epoch`.
 | 
			
		||||
    /// @param poolId Unique id of pool.
 | 
			
		||||
    /// @param epoch of cumulative reward to unset.
 | 
			
		||||
    function _unsetCumulativeReward(bytes32 poolId, uint256 epoch)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        assert(cumulativeRewardsByPoolReferenceCounter[poolId][epoch] == 0);
 | 
			
		||||
        cumulativeRewardsByPool[poolId][epoch] = IStructs.Fraction({numerator: 0, denominator: 0});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Returns info on most recent cumulative reward.
 | 
			
		||||
    function _getMostRecentCumulativeRewardInfo(bytes32 poolId)
 | 
			
		||||
        internal
 | 
			
		||||
        returns (IStructs.CumulativeRewardInfo memory)
 | 
			
		||||
    {
 | 
			
		||||
        // fetch the last epoch at which we stored a cumulative reward for this pool
 | 
			
		||||
        uint256 cumulativeRewardsLastStored = cumulativeRewardsByPoolLastStored[poolId];
 | 
			
		||||
 | 
			
		||||
        // query and return cumulative reward info for this pool
 | 
			
		||||
        return IStructs.CumulativeRewardInfo({
 | 
			
		||||
            cumulativeReward: cumulativeRewardsByPool[poolId][cumulativeRewardsLastStored],
 | 
			
		||||
            cumulativeRewardEpoch: cumulativeRewardsLastStored
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Adds a dependency on a cumulative reward for a given epoch.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch to remove dependency from.
 | 
			
		||||
    /// @param mostRecentCumulativeRewardInfo Epoch of the most recent cumulative reward.
 | 
			
		||||
    /// @param isDependent still FGREG EEGGEGREG
 | 
			
		||||
    function _addOrRemoveDependencyOnCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch,
 | 
			
		||||
        IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo,
 | 
			
		||||
        bool isDependent
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        if (isDependent) {
 | 
			
		||||
            _addDependencyOnCumulativeReward(
 | 
			
		||||
                poolId,
 | 
			
		||||
                epoch,
 | 
			
		||||
                mostRecentCumulativeRewardInfo
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            _removeDependencyOnCumulativeReward(
 | 
			
		||||
                poolId,
 | 
			
		||||
                epoch,
 | 
			
		||||
                mostRecentCumulativeRewardInfo.cumulativeRewardEpoch
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Adds a dependency on a cumulative reward for a given epoch.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch to remove dependency from.
 | 
			
		||||
    /// @param mostRecentCumulativeRewardInfo Info on the most recent cumulative reward.
 | 
			
		||||
    function _addDependencyOnCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch,
 | 
			
		||||
        IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // add dependency by increasing the reference counter
 | 
			
		||||
        cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeAdd(1);
 | 
			
		||||
 | 
			
		||||
        // ensure dependency has a value, otherwise copy it from the most recent reward
 | 
			
		||||
        if (!_isCumulativeRewardSet(cumulativeRewardsByPool[poolId][epoch])) {
 | 
			
		||||
            _setCumulativeReward(poolId, epoch, mostRecentCumulativeRewardInfo.cumulativeReward);
 | 
			
		||||
            _setMostRecentCumulativeReward(poolId, epoch);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Removes a dependency on a cumulative reward for a given epoch.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch to remove dependency from.
 | 
			
		||||
    /// @param mostRecentCumulativeRewardEpoch Epoch of the most recent cumulative reward.
 | 
			
		||||
    function _removeDependencyOnCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch,
 | 
			
		||||
        uint256 mostRecentCumulativeRewardEpoch
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // remove dependency by decreasing reference counter
 | 
			
		||||
        uint256 newReferenceCounter = cumulativeRewardsByPoolReferenceCounter[poolId][epoch].safeSub(1);
 | 
			
		||||
        cumulativeRewardsByPoolReferenceCounter[poolId][epoch] = newReferenceCounter;
 | 
			
		||||
 | 
			
		||||
        // clear cumulative reward from state, if it is no longer needed
 | 
			
		||||
        if (newReferenceCounter == 0 && epoch < mostRecentCumulativeRewardEpoch) {
 | 
			
		||||
            _unsetCumulativeReward(poolId, epoch);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Computes a member's reward over a given epoch interval.
 | 
			
		||||
    /// @param poolId Uniqud Id of pool.
 | 
			
		||||
    /// @param memberStakeOverInterval Stake delegated to pool by meber over the interval.
 | 
			
		||||
    /// @param beginEpoch beginning of interval.
 | 
			
		||||
    /// @param endEpoch end of interval.
 | 
			
		||||
    /// @return rewards accumulated over interval [beginEpoch, endEpoch]
 | 
			
		||||
    function _computeMemberRewardOverInterval(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 memberStakeOverInterval,
 | 
			
		||||
        uint256 beginEpoch,
 | 
			
		||||
        uint256 endEpoch
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256)
 | 
			
		||||
    {
 | 
			
		||||
        // sanity check
 | 
			
		||||
        if (memberStakeOverInterval == 0) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // compute reward
 | 
			
		||||
        IStructs.Fraction memory beginRatio = cumulativeRewardsByPool[poolId][beginEpoch];
 | 
			
		||||
        IStructs.Fraction memory endRatio = cumulativeRewardsByPool[poolId][endEpoch];
 | 
			
		||||
        uint256 reward = LibFractions.scaleFractionalDifference(
 | 
			
		||||
            endRatio.numerator,
 | 
			
		||||
            endRatio.denominator,
 | 
			
		||||
            beginRatio.numerator,
 | 
			
		||||
            beginRatio.denominator,
 | 
			
		||||
            memberStakeOverInterval
 | 
			
		||||
        );
 | 
			
		||||
        return reward;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Sets the most recent cumulative reward for the pool.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch The epoch of the most recent cumulative reward.
 | 
			
		||||
    function _setMostRecentCumulativeReward(bytes32 poolId, uint256 epoch)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // load the current value, sanity check that we're not trying to go back in time
 | 
			
		||||
        uint256 currentMostRecentEpoch = cumulativeRewardsByPoolLastStored[poolId];
 | 
			
		||||
        assert(epoch >= currentMostRecentEpoch);
 | 
			
		||||
 | 
			
		||||
        // check if we should do any work
 | 
			
		||||
        if (epoch == currentMostRecentEpoch) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // unset the current most recent reward if it has no more references
 | 
			
		||||
        if (cumulativeRewardsByPoolReferenceCounter[poolId][currentMostRecentEpoch] == 0) {
 | 
			
		||||
            _unsetCumulativeReward(poolId, currentMostRecentEpoch);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // update state to reflect the most recent cumulative reward
 | 
			
		||||
        cumulativeRewardsByPoolLastStored[poolId] = epoch;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,6 +25,7 @@ import "../immutable/MixinStorage.sol";
 | 
			
		||||
import "../immutable/MixinConstants.sol";
 | 
			
		||||
import "../stake/MixinStakeBalances.sol";
 | 
			
		||||
import "./MixinStakingPoolRewardVault.sol";
 | 
			
		||||
import "./MixinCumulativeRewards.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract MixinStakingPoolRewards is
 | 
			
		||||
@@ -36,7 +37,8 @@ contract MixinStakingPoolRewards is
 | 
			
		||||
    MixinStakingPoolRewardVault,
 | 
			
		||||
    MixinScheduler,
 | 
			
		||||
    MixinStakeStorage,
 | 
			
		||||
    MixinStakeBalances
 | 
			
		||||
    MixinStakeBalances,
 | 
			
		||||
    MixinCumulativeRewards
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    using LibSafeMath for uint256;
 | 
			
		||||
@@ -50,110 +52,51 @@ contract MixinStakingPoolRewards is
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256 totalReward)
 | 
			
		||||
    {
 | 
			
		||||
        // cache some values to reduce sloads
 | 
			
		||||
        IStructs.StoredBalance memory delegatedStake = _loadUnsyncedBalance(delegatedStakeToPoolByOwner[member][poolId]);
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
 | 
			
		||||
        // value is always zero in these two scenarios:
 | 
			
		||||
        //   1. The owner's delegated is current as of this epoch: their rewards have been moved to the ETH vault.
 | 
			
		||||
        //   2. The current epoch is zero: delegation begins at epoch 1
 | 
			
		||||
        if (delegatedStake.currentEpoch == currentEpoch || currentEpoch == 0) return 0;
 | 
			
		||||
 | 
			
		||||
        // compute reward accumulated during `delegatedStake.currentEpoch`;
 | 
			
		||||
        uint256 rewardsAccumulatedDuringLastStoredEpoch = (delegatedStake.currentEpochBalance != 0)
 | 
			
		||||
            ? _computeMemberRewardOverInterval(
 | 
			
		||||
                poolId,
 | 
			
		||||
                delegatedStake.currentEpochBalance,
 | 
			
		||||
                delegatedStake.currentEpoch - 1,
 | 
			
		||||
                delegatedStake.currentEpoch
 | 
			
		||||
            )
 | 
			
		||||
            : 0;
 | 
			
		||||
 | 
			
		||||
        // compute the reward accumulated by the `next` balance;
 | 
			
		||||
        // this starts at `delegatedStake.currentEpoch + 1` and goes up until the last epoch, during which
 | 
			
		||||
        // rewards were accumulated. This is at most the most recently finalized epoch (current epoch - 1).
 | 
			
		||||
        uint256 rewardsAccumulatedAfterLastStoredEpoch = (cumulativeRewardsByPoolLastStored[poolId] > delegatedStake.currentEpoch)
 | 
			
		||||
            ? _computeMemberRewardOverInterval(
 | 
			
		||||
                poolId,
 | 
			
		||||
                delegatedStake.nextEpochBalance,
 | 
			
		||||
                delegatedStake.currentEpoch,
 | 
			
		||||
                cumulativeRewardsByPoolLastStored[poolId]
 | 
			
		||||
            )
 | 
			
		||||
            : 0;
 | 
			
		||||
 | 
			
		||||
        // compute the total reward
 | 
			
		||||
        totalReward = rewardsAccumulatedDuringLastStoredEpoch.safeAdd(rewardsAccumulatedAfterLastStoredEpoch);
 | 
			
		||||
        return totalReward;
 | 
			
		||||
        return _computeRewardBalanceOfDelegator(
 | 
			
		||||
            poolId,
 | 
			
		||||
            _loadUnsyncedBalance(delegatedStakeToPoolByOwner[member][poolId]),
 | 
			
		||||
            getCurrentEpoch()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Transfers a delegators accumulated rewards from the transient pool Reward Pool vault
 | 
			
		||||
    ///      to the Eth Vault. This is required before the member's stake in the pool can be
 | 
			
		||||
    ///      modified.
 | 
			
		||||
    /// @dev Syncs rewards for a delegator. This includes transferring rewards from
 | 
			
		||||
    /// the Reward Vault to the Eth Vault, and adding/removing dependencies on cumulative rewards.
 | 
			
		||||
    /// @param poolId Unique id of pool.
 | 
			
		||||
    /// @param member The member of the pool.
 | 
			
		||||
    function _transferDelegatorsAccumulatedRewardsToEthVault(bytes32 poolId, address member)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // there are no delegators in the first epoch
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
        if (currentEpoch == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // compute balance owed to delegator
 | 
			
		||||
        uint256 balance = computeRewardBalanceOfDelegator(poolId, member);
 | 
			
		||||
        if (balance == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // transfer from transient Reward Pool vault to ETH Vault
 | 
			
		||||
        _transferMemberBalanceToEthVault(poolId, member, balance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Initializes Cumulative Rewards for a given pool.
 | 
			
		||||
    function _initializeCumulativeRewards(bytes32 poolId)
 | 
			
		||||
    /// @param member of the pool.
 | 
			
		||||
    /// @param initialDelegatedStakeToPoolByOwner The member's delegated balance at the beginning of this transaction.
 | 
			
		||||
    /// @param finalDelegatedStakeToPoolByOwner The member's delegated balance at the end of this transaction.
 | 
			
		||||
    function _syncRewardsForDelegator(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        address member,
 | 
			
		||||
        IStructs.StoredBalance memory initialDelegatedStakeToPoolByOwner,
 | 
			
		||||
        IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner
 | 
			
		||||
    )
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        uint256 currentEpoch = getCurrentEpoch();
 | 
			
		||||
        cumulativeRewardsByPool[poolId][currentEpoch] = IStructs.Fraction({numerator: 0, denominator: MIN_TOKEN_VALUE});
 | 
			
		||||
        cumulativeRewardsByPoolLastStored[poolId] = currentEpoch;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev To compute a delegator's reward we must know the cumulative reward
 | 
			
		||||
    ///      at the epoch before they delegated. If they were already delegated then
 | 
			
		||||
    ///      we also need to know the value at the epoch in which they modified
 | 
			
		||||
    ///      their delegated stake for this pool. See `computeRewardBalanceOfDelegator`.
 | 
			
		||||
    /// @param poolId Unique Id of pool.
 | 
			
		||||
    /// @param epoch at which the stake was delegated by the delegator.
 | 
			
		||||
    function _syncCumulativeRewardsNeededByDelegator(bytes32 poolId, uint256 epoch)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        // set default value if staking at epoch 0
 | 
			
		||||
        if (epoch == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // transfer any rewards from the transient pool vault to the eth vault;
 | 
			
		||||
        // this must be done before we can modify the owner's portion of the delegator pool.
 | 
			
		||||
        _transferDelegatorRewardsToEthVault(
 | 
			
		||||
            poolId,
 | 
			
		||||
            member,
 | 
			
		||||
            initialDelegatedStakeToPoolByOwner,
 | 
			
		||||
            currentEpoch
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // cache a storage pointer to the cumulative rewards for `poolId` indexed by epoch.
 | 
			
		||||
        mapping (uint256 => IStructs.Fraction) storage cumulativeRewardsByPoolPtr = cumulativeRewardsByPool[poolId];
 | 
			
		||||
        // add dependencies on cumulative rewards for this epoch and the previous epoch, if necessary.
 | 
			
		||||
        _setCumulativeRewardDependenciesForDelegator(
 | 
			
		||||
            poolId,
 | 
			
		||||
            finalDelegatedStakeToPoolByOwner,
 | 
			
		||||
            true
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 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];
 | 
			
		||||
 | 
			
		||||
        // copy our most up-to-date cumulative rewards for last epoch, if necessary.
 | 
			
		||||
        uint256 lastEpoch = currentEpoch.safeSub(1);
 | 
			
		||||
        if (cumulativeRewardsLastStored != lastEpoch) {
 | 
			
		||||
            cumulativeRewardsByPoolPtr[lastEpoch] = mostRecentCumulativeRewards;
 | 
			
		||||
            cumulativeRewardsByPoolLastStored[poolId] = lastEpoch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // copy our most up-to-date cumulative rewards for last epoch, if necessary.
 | 
			
		||||
        // this is necessary if the pool does not earn any rewards this epoch;
 | 
			
		||||
        // if it does then this value may be overwritten when the epoch is finalized.
 | 
			
		||||
        if (!_isCumulativeRewardSet(cumulativeRewardsByPoolPtr[epoch])) {
 | 
			
		||||
            cumulativeRewardsByPoolPtr[epoch] = mostRecentCumulativeRewards;
 | 
			
		||||
        }
 | 
			
		||||
        // remove dependencies on previous cumulative rewards, if they are no longer needed.
 | 
			
		||||
        _setCumulativeRewardDependenciesForDelegator(
 | 
			
		||||
            poolId,
 | 
			
		||||
            initialDelegatedStakeToPoolByOwner,
 | 
			
		||||
            false
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Records a reward for delegators. This adds to the `cumulativeRewardsByPool`.
 | 
			
		||||
@@ -191,51 +134,130 @@ contract MixinStakingPoolRewards is
 | 
			
		||||
            denominator.safeDiv(MIN_TOKEN_VALUE)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // store cumulative rewards
 | 
			
		||||
        cumulativeRewardsByPoolPtr[epoch] = IStructs.Fraction({
 | 
			
		||||
            numerator: numeratorNormalized,
 | 
			
		||||
            denominator: denominatorNormalized
 | 
			
		||||
        });
 | 
			
		||||
        cumulativeRewardsByPoolLastStored[poolId] = epoch;
 | 
			
		||||
        // store cumulative rewards and set most recent
 | 
			
		||||
        _setCumulativeReward(
 | 
			
		||||
            poolId,
 | 
			
		||||
            epoch,
 | 
			
		||||
            IStructs.Fraction({
 | 
			
		||||
                numerator: numeratorNormalized,
 | 
			
		||||
                denominator: denominatorNormalized
 | 
			
		||||
            })
 | 
			
		||||
        );
 | 
			
		||||
        _setMostRecentCumulativeReward(poolId, epoch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Computes a member's reward over a given epoch interval.
 | 
			
		||||
    /// @param poolId Uniqud Id of pool.
 | 
			
		||||
    /// @param memberStakeOverInterval Stake delegated to pool by meber over the interval.
 | 
			
		||||
    /// @param beginEpoch beginning of interval.
 | 
			
		||||
    /// @param endEpoch end of interval.
 | 
			
		||||
    /// @return rewards accumulated over interval [beginEpoch, endEpoch]
 | 
			
		||||
    function _computeMemberRewardOverInterval(
 | 
			
		||||
    /// @dev Transfers a delegators accumulated rewards from the transient pool Reward Pool vault
 | 
			
		||||
    ///      to the Eth Vault. This is required before the member's stake in the pool can be
 | 
			
		||||
    ///      modified.
 | 
			
		||||
    /// @param poolId Unique id of pool.
 | 
			
		||||
    /// @param member The member of the pool.
 | 
			
		||||
    function _transferDelegatorRewardsToEthVault(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 memberStakeOverInterval,
 | 
			
		||||
        uint256 beginEpoch,
 | 
			
		||||
        uint256 endEpoch
 | 
			
		||||
        address member,
 | 
			
		||||
        IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner,
 | 
			
		||||
        uint256 currentEpoch
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
    {
 | 
			
		||||
        // compute balance owed to delegator
 | 
			
		||||
        uint256 balance = _computeRewardBalanceOfDelegator(
 | 
			
		||||
            poolId,
 | 
			
		||||
            unsyncedDelegatedStakeToPoolByOwner,
 | 
			
		||||
            currentEpoch
 | 
			
		||||
        );
 | 
			
		||||
        if (balance == 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // transfer from transient Reward Pool vault to ETH Vault
 | 
			
		||||
        _transferMemberBalanceToEthVault(poolId, member, balance);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev Computes the reward balance in ETH of a specific member of a pool.
 | 
			
		||||
    /// @param poolId Unique id of pool.
 | 
			
		||||
    /// @param unsyncedDelegatedStakeToPoolByOwner Unsynced delegated stake to pool by owner
 | 
			
		||||
    /// @param currentEpoch The epoch in which this call is executing
 | 
			
		||||
    /// @return totalReward Balance in ETH.
 | 
			
		||||
    function _computeRewardBalanceOfDelegator(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        IStructs.StoredBalance memory unsyncedDelegatedStakeToPoolByOwner,
 | 
			
		||||
        uint256 currentEpoch
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
        view
 | 
			
		||||
        returns (uint256)
 | 
			
		||||
        returns (uint256 totalReward)
 | 
			
		||||
    {
 | 
			
		||||
        IStructs.Fraction memory beginRatio = cumulativeRewardsByPool[poolId][beginEpoch];
 | 
			
		||||
        IStructs.Fraction memory endRatio = cumulativeRewardsByPool[poolId][endEpoch];
 | 
			
		||||
        uint256 reward = LibFractions.scaleFractionalDifference(
 | 
			
		||||
            endRatio.numerator,
 | 
			
		||||
            endRatio.denominator,
 | 
			
		||||
            beginRatio.numerator,
 | 
			
		||||
            beginRatio.denominator,
 | 
			
		||||
            memberStakeOverInterval
 | 
			
		||||
        );
 | 
			
		||||
        return reward;
 | 
			
		||||
        // value is always zero in these two scenarios:
 | 
			
		||||
        //   1. The owner's delegated is current as of this epoch: their rewards have been moved to the ETH vault.
 | 
			
		||||
        //   2. The current epoch is zero: delegation begins at epoch 1
 | 
			
		||||
        if (unsyncedDelegatedStakeToPoolByOwner.currentEpoch == currentEpoch || currentEpoch == 0) return 0;
 | 
			
		||||
 | 
			
		||||
        // compute reward accumulated during `delegatedStake.currentEpoch`;
 | 
			
		||||
        uint256 rewardsAccumulatedDuringLastStoredEpoch = (unsyncedDelegatedStakeToPoolByOwner.currentEpochBalance != 0)
 | 
			
		||||
            ? _computeMemberRewardOverInterval(
 | 
			
		||||
                poolId,
 | 
			
		||||
                unsyncedDelegatedStakeToPoolByOwner.currentEpochBalance,
 | 
			
		||||
                uint256(unsyncedDelegatedStakeToPoolByOwner.currentEpoch).safeSub(1),
 | 
			
		||||
                unsyncedDelegatedStakeToPoolByOwner.currentEpoch
 | 
			
		||||
            )
 | 
			
		||||
            : 0;
 | 
			
		||||
 | 
			
		||||
        // compute the reward accumulated by the `next` balance;
 | 
			
		||||
        // this starts at `delegatedStake.currentEpoch + 1` and goes up until the last epoch, during which
 | 
			
		||||
        // rewards were accumulated. This is at most the most recently finalized epoch (current epoch - 1).
 | 
			
		||||
        uint256 rewardsAccumulatedAfterLastStoredEpoch = (cumulativeRewardsByPoolLastStored[poolId] > unsyncedDelegatedStakeToPoolByOwner.currentEpoch)
 | 
			
		||||
            ? _computeMemberRewardOverInterval(
 | 
			
		||||
                poolId,
 | 
			
		||||
                unsyncedDelegatedStakeToPoolByOwner.nextEpochBalance,
 | 
			
		||||
                unsyncedDelegatedStakeToPoolByOwner.currentEpoch,
 | 
			
		||||
                cumulativeRewardsByPoolLastStored[poolId]
 | 
			
		||||
            )
 | 
			
		||||
            : 0;
 | 
			
		||||
 | 
			
		||||
        // compute the total reward
 | 
			
		||||
        totalReward = rewardsAccumulatedDuringLastStoredEpoch.safeAdd(rewardsAccumulatedAfterLastStoredEpoch);
 | 
			
		||||
        return totalReward;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// @dev returns true iff Cumulative Rewards are set
 | 
			
		||||
    function _isCumulativeRewardSet(IStructs.Fraction memory cumulativeReward)
 | 
			
		||||
    /// @dev Adds or removes cumulative reward dependencies for a delegator.
 | 
			
		||||
    /// A delegator always depends on the cumulative reward for the current epoch.
 | 
			
		||||
    /// They will also depend on the previous epoch's reward, if they are already staked with the input pool.
 | 
			
		||||
    /// @param poolId Unique id of pool.
 | 
			
		||||
    /// @param delegatedStakeToPoolByOwner Amount of stake the member has delegated to the pool.
 | 
			
		||||
    /// @param isDependent is true iff adding a dependency. False, otherwise.
 | 
			
		||||
    function _setCumulativeRewardDependenciesForDelegator(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        IStructs.StoredBalance memory delegatedStakeToPoolByOwner,
 | 
			
		||||
        bool isDependent
 | 
			
		||||
    )
 | 
			
		||||
        private
 | 
			
		||||
        pure
 | 
			
		||||
        returns (bool)
 | 
			
		||||
    {
 | 
			
		||||
        // we use the denominator as a proxy for whether the cumulative
 | 
			
		||||
        // reward is set, as setting the cumulative reward always sets this
 | 
			
		||||
        // field to at least 1.
 | 
			
		||||
        return cumulativeReward.denominator != 0;
 | 
			
		||||
        // if this delegator is not yet initialized then there's no dependency to unset.
 | 
			
		||||
        if (!isDependent && !delegatedStakeToPoolByOwner.isInitialized) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // get the most recent cumulative reward, which will serve as a reference point when updating dependencies
 | 
			
		||||
        IStructs.CumulativeRewardInfo memory mostRecentCumulativeRewardInfo = _getMostRecentCumulativeRewardInfo(poolId);
 | 
			
		||||
 | 
			
		||||
        // record dependency on `lastEpoch`
 | 
			
		||||
        if (delegatedStakeToPoolByOwner.currentEpoch > 0 && delegatedStakeToPoolByOwner.currentEpochBalance != 0) {
 | 
			
		||||
            _addOrRemoveDependencyOnCumulativeReward(
 | 
			
		||||
                poolId,
 | 
			
		||||
                uint256(delegatedStakeToPoolByOwner.currentEpoch).safeSub(1),
 | 
			
		||||
                mostRecentCumulativeRewardInfo,
 | 
			
		||||
                isDependent
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // record dependency on current epoch.
 | 
			
		||||
        if (delegatedStakeToPoolByOwner.currentEpochBalance != 0 || delegatedStakeToPoolByOwner.nextEpochBalance != 0) {
 | 
			
		||||
            _addOrRemoveDependencyOnCumulativeReward(
 | 
			
		||||
                poolId,
 | 
			
		||||
                delegatedStakeToPoolByOwner.currentEpoch,
 | 
			
		||||
                mostRecentCumulativeRewardInfo,
 | 
			
		||||
                isDependent
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
  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 "./TestStaking.sol";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
contract TestCumulativeRewardTracking is
 | 
			
		||||
    TestStaking
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    event SetMostRecentCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    event UnsetCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    event SetCumulativeReward(
 | 
			
		||||
        bytes32 poolId,
 | 
			
		||||
        uint256 epoch
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function _setMostRecentCumulativeReward(bytes32 poolId, uint256 epoch)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        emit SetMostRecentCumulativeReward(poolId, epoch);
 | 
			
		||||
        MixinCumulativeRewards._setMostRecentCumulativeReward(poolId, epoch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _unsetCumulativeReward(bytes32 poolId, uint256 epoch)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        emit UnsetCumulativeReward(poolId, epoch);
 | 
			
		||||
        MixinCumulativeRewards._unsetCumulativeReward(poolId, epoch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function _setCumulativeReward(bytes32 poolId, uint256 epoch, IStructs.Fraction memory value)
 | 
			
		||||
        internal
 | 
			
		||||
    {
 | 
			
		||||
        emit SetCumulativeReward(poolId, epoch);
 | 
			
		||||
        MixinCumulativeRewards._setCumulativeReward(poolId, epoch, value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -97,6 +97,9 @@ contract TestStorageLayout is
 | 
			
		||||
            if sub(cumulativeRewardsByPool_slot, slot) { revertIncorrectStorageSlot() }
 | 
			
		||||
            slot := add(slot, 1)
 | 
			
		||||
 | 
			
		||||
            if sub(cumulativeRewardsByPoolReferenceCounter_slot, slot) { revertIncorrectStorageSlot() }
 | 
			
		||||
            slot := add(slot, 1)
 | 
			
		||||
 | 
			
		||||
            if sub(cumulativeRewardsByPoolLastStored_slot, slot) { revertIncorrectStorageSlot() }
 | 
			
		||||
            slot := add(slot, 1)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
 | 
			
		||||
        "abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStorageInit|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStakingProxy|TestStorageLayout|ZrxVault).json"
 | 
			
		||||
        "abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStorageInit|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestCumulativeRewardTracking|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStakingProxy|TestStorageLayout|ZrxVault).json"
 | 
			
		||||
    },
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ 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 MixinConstants from '../generated-artifacts/MixinConstants.json';
 | 
			
		||||
import * as MixinCumulativeRewards from '../generated-artifacts/MixinCumulativeRewards.json';
 | 
			
		||||
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
 | 
			
		||||
import * as MixinEthVault from '../generated-artifacts/MixinEthVault.json';
 | 
			
		||||
import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json';
 | 
			
		||||
@@ -41,6 +42,7 @@ import * as Staking from '../generated-artifacts/Staking.json';
 | 
			
		||||
import * as StakingPoolRewardVault from '../generated-artifacts/StakingPoolRewardVault.json';
 | 
			
		||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
 | 
			
		||||
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json';
 | 
			
		||||
import * as TestCumulativeRewardTracking from '../generated-artifacts/TestCumulativeRewardTracking.json';
 | 
			
		||||
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
 | 
			
		||||
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
 | 
			
		||||
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
 | 
			
		||||
@@ -79,6 +81,7 @@ export const artifacts = {
 | 
			
		||||
    MixinStakeBalances: MixinStakeBalances as ContractArtifact,
 | 
			
		||||
    MixinStakeStorage: MixinStakeStorage as ContractArtifact,
 | 
			
		||||
    MixinZrxVault: MixinZrxVault as ContractArtifact,
 | 
			
		||||
    MixinCumulativeRewards: MixinCumulativeRewards as ContractArtifact,
 | 
			
		||||
    MixinEthVault: MixinEthVault as ContractArtifact,
 | 
			
		||||
    MixinStakingPool: MixinStakingPool as ContractArtifact,
 | 
			
		||||
    MixinStakingPoolRewardVault: MixinStakingPoolRewardVault as ContractArtifact,
 | 
			
		||||
@@ -90,6 +93,7 @@ export const artifacts = {
 | 
			
		||||
    StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
 | 
			
		||||
    ZrxVault: ZrxVault as ContractArtifact,
 | 
			
		||||
    TestCobbDouglas: TestCobbDouglas as ContractArtifact,
 | 
			
		||||
    TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
 | 
			
		||||
    TestInitTarget: TestInitTarget as ContractArtifact,
 | 
			
		||||
    TestLibFixedMath: TestLibFixedMath as ContractArtifact,
 | 
			
		||||
    TestLibProxy: TestLibProxy as ContractArtifact,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ 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_constants';
 | 
			
		||||
export * from '../generated-wrappers/mixin_cumulative_rewards';
 | 
			
		||||
export * from '../generated-wrappers/mixin_deployment_constants';
 | 
			
		||||
export * from '../generated-wrappers/mixin_eth_vault';
 | 
			
		||||
export * from '../generated-wrappers/mixin_exchange_fees';
 | 
			
		||||
@@ -39,6 +40,7 @@ export * from '../generated-wrappers/staking';
 | 
			
		||||
export * from '../generated-wrappers/staking_pool_reward_vault';
 | 
			
		||||
export * from '../generated-wrappers/staking_proxy';
 | 
			
		||||
export * from '../generated-wrappers/test_cobb_douglas';
 | 
			
		||||
export * from '../generated-wrappers/test_cumulative_reward_tracking';
 | 
			
		||||
export * from '../generated-wrappers/test_init_target';
 | 
			
		||||
export * from '../generated-wrappers/test_lib_fixed_math';
 | 
			
		||||
export * from '../generated-wrappers/test_lib_proxy';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										404
									
								
								contracts/staking/test/cumulative_reward_tracking_test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								contracts/staking/test/cumulative_reward_tracking_test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,404 @@
 | 
			
		||||
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
 | 
			
		||||
import { blockchainTests, describe } from '@0x/contracts-test-utils';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { artifacts } from '../src';
 | 
			
		||||
 | 
			
		||||
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
 | 
			
		||||
import { CumulativeRewardTrackingSimulation, TestAction } from './utils/cumulative_reward_tracking_simulation';
 | 
			
		||||
 | 
			
		||||
// tslint:disable:no-unnecessary-type-assertion
 | 
			
		||||
// tslint:disable:max-file-line-count
 | 
			
		||||
blockchainTests.resets('Cumulative Reward Tracking', env => {
 | 
			
		||||
    // tokens & addresses
 | 
			
		||||
    let accounts: string[];
 | 
			
		||||
    let owner: string;
 | 
			
		||||
    // wrappers
 | 
			
		||||
    let stakingApiWrapper: StakingApiWrapper;
 | 
			
		||||
    let simulation: CumulativeRewardTrackingSimulation;
 | 
			
		||||
    // let testWrapper: TestRewardBalancesContract;
 | 
			
		||||
    let erc20Wrapper: ERC20Wrapper;
 | 
			
		||||
 | 
			
		||||
    // tests
 | 
			
		||||
    before(async () => {
 | 
			
		||||
        // create accounts
 | 
			
		||||
        accounts = await env.getAccountAddressesAsync();
 | 
			
		||||
        owner = accounts[0];
 | 
			
		||||
        const actors = accounts.slice(1);
 | 
			
		||||
        // set up ERC20Wrapper
 | 
			
		||||
        erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
 | 
			
		||||
        // deploy staking contracts
 | 
			
		||||
        stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
 | 
			
		||||
        simulation = new CumulativeRewardTrackingSimulation(stakingApiWrapper, actors);
 | 
			
		||||
        await simulation.deployAndConfigureTestContractsAsync(env);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe('Tracking Cumulative Rewards (CR)', () => {
 | 
			
		||||
        it('should set CR and Most Recent CR when a pool is created', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [],
 | 
			
		||||
                [TestAction.CreatePool],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 0 }, { event: 'SetMostRecentCumulativeReward', epoch: 0 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should not set CR or Most Recent CR when values already exist for the current epoch', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                ],
 | 
			
		||||
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                ],
 | 
			
		||||
                [],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should not set CR or Most Recent CR when user re-delegates and values already exist for the current epoch', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                ],
 | 
			
		||||
                [],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should not set CR or Most Recent CR when user undelegagtes and values already exist for the current epoch', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                ],
 | 
			
		||||
                [],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should (i) set CR and Most Recent CR when delegating, and (ii) unset previous Most Recent CR if there are no dependencies', async () => {
 | 
			
		||||
            // since there was no delegation in epoch 0 there is no longer a dependency on the CR for epoch 0
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [TestAction.CreatePool, TestAction.Finalize],
 | 
			
		||||
                [TestAction.Delegate],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should (i) set CR and Most Recent CR when delegating, and (ii) NOT unset previous Most Recent CR if there are dependencies', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                ],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 1 }, { event: 'SetMostRecentCumulativeReward', epoch: 1 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should not unset the current Most Recent CR, even if there are no dependencies', async () => {
 | 
			
		||||
            // note - we never unset the current Most Recent CR; only ever a previous value - given there are no depencies from delegators.
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // does nothing. This delegator no longer has dependency, but the most recent CR is 1 so we don't remove.
 | 
			
		||||
                ],
 | 
			
		||||
                [],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and update Most Recent CR when delegating more stake', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 2 }, { event: 'SetMostRecentCumulativeReward', epoch: 2 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and update Most Recent CR when undelegating', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 2 }, { event: 'SetMostRecentCumulativeReward', epoch: 2 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and update Most Recent CR when undelegating, plus remove the CR that is no longer depends on.', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate,
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and update Most Recent CR when redelegating, plus remove the CR that is no longer depends on.', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and Most Recent CR when a reward is earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR, as there is alread a CR set for this epoch.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.PayProtocolFee,
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Finalize, // adds a CR for epoch 1, plus updates most recent CR
 | 
			
		||||
                ],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 1 }, { event: 'SetMostRecentCumulativeReward', epoch: 1 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when redelegating, the epoch following a reward was earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 2; moves to epoch 3
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when redelegating, the epoch following a reward was earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 2; moves to epoch 3
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when redelegating, one full epoch after a reward was earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 2; moves to epoch 3
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 4
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 4 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 4 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when redelegating, one full epoch after a reward was earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 2; moves to epoch 3
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when delegating for the first time in an epoch with no CR', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 0; moves to epoch 1
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when delegating for the first time in an epoch with no CR, after an epoch where a reward was earned', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 0; moves to epoch 1
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 1 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set CR and update Most Recent CR when delegating in two subsequent epochs', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [{ event: 'SetCumulativeReward', epoch: 2 }, { event: 'SetMostRecentCumulativeReward', epoch: 2 }],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when delegating in two subsequent epochs, when there is an old CR to clear', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                    TestAction.Finalize, //  moves to epoch 2
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 3
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 3. Sets most recent CR to epoch 3.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 4
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 3. Sets most recent CR to epoch 3.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 4 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 4 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 2 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR re-delegating after one full epoch', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                    TestAction.Finalize, //  moves to epoch 2
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 3
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 1 to epoch 3. Sets most recent CR to epoch 3.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 2 },
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
        it('should set/unset CR and update Most Recent CR when redelegating after receiving a reward', async () => {
 | 
			
		||||
            await simulation.runTestAsync(
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.CreatePool, // creates CR in epoch 0
 | 
			
		||||
                    TestAction.Delegate, // does nothing wrt CR
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 1
 | 
			
		||||
                    TestAction.Delegate, // copies CR from epoch 0 to epoch 1. Sets most recent CR to epoch 1.
 | 
			
		||||
                    TestAction.Finalize, // moves to epoch 2
 | 
			
		||||
                    TestAction.PayProtocolFee, // this means a CR will be available upon finalization
 | 
			
		||||
                    TestAction.Finalize, // creates new CR for epoch 2; moves to epoch 3
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    TestAction.Undelegate, // copies CR from epoch 1 to epoch 2. Sets most recent CR to epoch 2.
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    { event: 'SetCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'SetMostRecentCumulativeReward', epoch: 3 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 0 },
 | 
			
		||||
                    { event: 'UnsetCumulativeReward', epoch: 1 },
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// tslint:enable:no-unnecessary-type-assertion
 | 
			
		||||
@@ -177,6 +177,15 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
            await finalizer.finalizeAsync([{ reward: fee, poolId }]);
 | 
			
		||||
        };
 | 
			
		||||
        const ZERO = new BigNumber(0);
 | 
			
		||||
        it('Reward balance should be zero if not delegated', async () => {
 | 
			
		||||
            // sanity balances - all zero
 | 
			
		||||
            await validateEndBalances({});
 | 
			
		||||
        });
 | 
			
		||||
        it('Reward balance should be zero if not delegated, when epoch is greater than 0', async () => {
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
            // sanity balances - all zero
 | 
			
		||||
            await validateEndBalances({});
 | 
			
		||||
        });
 | 
			
		||||
        it('Reward balance should be zero in same epoch as delegation', async () => {
 | 
			
		||||
            const amount = toBaseUnitAmount(4);
 | 
			
		||||
            await stakers[0].stakeAsync(amount);
 | 
			
		||||
@@ -269,6 +278,7 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
        });
 | 
			
		||||
        it('Should split pool reward between delegators, when they join in different epochs', async () => {
 | 
			
		||||
            // first staker delegates (epoch 0)
 | 
			
		||||
 | 
			
		||||
            const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
 | 
			
		||||
            const totalStakeAmount = toBaseUnitAmount(10);
 | 
			
		||||
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
			
		||||
@@ -277,8 +287,10 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                stakeAmounts[0],
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // skip epoch, so staker can start earning rewards
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
 | 
			
		||||
            // second staker delegates (epoch 1)
 | 
			
		||||
            await stakers[1].stakeAsync(stakeAmounts[1]);
 | 
			
		||||
            await stakers[1].moveStakeAsync(
 | 
			
		||||
@@ -286,9 +298,11 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                stakeAmounts[1],
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // skip epoch, so staker can start earning rewards
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
            // finalize
 | 
			
		||||
 | 
			
		||||
            const reward = toBaseUnitAmount(10);
 | 
			
		||||
            await payProtocolFeeAndFinalize(reward);
 | 
			
		||||
            // sanity check final balances
 | 
			
		||||
@@ -499,15 +513,19 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
            // earn reward
 | 
			
		||||
            await payProtocolFeeAndFinalize(rewardForDelegator);
 | 
			
		||||
 | 
			
		||||
            // undelegate stake and finalize epoch
 | 
			
		||||
            await stakers[0].moveStakeAsync(
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                stakeAmount,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
 | 
			
		||||
            // this should not go do the delegator
 | 
			
		||||
            await payProtocolFeeAndFinalize(rewardNotForDelegator);
 | 
			
		||||
 | 
			
		||||
            // sanity check final balances
 | 
			
		||||
            await validateEndBalances({
 | 
			
		||||
                stakerEthVaultBalance_1: rewardForDelegator,
 | 
			
		||||
@@ -602,6 +620,43 @@ blockchainTests.resets('Testing Rewards', env => {
 | 
			
		||||
                membersRewardVaultBalance: rewardsForDelegator[1],
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        it('Should collect fees correctly when there is a payout for `currentEpochBalance` but not `nextEpochBalance`', async () => {
 | 
			
		||||
            // first staker delegates (epoch 0)
 | 
			
		||||
            const rewardForDelegator = toBaseUnitAmount(10);
 | 
			
		||||
            const stakeAmount = toBaseUnitAmount(4);
 | 
			
		||||
            await stakers[0].stakeAsync(stakeAmount);
 | 
			
		||||
            await stakers[0].moveStakeAsync(
 | 
			
		||||
                new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                stakeAmount,
 | 
			
		||||
            );
 | 
			
		||||
            // skip epoch, so staker can start earning rewards
 | 
			
		||||
            await payProtocolFeeAndFinalize();
 | 
			
		||||
            // undelegate stake and finalize epoch
 | 
			
		||||
            await stakers[0].moveStakeAsync(
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                stakeAmount,
 | 
			
		||||
            );
 | 
			
		||||
            // this should go to the delegator
 | 
			
		||||
            await payProtocolFeeAndFinalize(rewardForDelegator);
 | 
			
		||||
 | 
			
		||||
            // delegate stake ~ this will result in a payout where rewards are computed on
 | 
			
		||||
            // the balance's `currentEpochBalance` field but not the `nextEpochBalance` field.
 | 
			
		||||
            await stakers[0].moveStakeAsync(
 | 
			
		||||
                new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                new StakeInfo(StakeStatus.Delegated, poolId),
 | 
			
		||||
                stakeAmount,
 | 
			
		||||
            );
 | 
			
		||||
            // sanity check final balances
 | 
			
		||||
            await validateEndBalances({
 | 
			
		||||
                stakerRewardVaultBalance_1: ZERO,
 | 
			
		||||
                stakerEthVaultBalance_1: rewardForDelegator,
 | 
			
		||||
                operatorRewardVaultBalance: ZERO,
 | 
			
		||||
                poolRewardVaultBalance: ZERO,
 | 
			
		||||
                membersRewardVaultBalance: ZERO,
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
// tslint:enable:no-unnecessary-type-assertion
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,172 @@
 | 
			
		||||
import { BlockchainTestsEnvironment, expect, txDefaults } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { DecodedLogArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { TestCumulativeRewardTrackingContract } from '../../generated-wrappers/test_cumulative_reward_tracking';
 | 
			
		||||
import { artifacts } from '../../src';
 | 
			
		||||
 | 
			
		||||
import { StakingApiWrapper } from './api_wrapper';
 | 
			
		||||
import { toBaseUnitAmount } from './number_utils';
 | 
			
		||||
import { StakeInfo, StakeStatus } from './types';
 | 
			
		||||
 | 
			
		||||
export enum TestAction {
 | 
			
		||||
    Finalize,
 | 
			
		||||
    Delegate,
 | 
			
		||||
    Undelegate,
 | 
			
		||||
    PayProtocolFee,
 | 
			
		||||
    CreatePool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TestLog {
 | 
			
		||||
    event: string;
 | 
			
		||||
    epoch: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CumulativeRewardTrackingSimulation {
 | 
			
		||||
    private readonly _amountToStake = toBaseUnitAmount(100);
 | 
			
		||||
    private readonly _protocolFeeAmount = new BigNumber(10);
 | 
			
		||||
    private readonly _stakingApiWrapper: StakingApiWrapper;
 | 
			
		||||
    private readonly _staker: string;
 | 
			
		||||
    private readonly _poolOperator: string;
 | 
			
		||||
    private readonly _takerAddress: string;
 | 
			
		||||
    private readonly _exchangeAddress: string;
 | 
			
		||||
    private _testCumulativeRewardTrackingContract?: TestCumulativeRewardTrackingContract;
 | 
			
		||||
    private _poolId: string;
 | 
			
		||||
 | 
			
		||||
    private static _extractTestLogs(txReceiptLogs: DecodedLogArgs[]): TestLog[] {
 | 
			
		||||
        const logs = [];
 | 
			
		||||
        for (const log of txReceiptLogs) {
 | 
			
		||||
            if ((log as any).event === 'SetMostRecentCumulativeReward') {
 | 
			
		||||
                logs.push({
 | 
			
		||||
                    event: 'SetMostRecentCumulativeReward',
 | 
			
		||||
                    epoch: (log as any).args.epoch,
 | 
			
		||||
                });
 | 
			
		||||
            } else if ((log as any).event === 'SetCumulativeReward') {
 | 
			
		||||
                logs.push({
 | 
			
		||||
                    event: 'SetCumulativeReward',
 | 
			
		||||
                    epoch: (log as any).args.epoch,
 | 
			
		||||
                });
 | 
			
		||||
            } else if ((log as any).event === 'UnsetCumulativeReward') {
 | 
			
		||||
                logs.push({
 | 
			
		||||
                    event: 'UnsetCumulativeReward',
 | 
			
		||||
                    epoch: (log as any).args.epoch,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return logs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static _assertTestLogs(expectedSequence: TestLog[], txReceiptLogs: DecodedLogArgs[]): void {
 | 
			
		||||
        const logs = CumulativeRewardTrackingSimulation._extractTestLogs(txReceiptLogs);
 | 
			
		||||
        expect(logs.length).to.be.equal(expectedSequence.length);
 | 
			
		||||
        for (let i = 0; i < expectedSequence.length; i++) {
 | 
			
		||||
            const expectedLog = expectedSequence[i];
 | 
			
		||||
            const actualLog = logs[i];
 | 
			
		||||
            expect(expectedLog.event, `testing event name of ${JSON.stringify(expectedLog)}`).to.be.equal(
 | 
			
		||||
                actualLog.event,
 | 
			
		||||
            );
 | 
			
		||||
            expect(expectedLog.epoch, `testing epoch of ${JSON.stringify(expectedLog)}`).to.be.equal(actualLog.epoch);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(stakingApiWrapper: StakingApiWrapper, actors: string[]) {
 | 
			
		||||
        this._stakingApiWrapper = stakingApiWrapper;
 | 
			
		||||
        // setup actors
 | 
			
		||||
        this._staker = actors[0];
 | 
			
		||||
        this._poolOperator = actors[1];
 | 
			
		||||
        this._takerAddress = actors[2];
 | 
			
		||||
        this._exchangeAddress = actors[3];
 | 
			
		||||
        this._poolId = '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async deployAndConfigureTestContractsAsync(env: BlockchainTestsEnvironment): Promise<void> {
 | 
			
		||||
        // set exchange address
 | 
			
		||||
        await this._stakingApiWrapper.stakingContract.addExchangeAddress.awaitTransactionSuccessAsync(
 | 
			
		||||
            this._exchangeAddress,
 | 
			
		||||
        );
 | 
			
		||||
        this._testCumulativeRewardTrackingContract = await TestCumulativeRewardTrackingContract.deployFrom0xArtifactAsync(
 | 
			
		||||
            artifacts.TestCumulativeRewardTracking,
 | 
			
		||||
            env.provider,
 | 
			
		||||
            txDefaults,
 | 
			
		||||
            artifacts,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getTestCumulativeRewardTrackingContract(): TestCumulativeRewardTrackingContract {
 | 
			
		||||
        if (this._testCumulativeRewardTrackingContract === undefined) {
 | 
			
		||||
            throw new Error(`Contract has not been deployed. Run 'deployAndConfigureTestContractsAsync'.`);
 | 
			
		||||
        }
 | 
			
		||||
        return this._testCumulativeRewardTrackingContract;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async runTestAsync(
 | 
			
		||||
        initActions: TestAction[],
 | 
			
		||||
        testActions: TestAction[],
 | 
			
		||||
        expectedTestLogs: TestLog[],
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        await this._executeActionsAsync(initActions);
 | 
			
		||||
        await this._stakingApiWrapper.stakingProxyContract.attachStakingContract.awaitTransactionSuccessAsync(
 | 
			
		||||
            this.getTestCumulativeRewardTrackingContract().address,
 | 
			
		||||
        );
 | 
			
		||||
        const testLogs = await this._executeActionsAsync(testActions);
 | 
			
		||||
        CumulativeRewardTrackingSimulation._assertTestLogs(expectedTestLogs, testLogs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async _executeActionsAsync(actions: TestAction[]): Promise<DecodedLogArgs[]> {
 | 
			
		||||
        let logs: DecodedLogArgs[] = [];
 | 
			
		||||
        for (const action of actions) {
 | 
			
		||||
            let txReceipt: TransactionReceiptWithDecodedLogs;
 | 
			
		||||
            switch (action) {
 | 
			
		||||
                case TestAction.Finalize:
 | 
			
		||||
                    txReceipt = await this._stakingApiWrapper.utils.skipToNextEpochAsync();
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case TestAction.Delegate:
 | 
			
		||||
                    await this._stakingApiWrapper.stakingContract.stake.sendTransactionAsync(this._amountToStake, {
 | 
			
		||||
                        from: this._staker,
 | 
			
		||||
                    });
 | 
			
		||||
                    txReceipt = await this._stakingApiWrapper.stakingContract.moveStake.awaitTransactionSuccessAsync(
 | 
			
		||||
                        new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                        new StakeInfo(StakeStatus.Delegated, this._poolId),
 | 
			
		||||
                        this._amountToStake,
 | 
			
		||||
                        { from: this._staker },
 | 
			
		||||
                    );
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case TestAction.Undelegate:
 | 
			
		||||
                    txReceipt = await this._stakingApiWrapper.stakingContract.moveStake.awaitTransactionSuccessAsync(
 | 
			
		||||
                        new StakeInfo(StakeStatus.Delegated, this._poolId),
 | 
			
		||||
                        new StakeInfo(StakeStatus.Active),
 | 
			
		||||
                        this._amountToStake,
 | 
			
		||||
                        { from: this._staker },
 | 
			
		||||
                    );
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case TestAction.PayProtocolFee:
 | 
			
		||||
                    txReceipt = await this._stakingApiWrapper.stakingContract.payProtocolFee.awaitTransactionSuccessAsync(
 | 
			
		||||
                        this._poolOperator,
 | 
			
		||||
                        this._takerAddress,
 | 
			
		||||
                        this._protocolFeeAmount,
 | 
			
		||||
                        { from: this._exchangeAddress, value: this._protocolFeeAmount },
 | 
			
		||||
                    );
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case TestAction.CreatePool:
 | 
			
		||||
                    txReceipt = await this._stakingApiWrapper.stakingContract.createStakingPool.awaitTransactionSuccessAsync(
 | 
			
		||||
                        0,
 | 
			
		||||
                        true,
 | 
			
		||||
                        { from: this._poolOperator },
 | 
			
		||||
                    );
 | 
			
		||||
                    const createStakingPoolLog = txReceipt.logs[0];
 | 
			
		||||
                    this._poolId = (createStakingPoolLog as any).args.poolId;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new Error('Unrecognized test action');
 | 
			
		||||
            }
 | 
			
		||||
            logs = logs.concat(txReceipt.logs);
 | 
			
		||||
        }
 | 
			
		||||
        return logs;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,6 +19,7 @@
 | 
			
		||||
        "generated-artifacts/LibSafeDowncast.json",
 | 
			
		||||
        "generated-artifacts/LibStakingRichErrors.json",
 | 
			
		||||
        "generated-artifacts/MixinConstants.json",
 | 
			
		||||
        "generated-artifacts/MixinCumulativeRewards.json",
 | 
			
		||||
        "generated-artifacts/MixinDeploymentConstants.json",
 | 
			
		||||
        "generated-artifacts/MixinEthVault.json",
 | 
			
		||||
        "generated-artifacts/MixinExchangeFees.json",
 | 
			
		||||
@@ -39,6 +40,7 @@
 | 
			
		||||
        "generated-artifacts/StakingPoolRewardVault.json",
 | 
			
		||||
        "generated-artifacts/StakingProxy.json",
 | 
			
		||||
        "generated-artifacts/TestCobbDouglas.json",
 | 
			
		||||
        "generated-artifacts/TestCumulativeRewardTracking.json",
 | 
			
		||||
        "generated-artifacts/TestInitTarget.json",
 | 
			
		||||
        "generated-artifacts/TestLibFixedMath.json",
 | 
			
		||||
        "generated-artifacts/TestLibProxy.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ library LibSafeMathRichErrors {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum DowncastErrorCodes {
 | 
			
		||||
        VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT32,
 | 
			
		||||
        VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT64,
 | 
			
		||||
        VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT96
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user