167 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { WETH9DepositEventArgs, WETH9Events } from '@0x/contracts-erc20';
 | 
						|
import {
 | 
						|
    AggregatedStats,
 | 
						|
    StakingEpochEndedEventArgs,
 | 
						|
    StakingEpochFinalizedEventArgs,
 | 
						|
    StakingEvents,
 | 
						|
    StakingRevertErrors,
 | 
						|
} from '@0x/contracts-staking';
 | 
						|
import { constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
 | 
						|
import { BigNumber } from '@0x/utils';
 | 
						|
import { TxData } from 'ethereum-types';
 | 
						|
import * as _ from 'lodash';
 | 
						|
 | 
						|
import { DeploymentManager } from '../deployment_manager';
 | 
						|
import { SimulationEnvironment } from '../simulation';
 | 
						|
 | 
						|
import { FunctionAssertion, FunctionResult } from './function_assertion';
 | 
						|
 | 
						|
interface EndEpochBeforeInfo {
 | 
						|
    wethReservedForPoolRewards: BigNumber;
 | 
						|
    aggregatedStatsBefore: AggregatedStats;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a FunctionAssertion for `endEpoch` which assumes valid input is provided. It checks
 | 
						|
 * that the staking proxy contract wrapped its ETH balance, aggregated stats were updated, and
 | 
						|
 * EpochFinalized/EpochEnded events were emitted.
 | 
						|
 */
 | 
						|
export function validEndEpochAssertion(
 | 
						|
    deployment: DeploymentManager,
 | 
						|
    simulationEnvironment: SimulationEnvironment,
 | 
						|
): FunctionAssertion<[], EndEpochBeforeInfo, void> {
 | 
						|
    const { stakingWrapper } = deployment.staking;
 | 
						|
    const { balanceStore } = simulationEnvironment;
 | 
						|
 | 
						|
    return new FunctionAssertion(stakingWrapper, 'endEpoch', {
 | 
						|
        before: async () => {
 | 
						|
            await balanceStore.updateEthBalancesAsync();
 | 
						|
            const aggregatedStatsBefore = AggregatedStats.fromArray(
 | 
						|
                await stakingWrapper.aggregatedStatsByEpoch(simulationEnvironment.currentEpoch).callAsync(),
 | 
						|
            );
 | 
						|
            const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
 | 
						|
            return { wethReservedForPoolRewards, aggregatedStatsBefore };
 | 
						|
        },
 | 
						|
        after: async (beforeInfo: EndEpochBeforeInfo, result: FunctionResult, _args: [], _txData: Partial<TxData>) => {
 | 
						|
            // Ensure that the tx succeeded.
 | 
						|
            expect(result.success, `Error: ${result.data}`).to.be.true();
 | 
						|
 | 
						|
            const { currentEpoch } = simulationEnvironment;
 | 
						|
            const logs = result.receipt!.logs; // tslint:disable-line
 | 
						|
 | 
						|
            // Check WETH deposit event
 | 
						|
            const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT;
 | 
						|
            const expectedDepositEvents = previousEthBalance.isGreaterThan(0)
 | 
						|
                ? [
 | 
						|
                      {
 | 
						|
                          _owner: deployment.staking.stakingProxy.address,
 | 
						|
                          _value: previousEthBalance,
 | 
						|
                      },
 | 
						|
                  ]
 | 
						|
                : [];
 | 
						|
            verifyEventsFromLogs<WETH9DepositEventArgs>(logs, expectedDepositEvents, WETH9Events.Deposit);
 | 
						|
 | 
						|
            // Check that the aggregated stats were updated
 | 
						|
            await balanceStore.updateErc20BalancesAsync();
 | 
						|
            const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo;
 | 
						|
            const expectedAggregatedStats = {
 | 
						|
                ...aggregatedStatsBefore,
 | 
						|
                rewardsAvailable: _.get(
 | 
						|
                    balanceStore.balances,
 | 
						|
                    ['erc20', stakingWrapper.address, deployment.tokens.weth.address],
 | 
						|
                    constants.ZERO_AMOUNT,
 | 
						|
                ).minus(wethReservedForPoolRewards),
 | 
						|
            };
 | 
						|
            const aggregatedStatsAfter = AggregatedStats.fromArray(
 | 
						|
                await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(),
 | 
						|
            );
 | 
						|
            expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats);
 | 
						|
 | 
						|
            // Check that an EpochEnded event was emitted
 | 
						|
            verifyEventsFromLogs<StakingEpochEndedEventArgs>(
 | 
						|
                logs,
 | 
						|
                [
 | 
						|
                    {
 | 
						|
                        epoch: currentEpoch,
 | 
						|
                        numPoolsToFinalize: aggregatedStatsAfter.numPoolsToFinalize,
 | 
						|
                        rewardsAvailable: aggregatedStatsAfter.rewardsAvailable,
 | 
						|
                        totalFeesCollected: aggregatedStatsAfter.totalFeesCollected,
 | 
						|
                        totalWeightedStake: aggregatedStatsAfter.totalWeightedStake,
 | 
						|
                    },
 | 
						|
                ],
 | 
						|
                StakingEvents.EpochEnded,
 | 
						|
            );
 | 
						|
 | 
						|
            // If there are no more pools to finalize, an EpochFinalized event should've been emitted
 | 
						|
            const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero()
 | 
						|
                ? [
 | 
						|
                      {
 | 
						|
                          epoch: currentEpoch,
 | 
						|
                          rewardsPaid: constants.ZERO_AMOUNT,
 | 
						|
                          rewardsRemaining: aggregatedStatsAfter.rewardsAvailable,
 | 
						|
                      },
 | 
						|
                  ]
 | 
						|
                : [];
 | 
						|
            verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
 | 
						|
                logs,
 | 
						|
                expectedEpochFinalizedEvents,
 | 
						|
                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(
 | 
						|
                aggregatedStatsAfter.numPoolsToFinalize,
 | 
						|
            );
 | 
						|
 | 
						|
            // Update currentEpoch locally
 | 
						|
            simulationEnvironment.currentEpoch = currentEpoch.plus(1);
 | 
						|
        },
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a FunctionAssertion for `endEpoch` which assumes it has been called while the previous
 | 
						|
 * epoch hasn't been fully finalized. Checks that the transaction reverts with PreviousEpochNotFinalizedError.
 | 
						|
 */
 | 
						|
export function endEpochUnfinalizedPoolsAssertion(
 | 
						|
    deployment: DeploymentManager,
 | 
						|
    simulationEnvironment: SimulationEnvironment,
 | 
						|
    numPoolsToFinalizeFromPrevEpoch: BigNumber,
 | 
						|
): FunctionAssertion<[], void, void> {
 | 
						|
    return new FunctionAssertion(deployment.staking.stakingWrapper, 'endEpoch', {
 | 
						|
        after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
						|
            // Ensure that the tx reverted.
 | 
						|
            expect(result.success).to.be.false();
 | 
						|
 | 
						|
            // Check revert error
 | 
						|
            expect(result.data).to.equal(
 | 
						|
                new StakingRevertErrors.PreviousEpochNotFinalizedError(
 | 
						|
                    simulationEnvironment.currentEpoch.minus(1),
 | 
						|
                    numPoolsToFinalizeFromPrevEpoch,
 | 
						|
                ),
 | 
						|
            );
 | 
						|
        },
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns a FunctionAssertion for `endEpoch` which assumes it has been called before the full epoch
 | 
						|
 * duration has elapsed. Checks that the transaction reverts with BlockTimestampTooLowError.
 | 
						|
 */
 | 
						|
export function endEpochTooEarlyAssertion(deployment: DeploymentManager): FunctionAssertion<[], void, void> {
 | 
						|
    const { stakingWrapper } = deployment.staking;
 | 
						|
    return new FunctionAssertion(stakingWrapper, 'endEpoch', {
 | 
						|
        after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
						|
            // Ensure that the tx reverted.
 | 
						|
            expect(result.success).to.be.false();
 | 
						|
 | 
						|
            // Check revert error
 | 
						|
            const epochEndTime = await stakingWrapper.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
 | 
						|
            const lastBlockTime = await deployment.web3Wrapper.getBlockTimestampAsync('latest');
 | 
						|
            expect(result.data).to.equal(
 | 
						|
                new StakingRevertErrors.BlockTimestampTooLowError(epochEndTime, lastBlockTime),
 | 
						|
            );
 | 
						|
        },
 | 
						|
    });
 | 
						|
}
 |