From 8e41cc76511eb2da49f1a59a0391f7df1d461d56 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Jun 2019 21:52:27 -0700 Subject: [PATCH] Finalization with payouts compiles. --- contracts/staking/contracts/src/Staking.sol | 1 + .../staking/contracts/src/core/MixinFees.sol | 58 ++++++++++++++++++- .../contracts/src/core/MixinRewards.sol | 2 + .../staking/contracts/src/core/MixinStake.sol | 2 + .../contracts/src/core/MixinStakeBalances.sol | 17 ++++++ .../src/immutable/MixinConstants.sol | 6 +- .../contracts/src/immutable/MixinStorage.sol | 3 + .../contracts/src/interfaces/IRewardVault.sol | 3 + .../contracts/src/interfaces/IStructs.sol | 5 ++ .../staking/contracts/src/libs/LibMath.sol | 57 ++++++++++++++++++ .../contracts/src/vaults/RewardVault.sol | 19 ++++++ contracts/staking/test/core_test.ts | 11 ++++ 12 files changed, 182 insertions(+), 2 deletions(-) diff --git a/contracts/staking/contracts/src/Staking.sol b/contracts/staking/contracts/src/Staking.sol index a51360cb66..3bc8e47b79 100644 --- a/contracts/staking/contracts/src/Staking.sol +++ b/contracts/staking/contracts/src/Staking.sol @@ -249,6 +249,7 @@ contract Staking is external { _goToNextEpoch(); + _payRebates(); } function getEpochPeriodInSeconds() diff --git a/contracts/staking/contracts/src/core/MixinFees.sol b/contracts/staking/contracts/src/core/MixinFees.sol index 8700ce9e3e..6434f2cf80 100644 --- a/contracts/staking/contracts/src/core/MixinFees.sol +++ b/contracts/staking/contracts/src/core/MixinFees.sol @@ -27,11 +27,14 @@ import "../interfaces/IStakingEvents.sol"; import "./MixinStakeBalances.sol"; import "./MixinEpoch.sol"; import "./MixinPools.sol"; +import "../interfaces/IStructs.sol"; +import "../libs/LibMath.sol"; contract MixinFees is SafeMath, IStakingEvents, + IStructs, MixinConstants, MixinStorage, MixinEpoch, @@ -70,6 +73,59 @@ contract MixinFees is function _payRebates() 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); + } } } \ No newline at end of file diff --git a/contracts/staking/contracts/src/core/MixinRewards.sol b/contracts/staking/contracts/src/core/MixinRewards.sol index 65b11dab46..1742a321bd 100644 --- a/contracts/staking/contracts/src/core/MixinRewards.sol +++ b/contracts/staking/contracts/src/core/MixinRewards.sol @@ -32,6 +32,8 @@ contract MixinRewards is // 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. + // @TODO -- add a MixinZrxVault and a MixinRewardVault that interact with the vaults. + function _computeOperatorReward(address operator, bytes32 poolId) diff --git a/contracts/staking/contracts/src/core/MixinStake.sol b/contracts/staking/contracts/src/core/MixinStake.sol index b15dc24d0f..36ecb9bdc5 100644 --- a/contracts/staking/contracts/src/core/MixinStake.sol +++ b/contracts/staking/contracts/src/core/MixinStake.sol @@ -69,6 +69,7 @@ contract MixinStake is "INSUFFICIENT_BALANCE" ); activeStakeByOwner[owner] = _safeAdd(activeStakeByOwner[owner], amount); + totalActivatedStake = _safeAdd(totalActivatedStake, amount); } function _activateAndDelegateStake( @@ -91,6 +92,7 @@ contract MixinStake is "INSUFFICIENT_BALANCE" ); activeStakeByOwner[owner] = _safeSub(activeStakeByOwner[owner], amount); + totalActivatedStake = _safeSub(totalActivatedStake, amount); _timelockStake(owner, amount); } diff --git a/contracts/staking/contracts/src/core/MixinStakeBalances.sol b/contracts/staking/contracts/src/core/MixinStakeBalances.sol index b19eef769e..69f73fd425 100644 --- a/contracts/staking/contracts/src/core/MixinStakeBalances.sol +++ b/contracts/staking/contracts/src/core/MixinStakeBalances.sol @@ -33,6 +33,14 @@ contract MixinStakeBalances is MixinEpoch { + function _getActivatedStakeAcrossAllOwners() + internal + view + returns (uint256) + { + return totalActivatedStake; + } + function _getTotalStake(address owner) internal view @@ -57,6 +65,15 @@ contract MixinStakeBalances is return _safeSub(_getTotalStake(owner), _getActivatedStake(owner)); } + + function _getActivatedAndUndelegatedStake(address owner) + internal + view + returns (uint256) + { + return _safeSub(activeStakeByOwner[owner], _getStakeDelegatedByOwner(owner)); + } + function _getActivatableStake(address owner) internal view diff --git a/contracts/staking/contracts/src/immutable/MixinConstants.sol b/contracts/staking/contracts/src/immutable/MixinConstants.sol index 559534c9e3..46b434ef38 100644 --- a/contracts/staking/contracts/src/immutable/MixinConstants.sol +++ b/contracts/staking/contracts/src/immutable/MixinConstants.sol @@ -23,7 +23,7 @@ contract MixinConstants { 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; @@ -38,4 +38,8 @@ contract MixinConstants { uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @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 } diff --git a/contracts/staking/contracts/src/immutable/MixinStorage.sol b/contracts/staking/contracts/src/immutable/MixinStorage.sol index 59c2a3eb37..5a37e5f366 100644 --- a/contracts/staking/contracts/src/immutable/MixinStorage.sol +++ b/contracts/staking/contracts/src/immutable/MixinStorage.sol @@ -57,6 +57,9 @@ contract MixinStorage is // mapping from Pool Id to Amount Delegated mapping (bytes32 => uint256) delegatedStakeByPoolId; + // total activated stake in the system + uint256 totalActivatedStake; + // tracking Pool Id bytes32 nextPoolId = INITIAL_POOL_ID; diff --git a/contracts/staking/contracts/src/interfaces/IRewardVault.sol b/contracts/staking/contracts/src/interfaces/IRewardVault.sol index 874046207c..9d6f8296cd 100644 --- a/contracts/staking/contracts/src/interfaces/IRewardVault.sol +++ b/contracts/staking/contracts/src/interfaces/IRewardVault.sol @@ -29,6 +29,9 @@ interface IRewardVault { external payable; + function recordDepositFor(bytes32 poolId, uint256 amount) + external; + function withdrawFor(bytes32 poolId, uint256 amount) external; diff --git a/contracts/staking/contracts/src/interfaces/IStructs.sol b/contracts/staking/contracts/src/interfaces/IStructs.sol index 9c1046536b..6897530327 100644 --- a/contracts/staking/contracts/src/interfaces/IStructs.sol +++ b/contracts/staking/contracts/src/interfaces/IStructs.sol @@ -31,4 +31,9 @@ interface IStructs { address operatorAddress; uint8 operatorShare; } + + struct ActivePool { + bytes32 poolId; + uint256 feesCollected; + } } \ No newline at end of file diff --git a/contracts/staking/contracts/src/libs/LibMath.sol b/contracts/staking/contracts/src/libs/LibMath.sol index aa699360fe..f032883178 100644 --- a/contracts/staking/contracts/src/libs/LibMath.sol +++ b/contracts/staking/contracts/src/libs/LibMath.sol @@ -129,6 +129,25 @@ library LibMath { 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 function _exp(uint256 numerator, uint256 scalar, uint256 denominator, uint256 power) internal @@ -202,4 +221,42 @@ library LibMath { return (_nthRootFixedPoint(ownerStake * totalFees, alphaDenominator) * totalRewards * ownerFees) / (_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); + } + + } diff --git a/contracts/staking/contracts/src/vaults/RewardVault.sol b/contracts/staking/contracts/src/vaults/RewardVault.sol index 71567d950f..42ad82d417 100644 --- a/contracts/staking/contracts/src/vaults/RewardVault.sol +++ b/contracts/staking/contracts/src/vaults/RewardVault.sol @@ -56,6 +56,25 @@ contract RewardVault is 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) external onlyStakingContract diff --git a/contracts/staking/test/core_test.ts b/contracts/staking/test/core_test.ts index fee483cee2..494670d705 100644 --- a/contracts/staking/test/core_test.ts +++ b/contracts/staking/test/core_test.ts @@ -692,6 +692,17 @@ describe('Staking Core', () => { 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 () => { const decimals = 18; const base = stakingWrapper.toFixedPoint(new BigNumber('5429503678976.295036789761543678', 10), decimals);