From 49538f272e5330a9dcf436b625e4101a124beec4 Mon Sep 17 00:00:00 2001 From: Michael Zhu Date: Wed, 11 Dec 2019 16:52:48 -0800 Subject: [PATCH] address comments --- .../test/framework/actors/base.ts | 1 + .../test/framework/actors/fee_recipient.ts | 1 + .../test/framework/actors/keeper.ts | 1 + .../test/framework/actors/maker.ts | 54 +++++++++++++ .../test/framework/actors/pool_operator.ts | 1 + .../test/framework/actors/staker.ts | 2 + .../test/framework/actors/taker.ts | 54 +------------ .../test/framework/actors/utils.ts | 5 +- .../test/framework/assertions/finalizePool.ts | 78 ++++--------------- .../test/framework/assertions/moveStake.ts | 39 +++++----- .../test/framework/assertions/stake.ts | 21 ++--- .../test/framework/assertions/unstake.ts | 21 ++--- contracts/staking/src/index.ts | 6 +- contracts/staking/src/types.ts | 26 ++++++- 14 files changed, 151 insertions(+), 159 deletions(-) diff --git a/contracts/integrations/test/framework/actors/base.ts b/contracts/integrations/test/framework/actors/base.ts index 610337f1f9..1b35825adb 100644 --- a/contracts/integrations/test/framework/actors/base.ts +++ b/contracts/integrations/test/framework/actors/base.ts @@ -29,6 +29,7 @@ export class Actor { public simulationActions: { [action: string]: AsyncIterableIterator; } = {}; + public mixins: string[] = []; protected readonly _transactionFactory: TransactionFactory; public static reset(): void { diff --git a/contracts/integrations/test/framework/actors/fee_recipient.ts b/contracts/integrations/test/framework/actors/fee_recipient.ts index bb60028cf0..ef1df62094 100644 --- a/contracts/integrations/test/framework/actors/fee_recipient.ts +++ b/contracts/integrations/test/framework/actors/fee_recipient.ts @@ -35,6 +35,7 @@ export function FeeRecipientMixin(Base: TBase): TBase // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('FeeRecipient'); const { verifyingContract } = args[0] as FeeRecipientConfig; if (verifyingContract !== undefined) { diff --git a/contracts/integrations/test/framework/actors/keeper.ts b/contracts/integrations/test/framework/actors/keeper.ts index 441055d6be..23142918f8 100644 --- a/contracts/integrations/test/framework/actors/keeper.ts +++ b/contracts/integrations/test/framework/actors/keeper.ts @@ -36,6 +36,7 @@ export function KeeperMixin(Base: TBase): TBase & Con // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('Keeper'); // Register this mixin's assertion generators this.actor.simulationActions = { diff --git a/contracts/integrations/test/framework/actors/maker.ts b/contracts/integrations/test/framework/actors/maker.ts index b724739305..94858ab7bb 100644 --- a/contracts/integrations/test/framework/actors/maker.ts +++ b/contracts/integrations/test/framework/actors/maker.ts @@ -1,3 +1,4 @@ +import { DummyERC20TokenContract } from '@0x/contracts-erc20'; import { constants, OrderFactory } from '@0x/contracts-test-utils'; import { Order, SignedOrder } from '@0x/types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; @@ -18,6 +19,7 @@ export interface MakerInterface { signOrderAsync: (customOrderParams?: Partial) => Promise; cancelOrderAsync: (order: SignedOrder) => Promise; joinStakingPoolAsync: (poolId: string) => Promise; + createFillableOrderAsync: (taker: Actor) => Promise; } /** @@ -39,6 +41,7 @@ export function MakerMixin(Base: TBase): TBase & Cons // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('Maker'); const { orderConfig } = args[0] as MakerConfig; const defaultOrderParams = { @@ -84,6 +87,57 @@ export function MakerMixin(Base: TBase): TBase & Cons }); } + public async createFillableOrderAsync(taker: Actor): Promise { + const { actors, balanceStore } = this.actor.simulationEnvironment!; + await balanceStore.updateErc20BalancesAsync(); + + // Choose the assets for the order + const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize( + this.actor.deployment.tokens.erc20, + 4, // tslint:disable-line:custom-no-magic-numbers + ); + + // Maker and taker set balances/allowances to guarantee that the fill succeeds. + // Amounts are chosen to be within each actor's balance (divided by 2, in case + // e.g. makerAsset = makerFeeAsset) + const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all( + [ + [this.actor, makerToken], + [this.actor, makerFeeToken], + [taker, takerToken], + [taker, takerFeeToken], + ].map(async ([owner, token]) => { + 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] = [ + makerToken, + makerFeeToken, + takerToken, + takerFeeToken, + ].map(token => + this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(), + ); + + // Maker signs the order + return this.signOrderAsync({ + makerAssetData, + takerAssetData, + makerFeeAssetData, + takerFeeAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee, + takerFee, + feeRecipientAddress: Pseudorandom.sample(actors)!.address, + }); + } + private async *_validJoinStakingPool(): AsyncIterableIterator { const { stakingPools } = this.actor.simulationEnvironment!; const assertion = validJoinStakingPoolAssertion(this.actor.deployment); diff --git a/contracts/integrations/test/framework/actors/pool_operator.ts b/contracts/integrations/test/framework/actors/pool_operator.ts index 71734a7d7d..6352a8a863 100644 --- a/contracts/integrations/test/framework/actors/pool_operator.ts +++ b/contracts/integrations/test/framework/actors/pool_operator.ts @@ -35,6 +35,7 @@ export function PoolOperatorMixin(Base: TBase): TBase // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('PoolOperator'); // Register this mixin's assertion generators this.actor.simulationActions = { diff --git a/contracts/integrations/test/framework/actors/staker.ts b/contracts/integrations/test/framework/actors/staker.ts index 6263408339..48ef2694a9 100644 --- a/contracts/integrations/test/framework/actors/staker.ts +++ b/contracts/integrations/test/framework/actors/staker.ts @@ -34,6 +34,8 @@ export function StakerMixin(Base: TBase): TBase & Con // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('Staker'); + this.stake = { [StakeStatus.Undelegated]: new StoredBalance(), [StakeStatus.Delegated]: { total: new StoredBalance() }, diff --git a/contracts/integrations/test/framework/actors/taker.ts b/contracts/integrations/test/framework/actors/taker.ts index 5527891844..9da1fca91b 100644 --- a/contracts/integrations/test/framework/actors/taker.ts +++ b/contracts/integrations/test/framework/actors/taker.ts @@ -1,5 +1,3 @@ -import { DummyERC20TokenContract } from '@0x/contracts-erc20'; -import { constants } from '@0x/contracts-test-utils'; import { SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; @@ -38,6 +36,7 @@ export function TakerMixin(Base: TBase): TBase & Cons // tslint:disable-next-line:no-inferred-empty-object-type super(...args); this.actor = (this as any) as Actor; + this.actor.mixins.push('Taker'); // Register this mixin's assertion generators this.actor.simulationActions = { @@ -65,7 +64,7 @@ export function TakerMixin(Base: TBase): TBase & Cons } private async *_validFillOrder(): AsyncIterableIterator { - const { actors, balanceStore } = this.actor.simulationEnvironment!; + const { actors } = this.actor.simulationEnvironment!; const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!); while (true) { // Choose a maker to be the other side of the order @@ -73,53 +72,8 @@ export function TakerMixin(Base: TBase): TBase & Cons if (maker === undefined) { yield; } else { - await balanceStore.updateErc20BalancesAsync(); - // Choose the assets for the order - const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize( - this.actor.deployment.tokens.erc20, - 4, // tslint:disable-line:custom-no-magic-numbers - ); - - // Maker and taker set balances/allowances to guarantee that the fill succeeds. - // Amounts are chosen to be within each actor's balance (divided by 2, in case - // e.g. makerAsset = makerFeeAsset) - const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all( - [ - [maker, makerToken], - [maker, makerFeeToken], - [this.actor, takerToken], - [this.actor, takerFeeToken], - ].map(async ([owner, token]) => { - 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] = [ - makerToken, - makerFeeToken, - takerToken, - takerFeeToken, - ].map(token => - this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(), - ); - - // Maker signs the order - const order = await maker.signOrderAsync({ - makerAssetData, - takerAssetData, - makerFeeAssetData, - takerFeeAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee, - takerFee, - feeRecipientAddress: Pseudorandom.sample(actors)!.address, - }); - + // Maker creates and signs a fillable order + const order = await maker.createFillableOrderAsync(this.actor); // Taker fills the order by a random amount (up to the order's takerAssetAmount) const fillAmount = Pseudorandom.integer(order.takerAssetAmount); // Taker executes the fill with a random msg.value, so that sometimes the diff --git a/contracts/integrations/test/framework/actors/utils.ts b/contracts/integrations/test/framework/actors/utils.ts index 6206d842a1..b958a1b216 100644 --- a/contracts/integrations/test/framework/actors/utils.ts +++ b/contracts/integrations/test/framework/actors/utils.ts @@ -12,11 +12,12 @@ export function actorAddressesByName(actors: Actor[]): ObjectMap { } /** - * Filters the given actors by class. + * Filters the given actors by role, specified by the class exported by an actor mixin file, + * e.g, 'Maker', 'Taker', etc. */ export function filterActorsByRole( actors: Actor[], role: TClass, ): Array> { - return actors.filter(actor => actor instanceof role) as InstanceType; + return actors.filter(actor => actor.mixins.includes(role.name)) as InstanceType; } diff --git a/contracts/integrations/test/framework/assertions/finalizePool.ts b/contracts/integrations/test/framework/assertions/finalizePool.ts index 6a5ff99334..6d4e62405c 100644 --- a/contracts/integrations/test/framework/assertions/finalizePool.ts +++ b/contracts/integrations/test/framework/assertions/finalizePool.ts @@ -24,9 +24,9 @@ import { SimulationEnvironment } from '../simulation'; import { FunctionAssertion, FunctionResult } from './function_assertion'; const PRECISION = 15; -const ALPHA_NUMERATOR = 1; -const ALPHA_DENOMINATOR = 3; -const COBB_DOUGLAS_ALPHA = toDecimal(ALPHA_NUMERATOR).dividedBy(toDecimal(ALPHA_DENOMINATOR)); +const COBB_DOUGLAS_ALPHA = toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator).dividedBy( + toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaDenominator), +); // Reference function for Cobb-Douglas function cobbDouglas(poolStats: PoolStats, aggregatedStats: AggregatedStats): BigNumber { @@ -95,34 +95,19 @@ export function validFinalizePoolAssertion( expect(result.success, `Error: ${result.data}`).to.be.true(); const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion - - // // Compute relevant epochs - // uint256 currentEpoch_ = currentEpoch; - // uint256 prevEpoch = currentEpoch_.safeSub(1); const { stakingPools, currentEpoch } = simulationEnvironment; const prevEpoch = currentEpoch.minus(1); const [poolId] = args; const pool = stakingPools[poolId]; - // // Load the aggregated stats into memory; noop if no pools to finalize. - // IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[prevEpoch]; - // if (aggregatedStats.numPoolsToFinalize == 0) { - // return; - // } - // - // // Noop if the pool did not earn rewards or already finalized (has no fees). - // IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][prevEpoch]; - // if (poolStats.feesCollected == 0) { - // return; - // } + // finalizePool noops if there are no pools to finalize or + // the pool did not earn rewards or already finalized (has no fees). if (beforeInfo.aggregatedStats.numPoolsToFinalize.isZero() || beforeInfo.poolStats.feesCollected.isZero()) { expect(logs.length, 'Expect no events to be emitted').to.equal(0); return; } - // // Clear the pool stats so we don't finalize it again, and to recoup - // // some gas. - // delete poolStatsByEpoch[poolId][prevEpoch]; + // It should have cleared the pool stats for prevEpoch const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync()); expect(poolStats).to.deep.equal({ feesCollected: constants.ZERO_AMOUNT, @@ -130,43 +115,26 @@ export function validFinalizePoolAssertion( membersStake: constants.ZERO_AMOUNT, }); - // // Compute the rewards. // uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats); const rewards = BigNumber.min( cobbDouglas(beforeInfo.poolStats, beforeInfo.aggregatedStats), beforeInfo.aggregatedStats.rewardsAvailable.minus(beforeInfo.aggregatedStats.totalRewardsFinalized), ); - // // Pay the operator and update rewards for the pool. - // // Note that we credit at the CURRENT epoch even though these rewards - // // were earned in the previous epoch. - // (uint256 operatorReward, uint256 membersReward) = _syncPoolRewards( - // poolId, - // rewards, - // poolStats.membersStake - // ); - // - // // Emit an event. - // emit RewardsPaid( - // currentEpoch_, - // poolId, - // operatorReward, - // membersReward - // ); - // - // uint256 totalReward = operatorReward.safeAdd(membersReward); + // Check that a RewardsPaid event was emitted const events = filterLogsToArguments(logs, StakingEvents.RewardsPaid); expect(events.length, 'Number of RewardsPaid events emitted').to.equal(1); const [rewardsPaidEvent] = events; - expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.equal(poolId); expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch); + // Pull the operator and members' reward from the event const { operatorReward, membersReward } = rewardsPaidEvent; const totalReward = operatorReward.plus(membersReward); + // Should be approximately equal to the rewards compute using the Cobb-Douglas reference function assertRoughlyEquals(totalReward, rewards, PRECISION); - // See _computePoolRewardsSplit + // Operator takes their share of the rewards if (beforeInfo.poolStats.membersStake.isZero()) { expect( operatorReward, @@ -182,7 +150,7 @@ export function validFinalizePoolAssertion( ); } - // See _syncPoolRewards + // Pays the operator in WETH if the operator's reward is non-zero const expectedTransferEvents = operatorReward.isGreaterThan(0) ? [ { @@ -211,22 +179,15 @@ export function validFinalizePoolAssertion( beforeInfo.poolStats.membersStake, ); [numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator); - // There's a bug in our reference functions, but I can't figure it out :/ + // There's a bug in our reference functions, probably due to the fact that safeDiv in + // Solidity truncates in bits, whereas the safeDiv reference function truncates in base 10. assertRoughlyEquals( mostRecentCumulativeRewards.numerator.dividedBy(mostRecentCumulativeRewards.denominator), numerator.dividedBy(denominator), PRECISION, ); - // // Increase `totalRewardsFinalized`. - // aggregatedStatsByEpoch[prevEpoch].totalRewardsFinalized = - // aggregatedStats.totalRewardsFinalized = - // aggregatedStats.totalRewardsFinalized.safeAdd(totalReward); - // - // // Decrease the number of unfinalized pools left. - // aggregatedStatsByEpoch[prevEpoch].numPoolsToFinalize = - // aggregatedStats.numPoolsToFinalize = - // aggregatedStats.numPoolsToFinalize.safeSub(1); + // Check that aggregated stats have been updated const aggregatedStats = AggregatedStats.fromArray( await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(), ); @@ -236,15 +197,7 @@ export function validFinalizePoolAssertion( numPoolsToFinalize: beforeInfo.aggregatedStats.numPoolsToFinalize.minus(1), }); - // // If there are no more unfinalized pools remaining, the epoch is - // // finalized. - // if (aggregatedStats.numPoolsToFinalize == 0) { - // emit EpochFinalized( - // prevEpoch, - // aggregatedStats.totalRewardsFinalized, - // aggregatedStats.rewardsAvailable.safeSub(aggregatedStats.totalRewardsFinalized) - // ); - // } + // If there are no more unfinalized pools remaining, the epoch is finalized. const expectedEpochFinalizedEvents = aggregatedStats.numPoolsToFinalize.isZero() ? [ { @@ -262,6 +215,7 @@ export function validFinalizePoolAssertion( StakingEvents.EpochFinalized, ); + // Update local state pool.lastFinalized = prevEpoch; }, }); diff --git a/contracts/integrations/test/framework/assertions/moveStake.ts b/contracts/integrations/test/framework/assertions/moveStake.ts index e29dbe3ccf..7290dcc7f2 100644 --- a/contracts/integrations/test/framework/assertions/moveStake.ts +++ b/contracts/integrations/test/framework/assertions/moveStake.ts @@ -1,6 +1,6 @@ import { - decrementNextEpochBalance, - incrementNextEpochBalance, + decreaseNextBalance, + increaseNextBalance, loadCurrentBalance, OwnerStakeByStatus, StakeInfo, @@ -32,18 +32,18 @@ function updateNextEpochBalances( // Decrement next epoch balances associated with the `from` stake if (from.status === StakeStatus.Undelegated) { // Decrement owner undelegated stake - decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); + decreaseNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); // Decrement global undelegated stake - decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); + decreaseNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); } else if (from.status === StakeStatus.Delegated) { // Decrement owner's delegated stake to this pool - decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount, currentEpoch); + decreaseNextBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount, currentEpoch); // Decrement owner's total delegated stake - decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch); + decreaseNextBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch); // Decrement global delegated stake - decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); + decreaseNextBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); // Decrement pool's delegated stake - decrementNextEpochBalance(stakingPools[from.poolId].delegatedStake, amount, currentEpoch); + decreaseNextBalance(stakingPools[from.poolId].delegatedStake, amount, currentEpoch); updatedPools.push(from.poolId); // TODO: Check that delegator rewards have been withdrawn/synced @@ -52,22 +52,22 @@ function updateNextEpochBalances( // Increment next epoch balances associated with the `to` stake if (to.status === StakeStatus.Undelegated) { // Increment owner undelegated stake - incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); + increaseNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); // Increment global undelegated stake - incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); + increaseNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); } else if (to.status === StakeStatus.Delegated) { // Initializes the balance for this pool if the user has not previously delegated to it _.defaults(ownerStake[StakeStatus.Delegated], { [to.poolId]: new StoredBalance(), }); // Increment owner's delegated stake to this pool - incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount, currentEpoch); + increaseNextBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount, currentEpoch); // Increment owner's total delegated stake - incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch); + increaseNextBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch); // Increment global delegated stake - incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); + increaseNextBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); // Increment pool's delegated stake - incrementNextEpochBalance(stakingPools[to.poolId].delegatedStake, amount, currentEpoch); + increaseNextBalance(stakingPools[to.poolId].delegatedStake, amount, currentEpoch); updatedPools.push(to.poolId); // TODO: Check that delegator rewards have been withdrawn/synced @@ -84,7 +84,7 @@ export function validMoveStakeAssertion( simulationEnvironment: SimulationEnvironment, ownerStake: OwnerStakeByStatus, ): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> { - const { stakingWrapper, zrxVault } = deployment.staking; + const { stakingWrapper } = deployment.staking; return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', { after: async ( @@ -125,15 +125,12 @@ export function validMoveStakeAssertion( const globalUndelegatedStake = await stakingWrapper .getGlobalStakeByStatus(StakeStatus.Undelegated) .callAsync(); - const totalStake = await zrxVault.balanceOfZrxVault().callAsync(); expect(globalDelegatedStake).to.deep.equal( loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch), ); - expect(globalUndelegatedStake).to.deep.equal({ - currentEpochBalance: totalStake.minus(globalDelegatedStake.currentEpochBalance), - nextEpochBalance: totalStake.minus(globalDelegatedStake.nextEpochBalance), - currentEpoch, - }); + expect(globalUndelegatedStake).to.deep.equal( + loadCurrentBalance(globalStake[StakeStatus.Undelegated], currentEpoch), + ); // Fetches on-chain pool stake balances and checks against local balances for (const poolId of updatedPools) { diff --git a/contracts/integrations/test/framework/assertions/stake.ts b/contracts/integrations/test/framework/assertions/stake.ts index e4921eea24..d7ceb2a91e 100644 --- a/contracts/integrations/test/framework/assertions/stake.ts +++ b/contracts/integrations/test/framework/assertions/stake.ts @@ -1,4 +1,4 @@ -import { loadCurrentBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; +import { increaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; @@ -47,26 +47,29 @@ export function validStakeAssertion( expect(result.success, `Error: ${result.data}`).to.be.true(); const [amount] = args; - const { balanceStore, currentEpoch } = simulationEnvironment; + const { balanceStore, currentEpoch, globalStake } = simulationEnvironment; // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); // _increaseCurrentAndNextBalance - loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); - ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ - StakeStatus.Undelegated - ].currentEpochBalance.plus(amount); - ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[ - StakeStatus.Undelegated - ].nextEpochBalance.plus(amount); + increaseCurrentAndNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); + increaseCurrentAndNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); // 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]); + + // Checks that the global undelegated stake has also increased by the stake amount + const globalUndelegatedStake = await stakingWrapper + .getGlobalStakeByStatus(StakeStatus.Undelegated) + .callAsync(); + expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal( + globalStake[StakeStatus.Undelegated], + ); }, }); } diff --git a/contracts/integrations/test/framework/assertions/unstake.ts b/contracts/integrations/test/framework/assertions/unstake.ts index fc69f991a8..2d736f3fe3 100644 --- a/contracts/integrations/test/framework/assertions/unstake.ts +++ b/contracts/integrations/test/framework/assertions/unstake.ts @@ -1,4 +1,4 @@ -import { loadCurrentBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; +import { decreaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; import { expect } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; import { TxData } from 'ethereum-types'; @@ -47,26 +47,29 @@ export function validUnstakeAssertion( expect(result.success, `Error: ${result.data}`).to.be.true(); const [amount] = args; - const { balanceStore, currentEpoch } = simulationEnvironment; + const { balanceStore, currentEpoch, globalStake } = simulationEnvironment; // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); // _decreaseCurrentAndNextBalance - loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); - ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ - StakeStatus.Undelegated - ].currentEpochBalance.minus(amount); - ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[ - StakeStatus.Undelegated - ].nextEpochBalance.minus(amount); + decreaseCurrentAndNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); + decreaseCurrentAndNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); // 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]); + + // Checks that the global undelegated stake has also increased by the stake amount + const globalUndelegatedStake = await stakingWrapper + .getGlobalStakeByStatus(StakeStatus.Undelegated) + .callAsync(); + expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal( + globalStake[StakeStatus.Undelegated], + ); }, }); } diff --git a/contracts/staking/src/index.ts b/contracts/staking/src/index.ts index 41508e47ff..884bd55dd8 100644 --- a/contracts/staking/src/index.ts +++ b/contracts/staking/src/index.ts @@ -49,8 +49,10 @@ export { StakeStatus, StoredBalance, loadCurrentBalance, - incrementNextEpochBalance, - decrementNextEpochBalance, + increaseNextBalance, + decreaseNextBalance, + increaseCurrentAndNextBalance, + decreaseCurrentAndNextBalance, StakingPoolById, OwnerStakeByStatus, GlobalStakeByStatus, diff --git a/contracts/staking/src/types.ts b/contracts/staking/src/types.ts index 9890d99e48..468c1e426d 100644 --- a/contracts/staking/src/types.ts +++ b/contracts/staking/src/types.ts @@ -90,21 +90,39 @@ export function loadCurrentBalance( } /** - * Simulates _incrementNextEpochBalance + * Simulates _increaseNextBalance */ -export function incrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { +export function increaseNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { loadCurrentBalance(balance, epoch, true); balance.nextEpochBalance = balance.nextEpochBalance.plus(amount); } /** - * Simulates _decrementNextEpochBalance + * Simulates _decreaseNextBalance */ -export function decrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { +export function decreaseNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { loadCurrentBalance(balance, epoch, true); balance.nextEpochBalance = balance.nextEpochBalance.minus(amount); } +/** + * Simulates _increaseCurrentAndNextBalance + */ +export function increaseCurrentAndNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { + loadCurrentBalance(balance, epoch, true); + balance.currentEpochBalance = balance.currentEpochBalance.plus(amount); + balance.nextEpochBalance = balance.nextEpochBalance.plus(amount); +} + +/** + * Simulates _decreaseCurrentAndNextBalance + */ +export function decreaseCurrentAndNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { + loadCurrentBalance(balance, epoch, true); + balance.currentEpochBalance = balance.currentEpochBalance.minus(amount); + balance.nextEpochBalance = balance.nextEpochBalance.minus(amount); +} + export interface StakeBalanceByPool { [key: string]: StoredBalance; }