2019-09-06 16:09:35 -07:00

280 lines
13 KiB
TypeScript

/*
@TODO (hysz) - update once new staking mechanics are merged
import { expect } from '@0x/contracts-test-utils';
import { chaiSetup } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { MakerActor } from '../actors/maker_actor';
import { PoolOperatorActor } from '../actors/pool_operator_actor';
import { Queue } from './queue';
import { StakingWrapper } from './staking_wrapper';
import { SimulationParams } from './types';
const REWARD_PRECISION = 12;
export class Simulation {
private readonly _stakingWrapper: StakingWrapper;
private readonly _p: SimulationParams;
private _userQueue: Queue<string>;
private readonly _poolOperators: PoolOperatorActor[];
private readonly _poolOperatorsAsDelegators: DelegatorActor[];
private readonly _poolIds: string[];
private readonly _makers: MakerActor[];
private readonly _delegators: DelegatorActor[];
private static _assertRewardsEqual(actual: BigNumber, expected: BigNumber, message?: string): void {
expect(
StakingWrapper.trimFloat(StakingWrapper.toFloatingPoint(actual, 18), REWARD_PRECISION),
message,
).to.be.bignumber.equal(StakingWrapper.trimFloat(expected, REWARD_PRECISION));
}
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 into the vault. check balances.
await this._assertVaultBalancesAsync(this._p);
await this._withdrawRewardForStakingPoolMemberForOperatorsAsync(this._p);
if (this._p.withdrawByUndelegating) {
await this._withdrawRewardForStakingPoolMemberForDelegatorsAsync(this._p);
} else {
await this._withdrawRewardForStakingPoolMemberForDelegatorsByUndelegatingAsync(this._p);
}
// @TODO cleanup status and verify the staking contract is empty
}
private async _withdrawRewardForStakingPoolMemberForDelegatorsByUndelegatingAsync(
p: SimulationParams,
): Promise<void> {
let delegatorIdx = 0;
let poolIdx = 0;
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
const poolId = this._poolIds[poolIdx];
// tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfDelegatorsInPool)) {
const delegator = this._delegators[delegatorIdx];
const delegatorAddress = delegator.getOwner();
const amountOfStakeDelegated = p.stakeByDelegator[delegatorIdx];
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
await delegator.deactivateAndTimeLockDelegatedStakeAsync(poolId, amountOfStakeDelegated);
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
const reward = finalEthBalance.minus(initEthBalance);
const expectedReward = p.expectedPayoutByDelegator[delegatorIdx];
Simulation._assertRewardsEqual(
reward,
expectedReward,
`reward withdrawn from pool ${poolId} for delegator ${delegatorAddress}`,
);
delegatorIdx += 1;
}
poolIdx += 1;
}
}
private async _withdrawRewardForStakingPoolMemberForDelegatorsAsync(p: SimulationParams): Promise<void> {
let delegatorIdx = 0;
let poolIdx = 0;
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
const poolId = this._poolIds[poolIdx];
// tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfDelegatorsInPool)) {
const delegator = this._delegators[delegatorIdx];
const delegatorAddress = delegator.getOwner();
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
await this._stakingWrapper.withdrawTotalRewardForStakingPoolMemberAsync(poolId, delegatorAddress);
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
const reward = finalEthBalance.minus(initEthBalance);
const expectedReward = p.expectedPayoutByDelegator[delegatorIdx];
Simulation._assertRewardsEqual(
reward,
expectedReward,
`reward withdrawn from pool ${poolId} for delegator ${delegatorAddress}`,
);
delegatorIdx += 1;
}
poolIdx += 1;
}
}
private async _setupPoolsAsync(p: SimulationParams): Promise<void> {
// tslint:disable-next-line no-unused-variable
for (const i of _.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.createStakingPoolAsync(p.poolOperatorShares[i], false);
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.depositZrxAndDelegateToStakingPoolAsync(poolId, amountOfStake);
}
}
private async _setupMakersAsync(p: SimulationParams): Promise<void> {
// create makers
// tslint:disable-next-line no-unused-variable
for (const i of _.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];
// tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfMakersInPool)) {
const maker = this._makers[makerIdx];
await maker.joinStakingPoolAsMakerAsync(poolId);
const makerAddress = maker.getOwner();
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
makerIdx += 1;
}
poolIdx += 1;
}
}
private async _setupDelegatorsAsync(p: SimulationParams): Promise<void> {
// create delegators
// tslint:disable-next-line no-unused-variable
for (const i of _.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];
// tslint:disable-next-line no-unused-variable
for (const j of _.range(numberOfDelegatorsInPool)) {
const delegator = this._delegators[delegatorIdx];
const amount = p.stakeByDelegator[delegatorIdx];
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amount);
delegatorIdx += 1;
}
poolIdx += 1;
}
}
private async _payProtocolFeesAsync(p: SimulationParams): Promise<void> {
// pay fees
// tslint:disable-next-line no-unused-variable
for (const i of _.range(this._makers.length)) {
const maker = this._makers[i];
const makerAddress = maker.getOwner();
const feeAmount = p.protocolFeesByMaker[i];
// TODO(jalextowle): I'll need to fix this once I make my PR on protocol fees. The arguments
// I'm adding are just placeholders for now.
await this._stakingWrapper.payProtocolFeeAsync(
makerAddress,
makerAddress,
feeAmount,
feeAmount,
p.exchangeAddress,
);
}
// validate fees per pool
let expectedTotalFeesThisEpoch = new BigNumber(0);
// tslint:disable-next-line no-unused-variable
for (const i of _.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> {
// tslint:disable-next-line no-unused-variable
for (const i of _.range(p.numberOfPools)) {
// @TODO - we trim balances in here because payouts are accurate only to REWARD_PRECISION decimal places.
// update once more accurate.
// check pool balance in vault
const poolId = this._poolIds[i];
const rewardVaultBalance = await this._stakingWrapper.rewardVaultBalanceOfAsync(poolId);
const expectedRewardBalance = p.expectedPayoutByPool[i];
Simulation._assertRewardsEqual(
rewardVaultBalance,
expectedRewardBalance,
`expected balance in vault for pool with id ${poolId}`,
);
// check operator's balance
const poolOperatorVaultBalance = await this._stakingWrapper.getRewardBalanceOfStakingPoolOperatorAsync(
poolId,
);
const expectedPoolOperatorVaultBalance = p.expectedPayoutByPoolOperator[i];
Simulation._assertRewardsEqual(
poolOperatorVaultBalance,
expectedPoolOperatorVaultBalance,
`operator balance in vault for pool with id ${poolId}`,
);
// check balance of pool members
const membersVaultBalance = await this._stakingWrapper.getRewardBalanceOfStakingPoolMembersAsync(poolId);
const expectedMembersVaultBalance = p.expectedMembersPayoutByPool[i];
Simulation._assertRewardsEqual(
membersVaultBalance,
expectedMembersVaultBalance,
`members balance in vault for pool with id ${poolId}`,
);
// @TODO compute balance of each member
}
}
private async _withdrawRewardForStakingPoolMemberForOperatorsAsync(p: SimulationParams): Promise<void> {
// tslint:disable-next-line no-unused-variable
for (const i of _.range(p.numberOfPools)) {
// @TODO - we trim balances in here because payouts are accurate only to REWARD_PRECISION decimal places.
// update once more accurate.
// check pool balance in vault
const poolId = this._poolIds[i];
const poolOperator = this._poolOperators[i];
const poolOperatorAddress = poolOperator.getOwner();
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(poolOperatorAddress);
await this._stakingWrapper.withdrawTotalRewardForStakingPoolOperatorAsync(poolId, poolOperatorAddress);
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(poolOperatorAddress);
const reward = finalEthBalance.minus(initEthBalance);
const expectedReward = p.expectedPayoutByPoolOperator[i];
Simulation._assertRewardsEqual(reward, expectedReward, `reward withdrawn from pool ${poolId} for operator`);
}
}
}
*/