Working on simulation to make it easier to follow the end-to-end tests. Mostly working.

This commit is contained in:
Greg Hysen
2019-06-25 23:13:36 -07:00
parent 74d9891e06
commit 8a2df9cd1f
16 changed files with 454 additions and 38 deletions

View File

@@ -0,0 +1,155 @@
import {
chaiSetup,
} from '@0x/contracts-test-utils';
import * as _ from 'lodash';
import * as chai from 'chai';
import { PoolOperatorActor } from '../actors/pool_operator_actor';
import { MakerActor } from '../actors/maker_actor';
import { BigNumber } from '@0x/utils';
import { SimulationParams } from './types';
import { StakingWrapper } from './staking_wrapper';
import { Queue } from './queue';
import { DelegatorActor } from '../actors/delegator_actor';
chaiSetup.configure();
const expect = chai.expect;
export class Simulation {
private readonly _stakingWrapper: StakingWrapper;
private readonly _p: SimulationParams;
private _userQueue: Queue<string>;
private _poolOperators: PoolOperatorActor[];
private _poolOperatorsAsDelegators: DelegatorActor[];
private _poolIds: string[];
private _makers: MakerActor[];
private _delegators: DelegatorActor[];
constructor(stakingWrapper: StakingWrapper, simulationParams: SimulationParams) {
this._stakingWrapper = stakingWrapper;
this._p = simulationParams;
this._userQueue = new Queue<string>();
this._poolOperators = [];
this._poolOperatorsAsDelegators = [];
this._poolIds = [];
this._makers = [];
this._delegators = [];
}
public async runAsync(): Promise<void> {
this._userQueue = new Queue<string>(this._p.users);
await this._stakingWrapper.addExchangeAddressAsync(this._p.exchangeAddress);
await this._setupPoolsAsync(this._p);
await this._setupMakersAsync(this._p);
await this._payProtocolFeesAsync(this._p);
if (this._p.delegateInNextEpoch) {
// this property forces the staking contracts to use shadow ether
await this._stakingWrapper.skipToNextEpochAsync();
}
await this._setupDelegatorsAsync(this._p);
await this._stakingWrapper.skipToNextEpochAsync();
// everyone has been paid out. check balances.
await this._assertVaultBalancesAsync(this._p);
}
private async _setupPoolsAsync(p: SimulationParams): Promise<void> {
for (const i in _.range(p.numberOfPools)) {
// create operator
const poolOperatorAddress = this._userQueue.popFront();
const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper);
this._poolOperators.push(poolOperator);
// create a pool id for this operator
const poolId = await poolOperator.createPoolAsync(p.poolOperatorShares[i]);
this._poolIds.push(poolId);
// each pool operator can also be a staker/delegator
const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper);
this._poolOperatorsAsDelegators.push(poolOperatorAsDelegator);
// add stake to the operator's pool
const amountOfStake = p.stakeByPoolOperator[i];
await poolOperatorAsDelegator.depositAndStakeAsync(amountOfStake);
}
}
private async _setupMakersAsync(p: SimulationParams): Promise<void> {
// create makers
for (const i in _.range(p.numberOfMakers)) {
const makerAddress = this._userQueue.popFront();
const maker = new MakerActor(makerAddress, this._stakingWrapper);
this._makers.push(maker);
}
// add each maker to their respective pool
let makerIdx = 0;
let poolIdx = 0;
for (const numberOfMakersInPool of p.numberOfMakersPerPool) {
const poolId = this._poolIds[poolIdx];
const poolOperator = this._poolOperators[poolIdx];
for (const j in _.range(numberOfMakersInPool)) {
const maker = this._makers[makerIdx];
const makerApproval = maker.signApprovalForStakingPool(poolId);
const makerAddress = maker.getOwner();
await poolOperator.addMakerToPoolAsync(poolId, makerAddress, makerApproval.signature);
makerIdx += 1;
}
poolIdx += 1;
}
}
private async _setupDelegatorsAsync(p: SimulationParams): Promise<void> {
// create delegators
for (const i in _.range(p.numberOfDelegators)) {
const delegatorAddress = this._userQueue.popFront();
const delegator = new DelegatorActor(delegatorAddress, this._stakingWrapper);
this._delegators.push(delegator);
}
// delegate to pools
// currently each actor delegates to a single pool
let delegatorIdx = 0;
let poolIdx = 0;
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
const poolId = this._poolIds[poolIdx];
for (const j in _.range(numberOfDelegatorsInPool)) {
const delegator = this._delegators[delegatorIdx];
const amount = p.stakeByDelegator[delegatorIdx];
await delegator.depositAndDelegateAsync(poolId, amount);
delegatorIdx += 1;
}
poolIdx += 1;
}
}
private async _payProtocolFeesAsync(p: SimulationParams): Promise<void> {
// pay fees
for (const i in _.range(this._makers.length)) {
const maker = this._makers[i];
const makerAddress = maker.getOwner();
const feeAmount = p.protocolFeesByMaker[i];
await this._stakingWrapper.payProtocolFeeAsync(makerAddress, feeAmount, p.exchangeAddress);
}
// validate fees per pool
let expectedTotalFeesThisEpoch = new BigNumber(0);
for (const i in _.range(this._poolIds.length)) {
const poolId = this._poolIds[i];
const expectedFees = p.expectedFeesByPool[i];
const fees = await this._stakingWrapper.getProtocolFeesThisEpochByPoolAsync(poolId);
expect(fees, `fees for pool ${poolId}`).to.be.bignumber.equal(expectedFees);
expectedTotalFeesThisEpoch = expectedTotalFeesThisEpoch.plus(fees);
}
// validate total fees
const totalFeesThisEpoch = await this._stakingWrapper.getTotalProtocolFeesThisEpochAsync();
expect(expectedTotalFeesThisEpoch, 'total fees earned').to.be.bignumber.equal(totalFeesThisEpoch);
}
private async _assertVaultBalancesAsync(p: SimulationParams): Promise<void> {
for (const i in _.range(p.numberOfPools)) {
// check pool balance in vault
const poolId = this._poolIds[i];
const rewardVaultBalance = await this._stakingWrapper.rewardVaultBalanceOfAsync(poolId);
const rewardVaultBalanceTrimmed = this._stakingWrapper.trimFloat(this._stakingWrapper.toFloatingPoint(rewardVaultBalance, 18), 5);
const expectedRewardBalance = p.expectedPayoutByPool[i];
expect(rewardVaultBalanceTrimmed, `expected balance in vault for pool with id ${poolId}`).to.be.bignumber.equal(expectedRewardBalance);
// check operator's balance
const poolOperator = this._poolOperators[i];
}
}
}

