From bcfabf18bc6bcc621e45fe1c8a01d664c2290922 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 23 May 2019 23:00:16 -0700 Subject: [PATCH] cobb douglas / simplified / inverse simplified (better results across the board - esp w simplified impls) --- .../staking/contracts/src/libs/LibMath.sol | 80 +++++++----- .../staking/contracts/test/LibMathTest.sol | 44 +++++++ contracts/staking/test/core_test.ts | 123 ++++++------------ .../staking/test/utils/staking_wrapper.ts | 42 ++++++ 4 files changed, 174 insertions(+), 115 deletions(-) diff --git a/contracts/staking/contracts/src/libs/LibMath.sol b/contracts/staking/contracts/src/libs/LibMath.sol index 2637447d43..3edd2b13aa 100644 --- a/contracts/staking/contracts/src/libs/LibMath.sol +++ b/contracts/staking/contracts/src/libs/LibMath.sol @@ -123,39 +123,13 @@ library LibMath { root = (scalar * numerator) / denominator; } - // workaround for bug in ganache - function _exp(uint256 base, uint256 power) + // scalar gets multiplied by once at the beginning + function _exp(uint256 numerator, uint256 scalar, uint256 denominator, uint256 power) internal pure returns (uint256 result) { - result = base; - for(power = power - 1; power > 0; power -= 1) { - result *= base; - } - return result; - } - - function _exp2(uint256 numerator_1, uint256 numerator_2, uint256 denominator, uint256 power) - internal - pure - returns (uint256 result) - { - result = (numerator_1 * numerator_2) / denominator; - for(power = power - 1; power > 0; power -= 1) { - result *= numerator_1; - result /= denominator; - result *= numerator_2; - } - return result; - } - - function _exp3(uint256 numerator, uint256 denominator, uint256 power) - internal - pure - returns (uint256 result) - { - result = numerator / denominator; + result = (numerator * scalar) / denominator; for(power = power - 1; power > 0; power -= 1) { result = (result * numerator) / denominator; } @@ -166,7 +140,9 @@ library LibMath { uint256 constant scalar = 10**fixedPointDecimals; uint256 constant halfScalar = 10**(fixedPointDecimals/2); - // rough implementation of cobb-douglas using the nth root fixed point algorithm above + // cobb-douglas using the nth root fixed point algorithm above + // no limitation on alpha. We tend to get better rounding + // on the simplified versions below. function _cobbDouglas( uint256 totalRewards, uint256 ownerFees, @@ -180,10 +156,44 @@ library LibMath { pure returns (uint256) { - uint256 lhs = _exp3(_nthRootFixedPoint(ownerFees * totalStake, alphaDenominator, 18), - _nthRootFixedPoint(totalFees * ownerStake, alphaDenominator, 18), - alphaNumerator - ); - return lhs * ((totalRewards * ownerStake) / totalStake); + return _exp(_nthRootFixedPoint(ownerFees * totalStake, alphaDenominator, 18), + ((totalRewards * ownerStake) / totalStake), + _nthRootFixedPoint(totalFees * ownerStake, alphaDenominator, 18), + alphaNumerator + ); + } + + // alpha = 1/x + function _cobbDouglasSimplified( + uint256 totalRewards, + uint256 ownerFees, + uint256 totalFees, + uint256 ownerStake, + uint256 totalStake, + uint8 alphaDenominator + ) + internal + pure + returns (uint256) + { + return (_nthRootFixedPoint(ownerFees * totalStake, alphaDenominator, 18) * totalRewards * ownerStake) / + (_nthRootFixedPoint(totalFees * ownerStake, alphaDenominator, 18) * totalStake); + } + + // (1 - alpha) = 1/x + function _cobbDouglasSimplifiedInverse( + uint256 totalRewards, + uint256 ownerFees, + uint256 totalFees, + uint256 ownerStake, + uint256 totalStake, + uint8 alphaDenominator + ) + internal + pure + returns (uint256) + { + return (_nthRootFixedPoint(ownerStake * totalFees, alphaDenominator, 18) * totalRewards * ownerFees) / + (_nthRootFixedPoint(totalStake * ownerFees, alphaDenominator, 18) * totalFees); } } diff --git a/contracts/staking/contracts/test/LibMathTest.sol b/contracts/staking/contracts/test/LibMathTest.sol index 70d5a21de5..58bedc7681 100644 --- a/contracts/staking/contracts/test/LibMathTest.sol +++ b/contracts/staking/contracts/test/LibMathTest.sol @@ -64,6 +64,50 @@ contract LibMathTest { alphaDenominator ); } + + function cobbDouglasSimplified( + uint256 totalRewards, + uint256 ownerFees, + uint256 totalFees, + uint256 ownerStake, + uint256 totalStake, + uint8 alphaDenominator + ) + public + pure + returns (uint256) + { + return LibMath._cobbDouglasSimplified( + totalRewards, + ownerFees, + totalFees, + ownerStake, + totalStake, + alphaDenominator + ); + } + + function cobbDouglasSimplifiedInverse( + uint256 totalRewards, + uint256 ownerFees, + uint256 totalFees, + uint256 ownerStake, + uint256 totalStake, + uint8 alphaDenominator + ) + public + pure + returns (uint256) + { + return LibMath._cobbDouglasSimplifiedInverse( + totalRewards, + ownerFees, + totalFees, + ownerStake, + totalStake, + alphaDenominator + ); + } } diff --git a/contracts/staking/test/core_test.ts b/contracts/staking/test/core_test.ts index d3d4bab3b0..82aa74dcdf 100644 --- a/contracts/staking/test/core_test.ts +++ b/contracts/staking/test/core_test.ts @@ -145,79 +145,18 @@ describe('Staking Core', () => { expect(rootAsFloatingPoint).to.be.bignumber.equal(expectedResult); }); - it.skip('cobb douglas - basic computation', async() => { - const totalRewards = new BigNumber(50); - const ownerFees = new BigNumber(5); - const totalFees = new BigNumber(10); - const ownerStake = new BigNumber(5); - const totalStake = new BigNumber(10); - const alphaNumerator = new BigNumber(1); - const alphaDenominator = new BigNumber(2); - - const expectedOwnerReward = totalRewards - .times( - (ownerFees.div(totalFees)).squareRoot() - ).times( - (ownerStake.div(totalStake)).squareRoot() - ).dividedToIntegerBy(1); // 25 - - const ownerReward = await stakingWrapper.cobbDouglas( - totalRewards, - ownerFees, - totalFees, - ownerStake, - totalStake, - alphaNumerator, - alphaDenominator - ); - expect(ownerReward).to.be.bignumber.equal(expectedOwnerReward); - }); - - it.skip('cobb douglas - token computation', async() => { - const totalRewards = stakingWrapper.toBaseUnitAmount(50); - const ownerFees = stakingWrapper.toBaseUnitAmount(5); - const totalFees = stakingWrapper.toBaseUnitAmount(10); - const ownerStake = stakingWrapper.toBaseUnitAmount(5); - const totalStake = stakingWrapper.toBaseUnitAmount(10); - const alphaNumerator = new BigNumber(1); - const alphaDenominator = new BigNumber(2); - - const expectedOwnerReward = totalRewards - .times( - (ownerFees.div(totalFees)).squareRoot() - ).times( - (ownerStake.div(totalStake)).squareRoot() - ).dividedToIntegerBy(1); // 25000000000000000000 - - const ownerReward = await stakingWrapper.cobbDouglas( - totalRewards, - ownerFees, - totalFees, - ownerStake, - totalStake, - alphaNumerator, - alphaDenominator - ); - expect(ownerReward).to.be.bignumber.equal(expectedOwnerReward); - }); - - it.only('cobb douglas - complex token computation', async() => { + it('cobb douglas - approximate', async() => { const totalRewards = stakingWrapper.toBaseUnitAmount(57.154398); const ownerFees = stakingWrapper.toBaseUnitAmount(5.64375); const totalFees = stakingWrapper.toBaseUnitAmount(29.00679); const ownerStake = stakingWrapper.toBaseUnitAmount(56); const totalStake = stakingWrapper.toBaseUnitAmount(10906); - const alphaNumerator = new BigNumber(1); - const alphaDenominator = new BigNumber(2); - - const expectedOwnerReward = totalRewards - .times( - (ownerFees.div(totalFees)).squareRoot() - ).times( - (ownerStake.div(totalStake)).squareRoot() - ).dividedToIntegerBy(1); // 25000000000000000000*/ - console.log(`EXPECTED - `, stakingWrapper.toFloatingPoint(expectedOwnerReward, 18)); - + const alphaNumerator = new BigNumber(3); + const alphaDenominator = new BigNumber(7); + // create expected output + // https://www.wolframalpha.com/input/?i=57.154398+*+(5.64375%2F29.00679)+%5E+(3%2F7)+*+(56+%2F+10906)+%5E+(1+-+3%2F7) + const expectedOwnerReward = new BigNumber(1.3934); + // run computation const ownerReward = await stakingWrapper.cobbDouglas( totalRewards, ownerFees, @@ -227,34 +166,58 @@ describe('Staking Core', () => { alphaNumerator, alphaDenominator ); - console.log(`ACTUAL - `, stakingWrapper.toFloatingPoint(ownerReward, 18)); - //expect(ownerReward).to.be.bignumber.equal(expectedOwnerReward); + const ownerRewardFloatingPoint = stakingWrapper.trimFloat(stakingWrapper.toFloatingPoint(ownerReward, 18), 4); + // validation + expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward); }); - it.only('cobb douglas - complex token computation #2', async() => { + it('cobb douglas - simplified (alpha = 1/x)', async() => { + // setup test parameters const totalRewards = stakingWrapper.toBaseUnitAmount(57.154398); const ownerFees = stakingWrapper.toBaseUnitAmount(5.64375); const totalFees = stakingWrapper.toBaseUnitAmount(29.00679); const ownerStake = stakingWrapper.toBaseUnitAmount(56); const totalStake = stakingWrapper.toBaseUnitAmount(10906); - const alphaNumerator = new BigNumber(1); const alphaDenominator = new BigNumber(3); - + // create expected output // https://www.wolframalpha.com/input/?i=57.154398+*+(5.64375%2F29.00679)+%5E+(1%2F3)+*+(56+%2F+10906)+%5E+(1+-+1%2F3) - console.log(`EXPECTED - 0.9857...`); - - const ownerReward = await stakingWrapper.cobbDouglas( + const expectedOwnerReward = new BigNumber(0.98572107681878); + // run computation + const ownerReward = await stakingWrapper.cobbDouglasSimplified( totalRewards, ownerFees, totalFees, ownerStake, totalStake, - alphaNumerator, alphaDenominator ); - //console.log(ownerReward); - console.log(`ACTUAL - `, stakingWrapper.toFloatingPoint(ownerReward, 18)); - //expect(ownerReward).to.be.bignumber.equal(expectedOwnerReward); + const ownerRewardFloatingPoint = stakingWrapper.trimFloat(stakingWrapper.toFloatingPoint(ownerReward, 18), 14); + // validation + expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward); + }); + + it('cobb douglas - simplified inverse (1 - alpha = 1/x)', async() => { + const totalRewards = stakingWrapper.toBaseUnitAmount(57.154398); + const ownerFees = stakingWrapper.toBaseUnitAmount(5.64375); + const totalFees = stakingWrapper.toBaseUnitAmount(29.00679); + const ownerStake = stakingWrapper.toBaseUnitAmount(56); + const totalStake = stakingWrapper.toBaseUnitAmount(10906); + const inverseAlphaDenominator = new BigNumber(3); + // create expected output + // https://www.wolframalpha.com/input/?i=57.154398+*+(5.64375%2F29.00679)+%5E+(2%2F3)+*+(56+%2F+10906)+%5E+(1+-+2%2F3) + const expectedOwnerReward = new BigNumber(3.310822494188); + // run computation + const ownerReward = await stakingWrapper.cobbDouglasSimplifiedInverse( + totalRewards, + ownerFees, + totalFees, + ownerStake, + totalStake, + inverseAlphaDenominator + ); + const ownerRewardFloatingPoint = stakingWrapper.trimFloat(stakingWrapper.toFloatingPoint(ownerReward, 18), 12); + // validation + expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward); }); }); }); diff --git a/contracts/staking/test/utils/staking_wrapper.ts b/contracts/staking/test/utils/staking_wrapper.ts index 2bc3e7d76b..3d716203b5 100644 --- a/contracts/staking/test/utils/staking_wrapper.ts +++ b/contracts/staking/test/utils/staking_wrapper.ts @@ -126,6 +126,42 @@ export class StakingWrapper { ); return output; } + public async cobbDouglasSimplified( + totalRewards: BigNumber, + ownerFees: BigNumber, + totalFees: BigNumber, + ownerStake: BigNumber, + totalStake: BigNumber, + alphaDenominator: BigNumber + ) { + const output = await this.getLibMathTestContract().cobbDouglasSimplified.callAsync( + totalRewards, + ownerFees, + totalFees, + ownerStake, + totalStake, + alphaDenominator + ); + return output; + } + public async cobbDouglasSimplifiedInverse( + totalRewards: BigNumber, + ownerFees: BigNumber, + totalFees: BigNumber, + ownerStake: BigNumber, + totalStake: BigNumber, + alphaDenominator: BigNumber + ) { + const output = await this.getLibMathTestContract().cobbDouglasSimplifiedInverse.callAsync( + totalRewards, + ownerFees, + totalFees, + ownerStake, + totalStake, + alphaDenominator + ); + return output; + } public toBaseUnitAmount(amount: BigNumber | number): BigNumber { const decimals = 18; const amountAsBigNumber = typeof(amount) === 'number' ? new BigNumber(amount) : amount; @@ -144,6 +180,12 @@ export class StakingWrapper { const amountAsFloatingPoint = amountAsBigNumber.dividedBy(scalar); return amountAsFloatingPoint; } + public trimFloat(amount: BigNumber | number, decimals: number): BigNumber { + const amountAsBigNumber = typeof(amount) === 'number' ? new BigNumber(amount) : amount; + const scalar = Math.pow(10, decimals); + const amountAsFloatingPoint = ((amountAsBigNumber.multipliedBy(scalar)).dividedToIntegerBy(1)).dividedBy(scalar); + return amountAsFloatingPoint; + } private _validateDeployedOrThrow() { if (this._stakingContractIfExists === undefined) { throw new Error('Staking contract not deployed. Call `deployStakingContracts`');