Finalization with payouts compiles.

This commit is contained in:
Greg Hysen 2019-06-10 21:52:27 -07:00
parent 55238b9669
commit 8e41cc7651
12 changed files with 182 additions and 2 deletions

View File

@ -249,6 +249,7 @@ contract Staking is
external external
{ {
_goToNextEpoch(); _goToNextEpoch();
_payRebates();
} }
function getEpochPeriodInSeconds() function getEpochPeriodInSeconds()

View File

@ -27,11 +27,14 @@ import "../interfaces/IStakingEvents.sol";
import "./MixinStakeBalances.sol"; import "./MixinStakeBalances.sol";
import "./MixinEpoch.sol"; import "./MixinEpoch.sol";
import "./MixinPools.sol"; import "./MixinPools.sol";
import "../interfaces/IStructs.sol";
import "../libs/LibMath.sol";
contract MixinFees is contract MixinFees is
SafeMath, SafeMath,
IStakingEvents, IStakingEvents,
IStructs,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinEpoch, MixinEpoch,
@ -70,6 +73,59 @@ contract MixinFees is
function _payRebates() function _payRebates()
internal internal
{ {
// Step 1 - compute total fees this epoch
uint256 numberOfActivePoolIds = activePoolIdsThisEpoch.length;
ActivePool[] memory activePoolIds = new 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 = _safeAdd(totalFees, activePoolIds[i].feesCollected);
}
// Step 2 - payout
uint256 totalRewards = address(this).balance;
uint256 totalStake = _getActivatedStakeAcrossAllOwners();
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 = _safeAdd(
stakeHeldByPoolOperator,
_safeDiv(
_safeMul(
stakeDelegatedToPool,
REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE
),
100
)
);
uint256 reward = LibMath._cobbDouglasSuperSimplified(
totalRewards,
activePoolIds[i].feesCollected,
totalFees,
scaledStake,
totalStake
);
// record reward in vault
rewardVault.recordDepositFor(activePoolIds[i].poolId, reward);
totalRewardsRecordedInVault = _safeAdd(totalRewardsRecordedInVault, reward);
// clear state
protocolFeesThisEpochByPool[activePoolIds[i].poolId] = 0;
activePoolIdsThisEpoch[i] = 0;
}
activePoolIdsThisEpoch.length = 0;
// Step 3 send total payout to vault
require(
totalRewardsRecordedInVault <= totalRewards,
"MISCALCULATED_REWARDS"
);
if (totalRewardsRecordedInVault > 0) {
address payable rewardVaultAddress = address(uint160(address(rewardVault)));
rewardVaultAddress.transfer(totalRewardsRecordedInVault);
}
} }
} }

View File

@ -32,6 +32,8 @@ contract MixinRewards is
// Pinciple - design any Mixin such that internal members are callable without messing up internal state // Pinciple - design any Mixin such that internal members are callable without messing up internal state
// any function that could mess up internal state should be private. // any function that could mess up internal state should be private.
// @TODO -- add a MixinZrxVault and a MixinRewardVault that interact with the vaults.
function _computeOperatorReward(address operator, bytes32 poolId) function _computeOperatorReward(address operator, bytes32 poolId)

View File

@ -69,6 +69,7 @@ contract MixinStake is
"INSUFFICIENT_BALANCE" "INSUFFICIENT_BALANCE"
); );
activeStakeByOwner[owner] = _safeAdd(activeStakeByOwner[owner], amount); activeStakeByOwner[owner] = _safeAdd(activeStakeByOwner[owner], amount);
totalActivatedStake = _safeAdd(totalActivatedStake, amount);
} }
function _activateAndDelegateStake( function _activateAndDelegateStake(
@ -91,6 +92,7 @@ contract MixinStake is
"INSUFFICIENT_BALANCE" "INSUFFICIENT_BALANCE"
); );
activeStakeByOwner[owner] = _safeSub(activeStakeByOwner[owner], amount); activeStakeByOwner[owner] = _safeSub(activeStakeByOwner[owner], amount);
totalActivatedStake = _safeSub(totalActivatedStake, amount);
_timelockStake(owner, amount); _timelockStake(owner, amount);
} }

