@0x/contracts-staking
: Add some extra finalizer tests and light refactorings.
`@0x/contracts-staking`: Add finalization-related protocol fees unit tests.
This commit is contained in:
parent
2eff213840
commit
14c4491b8c
@ -107,6 +107,13 @@ contract TestFinalizer is
|
|||||||
reward.membersStake) = _finalizePool(poolId);
|
reward.membersStake) = _finalizePool(poolId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Drain the balance of this contract.
|
||||||
|
function drainBalance()
|
||||||
|
external
|
||||||
|
{
|
||||||
|
address(0).transfer(address(this).balance);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Get finalization-related state variables.
|
/// @dev Get finalization-related state variables.
|
||||||
function getFinalizationState()
|
function getFinalizationState()
|
||||||
external
|
external
|
||||||
|
@ -53,6 +53,12 @@ contract TestProtocolFees is
|
|||||||
poolJoinedByMakerAddress[makerAddress].confirmed = true;
|
poolJoinedByMakerAddress[makerAddress].confirmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function advanceEpoch()
|
||||||
|
external
|
||||||
|
{
|
||||||
|
currentEpoch += 1;
|
||||||
|
}
|
||||||
|
|
||||||
function getWethAssetData() external pure returns (bytes memory) {
|
function getWethAssetData() external pure returns (bytes memory) {
|
||||||
return WETH_ASSET_DATA;
|
return WETH_ASSET_DATA;
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,14 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
artifacts,
|
artifacts,
|
||||||
|
IStakingEventsEvents,
|
||||||
|
IStakingEventsStakingPoolActivatedEventArgs,
|
||||||
TestProtocolFeesContract,
|
TestProtocolFeesContract,
|
||||||
TestProtocolFeesERC20ProxyContract,
|
TestProtocolFeesERC20ProxyContract,
|
||||||
TestProtocolFeesERC20ProxyTransferFromCalledEventArgs,
|
TestProtocolFeesERC20ProxyTransferFromCalledEventArgs,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
import { getRandomPortion } from './utils/number_utils';
|
import { getRandomInteger } from './utils/number_utils';
|
||||||
|
|
||||||
blockchainTests('Protocol Fee Unit Tests', env => {
|
blockchainTests('Protocol Fee Unit Tests', env => {
|
||||||
let ownerAddress: string;
|
let ownerAddress: string;
|
||||||
@ -27,6 +29,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
let notExchangeAddress: string;
|
let notExchangeAddress: string;
|
||||||
let testContract: TestProtocolFeesContract;
|
let testContract: TestProtocolFeesContract;
|
||||||
let wethAssetData: string;
|
let wethAssetData: string;
|
||||||
|
let minimumStake: BigNumber;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[ownerAddress, exchangeAddress, notExchangeAddress] = await env.web3Wrapper.getAvailableAddressesAsync();
|
[ownerAddress, exchangeAddress, notExchangeAddress] = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||||
@ -53,29 +56,31 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
wethAssetData = await testContract.getWethAssetData.callAsync();
|
wethAssetData = await testContract.getWethAssetData.callAsync();
|
||||||
|
minimumStake = (await testContract.getParams.callAsync())[2];
|
||||||
});
|
});
|
||||||
|
|
||||||
interface CreatePoolOpts {
|
interface CreateTestPoolOpts {
|
||||||
|
poolId: string;
|
||||||
operatorStake: Numberish;
|
operatorStake: Numberish;
|
||||||
membersStake: Numberish;
|
membersStake: Numberish;
|
||||||
makers: string[];
|
makers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTestPoolAsync(opts: Partial<CreatePoolOpts>): Promise<string> {
|
async function createTestPoolAsync(opts?: Partial<CreateTestPoolOpts>): Promise<CreateTestPoolOpts> {
|
||||||
const _opts = {
|
const _opts = {
|
||||||
operatorStake: 0,
|
poolId: hexRandom(),
|
||||||
membersStake: 0,
|
operatorStake: getRandomInteger(minimumStake, '100e18'),
|
||||||
makers: [],
|
membersStake: getRandomInteger(minimumStake, '100e18'),
|
||||||
|
makers: _.times(2, () => randomAddress()),
|
||||||
...opts,
|
...opts,
|
||||||
};
|
};
|
||||||
const poolId = hexRandom();
|
|
||||||
await testContract.createTestPool.awaitTransactionSuccessAsync(
|
await testContract.createTestPool.awaitTransactionSuccessAsync(
|
||||||
poolId,
|
_opts.poolId,
|
||||||
new BigNumber(_opts.operatorStake),
|
new BigNumber(_opts.operatorStake),
|
||||||
new BigNumber(_opts.membersStake),
|
new BigNumber(_opts.membersStake),
|
||||||
_opts.makers,
|
_opts.makers,
|
||||||
);
|
);
|
||||||
return poolId;
|
return _opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockchainTests.resets('payProtocolFee()', () => {
|
blockchainTests.resets('payProtocolFee()', () => {
|
||||||
@ -83,11 +88,6 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
const { ZERO_AMOUNT } = constants;
|
const { ZERO_AMOUNT } = constants;
|
||||||
const makerAddress = randomAddress();
|
const makerAddress = randomAddress();
|
||||||
const payerAddress = randomAddress();
|
const payerAddress = randomAddress();
|
||||||
let minimumStake: BigNumber;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
minimumStake = (await testContract.getParams.callAsync())[2];
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('forbidden actions', () => {
|
describe('forbidden actions', () => {
|
||||||
it('should revert if called by a non-exchange', async () => {
|
it('should revert if called by a non-exchange', async () => {
|
||||||
@ -187,7 +187,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should credit pool if the maker is in a pool', async () => {
|
it('should credit pool if the maker is in a pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -200,7 +200,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not credit the pool if maker is not in a pool', async () => {
|
it('should not credit the pool if maker is not in a pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake });
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -213,7 +213,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fees paid to the same maker should go to the same pool', async () => {
|
it('fees paid to the same maker should go to the same pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const payAsync = async () => {
|
const payAsync = async () => {
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
@ -258,7 +258,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -271,7 +271,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake });
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -284,7 +284,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fees paid to the same maker should go to the same pool', async () => {
|
it('fees paid to the same maker should go to the same pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const payAsync = async () => {
|
const payAsync = async () => {
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
@ -302,7 +302,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('fees paid to the same maker in WETH then ETH should go to the same pool', async () => {
|
it('fees paid to the same maker in WETH then ETH should go to the same pool', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const payAsync = async (inWETH: boolean) => {
|
const payAsync = async (inWETH: boolean) => {
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
@ -322,60 +322,11 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Multiple makers', () => {
|
|
||||||
it('fees paid to different makers in the same pool go to that pool', async () => {
|
|
||||||
const otherMakerAddress = randomAddress();
|
|
||||||
const poolId = await createTestPoolAsync({
|
|
||||||
operatorStake: minimumStake,
|
|
||||||
makers: [makerAddress, otherMakerAddress],
|
|
||||||
});
|
|
||||||
const payAsync = async (_makerAddress: string) => {
|
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
|
||||||
_makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
await payAsync(makerAddress);
|
|
||||||
await payAsync(otherMakerAddress);
|
|
||||||
const expectedTotalFees = DEFAULT_PROTOCOL_FEE_PAID.times(2);
|
|
||||||
const poolFees = await getProtocolFeesAsync(poolId);
|
|
||||||
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fees paid to makers in different pools go to their respective pools', async () => {
|
|
||||||
const [fee, otherFee] = _.times(2, () => getRandomPortion(DEFAULT_PROTOCOL_FEE_PAID));
|
|
||||||
const otherMakerAddress = randomAddress();
|
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
|
||||||
const otherPoolId = await createTestPoolAsync({
|
|
||||||
operatorStake: minimumStake,
|
|
||||||
makers: [otherMakerAddress],
|
|
||||||
});
|
|
||||||
const payAsync = async (_poolId: string, _makerAddress: string, _fee: BigNumber) => {
|
|
||||||
// prettier-ignore
|
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
|
||||||
_makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
_fee,
|
|
||||||
{ from: exchangeAddress, value: _fee },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
await payAsync(poolId, makerAddress, fee);
|
|
||||||
await payAsync(otherPoolId, otherMakerAddress, otherFee);
|
|
||||||
const [poolFees, otherPoolFees] = await Promise.all([
|
|
||||||
getProtocolFeesAsync(poolId),
|
|
||||||
getProtocolFeesAsync(otherPoolId),
|
|
||||||
]);
|
|
||||||
expect(poolFees).to.bignumber.eq(fee);
|
|
||||||
expect(otherPoolFees).to.bignumber.eq(otherFee);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Dust stake', () => {
|
describe('Dust stake', () => {
|
||||||
it('credits pools with stake > minimum', async () => {
|
it('credits pools with stake > minimum', async () => {
|
||||||
const poolId = await createTestPoolAsync({
|
const { poolId } = await createTestPoolAsync({
|
||||||
operatorStake: minimumStake.plus(1),
|
operatorStake: minimumStake.plus(1),
|
||||||
|
membersStake: 0,
|
||||||
makers: [makerAddress],
|
makers: [makerAddress],
|
||||||
});
|
});
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
@ -389,7 +340,11 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('credits pools with stake == minimum', async () => {
|
it('credits pools with stake == minimum', async () => {
|
||||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
const { poolId } = await createTestPoolAsync({
|
||||||
|
operatorStake: minimumStake,
|
||||||
|
membersStake: 0,
|
||||||
|
makers: [makerAddress],
|
||||||
|
});
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
constants.NULL_ADDRESS,
|
constants.NULL_ADDRESS,
|
||||||
@ -401,8 +356,9 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not credit pools with stake < minimum', async () => {
|
it('does not credit pools with stake < minimum', async () => {
|
||||||
const poolId = await createTestPoolAsync({
|
const { poolId } = await createTestPoolAsync({
|
||||||
operatorStake: minimumStake.minus(1),
|
operatorStake: minimumStake.minus(1),
|
||||||
|
membersStake: 0,
|
||||||
makers: [makerAddress],
|
makers: [makerAddress],
|
||||||
});
|
});
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
@ -415,5 +371,184 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
expect(feesCredited).to.bignumber.eq(0);
|
expect(feesCredited).to.bignumber.eq(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
blockchainTests.resets('Finalization', () => {
|
||||||
|
let membersStakeWeight: number;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
membersStakeWeight = (await testContract.getParams.callAsync())[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FinalizationState {
|
||||||
|
numActivePools: BigNumber;
|
||||||
|
totalFeesCollected: BigNumber;
|
||||||
|
totalWeightedStake: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFinalizationStateAsync(): Promise<FinalizationState> {
|
||||||
|
return {
|
||||||
|
numActivePools: await testContract.numActivePoolsThisEpoch.callAsync(),
|
||||||
|
totalFeesCollected: await testContract.totalFeesCollectedThisEpoch.callAsync(),
|
||||||
|
totalWeightedStake: await testContract.totalWeightedStakeThisEpoch.callAsync(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PayToMakerResult {
|
||||||
|
poolActivatedEvents: IStakingEventsStakingPoolActivatedEventArgs[];
|
||||||
|
fee: BigNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function payToMakerAsync(poolMaker: string, fee?: Numberish): Promise<PayToMakerResult> {
|
||||||
|
const _fee = fee === undefined ? getRandomInteger(1, '1e18') : fee;
|
||||||
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
poolMaker,
|
||||||
|
payerAddress,
|
||||||
|
new BigNumber(_fee),
|
||||||
|
{ from: exchangeAddress, value: _fee },
|
||||||
|
);
|
||||||
|
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>(
|
||||||
|
receipt.logs,
|
||||||
|
IStakingEventsEvents.StakingPoolActivated,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
fee: new BigNumber(_fee),
|
||||||
|
poolActivatedEvents: events,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toWeightedStake(operatorStake: Numberish, membersStake: Numberish): BigNumber {
|
||||||
|
return new BigNumber(membersStake)
|
||||||
|
.times(membersStakeWeight)
|
||||||
|
.dividedToIntegerBy(constants.PPM_DENOMINATOR)
|
||||||
|
.plus(operatorStake);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('no active pools to start', async () => {
|
||||||
|
const state = await getFinalizationStateAsync();
|
||||||
|
expect(state.numActivePools).to.bignumber.eq(0);
|
||||||
|
expect(state.totalFeesCollected).to.bignumber.eq(0);
|
||||||
|
expect(state.totalWeightedStake).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pool is not registered to start', async () => {
|
||||||
|
const { poolId } = await createTestPoolAsync();
|
||||||
|
const pool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||||
|
expect(pool.feesCollected).to.bignumber.eq(0);
|
||||||
|
expect(pool.membersStake).to.bignumber.eq(0);
|
||||||
|
expect(pool.weightedStake).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('activates a active pool the first time it earns a fee', async () => {
|
||||||
|
const pool = await createTestPoolAsync();
|
||||||
|
const {
|
||||||
|
poolId,
|
||||||
|
makers: [poolMaker],
|
||||||
|
} = pool;
|
||||||
|
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||||
|
expect(poolActivatedEvents.length).to.eq(1);
|
||||||
|
expect(poolActivatedEvents[0].poolId).to.eq(poolId);
|
||||||
|
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||||
|
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||||
|
expect(actualPool.feesCollected).to.bignumber.eq(fee);
|
||||||
|
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||||
|
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||||
|
const state = await getFinalizationStateAsync();
|
||||||
|
expect(state.numActivePools).to.bignumber.eq(1);
|
||||||
|
expect(state.totalFeesCollected).to.bignumber.eq(fee);
|
||||||
|
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only adds to the already activated pool in the same epoch', async () => {
|
||||||
|
const pool = await createTestPoolAsync();
|
||||||
|
const {
|
||||||
|
poolId,
|
||||||
|
makers: [poolMaker],
|
||||||
|
} = pool;
|
||||||
|
const { fee: fee1 } = await payToMakerAsync(poolMaker);
|
||||||
|
const { fee: fee2, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||||
|
expect(poolActivatedEvents).to.deep.eq([]);
|
||||||
|
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||||
|
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||||
|
const fees = BigNumber.sum(fee1, fee2);
|
||||||
|
expect(actualPool.feesCollected).to.bignumber.eq(fees);
|
||||||
|
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||||
|
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||||
|
const state = await getFinalizationStateAsync();
|
||||||
|
expect(state.numActivePools).to.bignumber.eq(1);
|
||||||
|
expect(state.totalFeesCollected).to.bignumber.eq(fees);
|
||||||
|
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can activate multiple pools in the same epoch', async () => {
|
||||||
|
const pools = await Promise.all(_.times(3, async () => createTestPoolAsync()));
|
||||||
|
let totalFees = new BigNumber(0);
|
||||||
|
let totalWeightedStake = new BigNumber(0);
|
||||||
|
for (const pool of pools) {
|
||||||
|
const {
|
||||||
|
poolId,
|
||||||
|
makers: [poolMaker],
|
||||||
|
} = pool;
|
||||||
|
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||||
|
expect(poolActivatedEvents.length).to.eq(1);
|
||||||
|
expect(poolActivatedEvents[0].poolId).to.eq(poolId);
|
||||||
|
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||||
|
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||||
|
expect(actualPool.feesCollected).to.bignumber.eq(fee);
|
||||||
|
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||||
|
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||||
|
totalFees = totalFees.plus(fee);
|
||||||
|
totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake);
|
||||||
|
}
|
||||||
|
const state = await getFinalizationStateAsync();
|
||||||
|
expect(state.numActivePools).to.bignumber.eq(pools.length);
|
||||||
|
expect(state.totalFeesCollected).to.bignumber.eq(totalFees);
|
||||||
|
expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resets the pool after the epoch advances', async () => {
|
||||||
|
const pool = await createTestPoolAsync();
|
||||||
|
const {
|
||||||
|
poolId,
|
||||||
|
makers: [poolMaker],
|
||||||
|
} = pool;
|
||||||
|
await payToMakerAsync(poolMaker);
|
||||||
|
await testContract.advanceEpoch.awaitTransactionSuccessAsync();
|
||||||
|
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||||
|
expect(actualPool.feesCollected).to.bignumber.eq(0);
|
||||||
|
expect(actualPool.membersStake).to.bignumber.eq(0);
|
||||||
|
expect(actualPool.weightedStake).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Multiple makers', () => {
|
||||||
|
it('fees paid to different makers in the same pool go to that pool', async () => {
|
||||||
|
const { poolId, makers } = await createTestPoolAsync();
|
||||||
|
const { fee: fee1 } = await payToMakerAsync(makers[0]);
|
||||||
|
const { fee: fee2 } = await payToMakerAsync(makers[1]);
|
||||||
|
const expectedTotalFees = BigNumber.sum(fee1, fee2);
|
||||||
|
const poolFees = await getProtocolFeesAsync(poolId);
|
||||||
|
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fees paid to makers in different pools go to their respective pools', async () => {
|
||||||
|
const {
|
||||||
|
poolId: poolId1,
|
||||||
|
makers: [maker1],
|
||||||
|
} = await createTestPoolAsync();
|
||||||
|
const {
|
||||||
|
poolId: poolId2,
|
||||||
|
makers: [maker2],
|
||||||
|
} = await createTestPoolAsync();
|
||||||
|
const { fee: fee1 } = await payToMakerAsync(maker1);
|
||||||
|
const { fee: fee2 } = await payToMakerAsync(maker2);
|
||||||
|
const [poolFees, otherPoolFees] = await Promise.all([
|
||||||
|
getProtocolFeesAsync(poolId1),
|
||||||
|
getProtocolFeesAsync(poolId2),
|
||||||
|
]);
|
||||||
|
expect(poolFees).to.bignumber.eq(fee1);
|
||||||
|
expect(otherPoolFees).to.bignumber.eq(fee2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// tslint:disable: max-file-line-count
|
||||||
|
@ -157,20 +157,19 @@ blockchainTests.resets('finalizer unit tests', env => {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const currentEpoch = await getCurrentEpochAsync();
|
const currentEpoch = await getCurrentEpochAsync();
|
||||||
// Compute the expected rewards for each pool.
|
// Compute the expected rewards for each pool.
|
||||||
const poolRewards = await calculatePoolRewardsAsync(rewardsAvailable, activePools);
|
const poolsWithStake = activePools.filter(p => !new BigNumber(p.weightedStake).isZero());
|
||||||
|
const poolRewards = await calculatePoolRewardsAsync(rewardsAvailable, poolsWithStake);
|
||||||
const totalRewards = BigNumber.sum(...poolRewards);
|
const totalRewards = BigNumber.sum(...poolRewards);
|
||||||
const rewardsRemaining = new BigNumber(rewardsAvailable).minus(totalRewards);
|
const rewardsRemaining = new BigNumber(rewardsAvailable).minus(totalRewards);
|
||||||
const nonZeroPoolRewards = poolRewards.filter(r => !r.isZero());
|
|
||||||
const poolsWithNonZeroRewards = _.filter(activePools, (p, i) => !poolRewards[i].isZero());
|
|
||||||
const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(activePools, poolRewards);
|
const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(activePools, poolRewards);
|
||||||
|
|
||||||
// Assert the `RewardsPaid` logs.
|
// Assert the `RewardsPaid` logs.
|
||||||
const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs);
|
const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs);
|
||||||
expect(rewardsPaidEvents.length).to.eq(poolsWithNonZeroRewards.length);
|
expect(rewardsPaidEvents.length).to.eq(poolsWithStake.length);
|
||||||
for (const i of _.times(rewardsPaidEvents.length)) {
|
for (const i of _.times(rewardsPaidEvents.length)) {
|
||||||
const event = rewardsPaidEvents[i];
|
const event = rewardsPaidEvents[i];
|
||||||
const pool = poolsWithNonZeroRewards[i];
|
const pool = poolsWithStake[i];
|
||||||
const reward = nonZeroPoolRewards[i];
|
const reward = poolRewards[i];
|
||||||
const [operatorReward, membersReward] = splitRewards(pool, reward);
|
const [operatorReward, membersReward] = splitRewards(pool, reward);
|
||||||
expect(event.epoch).to.bignumber.eq(currentEpoch);
|
expect(event.epoch).to.bignumber.eq(currentEpoch);
|
||||||
assertRoughlyEquals(event.operatorReward, operatorReward);
|
assertRoughlyEquals(event.operatorReward, operatorReward);
|
||||||
@ -179,10 +178,11 @@ blockchainTests.resets('finalizer unit tests', env => {
|
|||||||
|
|
||||||
// Assert the `RecordStakingPoolRewards` logs.
|
// Assert the `RecordStakingPoolRewards` logs.
|
||||||
const recordStakingPoolRewardsEvents = getRecordStakingPoolRewardsEvents(finalizationLogs);
|
const recordStakingPoolRewardsEvents = getRecordStakingPoolRewardsEvents(finalizationLogs);
|
||||||
|
expect(recordStakingPoolRewardsEvents.length).to.eq(poolsWithStake.length);
|
||||||
for (const i of _.times(recordStakingPoolRewardsEvents.length)) {
|
for (const i of _.times(recordStakingPoolRewardsEvents.length)) {
|
||||||
const event = recordStakingPoolRewardsEvents[i];
|
const event = recordStakingPoolRewardsEvents[i];
|
||||||
const pool = poolsWithNonZeroRewards[i];
|
const pool = poolsWithStake[i];
|
||||||
const reward = nonZeroPoolRewards[i];
|
const reward = poolRewards[i];
|
||||||
expect(event.poolId).to.eq(pool.poolId);
|
expect(event.poolId).to.eq(pool.poolId);
|
||||||
assertRoughlyEquals(event.totalReward, reward);
|
assertRoughlyEquals(event.totalReward, reward);
|
||||||
assertRoughlyEquals(event.membersStake, pool.membersStake);
|
assertRoughlyEquals(event.membersStake, pool.membersStake);
|
||||||
@ -191,7 +191,7 @@ blockchainTests.resets('finalizer unit tests', env => {
|
|||||||
// Assert the `DepositStakingPoolRewards` logs.
|
// Assert the `DepositStakingPoolRewards` logs.
|
||||||
// Make sure they all sum up to the totals.
|
// Make sure they all sum up to the totals.
|
||||||
const depositStakingPoolRewardsEvents = getDepositStakingPoolRewardsEvents(finalizationLogs);
|
const depositStakingPoolRewardsEvents = getDepositStakingPoolRewardsEvents(finalizationLogs);
|
||||||
{
|
if (depositStakingPoolRewardsEvents.length > 0) {
|
||||||
const totalDepositedOperatorRewards = BigNumber.sum(
|
const totalDepositedOperatorRewards = BigNumber.sum(
|
||||||
...depositStakingPoolRewardsEvents.map(e => e.operatorReward),
|
...depositStakingPoolRewardsEvents.map(e => e.operatorReward),
|
||||||
);
|
);
|
||||||
@ -406,6 +406,17 @@ blockchainTests.resets('finalizer unit tests', env => {
|
|||||||
return assertFinalizationLogsAndBalancesAsync(INITIAL_BALANCE, pools, allLogs);
|
return assertFinalizationLogsAndBalancesAsync(INITIAL_BALANCE, pools, allLogs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can finalize with no rewards', async () => {
|
||||||
|
await testContract.drainBalance.awaitTransactionSuccessAsync();
|
||||||
|
const pools = await Promise.all(_.times(2, async () => addActivePoolAsync()));
|
||||||
|
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||||
|
const receipts = await Promise.all(
|
||||||
|
pools.map(pool => testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId])),
|
||||||
|
);
|
||||||
|
const allLogs = _.flatten(receipts.map(r => r.logs));
|
||||||
|
return assertFinalizationLogsAndBalancesAsync(0, pools, allLogs);
|
||||||
|
});
|
||||||
|
|
||||||
it('ignores a non-active pool', async () => {
|
it('ignores a non-active pool', async () => {
|
||||||
const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
|
const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
|
||||||
const nonActivePoolId = hexRandom();
|
const nonActivePoolId = hexRandom();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user