cobb douglas / simplified / inverse simplified (better results across the board - esp w simplified impls)

This commit is contained in:
Greg Hysen 2019-05-23 23:00:16 -07:00
parent f15693af1d
commit bcfabf18bc
4 changed files with 174 additions and 115 deletions

View File

@ -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);
}
}

View File

@ -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
);
}
}

View File

@ -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);
});
});
});

View File

@ -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`');