View File

@@ -0,0 +1,37 @@
export class Queue<T> {
private _store: T[] = [];
constructor (store?: T[]) {
this._store = store !== undefined ? store : [];
}
public pushBack(val: T): void {
this._store.push(val);
}
public pushFront(val: T): void {
this._store.unshift(val);
}
public popFront(): T {
if (this._store.length === 0) {
throw new Error('Queue is empty');
}
return this._store.shift() as T;
}
public popBack(): T {
if (this._store.length === 0) {
throw new Error('Queue is empty');
}
const backElement = this._store.splice(-1, 1)[0];
return backElement;
}
public mergeBack(q: Queue<T>): void {
this._store = this._store.concat(q._store);
}
public mergeFront(q: Queue<T>): void {
this._store = q._store.concat(this._store);
}
public getStore(): T[] {
return this._store;
}
public peekFront(): T | undefined {
return this._store.length >= 0 ? this._store[0] : undefined;
}
}

View File

@@ -26,4 +26,24 @@ export interface DelegatorBalances extends StakerBalances {
delegatedStakeBalance: BigNumber;
stakeDelegatedToPoolByOwner: BigNumber[];
stakeDelegatedToPool: BigNumber[];
}
export interface SimulationParams {
users: string[],
numberOfPools: number,
poolOperatorShares: number[],
stakeByPoolOperator: BigNumber[],
numberOfMakers: number,
numberOfMakersPerPool: number[],
protocolFeesByMaker: BigNumber[],
numberOfDelegators: number,
numberOfDelegatorsPerPool: number[],
stakeByDelegator: BigNumber[],
expectedFeesByPool: BigNumber[],
expectedPayoutByPool: BigNumber[],
expectedPayoutByPoolOperator: BigNumber[],
expectedPayoutByDelegator: BigNumber[],
exchangeAddress: string,
delegateInNextEpoch: Boolean,
undelegateAtEnd: Boolean,
}