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