Merge pull request #2316 from 0xProject/tests/3.0/StakingMixinCumulativeRewardsUnitTEsts
Unit tests for MixinCumulativeRewards
This commit is contained in:
commit
44793a9cf9
@ -29,6 +29,10 @@
|
||||
{
|
||||
"note": "Unit tests for MixinScheduler",
|
||||
"pr": 2314
|
||||
},
|
||||
{
|
||||
"note": "Unit tests for MixinCumulativeRewards",
|
||||
"pr": 2316
|
||||
}
|
||||
]
|
||||
},
|
||||
|
125
contracts/staking/contracts/test/TestMixinCumulativeRewards.sol
Normal file
125
contracts/staking/contracts/test/TestMixinCumulativeRewards.sol
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
|
||||
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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./TestStaking.sol";
|
||||
|
||||
|
||||
contract TestMixinCumulativeRewards is
|
||||
TestStaking
|
||||
{
|
||||
|
||||
constructor(
|
||||
address wethAddress,
|
||||
address zrxVaultAddress
|
||||
)
|
||||
public
|
||||
TestStaking(
|
||||
wethAddress,
|
||||
zrxVaultAddress
|
||||
)
|
||||
{
|
||||
_addAuthorizedAddress(msg.sender);
|
||||
init();
|
||||
_removeAuthorizedAddressAtIndex(msg.sender, 0);
|
||||
}
|
||||
|
||||
/// @dev Exposes `_isCumulativeRewardSet`
|
||||
function isCumulativeRewardSet(IStructs.Fraction memory cumulativeReward)
|
||||
public
|
||||
pure
|
||||
returns (bool)
|
||||
{
|
||||
return _isCumulativeRewardSet(cumulativeReward);
|
||||
}
|
||||
|
||||
/// @dev Exposes `_addCumulativeReward`
|
||||
function addCumulativeReward(
|
||||
bytes32 poolId,
|
||||
uint256 reward,
|
||||
uint256 stake
|
||||
)
|
||||
public
|
||||
{
|
||||
_addCumulativeReward(poolId, reward, stake);
|
||||
}
|
||||
|
||||
/// @dev Exposes `_updateCumulativeReward`
|
||||
function updateCumulativeReward(bytes32 poolId)
|
||||
public
|
||||
{
|
||||
_updateCumulativeReward(poolId);
|
||||
}
|
||||
|
||||
/// @dev Exposes _computeMemberRewardOverInterval
|
||||
function computeMemberRewardOverInterval(
|
||||
bytes32 poolId,
|
||||
uint256 memberStakeOverInterval,
|
||||
uint256 beginEpoch,
|
||||
uint256 endEpoch
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256 reward)
|
||||
{
|
||||
return _computeMemberRewardOverInterval(poolId, memberStakeOverInterval, beginEpoch, endEpoch);
|
||||
}
|
||||
|
||||
/// @dev Increments current epoch by 1
|
||||
function incrementEpoch()
|
||||
public
|
||||
{
|
||||
currentEpoch += 1;
|
||||
}
|
||||
|
||||
/// @dev Stores an arbitrary cumulative reward for a given epoch.
|
||||
/// Also sets the `_cumulativeRewardsByPoolLastStored` to the input epoch.
|
||||
function storeCumulativeReward(
|
||||
bytes32 poolId,
|
||||
IStructs.Fraction memory cumulativeReward,
|
||||
uint256 epoch
|
||||
)
|
||||
public
|
||||
{
|
||||
_cumulativeRewardsByPool[poolId][epoch] = cumulativeReward;
|
||||
_cumulativeRewardsByPoolLastStored[poolId] = epoch;
|
||||
}
|
||||
|
||||
/// @dev Returns the most recent cumulative reward for a given pool.
|
||||
function getMostRecentCumulativeReward(bytes32 poolId)
|
||||
public
|
||||
returns (IStructs.Fraction memory)
|
||||
{
|
||||
uint256 mostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId];
|
||||
return _cumulativeRewardsByPool[poolId][mostRecentEpoch];
|
||||
}
|
||||
|
||||
/// @dev Returns the raw cumulative reward for a given pool in an epoch.
|
||||
/// This is considered "raw" because the internal implementation
|
||||
/// (_getCumulativeRewardAtEpochRaw) will query other state variables
|
||||
/// to determine the most accurate cumulative reward for a given epoch.
|
||||
function getCumulativeRewardAtEpochRaw(bytes32 poolId, uint256 epoch)
|
||||
public
|
||||
returns (IStructs.Fraction memory)
|
||||
{
|
||||
return _cumulativeRewardsByPool[poolId][epoch];
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
||||
},
|
||||
"config": {
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibSafeDowncast|TestMixinParams|TestMixinScheduler|TestMixinStake|TestMixinStakeBalances|TestMixinStakeStorage|TestMixinStakingPool|TestMixinStakingPoolRewards|TestProtocolFees|TestProxyDestination|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStakingProxyUnit|TestStorageLayoutAndConstants|ZrxVault).json"
|
||||
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibSafeDowncast|TestMixinCumulativeRewards|TestMixinParams|TestMixinScheduler|TestMixinStake|TestMixinStakeBalances|TestMixinStakeStorage|TestMixinStakingPool|TestMixinStakingPoolRewards|TestProtocolFees|TestProxyDestination|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStakingProxyUnit|TestStorageLayoutAndConstants|ZrxVault).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -43,6 +43,7 @@ import * as TestFinalizer from '../generated-artifacts/TestFinalizer.json';
|
||||
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
|
||||
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
|
||||
import * as TestLibSafeDowncast from '../generated-artifacts/TestLibSafeDowncast.json';
|
||||
import * as TestMixinCumulativeRewards from '../generated-artifacts/TestMixinCumulativeRewards.json';
|
||||
import * as TestMixinParams from '../generated-artifacts/TestMixinParams.json';
|
||||
import * as TestMixinScheduler from '../generated-artifacts/TestMixinScheduler.json';
|
||||
import * as TestMixinStake from '../generated-artifacts/TestMixinStake.json';
|
||||
@ -98,6 +99,7 @@ export const artifacts = {
|
||||
TestInitTarget: TestInitTarget as ContractArtifact,
|
||||
TestLibFixedMath: TestLibFixedMath as ContractArtifact,
|
||||
TestLibSafeDowncast: TestLibSafeDowncast as ContractArtifact,
|
||||
TestMixinCumulativeRewards: TestMixinCumulativeRewards as ContractArtifact,
|
||||
TestMixinParams: TestMixinParams as ContractArtifact,
|
||||
TestMixinScheduler: TestMixinScheduler as ContractArtifact,
|
||||
TestMixinStake: TestMixinStake as ContractArtifact,
|
||||
|
@ -41,6 +41,7 @@ export * from '../generated-wrappers/test_finalizer';
|
||||
export * from '../generated-wrappers/test_init_target';
|
||||
export * from '../generated-wrappers/test_lib_fixed_math';
|
||||
export * from '../generated-wrappers/test_lib_safe_downcast';
|
||||
export * from '../generated-wrappers/test_mixin_cumulative_rewards';
|
||||
export * from '../generated-wrappers/test_mixin_params';
|
||||
export * from '../generated-wrappers/test_mixin_scheduler';
|
||||
export * from '../generated-wrappers/test_mixin_stake';
|
||||
|
@ -0,0 +1,258 @@
|
||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, TestMixinCumulativeRewardsContract } from '../../src';
|
||||
|
||||
import { constants as stakingConstants } from '../utils/constants';
|
||||
import { toBaseUnitAmount } from '../utils/number_utils';
|
||||
|
||||
blockchainTests.resets('MixinCumulativeRewards unit tests', env => {
|
||||
const ZERO = new BigNumber(0);
|
||||
const testRewards = [
|
||||
{
|
||||
numerator: new BigNumber(1),
|
||||
denominator: new BigNumber(2),
|
||||
},
|
||||
{
|
||||
numerator: new BigNumber(3),
|
||||
denominator: new BigNumber(4),
|
||||
},
|
||||
];
|
||||
const sumOfTestRewardsNormalized = {
|
||||
numerator: new BigNumber(10),
|
||||
denominator: new BigNumber(8),
|
||||
};
|
||||
let testPoolId: string;
|
||||
let testContract: TestMixinCumulativeRewardsContract;
|
||||
|
||||
before(async () => {
|
||||
// Deploy contracts
|
||||
testContract = await TestMixinCumulativeRewardsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestMixinCumulativeRewards,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
stakingConstants.NIL_ADDRESS,
|
||||
stakingConstants.NIL_ADDRESS,
|
||||
);
|
||||
|
||||
// Create a test pool
|
||||
const operatorShare = new BigNumber(1);
|
||||
const addOperatorAsMaker = true;
|
||||
const txReceipt = await testContract.createStakingPool.awaitTransactionSuccessAsync(
|
||||
operatorShare,
|
||||
addOperatorAsMaker,
|
||||
);
|
||||
const createStakingPoolLog = txReceipt.logs[0];
|
||||
testPoolId = (createStakingPoolLog as any).args.poolId;
|
||||
});
|
||||
|
||||
describe('_isCumulativeRewardSet', () => {
|
||||
it('Should return true iff denominator is non-zero', async () => {
|
||||
const isSet = await testContract.isCumulativeRewardSet.callAsync({
|
||||
numerator: ZERO,
|
||||
denominator: new BigNumber(1),
|
||||
});
|
||||
expect(isSet).to.be.true();
|
||||
});
|
||||
it('Should return false iff denominator is zero', async () => {
|
||||
const isSet = await testContract.isCumulativeRewardSet.callAsync({
|
||||
numerator: new BigNumber(1),
|
||||
denominator: ZERO,
|
||||
});
|
||||
expect(isSet).to.be.false();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_addCumulativeReward', () => {
|
||||
it('Should set value to `reward/stake` if this is the first cumulative reward', async () => {
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[0].numerator,
|
||||
testRewards[0].denominator,
|
||||
);
|
||||
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward.callAsync(testPoolId);
|
||||
expect(mostRecentCumulativeReward).to.deep.equal(testRewards[0]);
|
||||
});
|
||||
|
||||
it('Should do nothing if a cumulative reward has already been recorded in the current epoch (`lastStoredEpoch == currentEpoch_`)', async () => {
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[0].numerator,
|
||||
testRewards[0].denominator,
|
||||
);
|
||||
// this call should not overwrite existing value (testRewards[0])
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[1].numerator,
|
||||
testRewards[1].denominator,
|
||||
);
|
||||
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward.callAsync(testPoolId);
|
||||
expect(mostRecentCumulativeReward).to.deep.equal(testRewards[0]);
|
||||
});
|
||||
|
||||
it('Should set value to normalized sum of `reward/stake` plus most recent cumulative reward, given one exists', async () => {
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[0].numerator,
|
||||
testRewards[0].denominator,
|
||||
);
|
||||
await testContract.incrementEpoch.awaitTransactionSuccessAsync();
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[1].numerator,
|
||||
testRewards[1].denominator,
|
||||
);
|
||||
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward.callAsync(testPoolId);
|
||||
expect(mostRecentCumulativeReward).to.deep.equal(sumOfTestRewardsNormalized);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_updateCumulativeReward', () => {
|
||||
it('Should set current cumulative reward to most recent cumulative reward', async () => {
|
||||
await testContract.addCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[0].numerator,
|
||||
testRewards[0].denominator,
|
||||
);
|
||||
await testContract.incrementEpoch.awaitTransactionSuccessAsync();
|
||||
await testContract.updateCumulativeReward.awaitTransactionSuccessAsync(testPoolId);
|
||||
const epoch = new BigNumber(2);
|
||||
const mostRecentCumulativeReward = await testContract.getCumulativeRewardAtEpochRaw.callAsync(
|
||||
testPoolId,
|
||||
epoch,
|
||||
);
|
||||
expect(mostRecentCumulativeReward).to.deep.equal(testRewards[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_computeMemberRewardOverInterval', () => {
|
||||
const runTest = async (
|
||||
amountToStake: BigNumber,
|
||||
epochOfFirstReward: BigNumber,
|
||||
epochOfSecondReward: BigNumber,
|
||||
epochOfIntervalStart: BigNumber,
|
||||
epochOfIntervalEnd: BigNumber,
|
||||
): Promise<void> => {
|
||||
// Simulate earning reward
|
||||
await testContract.storeCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
testRewards[0],
|
||||
epochOfFirstReward,
|
||||
);
|
||||
await testContract.storeCumulativeReward.awaitTransactionSuccessAsync(
|
||||
testPoolId,
|
||||
sumOfTestRewardsNormalized,
|
||||
epochOfSecondReward,
|
||||
);
|
||||
const reward = await testContract.computeMemberRewardOverInterval.callAsync(
|
||||
testPoolId,
|
||||
amountToStake,
|
||||
epochOfIntervalStart,
|
||||
epochOfIntervalEnd,
|
||||
);
|
||||
// Compute expected reward
|
||||
const lhs = sumOfTestRewardsNormalized.numerator.dividedBy(sumOfTestRewardsNormalized.denominator);
|
||||
const rhs = testRewards[0].numerator.dividedBy(testRewards[0].denominator);
|
||||
const expectedReward = lhs.minus(rhs).multipliedBy(amountToStake);
|
||||
// Assert correctness
|
||||
expect(reward).to.bignumber.equal(expectedReward);
|
||||
};
|
||||
|
||||
it('Should successfully compute reward over a valid interval when staking non-zero ZRX', async () => {
|
||||
const amountToStake = toBaseUnitAmount(1);
|
||||
const epochOfFirstReward = new BigNumber(1);
|
||||
const epochOfSecondReward = new BigNumber(2);
|
||||
const epochOfIntervalStart = new BigNumber(1);
|
||||
const epochOfIntervalEnd = new BigNumber(2);
|
||||
await runTest(
|
||||
amountToStake,
|
||||
epochOfFirstReward,
|
||||
epochOfSecondReward,
|
||||
epochOfIntervalStart,
|
||||
epochOfIntervalEnd,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should successfully compute reward if no entry for current epoch, but there is an entry for epoch n-1', async () => {
|
||||
// End epoch = n-1 forces the code to query the previous epoch's cumulative reward
|
||||
const amountToStake = toBaseUnitAmount(1);
|
||||
const epochOfFirstReward = new BigNumber(1);
|
||||
const epochOfSecondReward = new BigNumber(2);
|
||||
const epochOfIntervalStart = new BigNumber(1);
|
||||
const epochOfIntervalEnd = new BigNumber(3);
|
||||
await runTest(
|
||||
amountToStake,
|
||||
epochOfFirstReward,
|
||||
epochOfSecondReward,
|
||||
epochOfIntervalStart,
|
||||
epochOfIntervalEnd,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should successfully compute reward if no entry for current epoch, but there is an entry for epoch n-2', async () => {
|
||||
// End epoch = n-2 forces the code to query the most recent cumulative reward
|
||||
const amountToStake = toBaseUnitAmount(1);
|
||||
const epochOfFirstReward = new BigNumber(1);
|
||||
const epochOfSecondReward = new BigNumber(2);
|
||||
const epochOfIntervalStart = new BigNumber(1);
|
||||
const epochOfIntervalEnd = new BigNumber(4);
|
||||
await runTest(
|
||||
amountToStake,
|
||||
epochOfFirstReward,
|
||||
epochOfSecondReward,
|
||||
epochOfIntervalStart,
|
||||
epochOfIntervalEnd,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should successfully compute reward are no cumulative reward entries', async () => {
|
||||
// No entries forces the default cumulatie reward to be used in computations
|
||||
const stake = toBaseUnitAmount(1);
|
||||
const beginEpoch = new BigNumber(1);
|
||||
const endEpoch = new BigNumber(2);
|
||||
const reward = await testContract.computeMemberRewardOverInterval.callAsync(
|
||||
testPoolId,
|
||||
stake,
|
||||
beginEpoch,
|
||||
endEpoch,
|
||||
);
|
||||
expect(reward).to.bignumber.equal(ZERO);
|
||||
});
|
||||
|
||||
it('Should return zero if no stake was delegated', async () => {
|
||||
const stake = toBaseUnitAmount(0);
|
||||
const beginEpoch = new BigNumber(1);
|
||||
const endEpoch = new BigNumber(2);
|
||||
const reward = await testContract.computeMemberRewardOverInterval.callAsync(
|
||||
testPoolId,
|
||||
stake,
|
||||
beginEpoch,
|
||||
endEpoch,
|
||||
);
|
||||
expect(reward).to.bignumber.equal(ZERO);
|
||||
});
|
||||
|
||||
it('Should return zero if the start/end of the interval are the same epoch', async () => {
|
||||
const stake = toBaseUnitAmount(1);
|
||||
const beginEpoch = new BigNumber(1);
|
||||
const endEpoch = new BigNumber(1);
|
||||
const reward = await testContract.computeMemberRewardOverInterval.callAsync(
|
||||
testPoolId,
|
||||
stake,
|
||||
beginEpoch,
|
||||
endEpoch,
|
||||
);
|
||||
expect(reward).to.bignumber.equal(ZERO);
|
||||
});
|
||||
|
||||
it('Should revert if start is greater than the end of the interval', async () => {
|
||||
const stake = toBaseUnitAmount(1);
|
||||
const beginEpoch = new BigNumber(2);
|
||||
const endEpoch = new BigNumber(1);
|
||||
const tx = testContract.computeMemberRewardOverInterval.callAsync(testPoolId, stake, beginEpoch, endEpoch);
|
||||
return expect(tx).to.revertWith('CR_INTERVAL_INVALID');
|
||||
});
|
||||
});
|
||||
});
|
@ -41,6 +41,7 @@
|
||||
"generated-artifacts/TestInitTarget.json",
|
||||
"generated-artifacts/TestLibFixedMath.json",
|
||||
"generated-artifacts/TestLibSafeDowncast.json",
|
||||
"generated-artifacts/TestMixinCumulativeRewards.json",
|
||||
"generated-artifacts/TestMixinParams.json",
|
||||
"generated-artifacts/TestMixinScheduler.json",
|
||||
"generated-artifacts/TestMixinStake.json",
|
||||
|
Loading…
x
Reference in New Issue
Block a user