281 lines
11 KiB
TypeScript
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,
|
|
);
|
|
}
|