View File

@ -33,6 +33,14 @@ contract MixinStakeBalances is
MixinEpoch MixinEpoch
{ {
function _getActivatedStakeAcrossAllOwners()
internal
view
returns (uint256)
{
return totalActivatedStake;
}
function _getTotalStake(address owner) function _getTotalStake(address owner)
internal internal
view view
@ -57,6 +65,15 @@ contract MixinStakeBalances is
return _safeSub(_getTotalStake(owner), _getActivatedStake(owner)); return _safeSub(_getTotalStake(owner), _getActivatedStake(owner));
} }
function _getActivatedAndUndelegatedStake(address owner)
internal
view
returns (uint256)
{
return _safeSub(activeStakeByOwner[owner], _getStakeDelegatedByOwner(owner));
}
function _getActivatableStake(address owner) function _getActivatableStake(address owner)
internal internal
view view

View File

@ -23,7 +23,7 @@ contract MixinConstants {
uint64 constant MAX_UINT_64 = 2**64 - 1; uint64 constant MAX_UINT_64 = 2**64 - 1;
uint256 constant TOKEN_MULTIPLIER = 10**18; uint256 constant TOKEN_MULTIPLIER = 1000000000000000000; // 10**18
bytes32 constant INITIAL_POOL_ID = 0x0000000000000000000000000000000100000000000000000000000000000000; bytes32 constant INITIAL_POOL_ID = 0x0000000000000000000000000000000100000000000000000000000000000000;
@ -38,4 +38,8 @@ contract MixinConstants {
uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @TODO SET FOR DEPLOYMENT uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @TODO SET FOR DEPLOYMENT
uint64 constant public TIMELOCK_PERIOD_IN_EPOCHS = 3; // @TODO SET FOR DEPLOYMENT uint64 constant public TIMELOCK_PERIOD_IN_EPOCHS = 3; // @TODO SET FOR DEPLOYMENT
uint256 constant public COBB_DOUGLAS_ALPHA_DENOMINATOR = 6; // @TODO SET FOR DEPLOYMENT
uint256 constant public REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE = 90; // @TODO SET FOR DEPLOYMENT
} }

View File

@ -57,6 +57,9 @@ contract MixinStorage is
// mapping from Pool Id to Amount Delegated // mapping from Pool Id to Amount Delegated
mapping (bytes32 => uint256) delegatedStakeByPoolId; mapping (bytes32 => uint256) delegatedStakeByPoolId;
// total activated stake in the system
uint256 totalActivatedStake;
// tracking Pool Id // tracking Pool Id
bytes32 nextPoolId = INITIAL_POOL_ID; bytes32 nextPoolId = INITIAL_POOL_ID;

View File

@ -29,6 +29,9 @@ interface IRewardVault {
external external
payable; payable;
function recordDepositFor(bytes32 poolId, uint256 amount)
external;
function withdrawFor(bytes32 poolId, uint256 amount) function withdrawFor(bytes32 poolId, uint256 amount)
external; external;

View File

@ -31,4 +31,9 @@ interface IStructs {
address operatorAddress; address operatorAddress;
uint8 operatorShare; uint8 operatorShare;
} }
struct ActivePool {
bytes32 poolId;
uint256 feesCollected;
}
} }

View File

