Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Staking math upgrades #2109

Merged
merged 36 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
93c8284
`@0x/contracts-staking`: Add `LibFeeMath` library.
dorothy-zbornak Aug 24, 2019
c1fc454
`@0x/contracts-staking`: Add `uintMul()` function to `LibFixedMath`.
dorothy-zbornak Aug 24, 2019
a09cd03
`@0x/contracts-staking`: Remove `LibFeeMath` and just put `_cobbDougl…
dorothy-zbornak Aug 27, 2019
7b5e3da
`@0x/contracts-staking`: Add rich reverts.
dorothy-zbornak Aug 27, 2019
0999805
`@0x/contracts-test-utils`: Add `toHex()`, `hexInvert()`, `hexLeftPad…
dorothy-zbornak Aug 27, 2019
1a3da4b
`@0x/utils`: Fix registering `FixedMathSignedValueError` twice.
dorothy-zbornak Aug 27, 2019
018bcf2
`@0x/utils`: Fix `FixedMathRevertError.FixedMathBinOpError` type.
dorothy-zbornak Aug 27, 2019
2cf74a7
`@0x/utils`: Make `RevertError.decode()` return a `RawRevertError` if…
dorothy-zbornak Aug 28, 2019
f71484c
`@0x/order-utils`: Add `TransactionGasPriceError` to error registry.
dorothy-zbornak Aug 28, 2019
0542c70
`@0x/utils`: Make decoding of unknown selectors to `RawRevertError` o…
dorothy-zbornak Aug 28, 2019
b100364
`@0x/order-utils`: Add `TransactionInvalidContextError` to the `Rever…
dorothy-zbornak Aug 28, 2019
7f40665
`@0x/utils`: Fix order of `BinOpErrorCodes` enum in `FixedMathRevertE…
dorothy-zbornak Aug 28, 2019
1c37334
`@0x/contracts-staking`: Add a bunch of `LibFixedMath` unit tests.
dorothy-zbornak Aug 28, 2019
0c6a674
`@0x/contracts-staking`: Write `LibFixedMath` unit tests.
dorothy-zbornak Aug 28, 2019
9a63bea
`@0x/contracts-staking`: Update `LibFixedMath` `ln()` and `exp()` inp…
dorothy-zbornak Aug 29, 2019
c774b98
`@0x/contracts-staking`: Remove unused tslint directives.
dorothy-zbornak Aug 29, 2019
ed8a6bb
`@0x/contracts-staking`: Emit `CobbDouglasAlphaChanged` event when ca…
dorothy-zbornak Aug 29, 2019
af10f52
`@0x/contracts-staking`: Rebase with 3.0
dorothy-zbornak Aug 29, 2019
2e357ff
`@0x/dev-utils`: Add `total_accounts` option to `Web3Config`.
dorothy-zbornak Aug 30, 2019
5ccbe16
`@0x/contracts-test-utils`: Increase the number of ganache accounts t…
dorothy-zbornak Aug 30, 2019
f724212
`@0x/utils`: Prettier.
dorothy-zbornak Aug 30, 2019
a1a5bdc
`@0x/order-utils`: Prettier.
dorothy-zbornak Aug 30, 2019
495bf08
`@0x/utils`: Update CHANGELOG.
dorothy-zbornak Aug 30, 2019
cb1dc92
`@0x/order-utils`: Rename `OperatorShareMustBeBetween0And100Error` `R…
dorothy-zbornak Aug 30, 2019
8d5e28f
`@0x/contracts-staking`: Change the way operator stake is computed.
dorothy-zbornak Aug 30, 2019
2b3e7e7
`@0x/dev-utils`: Remove no longer applicable test case in `chai_test.…
dorothy-zbornak Aug 30, 2019
f601329
`@0x/utils`: Rename `length` field to `len` in `AuthorizableRevertErr…
dorothy-zbornak Aug 30, 2019
b787051
`@0x/contracts-utils`: Fix failing tests due to `RevertError` behavio…
dorothy-zbornak Aug 30, 2019
20ba23f
`@0x/contracts-test-utils`: Allow negative values in `toHex()`.
merklejerk Sep 4, 2019
b07fc95
`@0x/utils`: Add docstring for `raw` constructor parameter in `Revert…
merklejerk Sep 4, 2019
0be2c25
Commit yarn.lock
merklejerk Sep 4, 2019
19f44fa
`@0x/contracts-staking`: Reformulate cobb-douglas to be more efficient.
merklejerk Sep 4, 2019
9bbbaad
`@0x/conracts-staking`: Fix idiotic linter error.
merklejerk Sep 4, 2019
e9eb3ba
`@0x/contracts-staking`: Keep fees not associated with a pool.
merklejerk Sep 4, 2019
7c3567f
`@0x/utils`: Remove redundant `FixedMath` prefix from `FixedMath` `Re…
merklejerk Sep 4, 2019
356660a
`@0x/contracts-staking`: Remove redundant `"FixedMath"` prefix from `…
merklejerk Sep 4, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions contracts/staking/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@
{
"note": "First implementation",
"pr": 1910
},
{
"note": "Replace `LibFeeMath` with `LibFixedMath`.",
"pr": 2109
},
{
"note": "Use a more precise cobb-douglas implementation.",
"pr": 2109
},
{
"note": "Change the way operator stake is computed.",
"pr": 2109
},
{
"note": "Denominate pool operator shares in parts-per-million.",
"pr": 2109
}
]
}
Expand Down
124 changes: 111 additions & 13 deletions contracts/staking/contracts/src/fees/MixinExchangeFees.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*

