2019-10-27 11:12:22 -07:00

281 lines
11 KiB
TypeScript

import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts, DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { BlockchainTestsEnvironment, constants, filterLogsToArguments, txDefaults } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { BlockParamLiteral, ContractArtifact, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import {
artifacts,
IStakingEventsEpochEndedEventArgs,
IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
StakingProxyContract,
TestCobbDouglasContract,
TestStakingContract,
TestStakingEvents,
ZrxVaultContract,
} from '../../src';
import { constants as stakingConstants } from './constants';
import { DecodedLogs, EndOfEpochInfo, StakingParams } from './types';
export class StakingApiWrapper {
// The address of the real Staking.sol contract
public stakingContractAddress: string;
// The StakingProxy.sol contract wrapped as a StakingContract to borrow API
public stakingContract: TestStakingContract;
// The StakingProxy.sol contract as a StakingProxyContract
public stakingProxyContract: StakingProxyContract;
public zrxVaultContract: ZrxVaultContract;
public zrxTokenContract: DummyERC20TokenContract;
public wethContract: WETH9Contract;
public cobbDouglasContract: TestCobbDouglasContract;
public utils = {
// Epoch Utils
fastForwardToNextEpochAsync: async (): Promise<void> => {
// increase timestamp of next block by how many seconds we need to
// get to the next epoch.
const epochEndTime = await this.stakingContract.getCurrentEpochEarliestEndTimeInSeconds.callAsync();
const lastBlockTime = await this._web3Wrapper.getBlockTimestampAsync('latest');
const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
await this._web3Wrapper.increaseTimeAsync(dt);
// mine next block
await this._web3Wrapper.mineBlockAsync();
},
skipToNextEpochAndFinalizeAsync: async (): Promise<DecodedLogs> => {
await this.utils.fastForwardToNextEpochAsync();
const endOfEpochInfo = await this.utils.endEpochAsync();
const allLogs = [] as DecodedLogs;
for (const poolId of endOfEpochInfo.activePoolIds) {
const receipt = await this.stakingContract.finalizePool.awaitTransactionSuccessAsync(poolId);
allLogs.splice(allLogs.length, 0, ...(receipt.logs as DecodedLogs));
}
return allLogs;
},
endEpochAsync: async (): Promise<EndOfEpochInfo> => {
const activePoolIds = await this.utils.findActivePoolIdsAsync();
const receipt = await this.stakingContract.endEpoch.awaitTransactionSuccessAsync();
const [epochEndedEvent] = filterLogsToArguments<IStakingEventsEpochEndedEventArgs>(
receipt.logs,
TestStakingEvents.EpochEnded,
);
return {
closingEpoch: epochEndedEvent.epoch,
activePoolIds,
rewardsAvailable: epochEndedEvent.rewardsAvailable,
totalFeesCollected: epochEndedEvent.totalFeesCollected,
totalWeightedStake: epochEndedEvent.totalWeightedStake,
};
},
findActivePoolIdsAsync: async (epoch?: number): Promise<string[]> => {
const _epoch = epoch !== undefined ? epoch : await this.stakingContract.currentEpoch.callAsync();
const events = filterLogsToArguments<IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs>(
await this.stakingContract.getLogsAsync(
TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
{ fromBlock: BlockParamLiteral.Earliest, toBlock: BlockParamLiteral.Latest },
{ epoch: new BigNumber(_epoch) },
),
TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
);
return events.map(e => e.poolId);
},
// Other Utils
createStakingPoolAsync: async (
operatorAddress: string,
operatorShare: number,
addOperatorAsMaker: boolean,
): Promise<string> => {
const txReceipt = await this.stakingContract.createStakingPool.awaitTransactionSuccessAsync(
operatorShare,
addOperatorAsMaker,
{ from: operatorAddress },
);
const createStakingPoolLog = txReceipt.logs[0];
const poolId = (createStakingPoolLog as any).args.poolId;
return poolId;
},
getZrxTokenBalanceOfZrxVaultAsync: async (): Promise<BigNumber> => {
return this.zrxTokenContract.balanceOf.callAsync(this.zrxVaultContract.address);
},
setParamsAsync: async (params: Partial<StakingParams>): Promise<TransactionReceiptWithDecodedLogs> => {
const _params = {
...stakingConstants.DEFAULT_PARAMS,
...params,
};
return this.stakingContract.setParams.awaitTransactionSuccessAsync(
new BigNumber(_params.epochDurationInSeconds),
new BigNumber(_params.rewardDelegatedStakeWeight),
new BigNumber(_params.minimumPoolStake),
new BigNumber(_params.cobbDouglasAlphaNumerator),
new BigNumber(_params.cobbDouglasAlphaDenominator),
);
},
getAvailableRewardsBalanceAsync: async (): Promise<BigNumber> => {
const [ethBalance, wethBalance, reservedRewards] = await Promise.all([
this._web3Wrapper.getBalanceInWeiAsync(this.stakingProxyContract.address),
this.wethContract.balanceOf.callAsync(this.stakingProxyContract.address),
this.stakingContract.wethReservedForPoolRewards.callAsync(),
]);
return BigNumber.sum(ethBalance, wethBalance).minus(reservedRewards);
},
getParamsAsync: async (): Promise<StakingParams> => {
return (_.zipObject(
[
'epochDurationInSeconds',
'rewardDelegatedStakeWeight',
'minimumPoolStake',
'cobbDouglasAlphaNumerator',
'cobbDouglasAlphaDenominator',
'wethProxyAddress',
'zrxVaultAddress',
],
await this.stakingContract.getParams.callAsync(),
) as any) as StakingParams;
},
cobbDouglasAsync: async (
totalRewards: BigNumber,
ownerFees: BigNumber,
totalFees: BigNumber,
ownerStake: BigNumber,
totalStake: BigNumber,
): Promise<BigNumber> => {
const { cobbDouglasAlphaNumerator, cobbDouglasAlphaDenominator } = await this.utils.getParamsAsync();
return this.cobbDouglasContract.cobbDouglas.callAsync(
totalRewards,
ownerFees,
totalFees,
ownerStake,
totalStake,
new BigNumber(cobbDouglasAlphaNumerator),
new BigNumber(cobbDouglasAlphaDenominator),
);
},
};
private readonly _web3Wrapper: Web3Wrapper;
constructor(
env: BlockchainTestsEnvironment,
ownerAddress: string,
stakingProxyContract: StakingProxyContract,
stakingContract: TestStakingContract,
zrxVaultContract: ZrxVaultContract,
zrxTokenContract: DummyERC20TokenContract,
wethContract: WETH9Contract,
cobbDouglasContract: TestCobbDouglasContract,
) {
this._web3Wrapper = env.web3Wrapper;
this.zrxVaultContract = zrxVaultContract;
this.zrxTokenContract = zrxTokenContract;
this.wethContract = wethContract;
this.cobbDouglasContract = cobbDouglasContract;
this.stakingContractAddress = stakingContract.address;
this.stakingProxyContract = stakingProxyContract;
// disguise the staking proxy as a StakingContract
const logDecoderDependencies = _.mapValues({ ...artifacts, ...erc20Artifacts }, v => v.compilerOutput.abi);
this.stakingContract = new TestStakingContract(
stakingProxyContract.address,
env.provider,
{
...env.txDefaults,
from: ownerAddress,
to: stakingProxyContract.address,
gas: 3e6,
gasPrice: 0,
},
logDecoderDependencies,
);
}
}
/**
* Deploys and configures all staking contracts and returns a StakingApiWrapper instance, which
* holds the deployed contracts and serves as the entry point for their public functions.
*/
export async function deployAndConfigureContractsAsync(
env: BlockchainTestsEnvironment,
ownerAddress: string,
erc20Wrapper: ERC20Wrapper,
customStakingArtifact?: ContractArtifact,
): Promise<StakingApiWrapper> {
// deploy erc20 proxy
const erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
// deploy zrx token
const [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, constants.DUMMY_TOKEN_DECIMALS);
await erc20Wrapper.setBalancesAndAllowancesAsync();
// deploy WETH
const wethContract = await WETH9Contract.deployFrom0xArtifactAsync(
erc20Artifacts.WETH9,
env.provider,
txDefaults,
artifacts,
);
// deploy zrx vault
const zrxVaultContract = await ZrxVaultContract.deployFrom0xArtifactAsync(
artifacts.ZrxVault,
env.provider,
env.txDefaults,
artifacts,
erc20ProxyContract.address,
zrxTokenContract.address,
);
await zrxVaultContract.addAuthorizedAddress.awaitTransactionSuccessAsync(ownerAddress);
// deploy staking contract
const stakingContract = await TestStakingContract.deployFrom0xArtifactAsync(
customStakingArtifact !== undefined ? customStakingArtifact : artifacts.TestStaking,
env.provider,
env.txDefaults,
artifacts,
wethContract.address,
zrxVaultContract.address,
);
// deploy staking proxy
const stakingProxyContract = await StakingProxyContract.deployFrom0xArtifactAsync(
artifacts.StakingProxy,
env.provider,
env.txDefaults,
artifacts,
stakingContract.address,
);
await stakingProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(ownerAddress);
// deploy cobb douglas contract
const cobbDouglasContract = await TestCobbDouglasContract.deployFrom0xArtifactAsync(
artifacts.TestCobbDouglas,
env.provider,
txDefaults,
artifacts,
);
// configure erc20 proxy to accept calls from zrx vault
await erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(zrxVaultContract.address);
// set staking proxy contract in zrx vault
await zrxVaultContract.setStakingProxy.awaitTransactionSuccessAsync(stakingProxyContract.address);
return new StakingApiWrapper(
env,
ownerAddress,
stakingProxyContract,
stakingContract,
zrxVaultContract,
zrxTokenContract,
wethContract,
cobbDouglasContract,
);
}