@ -129,6 +129,25 @@ library LibMath {
root = (scalar * numerator) / denominator; root = (scalar * numerator) / denominator;
} }
// N is defined in `MixinConstants.COBB_DOUGLAS_ALPHA_DENOMINATOR`
// Currently set to 6.
// @TODO Once better nth root - choose a value that is not a divisor of 18, like 7.
// @TODO Update this value for deployment
uint256 constant COBB_DOUGLAS_ALPHA_DENOMINATOR = 6;
uint256 constant TOKEN_MULTIPLIER = 1000000000000000000;
uint256 constant NTH_ROOT_OF_TOKEN_MULTIPLIER = 1000;
function _nthRootFixedPointFixedN(
uint256 base
)
internal
pure
returns (uint256 root)
{
root = (TOKEN_MULTIPLIER * _nthRoot(base, COBB_DOUGLAS_ALPHA_DENOMINATOR)) / NTH_ROOT_OF_TOKEN_MULTIPLIER;
return root;
}
// scalar gets multiplied by once at the beginning // scalar gets multiplied by once at the beginning
function _exp(uint256 numerator, uint256 scalar, uint256 denominator, uint256 power) function _exp(uint256 numerator, uint256 scalar, uint256 denominator, uint256 power)
internal internal
@ -202,4 +221,42 @@ library LibMath {
return (_nthRootFixedPoint(ownerStake * totalFees, alphaDenominator) * totalRewards * ownerFees) / return (_nthRootFixedPoint(ownerStake * totalFees, alphaDenominator) * totalRewards * ownerFees) /
(_nthRootFixedPoint(totalStake * ownerFees, alphaDenominator) * totalFees); (_nthRootFixedPoint(totalStake * ownerFees, alphaDenominator) * totalFees);
} }
// alpha = 1/x, where x is known
// x is defined in `MixinConstants.COBB_DOUGLAS_ALPHA_DENOMINATOR`
// Currently set to 6.
function _cobbDouglasSuperSimplified(
uint256 totalRewards,
uint256 ownerFees,
uint256 totalFees,
uint256 ownerStake,
uint256 totalStake
)
internal
pure
returns (uint256)
{
return (_nthRootFixedPointFixedN(ownerFees * totalStake) * totalRewards * ownerStake) /
(_nthRootFixedPointFixedN(totalFees * ownerStake) * totalStake);
}
// (1 - alpha) = 1/x, where x is known
// x is defined in `MixinConstants.COBB_DOUGLAS_ALPHA_DENOMINATOR`
// Currently set to 6.
function _cobbDouglasSuperSimplifiedInverse(
uint256 totalRewards,
uint256 ownerFees,
uint256 totalFees,
uint256 ownerStake,
uint256 totalStake
)
internal
pure
returns (uint256)
{
return (_nthRootFixedPointFixedN(ownerStake * totalFees) * totalRewards * ownerFees) /
(_nthRootFixedPointFixedN(totalStake * ownerFees) * totalFees);
}
} }

View File

@ -56,6 +56,25 @@ contract RewardVault is
balanceByPoolId[poolId] = _safeAdd(balanceByPoolId[poolId], msg.value); balanceByPoolId[poolId] = _safeAdd(balanceByPoolId[poolId], msg.value);
} }
function recordDepositFor(bytes32 poolId, uint256 amount)
external
onlyStakingContract
{
balanceByPoolId[poolId] = _safeAdd(balanceByPoolId[poolId], amount);
}
function deposit()
external
payable
onlyStakingContract
{}
function ()
external
payable
onlyStakingContract
{}
function withdrawFor(bytes32 poolId, uint256 amount) function withdrawFor(bytes32 poolId, uint256 amount)
external external
onlyStakingContract onlyStakingContract

View File

@ -692,6 +692,17 @@ describe('Staking Core', () => {
expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult); expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult);
}); });
it('nth root #3 with fixed point (integer nth root would fail here)', async () => {
const decimals = 18;
const base = stakingWrapper.toFixedPoint(10, decimals);
console.log(base);
const n = new BigNumber(9);
const root = await stakingWrapper.nthRootFixedPoint(base, n);
const rootAsFloatingPoint = stakingWrapper.toFloatingPoint(root, decimals);
const expectedResult = new BigNumber(26);
expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult);
});
it.skip('nth root #4 with fixed point (integer nth root would fail here) (max number of decimals - currently does not retain)', async () => { it.skip('nth root #4 with fixed point (integer nth root would fail here) (max number of decimals - currently does not retain)', async () => {
const decimals = 18; const decimals = 18;
const base = stakingWrapper.toFixedPoint(new BigNumber('5429503678976.295036789761543678', 10), decimals); const base = stakingWrapper.toFixedPoint(new BigNumber('5429503678976.295036789761543678', 10), decimals);