Copyright 2018 ZeroEx Intl.
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.
Expand All @@ -20,8 +20,8 @@ pragma solidity ^0.5.9;

import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibFeeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibFixedMath.sol";
import "../immutable/MixinStorage.sol";
import "../immutable/MixinConstants.sol";
import "../interfaces/IStakingEvents.sol";
Expand Down Expand Up @@ -56,13 +56,37 @@ contract MixinExchangeFees is
{
using LibSafeMath for uint256;

/// @dev Set the cobb douglas alpha value used when calculating rewards.
/// Valid inputs: 0 <= `numerator` / `denominator` <= 1.0
/// @param numerator The alpha numerator.
/// @param denominator The alpha denominator.
function setCobbDouglasAlpha(
uint256 numerator,
uint256 denominator
)
external
onlyOwner
{
if (int256(numerator) < 0 || int256(denominator) <= 0 || numerator > denominator) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidCobbDouglasAlphaError(
numerator,
denominator
));
}
cobbDouglasAlphaNumerator = numerator;
cobbDouglasAlphaDenomintor = denominator;
emit CobbDouglasAlphaChanged(numerator, denominator);
}

/// TODO(jalextowle): Add WETH to protocol fees. Should this be unwrapped?
/// @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,
// solhint-disable-next-line
address payerAddress,
// solhint-disable-next-line
uint256 protocolFeePaid
)
external
Expand All @@ -71,10 +95,16 @@ contract MixinExchangeFees is
{
uint256 amount = msg.value;
bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);
if (_feesCollectedThisEpoch == 0) {
activePoolsThisEpoch.push(poolId);
if (poolId != NIL_MAKER_ID) {
// There is a pool associated with `makerAddress`.
// TODO(dorothy-zbornak): When we have epoch locks on delegating, we could
// preclude pools that have no delegated stake, since they will never have
// stake in this epoch and are therefore not entitled to rewards.
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);
if (_feesCollectedThisEpoch == 0) {
activePoolsThisEpoch.push(poolId);
}
}
}

Expand Down Expand Up @@ -125,7 +155,7 @@ contract MixinExchangeFees is
}

