224 lines
8.9 KiB
TypeScript
224 lines
8.9 KiB
TypeScript
import {
|
|
decreaseNextBalance,
|
|
increaseNextBalance,
|
|
loadCurrentBalance,
|
|
OwnerStakeByStatus,
|
|
StakeInfo,
|
|
StakeStatus,
|
|
StakingRevertErrors,
|
|
StoredBalance,
|
|
} from '@0x/contracts-staking';
|
|
import { expect } from '@0x/contracts-test-utils';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { TxData } from 'ethereum-types';
|
|
import * as _ from 'lodash';
|
|
|
|
import { DeploymentManager } from '../deployment_manager';
|
|
import { SimulationEnvironment } from '../simulation';
|
|
|
|
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
|
|
|
function updateNextEpochBalances(
|
|
ownerStake: OwnerStakeByStatus,
|
|
from: StakeInfo,
|
|
to: StakeInfo,
|
|
amount: BigNumber,
|
|
simulationEnvironment: SimulationEnvironment,
|
|
): string[] {
|
|
const { globalStake, stakingPools, currentEpoch } = simulationEnvironment;
|
|
|
|
// The on-chain state of these updated pools will be verified in the `after` of the assertion.
|
|
const updatedPools = [];
|
|
|
|
// Decrement next epoch balances associated with the `from` stake
|
|
if (from.status === StakeStatus.Undelegated) {
|
|
// Decrement owner undelegated stake
|
|
ownerStake[StakeStatus.Undelegated] = decreaseNextBalance(
|
|
ownerStake[StakeStatus.Undelegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Decrement global undelegated stake
|
|
globalStake[StakeStatus.Undelegated] = decreaseNextBalance(
|
|
globalStake[StakeStatus.Undelegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
} else if (from.status === StakeStatus.Delegated) {
|
|
// Decrement owner's delegated stake to this pool
|
|
ownerStake[StakeStatus.Delegated][from.poolId] = decreaseNextBalance(
|
|
ownerStake[StakeStatus.Delegated][from.poolId],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Decrement owner's total delegated stake
|
|
ownerStake[StakeStatus.Delegated].total = decreaseNextBalance(
|
|
ownerStake[StakeStatus.Delegated].total,
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Decrement global delegated stake
|
|
globalStake[StakeStatus.Delegated] = decreaseNextBalance(
|
|
globalStake[StakeStatus.Delegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Decrement pool's delegated stake
|
|
stakingPools[from.poolId].delegatedStake = decreaseNextBalance(
|
|
stakingPools[from.poolId].delegatedStake,
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
updatedPools.push(from.poolId);
|
|
|
|
// TODO: Check that delegator rewards have been withdrawn/synced
|
|
}
|
|
|
|
// Increment next epoch balances associated with the `to` stake
|
|
if (to.status === StakeStatus.Undelegated) {
|
|
// Increment owner undelegated stake
|
|
ownerStake[StakeStatus.Undelegated] = increaseNextBalance(
|
|
ownerStake[StakeStatus.Undelegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Increment global undelegated stake
|
|
globalStake[StakeStatus.Undelegated] = increaseNextBalance(
|
|
globalStake[StakeStatus.Undelegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
} else if (to.status === StakeStatus.Delegated) {
|
|
// Initializes the balance for this pool if the user has not previously delegated to it
|
|
_.defaults(ownerStake[StakeStatus.Delegated], {
|
|
[to.poolId]: new StoredBalance(),
|
|
});
|
|
// Increment owner's delegated stake to this pool
|
|
ownerStake[StakeStatus.Delegated][to.poolId] = increaseNextBalance(
|
|
ownerStake[StakeStatus.Delegated][to.poolId],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Increment owner's total delegated stake
|
|
ownerStake[StakeStatus.Delegated].total = increaseNextBalance(
|
|
ownerStake[StakeStatus.Delegated].total,
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Increment global delegated stake
|
|
globalStake[StakeStatus.Delegated] = increaseNextBalance(
|
|
globalStake[StakeStatus.Delegated],
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
// Increment pool's delegated stake
|
|
stakingPools[to.poolId].delegatedStake = increaseNextBalance(
|
|
stakingPools[to.poolId].delegatedStake,
|
|
amount,
|
|
currentEpoch,
|
|
);
|
|
updatedPools.push(to.poolId);
|
|
|
|
// TODO: Check that delegator rewards have been withdrawn/synced
|
|
}
|
|
return updatedPools;
|
|
}
|
|
/**
|
|
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. Checks that
|
|
* the owner's stake and global stake by status get updated correctly.
|
|
*/
|
|
/* tslint:disable:no-unnecessary-type-assertion */
|
|
export function validMoveStakeAssertion(
|
|
deployment: DeploymentManager,
|
|
simulationEnvironment: SimulationEnvironment,
|
|
ownerStake: OwnerStakeByStatus,
|
|
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
|
|
const { stakingWrapper } = deployment.staking;
|
|
|
|
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', {
|
|
after: async (
|
|
_beforeInfo: void,
|
|
result: FunctionResult,
|
|
args: [StakeInfo, StakeInfo, BigNumber],
|
|
txData: Partial<TxData>,
|
|
) => {
|
|
// Ensure that the tx succeeded.
|
|
expect(result.success, `Error: ${result.data}`).to.be.true();
|
|
|
|
const [from, to, amount] = args;
|
|
const { stakingPools, globalStake, currentEpoch } = simulationEnvironment;
|
|
|
|
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
|
|
|
// Update local balances to match the expected result of this `moveStake` operation
|
|
const updatedPools = updateNextEpochBalances(ownerStake, from, to, amount, simulationEnvironment);
|
|
|
|
// Fetches on-chain owner stake balances and checks against local balances
|
|
const ownerUndelegatedStake = {
|
|
...new StoredBalance(),
|
|
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()),
|
|
};
|
|
const ownerDelegatedStake = {
|
|
...new StoredBalance(),
|
|
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
|
|
};
|
|
expect(ownerUndelegatedStake).to.deep.equal(
|
|
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch),
|
|
);
|
|
expect(ownerDelegatedStake).to.deep.equal(
|
|
loadCurrentBalance(ownerStake[StakeStatus.Delegated].total, currentEpoch),
|
|
);
|
|
|
|
// Fetches on-chain global stake balances and checks against local balances
|
|
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync();
|
|
const globalUndelegatedStake = await stakingWrapper
|
|
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
|
.callAsync();
|
|
expect(globalDelegatedStake).to.deep.equal(
|
|
loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch),
|
|
);
|
|
expect(globalUndelegatedStake).to.deep.equal(
|
|
loadCurrentBalance(globalStake[StakeStatus.Undelegated], currentEpoch),
|
|
);
|
|
|
|
// Fetches on-chain pool stake balances and checks against local balances
|
|
for (const poolId of updatedPools) {
|
|
const stakeDelegatedByOwner = await stakingWrapper
|
|
.getStakeDelegatedToPoolByOwner(owner, poolId)
|
|
.callAsync();
|
|
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
|
|
expect(stakeDelegatedByOwner).to.deep.equal(
|
|
loadCurrentBalance(ownerStake[StakeStatus.Delegated][poolId], currentEpoch),
|
|
);
|
|
expect(totalStakeDelegated).to.deep.equal(
|
|
loadCurrentBalance(stakingPools[poolId].delegatedStake, currentEpoch),
|
|
);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a FunctionAssertion for `moveStake` which asserts that the transaction reverts with a
|
|
* PoolExistenceError.
|
|
*/
|
|
export function moveStakeNonexistentPoolAssertion(
|
|
deployment: DeploymentManager,
|
|
nonExistentPoolId: string,
|
|
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
|
|
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(
|
|
deployment.staking.stakingWrapper,
|
|
'moveStake',
|
|
{
|
|
after: async (_beforeInfo: void, result: FunctionResult) => {
|
|
// Ensure that the tx reverted.
|
|
expect(result.success).to.be.false();
|
|
|
|
// Check revert error
|
|
expect(result.data).to.equal(new StakingRevertErrors.PoolExistenceError(nonExistentPoolId, false));
|
|
},
|
|
},
|
|
);
|
|
}
|
|
/* tslint:enable:no-unnecessary-type-assertion */
|