Add function assertions required for staking rewards fuzzing: withdrawDelegatorRewards, finalizePool, and endEpoch. Also adds payProtocolFee-related assertions to fillOrder

This commit is contained in:
Michael Zhu
2019-12-04 14:44:19 -08:00
parent fff3c1eb36
commit 4663eec950
27 changed files with 817 additions and 239 deletions

View File

@@ -102,15 +102,6 @@ contract TestMixinCumulativeRewards is
_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
@@ -122,4 +113,3 @@ contract TestMixinCumulativeRewards is
return _cumulativeRewardsByPool[poolId][epoch];
}
}

View File

@@ -21,7 +21,9 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-utils/contracts/src/LibFractions.sol";
import "../src/Staking.sol";
import "../src/interfaces/IStructs.sol";
contract TestStaking is
@@ -56,6 +58,78 @@ contract TestStaking is
testZrxVaultAddress = zrxVaultAddress;
}
function getMostRecentCumulativeReward(bytes32 poolId)
external
view
returns (IStructs.Fraction memory cumulativeRewards, uint256 lastStoredEpoch)
{
lastStoredEpoch = _cumulativeRewardsByPoolLastStored[poolId];
cumulativeRewards = _cumulativeRewardsByPool[poolId][lastStoredEpoch];
}
function computeMemberRewardOverInterval(
bytes32 poolId,
uint256 memberStakeOverInterval,
uint256 beginEpoch,
uint256 endEpoch
)
external
view
returns (uint256 reward)
{
// Sanity check if we can skip computation, as it will result in zero.
if (memberStakeOverInterval == 0 || beginEpoch == endEpoch) {
return 0;
}
// Sanity check interval
require(beginEpoch < endEpoch, "CR_INTERVAL_INVALID");
// Sanity check begin reward
IStructs.Fraction memory beginReward = getCumulativeRewardAtEpoch(poolId, beginEpoch);
IStructs.Fraction memory endReward = getCumulativeRewardAtEpoch(poolId, endEpoch);
// Compute reward
reward = LibFractions.scaleDifference(
endReward.numerator,
endReward.denominator,
beginReward.numerator,
beginReward.denominator,
memberStakeOverInterval
);
}
function getCumulativeRewardAtEpoch(bytes32 poolId, uint256 epoch)
public
view
returns (IStructs.Fraction memory cumulativeReward)
{
// Return CR at `epoch`, given it's set.
cumulativeReward = _cumulativeRewardsByPool[poolId][epoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
// Return CR at `epoch-1`, given it's set.
uint256 lastEpoch = epoch.safeSub(1);
cumulativeReward = _cumulativeRewardsByPool[poolId][lastEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
// Return the most recent CR, given it's less than `epoch`.
uint256 mostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId];
if (mostRecentEpoch < epoch) {
cumulativeReward = _cumulativeRewardsByPool[poolId][mostRecentEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
}
// Otherwise return an empty CR.
return IStructs.Fraction(0, 1);
}
/// @dev Overridden to use testWethAddress;
function getWethContract()
public

View File

@@ -45,6 +45,7 @@ export { StakingRevertErrors, FixedMathRevertErrors } from '@0x/utils';
export { constants } from './constants';
export {
AggregatedStats,
AggregatedStatsByEpoch,
StakeInfo,
StakeStatus,
StoredBalance,
@@ -52,6 +53,7 @@ export {
OwnerStakeByStatus,
GlobalStakeByStatus,
StakingPool,
PoolStats,
} from './types';
export {
ContractArtifact,

View File

@@ -147,45 +147,49 @@ export interface OwnerStakeByStatus {
};
}
interface Fraction {
numerator: BigNumber;
denominator: BigNumber;
}
export class StakingPool {
public delegatedStake: StoredBalance = new StoredBalance();
public rewards: BigNumber = constants.ZERO_AMOUNT;
public cumulativeRewards: {
[epoch: string]: Fraction;
} = {};
public cumulativeRewardsLastStored: string = stakingConstants.INITIAL_EPOCH.toString();
public stats: {
[epoch: string]: PoolStats;
} = {};
constructor(public readonly operator: string, public operatorShare: number) {}
public finalize(): void {}
public creditProtocolFee(): void {}
public withdrawDelegatorRewards(delegator: string): void {}
public delegateStake(delegator: string, amount: BigNumber): void {}
public undelegateStake(delegator: string, amount: BigNumber): void {}
export interface StakingPool {
operator: string;
operatorShare: number;
delegatedStake: StoredBalance;
lastFinalized: BigNumber; // Epoch during which the pool was most recently finalized
}
export interface StakingPoolById {
[poolId: string]: StakingPool;
}
export interface PoolStats {
feesCollected: BigNumber;
weightedStake: BigNumber;
membersStake: BigNumber;
export class PoolStats {
public feesCollected: BigNumber = constants.ZERO_AMOUNT;
public weightedStake: BigNumber = constants.ZERO_AMOUNT;
public membersStake: BigNumber = constants.ZERO_AMOUNT;
public static fromArray(arr: [BigNumber, BigNumber, BigNumber]): PoolStats {
const poolStats = new PoolStats();
[poolStats.feesCollected, poolStats.weightedStake, poolStats.membersStake] = arr;
return poolStats;
}
}
export interface AggregatedStats {
rewardsAvailable: BigNumber;
numPoolsToFinalize: BigNumber;
totalFeesCollected: BigNumber;
totalWeightedStake: BigNumber;
totalRewardsFinalized: BigNumber;
export class AggregatedStats {
public rewardsAvailable: BigNumber = constants.ZERO_AMOUNT;
public numPoolsToFinalize: BigNumber = constants.ZERO_AMOUNT;
public totalFeesCollected: BigNumber = constants.ZERO_AMOUNT;
public totalWeightedStake: BigNumber = constants.ZERO_AMOUNT;
public totalRewardsFinalized: BigNumber = constants.ZERO_AMOUNT;
public static fromArray(arr: [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber]): AggregatedStats {
const aggregatedStats = new AggregatedStats();
[
aggregatedStats.rewardsAvailable,
aggregatedStats.numPoolsToFinalize,
aggregatedStats.totalFeesCollected,
aggregatedStats.totalWeightedStake,
aggregatedStats.totalRewardsFinalized,
] = arr;
return aggregatedStats;
}
}
export interface AggregatedStatsByEpoch {
[epoch: string]: AggregatedStats;
}

View File

@@ -1,5 +1,5 @@
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { blockchainTests, constants, describe, expect, shortZip } from '@0x/contracts-test-utils';
import { blockchainTests, constants, describe, expect, shortZip, toBaseUnitAmount } from '@0x/contracts-test-utils';
import { BigNumber, StakingRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
@@ -9,7 +9,6 @@ import { FinalizerActor } from './actors/finalizer_actor';
import { PoolOperatorActor } from './actors/pool_operator_actor';
import { StakerActor } from './actors/staker_actor';
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
import { toBaseUnitAmount } from './utils/number_utils';
// tslint:disable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count

View File

@@ -1,5 +1,5 @@
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { blockchainTests, describe } from '@0x/contracts-test-utils';
import { blockchainTests, describe, toBaseUnitAmount } from '@0x/contracts-test-utils';
import { BigNumber, StakingRevertErrors } from '@0x/utils';
import * as _ from 'lodash';
@@ -7,7 +7,6 @@ import { StakeInfo, StakeStatus } from '../src/types';
import { StakerActor } from './actors/staker_actor';
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
import { toBaseUnitAmount } from './utils/number_utils';
// tslint:disable:no-unnecessary-type-assertion
blockchainTests.resets('Stake Statuses', env => {

View File

@@ -1,20 +1,18 @@
import {
assertIntegerRoughlyEquals as assertRoughlyEquals,
blockchainTests,
constants,
expect,
filterLogsToArguments,
getRandomInteger,
Numberish,
randomAddress,
toBaseUnitAmount,
} from '@0x/contracts-test-utils';
import { BigNumber, hexUtils } from '@0x/utils';
import { LogEntry } from 'ethereum-types';
import { artifacts } from '../artifacts';
import {
assertIntegerRoughlyEquals as assertRoughlyEquals,
getRandomInteger,
toBaseUnitAmount,
} from '../utils/number_utils';
import {
TestDelegatorRewardsContract,

View File

@@ -1,10 +1,13 @@
import {
assertIntegerRoughlyEquals,
blockchainTests,
constants,
expect,
filterLogsToArguments,
getRandomInteger,
Numberish,
shortZip,
toBaseUnitAmount,
} from '@0x/contracts-test-utils';
import { BigNumber, hexUtils, StakingRevertErrors } from '@0x/utils';
import { LogEntry } from 'ethereum-types';
@@ -13,7 +16,6 @@ import * as _ from 'lodash';
import { constants as stakingConstants } from '../../src/constants';
import { artifacts } from '../artifacts';
import { assertIntegerRoughlyEquals, getRandomInteger, toBaseUnitAmount } from '../utils/number_utils';
import {
IStakingEventsEpochEndedEventArgs,

View File

@@ -1,9 +1,14 @@
import { blockchainTests, Numberish } from '@0x/contracts-test-utils';
import {
assertRoughlyEquals,
blockchainTests,
getRandomInteger,
getRandomPortion,
Numberish,
toDecimal,
} from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { assertRoughlyEquals, getRandomInteger, getRandomPortion, toDecimal } from '../utils/number_utils';
import { artifacts } from '../artifacts';
import { TestCobbDouglasContract } from '../wrappers';

View File

@@ -1,10 +1,16 @@
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
import { BigNumber, FixedMathRevertErrors, hexUtils } from '@0x/utils';
import {
assertRoughlyEquals,
blockchainTests,
expect,
fromFixed,
Numberish,
toDecimal,
toFixed,
} from '@0x/contracts-test-utils';
import { BigNumber, FixedMathRevertErrors } from '@0x/utils';
import { Decimal } from 'decimal.js';
import * as _ from 'lodash';
import { assertRoughlyEquals, fromFixed, toDecimal, toFixed } from '../utils/number_utils';
import { artifacts } from '../artifacts';
import { TestLibFixedMathContract } from '../wrappers';

View File

@@ -1,9 +1,8 @@
import { blockchainTests, expect } from '@0x/contracts-test-utils';
import { blockchainTests, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { constants as stakingConstants } from '../../src/constants';
import { toBaseUnitAmount } from '../utils/number_utils';
import { artifacts } from '../artifacts';
import { TestMixinCumulativeRewardsContract } from '../wrappers';
@@ -74,7 +73,9 @@ blockchainTests.resets('MixinCumulativeRewards unit tests', env => {
await testContract
.addCumulativeReward(testPoolId, testRewards[0].numerator, testRewards[0].denominator)
.awaitTransactionSuccessAsync();
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward(testPoolId).callAsync();
const [mostRecentCumulativeReward] = await testContract
.getMostRecentCumulativeReward(testPoolId)
.callAsync();
expect(mostRecentCumulativeReward).to.deep.equal(testRewards[0]);
});
@@ -86,7 +87,9 @@ blockchainTests.resets('MixinCumulativeRewards unit tests', env => {
await testContract
.addCumulativeReward(testPoolId, testRewards[1].numerator, testRewards[1].denominator)
.awaitTransactionSuccessAsync();
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward(testPoolId).callAsync();
const [mostRecentCumulativeReward] = await testContract
.getMostRecentCumulativeReward(testPoolId)
.callAsync();
expect(mostRecentCumulativeReward).to.deep.equal(testRewards[0]);
});
@@ -98,7 +101,9 @@ blockchainTests.resets('MixinCumulativeRewards unit tests', env => {
await testContract
.addCumulativeReward(testPoolId, testRewards[1].numerator, testRewards[1].denominator)
.awaitTransactionSuccessAsync();
const mostRecentCumulativeReward = await testContract.getMostRecentCumulativeReward(testPoolId).callAsync();
const [mostRecentCumulativeReward] = await testContract
.getMostRecentCumulativeReward(testPoolId)
.callAsync();
expect(mostRecentCumulativeReward).to.deep.equal(sumOfTestRewardsNormalized);
});
});

View File

@@ -3,6 +3,7 @@ import {
constants,
expect,
filterLogsToArguments,
getRandomInteger,
Numberish,
randomAddress,
} from '@0x/contracts-test-utils';
@@ -19,8 +20,6 @@ import {
TestProtocolFeesEvents,
} from '../wrappers';
import { getRandomInteger } from '../utils/number_utils';
blockchainTests('Protocol Fees unit tests', env => {
let ownerAddress: string;
let exchangeAddress: string;

View File

@@ -1,4 +1,4 @@
import { BlockchainTestsEnvironment, expect, txDefaults } from '@0x/contracts-test-utils';
import { BlockchainTestsEnvironment, expect, toBaseUnitAmount, txDefaults } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { DecodedLogEntry, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
@@ -8,7 +8,6 @@ import { artifacts } from '../artifacts';
import { TestCumulativeRewardTrackingContract, TestCumulativeRewardTrackingEvents } from '../wrappers';
import { StakingApiWrapper } from './api_wrapper';
import { toBaseUnitAmount } from './number_utils';
export enum TestAction {
Finalize,

View File

@@ -1,116 +0,0 @@
import { expect, Numberish } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as crypto from 'crypto';
import { Decimal } from 'decimal.js';
Decimal.set({ precision: 80 });
/**
* Convert `x` to a `Decimal` type.
*/
export function toDecimal(x: Numberish): Decimal {
if (BigNumber.isBigNumber(x)) {
return new Decimal(x.toString(10));
}
return new Decimal(x);
}
/**
* Generate a random integer between `min` and `max`, inclusive.
*/
export function getRandomInteger(min: Numberish, max: Numberish): BigNumber {
const range = new BigNumber(max).minus(min);
return getRandomPortion(range).plus(min);
}
/**
* Generate a random integer between `0` and `total`, inclusive.
*/
export function getRandomPortion(total: Numberish): BigNumber {
return new BigNumber(total).times(getRandomFloat(0, 1)).integerValue(BigNumber.ROUND_HALF_UP);
}
/**
* Generate a random, high-precision decimal between `min` and `max`, inclusive.
*/
export function getRandomFloat(min: Numberish, max: Numberish): BigNumber {
// Generate a really high precision number between [0, 1]
const r = new BigNumber(crypto.randomBytes(32).toString('hex'), 16).dividedBy(new BigNumber(2).pow(256).minus(1));
return new BigNumber(max)
.minus(min)
.times(r)
.plus(min);
}
export const FIXED_POINT_BASE = new BigNumber(2).pow(127);
/**
* Convert `n` to fixed-point integer represenatation.
*/
export function toFixed(n: Numberish): BigNumber {
return new BigNumber(n).times(FIXED_POINT_BASE).integerValue();
}
/**
* Convert `n` from fixed-point integer represenatation.
*/
export function fromFixed(n: Numberish): BigNumber {
return new BigNumber(n).dividedBy(FIXED_POINT_BASE);
}
/**
* Converts two decimal numbers to integers with `precision` digits, then returns
* the absolute difference.
*/
export function getNumericalDivergence(a: Numberish, b: Numberish, precision: number = 18): number {
const _a = new BigNumber(a);
const _b = new BigNumber(b);
const maxIntegerDigits = Math.max(
_a.integerValue(BigNumber.ROUND_DOWN).sd(true),
_b.integerValue(BigNumber.ROUND_DOWN).sd(true),
);
const _toInteger = (n: BigNumber) => {
const base = 10 ** (precision - maxIntegerDigits);
return n.times(base).integerValue(BigNumber.ROUND_DOWN);
};
return _toInteger(_a)
.minus(_toInteger(_b))
.abs()
.toNumber();
}
/**
* Asserts that two numbers are equal up to `precision` digits.
*/
export function assertRoughlyEquals(actual: Numberish, expected: Numberish, precision: number = 18): void {
if (getNumericalDivergence(actual, expected, precision) <= 1) {
return;
}
expect(actual).to.bignumber.eq(expected);
}
/**
* Asserts that two numbers are equal with up to `maxError` difference between them.
*/
export function assertIntegerRoughlyEquals(actual: Numberish, expected: Numberish, maxError: number = 1): void {
const diff = new BigNumber(actual)
.minus(expected)
.abs()
.toNumber();
if (diff <= maxError) {
return;
}
expect(actual).to.bignumber.eq(expected);
}
/**
* Converts `amount` into a base unit amount with a specified number of digits. If
* no digits are provided, this defaults to 18 digits.
*/
export function toBaseUnitAmount(amount: Numberish, decimals?: number): BigNumber {
const amountAsBigNumber = new BigNumber(amount);
const baseDecimals = decimals !== undefined ? decimals : 18;
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountAsBigNumber, baseDecimals);
return baseUnitAmount;
}