add/update comments

This commit is contained in:
Michael Zhu 2019-12-09 17:40:31 -08:00
parent 1fde62eeb6
commit 865a2b1fb0
15 changed files with 88 additions and 42 deletions

View File

@ -132,9 +132,8 @@ export const LibFractions = {
maxValue: BigNumber = new BigNumber(2 ** 127), maxValue: BigNumber = new BigNumber(2 ** 127),
): [BigNumber, BigNumber] => { ): [BigNumber, BigNumber] => {
if (numerator.isGreaterThan(maxValue) || denominator.isGreaterThan(maxValue)) { if (numerator.isGreaterThan(maxValue) || denominator.isGreaterThan(maxValue)) {
const rescaleBase = numerator.isGreaterThanOrEqualTo(denominator) let rescaleBase = numerator.isGreaterThanOrEqualTo(denominator) ? numerator : denominator;
? safeDiv(numerator, maxValue) rescaleBase = safeDiv(rescaleBase, maxValue);
: safeDiv(denominator, maxValue);
return [safeDiv(numerator, rescaleBase), safeDiv(denominator, rescaleBase)]; return [safeDiv(numerator, rescaleBase), safeDiv(denominator, rescaleBase)];
} else { } else {
return [numerator, denominator]; return [numerator, denominator];

View File

@ -89,6 +89,7 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validFinalizePoolAssertion(this.actor.deployment, this.actor.simulationEnvironment!); const assertion = validFinalizePoolAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
while (true) { while (true) {
// Finalize a random pool, or do nothing if there are no pools in the simulation yet.
const poolId = Pseudorandom.sample(Object.keys(stakingPools)); const poolId = Pseudorandom.sample(Object.keys(stakingPools));
if (poolId === undefined) { if (poolId === undefined) {
yield; yield;

View File

@ -107,22 +107,29 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
while (true) { while (true) {
const { currentEpoch } = this.actor.simulationEnvironment!; const { currentEpoch } = this.actor.simulationEnvironment!;
// Pick a random pool that this staker has delegated to (undefined if no such pools exist)
const fromPoolId = Pseudorandom.sample( const fromPoolId = Pseudorandom.sample(
Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])), Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])),
); );
// The `from` status must be Undelegated if the staker isn't delegated to any pools
// at the moment, or if the chosen pool is unfinalized
const fromStatus = const fromStatus =
fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1)) fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId); const from = new StakeInfo(fromStatus, fromPoolId);
// Pick a random pool to move the stake to
const toPoolId = Pseudorandom.sample(Object.keys(stakingPools)); const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
// The `from` status must be Undelegated if no pools exist in the simulation yet,
// or if the chosen pool is unfinalized
const toStatus = const toStatus =
toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1)) toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const to = new StakeInfo(toStatus, toPoolId); const to = new StakeInfo(toStatus, toPoolId);
// The next epoch balance of the `from` stake is the amount that can be moved
const moveableStake = const moveableStake =
from.status === StakeStatus.Undelegated from.status === StakeStatus.Undelegated
? this.stake[StakeStatus.Undelegated].nextEpochBalance ? this.stake[StakeStatus.Undelegated].nextEpochBalance
@ -141,6 +148,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
); );
while (true) { while (true) {
const prevEpoch = this.actor.simulationEnvironment!.currentEpoch.minus(1); const prevEpoch = this.actor.simulationEnvironment!.currentEpoch.minus(1);
// Pick a finalized pool
const poolId = Pseudorandom.sample( const poolId = Pseudorandom.sample(
Object.keys(stakingPools).filter(id => Object.keys(stakingPools).filter(id =>
stakingPools[id].lastFinalized.isGreaterThanOrEqualTo(prevEpoch), stakingPools[id].lastFinalized.isGreaterThanOrEqualTo(prevEpoch),

View File

@ -68,37 +68,36 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
const { actors, balanceStore } = this.actor.simulationEnvironment!; const { actors, balanceStore } = this.actor.simulationEnvironment!;
const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!); const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
while (true) { while (true) {
// Choose a maker to be the other side of the order
const maker = Pseudorandom.sample(filterActorsByRole(actors, Maker)); const maker = Pseudorandom.sample(filterActorsByRole(actors, Maker));
if (maker === undefined) { if (maker === undefined) {
yield; yield;
} else { } else {
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
// Choose the assets for the order
const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize( const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
this.actor.deployment.tokens.erc20, this.actor.deployment.tokens.erc20,
4, // tslint:disable-line:custom-no-magic-numbers 4, // tslint:disable-line:custom-no-magic-numbers
); );
const configureOrderAssetAsync = async ( // Maker and taker set balances/allowances to guarantee that the fill succeeds.
owner: Actor, // Amounts are chosen to be within each actor's balance (divided by 2, in case
token: DummyERC20TokenContract, // e.g. makerAsset = makerFeeAsset)
): Promise<BigNumber> => {
let balance = balanceStore.balances.erc20[owner.address][token.address];
await owner.configureERC20TokenAsync(token);
balance = balanceStore.balances.erc20[owner.address][token.address] =
constants.INITIAL_ERC20_BALANCE;
return Pseudorandom.integer(balance.dividedToIntegerBy(2));
};
const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all( const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all(
[ [
[maker, makerToken], [maker, makerToken],
[maker, makerFeeToken], [maker, makerFeeToken],
[this.actor, takerToken], [this.actor, takerToken],
[this.actor, takerFeeToken], [this.actor, takerFeeToken],
].map(async ([owner, token]) => ].map(async ([owner, token]) => {
configureOrderAssetAsync(owner as Actor, token as DummyERC20TokenContract), let balance = balanceStore.balances.erc20[owner.address][token.address];
), await (owner as Actor).configureERC20TokenAsync(token as DummyERC20TokenContract);
balance = balanceStore.balances.erc20[owner.address][token.address] =
constants.INITIAL_ERC20_BALANCE;
return Pseudorandom.integer(balance.dividedToIntegerBy(2));
}),
); );
// Encode asset data
const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [ const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [
makerToken, makerToken,
makerFeeToken, makerFeeToken,
@ -108,6 +107,7 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(), this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(),
); );
// Maker signs the order
const order = await maker.signOrderAsync({ const order = await maker.signOrderAsync({
makerAssetData, makerAssetData,
takerAssetData, takerAssetData,
@ -120,7 +120,10 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
feeRecipientAddress: Pseudorandom.sample(actors)!.address, feeRecipientAddress: Pseudorandom.sample(actors)!.address,
}); });
// Taker fills the order by a random amount (up to the order's takerAssetAmount)
const fillAmount = Pseudorandom.integer(order.takerAssetAmount); const fillAmount = Pseudorandom.integer(order.takerAssetAmount);
// Taker executes the fill with a random msg.value, so that sometimes the
// protocol fee is paid in ETH and other times it's paid in WETH.
yield assertion.executeAsync([order, fillAmount, order.signature], { yield assertion.executeAsync([order, fillAmount, order.signature], {
from: this.actor.address, from: this.actor.address,
value: Pseudorandom.integer(DeploymentManager.protocolFee.times(2)), value: Pseudorandom.integer(DeploymentManager.protocolFee.times(2)),

View File

@ -21,11 +21,10 @@ interface EndEpochBeforeInfo {
} }
/** /**
* Returns a FunctionAssertion for `stake` which assumes valid input is provided. The * Returns a FunctionAssertion for `endEpoch` which assumes valid input is provided. It checks
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX decrease and increase, * that the staking proxy contract wrapped its ETH balance, aggregated stats were updated, and
* respectively, by the input amount. * EpochFinalized/EpochEnded events were emitted.
*/ */
/* tslint:disable:no-unnecessary-type-assertion */
export function validEndEpochAssertion( export function validEndEpochAssertion(
deployment: DeploymentManager, deployment: DeploymentManager,
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
@ -47,7 +46,7 @@ export function validEndEpochAssertion(
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const { currentEpoch } = simulationEnvironment; const { currentEpoch } = simulationEnvironment;
const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion const logs = result.receipt!.logs; // tslint:disable-line
// Check WETH deposit event // Check WETH deposit event
const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT; const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT;
@ -61,6 +60,7 @@ export function validEndEpochAssertion(
: []; : [];
verifyEventsFromLogs<WETH9DepositEventArgs>(logs, expectedDepositEvents, WETH9Events.Deposit); verifyEventsFromLogs<WETH9DepositEventArgs>(logs, expectedDepositEvents, WETH9Events.Deposit);
// Check that the aggregated stats were updated
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo; const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo;
const expectedAggregatedStats = { const expectedAggregatedStats = {
@ -71,12 +71,12 @@ export function validEndEpochAssertion(
constants.ZERO_AMOUNT, constants.ZERO_AMOUNT,
).minus(wethReservedForPoolRewards), ).minus(wethReservedForPoolRewards),
}; };
const aggregatedStatsAfter = AggregatedStats.fromArray( const aggregatedStatsAfter = AggregatedStats.fromArray(
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
); );
expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats); expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats);
// Check that an EpochEnded event was emitted
verifyEventsFromLogs<StakingEpochEndedEventArgs>( verifyEventsFromLogs<StakingEpochEndedEventArgs>(
logs, logs,
[ [
@ -91,6 +91,7 @@ export function validEndEpochAssertion(
StakingEvents.EpochEnded, StakingEvents.EpochEnded,
); );
// If there are no more pools to finalize, an EpochFinalized event should've been emitted
const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero() const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero()
? [ ? [
{ {
@ -106,12 +107,13 @@ export function validEndEpochAssertion(
StakingEvents.EpochFinalized, StakingEvents.EpochFinalized,
); );
// The function returns the remaining number of unfinalized pools for the epoch
expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal( expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal(
aggregatedStatsAfter.numPoolsToFinalize, aggregatedStatsAfter.numPoolsToFinalize,
); );
// Update currentEpoch locally
simulationEnvironment.currentEpoch = currentEpoch.plus(1); simulationEnvironment.currentEpoch = currentEpoch.plus(1);
}, },
}); });
} }
/* tslint:enable:no-unnecessary-type-assertion */

View File

@ -155,6 +155,7 @@ export function validFillOrderAssertion(
// Ensure that the correct events were emitted. // Ensure that the correct events were emitted.
verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount); verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount);
// If the maker is not in a staking pool, there's nothing to check
if (beforeInfo === undefined) { if (beforeInfo === undefined) {
return; return;
} }
@ -163,6 +164,7 @@ export function validFillOrderAssertion(
const expectedAggregatedStats = { ...beforeInfo.aggregatedStats }; const expectedAggregatedStats = { ...beforeInfo.aggregatedStats };
const expectedEvents = []; const expectedEvents = [];
// Refer to `payProtocolFee`
if (beforeInfo.poolStake.isGreaterThanOrEqualTo(stakingConstants.DEFAULT_PARAMS.minimumPoolStake)) { if (beforeInfo.poolStake.isGreaterThanOrEqualTo(stakingConstants.DEFAULT_PARAMS.minimumPoolStake)) {
if (beforeInfo.poolStats.feesCollected.isZero()) { if (beforeInfo.poolStats.feesCollected.isZero()) {
const membersStakeInPool = beforeInfo.poolStake.minus(beforeInfo.operatorStake); const membersStakeInPool = beforeInfo.poolStake.minus(beforeInfo.operatorStake);
@ -181,20 +183,23 @@ export function validFillOrderAssertion(
expectedAggregatedStats.numPoolsToFinalize = beforeInfo.aggregatedStats.numPoolsToFinalize.plus( expectedAggregatedStats.numPoolsToFinalize = beforeInfo.aggregatedStats.numPoolsToFinalize.plus(
1, 1,
); );
// StakingPoolEarnedRewardsInEpoch event emitted
expectedEvents.push({ expectedEvents.push({
epoch: currentEpoch, epoch: currentEpoch,
poolId: beforeInfo.poolId, poolId: beforeInfo.poolId,
}); });
} }
// Credit a protocol fee to the maker's staking pool
expectedPoolStats.feesCollected = beforeInfo.poolStats.feesCollected.plus( expectedPoolStats.feesCollected = beforeInfo.poolStats.feesCollected.plus(
DeploymentManager.protocolFee, DeploymentManager.protocolFee,
); );
// Update aggregated stats
expectedAggregatedStats.totalFeesCollected = beforeInfo.aggregatedStats.totalFeesCollected.plus( expectedAggregatedStats.totalFeesCollected = beforeInfo.aggregatedStats.totalFeesCollected.plus(
DeploymentManager.protocolFee, DeploymentManager.protocolFee,
); );
} }
// Check for updated stats and event
const poolStats = PoolStats.fromArray( const poolStats = PoolStats.fromArray(
await stakingWrapper.poolStatsByEpoch(beforeInfo.poolId, currentEpoch).callAsync(), await stakingWrapper.poolStatsByEpoch(beforeInfo.poolId, currentEpoch).callAsync(),
); );

View File

@ -11,16 +11,17 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
*/ */
/* tslint:disable:no-unnecessary-type-assertion */ /* tslint:disable:no-unnecessary-type-assertion */
/* tslint:disable:no-non-null-assertion */ /* tslint:disable:no-non-null-assertion */
export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> { export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], void, void> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[string], {}, void>(stakingWrapper, 'joinStakingPoolAsMaker', { return new FunctionAssertion<[string], void, void>(stakingWrapper, 'joinStakingPoolAsMaker', {
after: async (_beforeInfo, result: FunctionResult, args: [string], txData: Partial<TxData>) => { after: async (_beforeInfo: void, result: FunctionResult, args: [string], txData: Partial<TxData>) => {
// Ensure that the tx succeeded. // Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const [poolId] = args; const [poolId] = args;
// Verify a MakerStakingPoolSet event was emitted
const logs = result.receipt!.logs; const logs = result.receipt!.logs;
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>( const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
logs, logs,
@ -32,6 +33,7 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu
poolId, poolId,
}, },
]); ]);
// Verify that the maker's pool id has been updated in storage
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync(); const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
expect(joinedPoolId).to.be.eq(poolId); expect(joinedPoolId).to.be.eq(poolId);
}, },

View File

@ -75,8 +75,8 @@ function updateNextEpochBalances(
return updatedPools; return updatedPools;
} }
/** /**
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The * Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. Checks that
* FunctionAssertion checks that the staker's * the owner's stake and global stake by status get updated correctly.
*/ */
/* tslint:disable:no-unnecessary-type-assertion */ /* tslint:disable:no-unnecessary-type-assertion */
export function validMoveStakeAssertion( export function validMoveStakeAssertion(

View File

@ -53,10 +53,7 @@ export function validStakeAssertion(
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances); balanceStore.assertEquals(expectedBalances);
// Checks that the owner's undelegated stake has increased by the stake amount // _increaseCurrentAndNextBalance
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true);
ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[
StakeStatus.Undelegated StakeStatus.Undelegated
@ -64,6 +61,11 @@ export function validStakeAssertion(
ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[ ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[
StakeStatus.Undelegated StakeStatus.Undelegated
].nextEpochBalance.plus(amount); ].nextEpochBalance.plus(amount);
// Checks that the owner's undelegated stake has increased by the stake amount
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
}, },
}); });

View File

@ -53,10 +53,7 @@ export function validUnstakeAssertion(
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
balanceStore.assertEquals(expectedBalances); balanceStore.assertEquals(expectedBalances);
// Checks that the owner's undelegated stake has decreased by the stake amount // _decreaseCurrentAndNextBalance
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true);
ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[
StakeStatus.Undelegated StakeStatus.Undelegated
@ -64,6 +61,11 @@ export function validUnstakeAssertion(
ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[ ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[
StakeStatus.Undelegated StakeStatus.Undelegated
].nextEpochBalance.minus(amount); ].nextEpochBalance.minus(amount);
// Checks that the owner's undelegated stake has decreased by the stake amount
const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync();
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
}, },
}); });

View File

@ -16,9 +16,9 @@ interface WithdrawDelegatorRewardsBeforeInfo {
} }
/** /**
* Returns a FunctionAssertion for `stake` which assumes valid input is provided. The * Returns a FunctionAssertion for `withdrawDelegatorRewards` which assumes valid input is provided.
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX decrease and increase, * It checks that the delegator's stake gets synced and pool rewards are updated to reflect the
* respectively, by the input amount. * amount withdrawn.
*/ */
/* tslint:disable:no-unnecessary-type-assertion */ /* tslint:disable:no-unnecessary-type-assertion */
export function validWithdrawDelegatorRewardsAssertion( export function validWithdrawDelegatorRewardsAssertion(
@ -50,12 +50,14 @@ export function validWithdrawDelegatorRewardsAssertion(
const [poolId] = args; const [poolId] = args;
const { currentEpoch } = simulationEnvironment; const { currentEpoch } = simulationEnvironment;
// Check that delegator stake has been synced
const expectedDelegatorStake = loadCurrentBalance(beforeInfo.delegatorStake, currentEpoch); const expectedDelegatorStake = loadCurrentBalance(beforeInfo.delegatorStake, currentEpoch);
const delegatorStake = await stakingWrapper const delegatorStake = await stakingWrapper
.getStakeDelegatedToPoolByOwner(txData.from as string, poolId) .getStakeDelegatedToPoolByOwner(txData.from as string, poolId)
.callAsync(); .callAsync();
expect(delegatorStake).to.deep.equal(expectedDelegatorStake); expect(delegatorStake).to.deep.equal(expectedDelegatorStake);
// Check that pool rewards have been updated to reflect the amount withdrawn.
const transferEvents = filterLogsToArguments<WETH9TransferEventArgs>( const transferEvents = filterLogsToArguments<WETH9TransferEventArgs>(
result.receipt!.logs, // tslint:disable-line:no-non-null-assertion result.receipt!.logs, // tslint:disable-line:no-non-null-assertion
WETH9Events.Transfer, WETH9Events.Transfer,

View File

@ -58,12 +58,15 @@ blockchainTests('Staking rewards fuzz test', env => {
}); });
it('fuzz', async () => { it('fuzz', async () => {
// Deploy contracts
const deployment = await DeploymentManager.deployAsync(env, { const deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 4, numErc20TokensToDeploy: 4,
numErc721TokensToDeploy: 0, numErc721TokensToDeploy: 0,
numErc1155TokensToDeploy: 0, numErc1155TokensToDeploy: 0,
}); });
const [ERC20TokenA, ERC20TokenB, ERC20TokenC, ERC20TokenD] = deployment.tokens.erc20; const [ERC20TokenA, ERC20TokenB, ERC20TokenC, ERC20TokenD] = deployment.tokens.erc20;
// Set up balance store
const balanceStore = new BlockchainBalanceStore( const balanceStore = new BlockchainBalanceStore(
{ {
StakingProxy: deployment.staking.stakingProxy.address, StakingProxy: deployment.staking.stakingProxy.address,
@ -80,8 +83,11 @@ blockchainTests('Staking rewards fuzz test', env => {
}, },
}, },
); );
// Set up simulation environment
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore); const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore);
// Spin up actors
const actors = [ const actors = [
new Maker({ deployment, simulationEnvironment, name: 'Maker 1' }), new Maker({ deployment, simulationEnvironment, name: 'Maker 1' }),
new Maker({ deployment, simulationEnvironment, name: 'Maker 2' }), new Maker({ deployment, simulationEnvironment, name: 'Maker 2' }),
@ -98,14 +104,17 @@ blockchainTests('Staking rewards fuzz test', env => {
new OperatorStakerMaker({ deployment, simulationEnvironment, name: 'Operator/Staker/Maker' }), new OperatorStakerMaker({ deployment, simulationEnvironment, name: 'Operator/Staker/Maker' }),
]; ];
// Takers need to set a WETH allowance for the staking proxy in case they pay the protocol fee in WETH
const takers = filterActorsByRole(actors, Taker); const takers = filterActorsByRole(actors, Taker);
for (const taker of takers) { for (const taker of takers) {
await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address); await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
} }
// Stakers need to set a ZRX allowance to deposit their ZRX into the zrxVault
const stakers = filterActorsByRole(actors, Staker); const stakers = filterActorsByRole(actors, Staker);
for (const staker of stakers) { for (const staker of stakers) {
await staker.configureERC20TokenAsync(deployment.tokens.zrx); await staker.configureERC20TokenAsync(deployment.tokens.zrx);
} }
// Register each actor in the balance store
for (const actor of actors) { for (const actor of actors) {
balanceStore.registerTokenOwner(actor.address, actor.name); balanceStore.registerTokenOwner(actor.address, actor.name);
} }

View File

@ -58,6 +58,7 @@ contract TestStaking is
testZrxVaultAddress = zrxVaultAddress; testZrxVaultAddress = zrxVaultAddress;
} }
// @dev Gets the most recent cumulative reward for a pool, and the epoch it was stored.
function getMostRecentCumulativeReward(bytes32 poolId) function getMostRecentCumulativeReward(bytes32 poolId)
external external
view view

View File

@ -67,6 +67,10 @@ export class StoredBalance {
) {} ) {}
} }
/**
* Simulates _loadCurrentBalance. `shouldMutate` flag specifies whether or not to update the given
* StoredBalance instance.
*/
export function loadCurrentBalance( export function loadCurrentBalance(
balance: StoredBalance, balance: StoredBalance,
epoch: BigNumber, epoch: BigNumber,
@ -85,11 +89,17 @@ export function loadCurrentBalance(
return loadedBalance; return loadedBalance;
} }
/**
* Simulates _incrementNextEpochBalance
*/
export function incrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { export function incrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true); loadCurrentBalance(balance, epoch, true);
balance.nextEpochBalance = balance.nextEpochBalance.plus(amount); balance.nextEpochBalance = balance.nextEpochBalance.plus(amount);
} }
/**
* Simulates _decrementNextEpochBalance
*/
export function decrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { export function decrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true); loadCurrentBalance(balance, epoch, true);
balance.nextEpochBalance = balance.nextEpochBalance.minus(amount); balance.nextEpochBalance = balance.nextEpochBalance.minus(amount);