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