2019-09-22 12:07:46 -04:00

267 lines
11 KiB
TypeScript

import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { BlockchainTestsEnvironment, constants, filterLogsToArguments, txDefaults } from '@0x/contracts-test-utils';
import { BigNumber, logUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { BlockParamLiteral, ContractArtifact, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import {
artifacts,
EthVaultContract,
IStakingEventsEpochEndedEventArgs,
IStakingEventsStakingPoolActivatedEventArgs,
ReadOnlyProxyContract,
StakingContract,
StakingEvents,
StakingPoolRewardVaultContract,
StakingProxyContract,
ZrxVaultContract,
} from '../../src';
import { constants as stakingConstants } from './constants';
import { 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: StakingContract;
// The StakingProxy.sol contract as a StakingProxyContract
public stakingProxyContract: StakingProxyContract;
public zrxVaultContract: ZrxVaultContract;
public ethVaultContract: EthVaultContract;
public rewardVaultContract: StakingPoolRewardVaultContract;
public zrxTokenContract: DummyERC20TokenContract;
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<TransactionReceiptWithDecodedLogs> => {
await this.utils.fastForwardToNextEpochAsync();
const endOfEpochInfo = await this.utils.endEpochAsync();
const receipt = await this.stakingContract.finalizePools.awaitTransactionSuccessAsync(
endOfEpochInfo.activePoolIds,
);
logUtils.log(`Finalization cost ${receipt.gasUsed} gas`);
return receipt;
},
endEpochAsync: async (): Promise<EndOfEpochInfo> => {
const activePoolIds = await this.utils.findActivePoolIdsAsync();
const receipt = await this.stakingContract.endEpoch.awaitTransactionSuccessAsync();
const [epochEndedEvent] = filterLogsToArguments<IStakingEventsEpochEndedEventArgs>(
receipt.logs,
StakingEvents.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.getCurrentEpoch.callAsync();
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>(
await this.stakingContract.getLogsAsync(
StakingEvents.StakingPoolActivated,
{ fromBlock: BlockParamLiteral.Earliest, toBlock: BlockParamLiteral.Latest },
{ epoch: _epoch },
),
StakingEvents.StakingPoolActivated,
);
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(
_params.epochDurationInSeconds,
_params.rewardDelegatedStakeWeight,
_params.minimumPoolStake,
_params.maximumMakersInPool,
_params.cobbDouglasAlphaNumerator,
_params.cobbDouglasAlphaDenominator,
_params.wethProxyAddress,
_params.ethVaultAddress,
_params.rewardVaultAddress,
_params.zrxVaultAddress,
);
},
getParamsAsync: async (): Promise<StakingParams> => {
return (_.zipObject(
[
'epochDurationInSeconds',
'rewardDelegatedStakeWeight',
'minimumPoolStake',
'maximumMakersInPool',
'cobbDouglasAlphaNumerator',
'cobbDouglasAlphaDenominator',
'wethProxyAddress',
'ethVaultAddress',
'rewardVaultAddress',
'zrxVaultAddress',
],
await this.stakingContract.getParams.callAsync(),
) as any) as StakingParams;
},
};
private readonly _web3Wrapper: Web3Wrapper;
constructor(
env: BlockchainTestsEnvironment,
ownerAddress: string,
stakingProxyContract: StakingProxyContract,
stakingContract: StakingContract,
zrxVaultContract: ZrxVaultContract,
ethVaultContract: EthVaultContract,
rewardVaultContract: StakingPoolRewardVaultContract,
zrxTokenContract: DummyERC20TokenContract,
) {
this._web3Wrapper = env.web3Wrapper;
this.zrxVaultContract = zrxVaultContract;
this.ethVaultContract = ethVaultContract;
this.rewardVaultContract = rewardVaultContract;
this.zrxTokenContract = zrxTokenContract;
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 StakingContract(
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 staking contract
const stakingContract = await StakingContract.deployFrom0xArtifactAsync(
customStakingArtifact !== undefined ? customStakingArtifact : artifacts.Staking,
env.provider,
env.txDefaults,
artifacts,
);
// deploy read-only proxy
const readOnlyProxyContract = await ReadOnlyProxyContract.deployFrom0xArtifactAsync(
artifacts.ReadOnlyProxy,
env.provider,
env.txDefaults,
artifacts,
);
// deploy eth vault
const ethVaultContract = await EthVaultContract.deployFrom0xArtifactAsync(
artifacts.EthVault,
env.provider,
env.txDefaults,
artifacts,
);
// deploy reward vault
const rewardVaultContract = await StakingPoolRewardVaultContract.deployFrom0xArtifactAsync(
artifacts.StakingPoolRewardVault,
env.provider,
env.txDefaults,
artifacts,
);
// deploy zrx vault
const zrxVaultContract = await ZrxVaultContract.deployFrom0xArtifactAsync(
artifacts.ZrxVault,
env.provider,
env.txDefaults,
artifacts,
erc20ProxyContract.address,
zrxTokenContract.address,
);
// deploy staking proxy
const stakingProxyContract = await StakingProxyContract.deployFrom0xArtifactAsync(
artifacts.StakingProxy,
env.provider,
env.txDefaults,
artifacts,
stakingContract.address,
readOnlyProxyContract.address,
erc20ProxyContract.address,
ethVaultContract.address,
rewardVaultContract.address,
zrxVaultContract.address,
);
// 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);
// set staking proxy contract in reward vault
await rewardVaultContract.setStakingProxy.awaitTransactionSuccessAsync(stakingProxyContract.address);
return new StakingApiWrapper(
env,
ownerAddress,
stakingProxyContract,
stakingContract,
zrxVaultContract,
ethVaultContract,
rewardVaultContract,
zrxTokenContract,
);
}