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'; import { DecodedLogs, StakeInfo, StakeStatus } from '../../src/types'; import { artifacts } from '../artifacts'; import { TestCumulativeRewardTrackingContract, TestCumulativeRewardTrackingEvents } from '../wrappers'; import { StakingApiWrapper } from './api_wrapper'; export enum TestAction { Finalize, Delegate, Undelegate, PayProtocolFee, CreatePool, } interface TestLog { event: string; epoch: number; } export class CumulativeRewardTrackingSimulation { private readonly _amountToStake = toBaseUnitAmount(100); private readonly _protocolFee = new BigNumber(10); private readonly _stakingApiWrapper: StakingApiWrapper; private readonly _staker: string; private readonly _poolOperator: string; private readonly _takerAddress: string; private readonly _exchangeAddress: string; private _testCumulativeRewardTrackingContract?: TestCumulativeRewardTrackingContract; private _poolId: string; private static _extractTestLogs(txReceiptLogs: DecodedLogs): TestLog[] { const logs = []; for (const log of txReceiptLogs) { const wantedEvents = [TestCumulativeRewardTrackingEvents.SetCumulativeReward] as string[]; if (wantedEvents.indexOf(log.event) !== -1) { logs.push({ event: log.event, epoch: log.args.epoch.toNumber(), }); } } return logs; } private static _assertTestLogs(expectedSequence: TestLog[], txReceiptLogs: DecodedLogs): void { const logs = CumulativeRewardTrackingSimulation._extractTestLogs(txReceiptLogs); expect(logs.length).to.be.equal(expectedSequence.length); for (let i = 0; i < expectedSequence.length; i++) { const expectedLog = expectedSequence[i]; const actualLog = logs[i]; expect(expectedLog.event).to.exist(''); expect(actualLog.event, `testing event name of ${JSON.stringify(expectedLog)}`).to.be.equal( expectedLog.event, ); expect(actualLog.epoch, `testing epoch of ${JSON.stringify(expectedLog)}`).to.be.equal(expectedLog.epoch); } } constructor(stakingApiWrapper: StakingApiWrapper, actors: string[]) { this._stakingApiWrapper = stakingApiWrapper; // setup actors this._staker = actors[0]; this._poolOperator = actors[1]; this._takerAddress = actors[2]; this._exchangeAddress = actors[3]; this._poolId = ''; } public async deployAndConfigureTestContractsAsync(env: BlockchainTestsEnvironment): Promise { // set exchange address await this._stakingApiWrapper.stakingContract .addExchangeAddress(this._exchangeAddress) .awaitTransactionSuccessAsync(); this._testCumulativeRewardTrackingContract = await TestCumulativeRewardTrackingContract.deployFrom0xArtifactAsync( artifacts.TestCumulativeRewardTracking, env.provider, txDefaults, artifacts, this._stakingApiWrapper.wethContract.address, this._stakingApiWrapper.zrxVaultContract.address, ); } public getTestCumulativeRewardTrackingContract(): TestCumulativeRewardTrackingContract { if (this._testCumulativeRewardTrackingContract === undefined) { throw new Error(`Contract has not been deployed. Run 'deployAndConfigureTestContractsAsync'.`); } return this._testCumulativeRewardTrackingContract; } public async runTestAsync( initActions: TestAction[], testActions: TestAction[], expectedTestLogs: TestLog[], ): Promise { await this._executeActionsAsync(initActions); await this._stakingApiWrapper.stakingProxyContract .attachStakingContract(this.getTestCumulativeRewardTrackingContract().address) .awaitTransactionSuccessAsync(); const testLogs = await this._executeActionsAsync(testActions); CumulativeRewardTrackingSimulation._assertTestLogs(expectedTestLogs, testLogs); } private async _executeActionsAsync(actions: TestAction[]): Promise { const combinedLogs = [] as DecodedLogs; for (const action of actions) { let receipt: TransactionReceiptWithDecodedLogs | undefined; let logs = [] as DecodedLogs; switch (action) { case TestAction.Finalize: logs = await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync(); break; case TestAction.Delegate: await this._stakingApiWrapper.stakingContract.stake(this._amountToStake).sendTransactionAsync({ from: this._staker, }); receipt = await this._stakingApiWrapper.stakingContract .moveStake( new StakeInfo(StakeStatus.Undelegated), new StakeInfo(StakeStatus.Delegated, this._poolId), this._amountToStake, ) .awaitTransactionSuccessAsync({ from: this._staker }); break; case TestAction.Undelegate: receipt = await this._stakingApiWrapper.stakingContract .moveStake( new StakeInfo(StakeStatus.Delegated, this._poolId), new StakeInfo(StakeStatus.Undelegated), this._amountToStake, ) .awaitTransactionSuccessAsync({ from: this._staker }); break; case TestAction.PayProtocolFee: receipt = await this._stakingApiWrapper.stakingContract .payProtocolFee(this._poolOperator, this._takerAddress, this._protocolFee) .awaitTransactionSuccessAsync({ from: this._exchangeAddress, value: this._protocolFee }); break; case TestAction.CreatePool: receipt = await this._stakingApiWrapper.stakingContract .createStakingPool(0, true) .awaitTransactionSuccessAsync({ from: this._poolOperator }); const createStakingPoolLog = receipt.logs[0]; // tslint:disable-next-line no-unnecessary-type-assertion this._poolId = (createStakingPoolLog as DecodedLogEntry).args.poolId; break; default: throw new Error('Unrecognized test action'); } if (receipt !== undefined) { logs = receipt.logs as DecodedLogs; } combinedLogs.splice(combinedLogs.length, 0, ...logs); } return combinedLogs; } }