Working on simulation to make it easier to follow the end-to-end tests. Mostly working.
This commit is contained in:
@@ -1,17 +1,5 @@
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
|
||||
export class Actor {
|
||||
protected readonly _owner: string;
|
||||
protected readonly _stakingWrapper: StakingWrapper;
|
||||
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
this._owner = owner;
|
||||
this._stakingWrapper = stakingWrapper;
|
||||
}
|
||||
public getOwner(): string {
|
||||
return this._owner;
|
||||
}
|
||||
public getStakingWrapper(): StakingWrapper {
|
||||
return this._stakingWrapper;
|
||||
}
|
||||
}
|
||||
import { DelegatorActor } from './delegator_actor';
|
||||
import { MakerActor } from './maker_actor';
|
||||
import { PoolOperatorActor } from './pool_operator_actor';
|
||||
|
||||
17
contracts/staking/test/actors/base_actor.ts
Normal file
17
contracts/staking/test/actors/base_actor.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
|
||||
export class BaseActor {
|
||||
protected readonly _owner: string;
|
||||
protected readonly _stakingWrapper: StakingWrapper;
|
||||
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
this._owner = owner;
|
||||
this._stakingWrapper = stakingWrapper;
|
||||
}
|
||||
public getOwner(): string {
|
||||
return this._owner;
|
||||
}
|
||||
public getStakingWrapper(): StakingWrapper {
|
||||
return this._stakingWrapper;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import * as _ from 'lodash';
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { DelegatorBalances, StakerBalances } from '../utils/types';
|
||||
|
||||
import { StakerActor } from './StakerActor';
|
||||
import { StakerActor } from './staker_actor';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
0
contracts/staking/test/actors/exchange_actor.ts
Normal file
0
contracts/staking/test/actors/exchange_actor.ts
Normal file
0
contracts/staking/test/actors/finalizer_actor.ts
Normal file
0
contracts/staking/test/actors/finalizer_actor.ts
Normal file
@@ -15,12 +15,12 @@ import { SignatureType } from '@0x/types';
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { DelegatorBalances, StakerBalances } from '../utils/types';
|
||||
|
||||
import { Actor } from './Actor';
|
||||
import { BaseActor } from './base_actor';
|
||||
import { SignedStakingPoolApproval } from '../utils/types';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
export class MakerActor extends Actor {
|
||||
export class MakerActor extends BaseActor {
|
||||
private readonly _ownerPrivateKeyIfExists?: Buffer;
|
||||
private readonly _signatureVerifierIfExists?: string;
|
||||
private readonly _chainIdIfExists?: number;
|
||||
@@ -14,12 +14,12 @@ import * as _ from 'lodash';
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { DelegatorBalances, StakerBalances } from '../utils/types';
|
||||
|
||||
import { Actor } from './Actor';
|
||||
import { BaseActor } from './base_actor';
|
||||
import { constants as stakingConstants } from '../utils/constants';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
export class PoolOperatorActor extends Actor {
|
||||
export class PoolOperatorActor extends BaseActor {
|
||||
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
super(owner, stakingWrapper);
|
||||
@@ -13,11 +13,11 @@ import * as _ from 'lodash';
|
||||
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { StakerBalances } from '../utils/types';
|
||||
import { Actor } from './actor';
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
export class StakerActor extends Actor {
|
||||
export class StakerActor extends BaseActor {
|
||||
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
super(owner, stakingWrapper);
|
||||
@@ -22,8 +22,8 @@ import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingContract } from '../src';
|
||||
|
||||
|
||||
import { StakerActor } from './actors/StakerActor';
|
||||
import { DelegatorActor } from './actors/DelegatorActor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
@@ -23,10 +23,10 @@ import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingContract } from '../src';
|
||||
|
||||
|
||||
import { StakerActor } from './actors/StakerActor';
|
||||
import { DelegatorActor } from './actors/DelegatorActor';
|
||||
import { PoolOperatorActor } from './actors/PoolOperatorActor';
|
||||
import { MakerActor } from './actors/MakerActor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
import { PoolOperatorActor } from './actors/pool_operator_actor';
|
||||
import { MakerActor } from './actors/maker_actor';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
@@ -22,25 +22,32 @@ import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingContract } from '../src';
|
||||
|
||||
|
||||
import { StakerActor } from './actors/StakerActor';
|
||||
import { DelegatorActor } from './actors/DelegatorActor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
|
||||
import { SimulationParams } from './utils/types';
|
||||
import { Simulation } from './utils/Simulation';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
describe('Rewards', () => {
|
||||
describe.only('Rewards', () => {
|
||||
// constants
|
||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
||||
// tokens & addresses
|
||||
let accounts: string[];
|
||||
let owner: string;
|
||||
let exchange: string;
|
||||
let stakers: string[];
|
||||
let makers: string[];
|
||||
let delegators: string[];
|
||||
let users: string[];
|
||||
let zrxTokenContract: DummyERC20TokenContract;
|
||||
let erc20ProxyContract: ERC20ProxyContract;
|
||||
|
||||
let stakers: string[];
|
||||
let makers: string[];
|
||||
let delegators: string[];
|
||||
|
||||
|
||||
// wrappers
|
||||
let stakingWrapper: StakingWrapper;
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
@@ -56,8 +63,13 @@ describe('Rewards', () => {
|
||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
owner = accounts[0];
|
||||
exchange = accounts[1];
|
||||
users = accounts.slice(2);
|
||||
|
||||
stakers = accounts.slice(2, 5);
|
||||
makers = accounts.slice(4, 10);
|
||||
users = [...users, ...users]; // maybe this'll work? Not sure lol.
|
||||
|
||||
|
||||
// deploy erc20 proxy
|
||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||
@@ -778,6 +790,193 @@ describe('Rewards', () => {
|
||||
expect(rewardByDelegator[2]).to.be.bignumber.equal(expectedRewardByDelegator[2]);
|
||||
});
|
||||
|
||||
it.only('SIM - Finalization with Protocol Fees and Delegation with shadow ETH (withdraw w/o undelegating)', async () => {
|
||||
// @TODO - get computations more accurate
|
||||
/*
|
||||
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled)
|
||||
0 | 0.304958 | 42 | 0 | 42
|
||||
1 | 15.323258 | 84 | 0 | 84
|
||||
3 | 28.12222236 | 97 | 182 | 260.8
|
||||
...
|
||||
Cumulative Fees = 43.75043836
|
||||
Cumulative Stake = 405
|
||||
Total Rewards = 43.75043836
|
||||
|
||||
// In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
|
||||
// The first delegator got to claim it all. This is due to the necessary conservation of payouts.
|
||||
// When a new delegator arrives, their new stake should not affect existing delegator payouts.
|
||||
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
|
||||
*/
|
||||
const simulationParams = {
|
||||
users,
|
||||
numberOfPools: 3,
|
||||
poolOperatorShares: [39, 59, 43],
|
||||
stakeByPoolOperator: [
|
||||
stakingWrapper.toBaseUnitAmount(42),
|
||||
stakingWrapper.toBaseUnitAmount(84),
|
||||
stakingWrapper.toBaseUnitAmount(97),
|
||||
],
|
||||
numberOfMakers: 6,
|
||||
numberOfMakersPerPool: [1, 2, 3],
|
||||
protocolFeesByMaker: [
|
||||
// pool 1
|
||||
stakingWrapper.toBaseUnitAmount(0.304958),
|
||||
// pool 2
|
||||
stakingWrapper.toBaseUnitAmount(3.2),
|
||||
stakingWrapper.toBaseUnitAmount(12.123258),
|
||||
// pool 3
|
||||
stakingWrapper.toBaseUnitAmount(23.577),
|
||||
stakingWrapper.toBaseUnitAmount(4.54522236),
|
||||
stakingWrapper.toBaseUnitAmount(0)
|
||||
],
|
||||
numberOfDelegators: 3,
|
||||
numberOfDelegatorsPerPool: [0, 0, 3],
|
||||
stakeByDelegator: [
|
||||
stakingWrapper.toBaseUnitAmount(17),
|
||||
stakingWrapper.toBaseUnitAmount(75),
|
||||
stakingWrapper.toBaseUnitAmount(90),
|
||||
],
|
||||
delegateInNextEpoch: true, // forces shadow eth
|
||||
undelegateAtEnd: true, // profits are withdrawn as result of undelegating
|
||||
expectedFeesByPool: [
|
||||
stakingWrapper.toBaseUnitAmount(0.304958),
|
||||
stakingWrapper.toBaseUnitAmount(15.323258),
|
||||
stakingWrapper.toBaseUnitAmount(28.12222236),
|
||||
],
|
||||
expectedPayoutByPool: [
|
||||
new BigNumber('4.75677'), // 4.756772362932728793619590327361600155564384201215274334070
|
||||
new BigNumber('16.28130'), // 16.28130500394935316563988584956596823402223838026190634525
|
||||
new BigNumber('20.31028'), // 20.31028447343014834523983759032242063760612769662934308289
|
||||
],
|
||||
expectedPayoutByPoolOperator: [
|
||||
|
||||
],
|
||||
expectedMembersPayoutByPool: [
|
||||
new BigNumber('2.9016297'), // (1 - 0.39) * 4.75677
|
||||
new BigNumber('6.675333'), // (1 - 0.59) * 16.28130
|
||||
new BigNumber('11.5768596'), // (1 - 0.43) * 20.31028
|
||||
],
|
||||
expectedPayoutByDelegator: [
|
||||
new BigNumber('11.5768596'), // (1 - 0.43) * 20.31028
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
],
|
||||
exchangeAddress: exchange,
|
||||
|
||||
};
|
||||
const simulator = new Simulation(stakingWrapper, simulationParams);
|
||||
await simulator.runAsync();
|
||||
});
|
||||
|
||||
it.skip('Finalization with Protocol Fees and Delegation with shadow ETH (withdraw w/o undelegating)', async () => {
|
||||
|
||||
///// 1 SETUP POOLS /////
|
||||
|
||||
///// 2 PAY FEES /////
|
||||
|
||||
///// 3 VALIDATE FEES RECORDED FOR EACH POOL /////
|
||||
|
||||
///// 4 VALIDATE TOTAL FEES /////
|
||||
|
||||
///// 5 STAKE /////
|
||||
|
||||
///// 6 FINALIZE /////
|
||||
|
||||
///// 7 ADD DELEGATORS (Requires Shadow ETH) /////
|
||||
|
||||
///// 7 FINALIZE AGAIN /////
|
||||
|
||||
///// 7 CHECK PROFITS /////
|
||||
// the expected payouts were computed by hand
|
||||
|
||||
|
||||
/*
|
||||
///// 10 CHECK DELEGATOR PAYOUT BY WITHDRAWING /////
|
||||
{
|
||||
const poolPayoutById = await Promise.all([
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[0]),
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[1]),
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[2]),
|
||||
]);
|
||||
const ethBalancesByDelegatorInit = await Promise.all([
|
||||
stakingWrapper.getEthBalanceAsync(delegators[0]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[1]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[2]),
|
||||
]);
|
||||
await Promise.all([
|
||||
stakingWrapper.withdrawTotalRewardAsync(poolIds[2], delegators[0]),
|
||||
stakingWrapper.withdrawTotalRewardAsync(poolIds[2], delegators[1]),
|
||||
stakingWrapper.withdrawTotalRewardAsync(poolIds[2], delegators[2]),
|
||||
]);
|
||||
const ethBalancesByDelegatorFinal = await Promise.all([
|
||||
stakingWrapper.getEthBalanceAsync(delegators[0]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[1]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[2]),
|
||||
]);
|
||||
const rewardByDelegator = [
|
||||
ethBalancesByDelegatorFinal[0].minus(ethBalancesByDelegatorInit[0]),
|
||||
ethBalancesByDelegatorFinal[1].minus(ethBalancesByDelegatorInit[1]),
|
||||
ethBalancesByDelegatorFinal[2].minus(ethBalancesByDelegatorInit[2]),
|
||||
];
|
||||
|
||||
// In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
|
||||
// The first delegator got to claim it all. This is due to the necessary conservation of payouts.
|
||||
// When a new delegator arrives, their new stake should not affect existing delegator payouts.
|
||||
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
|
||||
const expectedRewardByDelegator = [
|
||||
poolPayoutById[2],
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
];
|
||||
expect(rewardByDelegator[0]).to.be.bignumber.equal(expectedRewardByDelegator[0]);
|
||||
expect(rewardByDelegator[1]).to.be.bignumber.equal(expectedRewardByDelegator[1]);
|
||||
expect(rewardByDelegator[2]).to.be.bignumber.equal(expectedRewardByDelegator[2]);
|
||||
}
|
||||
|
||||
{
|
||||
///// 10 CHECK DELEGATOR PAYOUT BY UNDELEGATING /////
|
||||
const poolPayoutById = await Promise.all([
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[0]),
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[1]),
|
||||
stakingWrapper.rewardVaultBalanceOfPoolAsync(poolIds[2]),
|
||||
]);
|
||||
const ethBalancesByDelegatorInit = await Promise.all([
|
||||
stakingWrapper.getEthBalanceAsync(delegators[0]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[1]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[2]),
|
||||
]);
|
||||
await Promise.all([
|
||||
stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[0], poolIds[2], stakeByDelegator[0]),
|
||||
stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[1], poolIds[2], stakeByDelegator[1]),
|
||||
stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[2], poolIds[2], stakeByDelegator[2]),
|
||||
]);
|
||||
const ethBalancesByDelegatorFinal = await Promise.all([
|
||||
stakingWrapper.getEthBalanceAsync(delegators[0]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[1]),
|
||||
stakingWrapper.getEthBalanceAsync(delegators[2]),
|
||||
]);
|
||||
const rewardByDelegator = [
|
||||
ethBalancesByDelegatorFinal[0].minus(ethBalancesByDelegatorInit[0]),
|
||||
ethBalancesByDelegatorFinal[1].minus(ethBalancesByDelegatorInit[1]),
|
||||
ethBalancesByDelegatorFinal[2].minus(ethBalancesByDelegatorInit[2]),
|
||||
];
|
||||
|
||||
// In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
|
||||
// The first delegator got to claim it all. This is due to the necessary conservation of payouts.
|
||||
// When a new delegator arrives, their new stake should not affect existing delegator payouts.
|
||||
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
|
||||
const expectedRewardByDelegator = [
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
];
|
||||
expect(rewardByDelegator[0]).to.be.bignumber.equal(expectedRewardByDelegator[0]);
|
||||
expect(rewardByDelegator[1]).to.be.bignumber.equal(expectedRewardByDelegator[1]);
|
||||
expect(rewardByDelegator[2]).to.be.bignumber.equal(expectedRewardByDelegator[2]);
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
it('Finalization with Protocol Fees and Delegation with shadow ETH (withdraw w/o undelegating)', async () => {
|
||||
///// 0 DEPLOY EXCHANGE /////
|
||||
await stakingWrapper.addExchangeAddressAsync(exchange);
|
||||
|
||||
@@ -22,8 +22,8 @@ import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingContract } from '../src';
|
||||
|
||||
|
||||
import { StakerActor } from './actors/StakerActor';
|
||||
import { DelegatorActor } from './actors/DelegatorActor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
155
contracts/staking/test/utils/Simulation.ts
Normal file
155
contracts/staking/test/utils/Simulation.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
37
contracts/staking/test/utils/queue.ts
Normal file
37
contracts/staking/test/utils/queue.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -22,8 +22,8 @@ import { ERC20Wrapper, ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { StakingContract } from '../src';
|
||||
|
||||
|
||||
import { StakerActor } from './actors/StakerActor';
|
||||
import { DelegatorActor } from './actors/DelegatorActor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
Reference in New Issue
Block a user