/// @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
/// Each pool receives a portion of the fees generated this epoch (see _cobbDouglas) 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 MixinStakingPoolRewardVault) where they can be withdraw by makers and
Expand All @@ -139,7 +169,7 @@ contract MixinExchangeFees is
/// @return initialContractBalance Balance of this contract before paying rewards.
/// @return finalContractBalance Balance of this contract after paying rewards.
function _distributeFeesAmongMakerPools()
private
internal
returns (
uint256 totalActivePools,
uint256 totalFeesCollected,
Expand Down Expand Up @@ -176,11 +206,12 @@ contract MixinExchangeFees is

// compute weighted stake
uint256 totalStakeDelegatedToPool = getTotalStakeDelegatedToPool(poolId);
uint256 stakeHeldByPoolOperator = getActivatedAndUndelegatedStake(getStakingPoolOperator(poolId));
uint256 stakeHeldByPoolOperator = getStakeDelegatedToPoolByOwner(getStakingPoolOperator(poolId), poolId);
uint256 weightedStake = stakeHeldByPoolOperator.safeAdd(
totalStakeDelegatedToPool
.safeMul(REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE)
.safeDiv(PERCENTAGE_DENOMINATOR)
.safeSub(stakeHeldByPoolOperator)
.safeMul(REWARD_DELEGATED_STAKE_WEIGHT)
.safeDiv(PPM_DENOMINATOR)
);

// store pool stats
Expand Down Expand Up @@ -209,12 +240,14 @@ contract MixinExchangeFees is
// step 2/3 - record reward for each pool
for (uint256 i = 0; i != totalActivePools; i++) {
// compute reward using cobb-douglas formula
uint256 reward = LibFeeMath._cobbDouglasSuperSimplified(
uint256 reward = _cobbDouglas(
initialContractBalance,
activePools[i].feesCollected,
totalFeesCollected,
activePools[i].weightedStake,
totalWeightedStake
totalWeightedStake,
cobbDouglasAlphaNumerator,
cobbDouglasAlphaDenomintor
);

// record reward in vault
Expand Down Expand Up @@ -250,4 +283,69 @@ contract MixinExchangeFees is
finalContractBalance
);
}

/// @dev The cobb-douglas function used to compute fee-based rewards for staking pools in a given epoch.
/// Note that in this function there is no limitation on alpha; we tend to get better rounding
/// on the simplified versions below.
/// @param totalRewards collected over an epoch.
/// @param ownerFees Fees attributed to the owner of the staking pool.
/// @param totalFees collected across all active staking pools in the epoch.
/// @param ownerStake Stake attributed to the owner of the staking pool.
/// @param totalStake collected across all active staking pools in the epoch.
/// @param alphaNumerator Numerator of `alpha` in the cobb-dougles function.
/// @param alphaDenominator Denominator of `alpha` in the cobb-douglas function.
/// @return ownerRewards Rewards for the owner.
function _cobbDouglas(
uint256 totalRewards,
uint256 ownerFees,
uint256 totalFees,
uint256 ownerStake,
uint256 totalStake,
uint256 alphaNumerator,
uint256 alphaDenominator
)
internal
pure
returns (uint256 ownerRewards)
{
int256 feeRatio = LibFixedMath._toFixed(ownerFees, totalFees);
int256 stakeRatio = LibFixedMath._toFixed(ownerStake, totalStake);
if (feeRatio == 0 || stakeRatio == 0) {
return ownerRewards = 0;
}

// The cobb-doublas function has the form:
// `totalRewards * feeRatio ^ alpha * stakeRatio ^ (1-alpha)`
// This is equivalent to:
// `totalRewards * stakeRatio * e^(alpha * (ln(feeRatio / stakeRatio)))`
// However, because `ln(x)` has the domain of `0 < x < 1`
// and `exp(x)` has the domain of `x < 0`,
// and fixed-point math easily overflows with multiplication,
// we will choose the following if `stakeRatio > feeRatio`:
// `totalRewards * stakeRatio / e^(alpha * (ln(stakeRatio / feeRatio)))`

// Compute
// `e^(alpha * (ln(feeRatio/stakeRatio)))` if feeRatio <= stakeRatio
// or
// `e^(ln(stakeRatio/feeRatio))` if feeRatio > stakeRatio
int256 n = feeRatio <= stakeRatio ?
LibFixedMath._div(feeRatio, stakeRatio) :
LibFixedMath._div(stakeRatio, feeRatio);
n = LibFixedMath._exp(
LibFixedMath._mulDiv(
LibFixedMath._ln(n),
int256(alphaNumerator),
int256(alphaDenominator)
)
);
// Compute
// `totalRewards * n` if feeRatio <= stakeRatio
// or
// `totalRewards / n` if stakeRatio > feeRatio
n = feeRatio <= stakeRatio ?
LibFixedMath._mul(stakeRatio, n) :
LibFixedMath._div(stakeRatio, n);
// Multiply the above with totalRewards.
ownerRewards = LibFixedMath._uintMul(n, totalRewards);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ import "./MixinDeploymentConstants.sol";
contract MixinConstants is
MixinDeploymentConstants
{
// TODO: Reevaluate this variable
uint8 constant internal PERCENTAGE_DENOMINATOR = 100;
uint32 constant internal PPM_DENOMINATOR = 1000000;

// The upper 16 bytes represent the pool id, so this would be pool id 1. See MixinStakinPool for more information.
bytes32 constant internal INITIAL_POOL_ID = 0x0000000000000000000000000000000100000000000000000000000000000000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ contract MixinDeploymentConstants {

uint256 constant internal TIMELOCK_DURATION_IN_EPOCHS = 3;

uint256 constant internal COBB_DOUGLAS_ALPHA_DENOMINATOR = 6;

uint256 constant internal REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE = 90;
// How much delegated stake is weighted vs operator stake, in ppm.
uint32 constant internal REWARD_DELEGATED_STAKE_WEIGHT = 900000; // 90%

uint256 constant internal CHAIN_ID = 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import "../interfaces/IStakingPoolRewardVault.sol";
import "../interfaces/IStructs.sol";


// solhint-disable max-states-count
// solhint-disable max-states-count, no-empty-blocks
contract MixinStorage is
MixinDeploymentConstants,
MixinConstants,
Expand Down Expand Up @@ -106,4 +106,10 @@ contract MixinStorage is

// Rebate Vault
IStakingPoolRewardVault internal rewardVault;

// Numerator for cobb douglas alpha factor.
uint256 internal cobbDouglasAlphaNumerator = 1;

// Denominator for cobb douglas alpha factor.
uint256 internal cobbDouglasAlphaDenomintor = 6;
}
12 changes: 10 additions & 2 deletions contracts/staking/contracts/src/interfaces/IStakingEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ interface IStakingEvents {
uint256 earliestEndTimeInSeconds
);

/// @dev Emitted by MixinExchangeFees when the cobb douglas alpha is updated.
/// @param numerator The alpha numerator.
/// @param denominator The alpha denominator.
event CobbDouglasAlphaChanged(
uint256 numerator,
uint256 denominator
);

/// @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.
Expand Down Expand Up @@ -70,11 +78,11 @@ interface IStakingEvents {
/// @dev Emitted by MixinStakingPool when a new pool is created.
/// @param poolId Unique id generated for pool.
/// @param operatorAddress Address of creator/operator of pool.
/// @param operatorShare The share of rewards given to the operator.
/// @param operatorShare The share of rewards given to the operator, in ppm.
event StakingPoolCreated(
bytes32 poolId,
address operatorAddress,
uint8 operatorShare
uint32 operatorShare
);

/// @dev Emitted by MixinStakingPool when a new maker is added to a pool.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ interface IStakingPoolRewardVault {

/// @dev Holds the balance for a staking pool.
/// @param initialzed True iff the balance struct is initialized.
/// @param operatorShare Percentage of the total balance owned by the operator.
/// @param operatorShare Fraction of the total balance owned by the operator, in ppm.
/// @param operatorBalance Balance in ETH of the operator.
/// @param membersBalance Balance in ETH co-owned by the pool members.
struct Balance {
bool initialized;
uint8 operatorShare;
uint32 operatorShare;
uint96 operatorBalance;
uint96 membersBalance;
}
Expand Down Expand Up @@ -69,10 +69,10 @@ interface IStakingPoolRewardVault {

/// @dev Emitted when a staking pool is registered.
/// @param poolId Unique Id of pool that was registered.
/// @param operatorShare Share of rewards owned by operator.
/// @param operatorShare Share of rewards owned by operator. in ppm.
event StakingPoolRegistered(
bytes32 poolId,
uint8 operatorShare
uint32 operatorShare
);

/// @dev Default constructor.
Expand Down Expand Up @@ -119,8 +119,8 @@ interface IStakingPoolRewardVault {
/// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode.
/// @param poolId Unique Id of pool.
/// @param poolOperatorShare Percentage of rewards given to the pool operator.
function registerStakingPool(bytes32 poolId, uint8 poolOperatorShare)
/// @param poolOperatorShare Share of rewards given to the pool operator, in ppm.
function registerStakingPool(bytes32 poolId, uint32 poolOperatorShare)
external;

/// @dev Returns the total balance of a pool.
Expand Down
4 changes: 2 additions & 2 deletions contracts/staking/contracts/src/interfaces/IStructs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ interface IStructs {

/// @dev State for Staking Pools (see MixinStakingPool).
/// @param operatorAddress Address of pool operator.
/// @param operatorShare Portion of pool rewards owned by operator.
/// @param operatorShare Portion of pool rewards owned by operator, in ppm.
struct Pool {
address payable operatorAddress;
uint8 operatorShare;
uint32 operatorShare;
}

/// @dev State for a pool that actively traded during the current epoch.
Expand Down
Loading