@0x/contracts-staking
: Fixing tests.
This commit is contained in:
parent
58a5ab4550
commit
d548ddac0d
10
contracts/staking/.prettierrc
Normal file
10
contracts/staking/.prettierrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "./test/**.ts",
|
||||
"options": {
|
||||
"printWidth": 80
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -8,7 +8,12 @@
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
"details": {
|
||||
"yul": true,
|
||||
"deduplicate": true,
|
||||
"cse": true,
|
||||
"constantOptimizer": true
|
||||
}
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
|
@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./interfaces/IStaking.sol";
|
||||
import "./sys/MixinParams.sol";
|
||||
import "./sys/MixinFinalizer.sol";
|
||||
import "./stake/MixinStake.sol";
|
||||
import "./staking_pools/MixinStakingPool.sol";
|
||||
import "./fees/MixinExchangeFees.sol";
|
||||
@ -31,7 +32,7 @@ contract Staking is
|
||||
MixinParams,
|
||||
MixinStakingPool,
|
||||
MixinStake,
|
||||
MixinExchangeFees
|
||||
MixinExchangeFees,
|
||||
{
|
||||
// this contract can receive ETH
|
||||
// solhint-disable no-empty-blocks
|
||||
|
@ -335,6 +335,15 @@ contract MixinFinalizer is
|
||||
rewards.membersStake = pool.delegatedStake;
|
||||
}
|
||||
|
||||
/// @dev Converts the entire WETH balance of the contract into ETH.
|
||||
function _unwrapWETH() internal {
|
||||
uint256 wethBalance = IEtherToken(WETH_ADDRESS)
|
||||
.balanceOf(address(this));
|
||||
if (wethBalance != 0) {
|
||||
IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Computes the reward owed to a pool during finalization and
|
||||
/// credits it to that pool for the CURRENT epoch.
|
||||
/// @param poolId The pool's ID.
|
||||
@ -377,13 +386,4 @@ contract MixinFinalizer is
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Converts the entire WETH balance of the contract into ETH.
|
||||
function _unwrapWETH() private {
|
||||
uint256 wethBalance = IEtherToken(WETH_ADDRESS)
|
||||
.balanceOf(address(this));
|
||||
if (wethBalance != 0) {
|
||||
IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
contracts/staking/contracts/test/TestDelegatorRewards.sol
Normal file
29
contracts/staking/contracts/test/TestDelegatorRewards.sol
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/Staking.sol";
|
||||
|
||||
|
||||
contract TestDelegatorRewards is
|
||||
Staking
|
||||
{
|
||||
// TODO
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
import { ContractArtifact } from "ethereum-types";
|
||||
|
||||
import * as EthVault from '../generated-artifacts/EthVault.json';
|
||||
import * as IEthVault from '../generated-artifacts/IEthVault.json';
|
||||
|
@ -62,7 +62,7 @@ export class FinalizerActor extends BaseActor {
|
||||
memberRewardByPoolId,
|
||||
);
|
||||
// finalize
|
||||
await this._stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||
await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
|
||||
// assert reward vault changes
|
||||
const finalRewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
|
||||
expect(finalRewardVaultBalanceByPoolId, 'final pool balances in reward vault').to.be.deep.equal(
|
||||
|
@ -151,7 +151,7 @@ export class StakerActor extends BaseActor {
|
||||
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initBalances = await this._getBalancesAsync();
|
||||
// go to next epoch
|
||||
await this._stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||
await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
|
||||
// check balances
|
||||
const expectedBalances = this._getNextEpochBalances(initBalances);
|
||||
await this._assertBalancesAsync(expectedBalances);
|
||||
|
@ -38,7 +38,7 @@ blockchainTests('Epochs', env => {
|
||||
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH);
|
||||
}
|
||||
///// 3/3 Increment Epoch (TimeLock Should Not Increment) /////
|
||||
await stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||
await stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
|
||||
{
|
||||
// epoch
|
||||
const currentEpoch = await stakingApiWrapper.stakingContract.currentEpoch.callAsync();
|
||||
|
@ -701,8 +701,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const stakeAmounts = [toBaseUnitAmount(5), toBaseUnitAmount(10)];
|
||||
const totalStakeAmount = BigNumber.sum(...stakeAmounts);
|
||||
// stake and delegate both
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as
|
||||
Array<[StakerActor, BigNumber]>;
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||
await staker.stakeAsync(stakeAmount);
|
||||
await staker.moveStakeAsync(
|
||||
@ -724,8 +723,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
toBaseUnitAmount(0),
|
||||
);
|
||||
}
|
||||
const expectedStakerRewards = stakeAmounts
|
||||
.map(n => reward.times(n).dividedToIntegerBy(totalStakeAmount));
|
||||
const expectedStakerRewards = stakeAmounts.map(n => reward.times(n).dividedToIntegerBy(totalStakeAmount));
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: toBaseUnitAmount(0),
|
||||
stakerRewardVaultBalance_2: toBaseUnitAmount(0),
|
||||
@ -739,8 +737,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const stakeAmounts = [toBaseUnitAmount(5), toBaseUnitAmount(10)];
|
||||
const totalStakeAmount = BigNumber.sum(...stakeAmounts);
|
||||
// stake and delegate both
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as
|
||||
Array<[StakerActor, BigNumber]>;
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||
await staker.stakeAsync(stakeAmount);
|
||||
await staker.moveStakeAsync(
|
||||
@ -754,8 +751,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// finalize
|
||||
const reward = toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
const expectedStakerRewards = stakeAmounts
|
||||
.map(n => reward.times(n).dividedToIntegerBy(totalStakeAmount));
|
||||
const expectedStakerRewards = stakeAmounts.map(n => reward.times(n).dividedToIntegerBy(totalStakeAmount));
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: expectedStakerRewards[0],
|
||||
stakerRewardVaultBalance_2: expectedStakerRewards[1],
|
||||
@ -776,19 +772,21 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const sneakyStakerExpectedEthVaultBalance = expectedStakerRewards[0];
|
||||
await undelegateZeroAsync(sneakyStaker);
|
||||
// Should have been credited the correct amount of rewards.
|
||||
let sneakyStakerEthVaultBalance = await stakingApiWrapper
|
||||
.ethVaultContract.balanceOf
|
||||
.callAsync(sneakyStaker.getOwner());
|
||||
expect(sneakyStakerEthVaultBalance, 'EthVault balance after first undelegate')
|
||||
.to.bignumber.eq(sneakyStakerExpectedEthVaultBalance);
|
||||
let sneakyStakerEthVaultBalance = await stakingApiWrapper.ethVaultContract.balanceOf.callAsync(
|
||||
sneakyStaker.getOwner(),
|
||||
);
|
||||
expect(sneakyStakerEthVaultBalance, 'EthVault balance after first undelegate').to.bignumber.eq(
|
||||
sneakyStakerExpectedEthVaultBalance,
|
||||
);
|
||||
// Now he'll try to do it again to see if he gets credited twice.
|
||||
await undelegateZeroAsync(sneakyStaker);
|
||||
/// The total amount credited should remain the same.
|
||||
sneakyStakerEthVaultBalance = await stakingApiWrapper
|
||||
.ethVaultContract.balanceOf
|
||||
.callAsync(sneakyStaker.getOwner());
|
||||
expect(sneakyStakerEthVaultBalance, 'EthVault balance after second undelegate')
|
||||
.to.bignumber.eq(sneakyStakerExpectedEthVaultBalance);
|
||||
sneakyStakerEthVaultBalance = await stakingApiWrapper.ethVaultContract.balanceOf.callAsync(
|
||||
sneakyStaker.getOwner(),
|
||||
);
|
||||
expect(sneakyStakerEthVaultBalance, 'EthVault balance after second undelegate').to.bignumber.eq(
|
||||
sneakyStakerExpectedEthVaultBalance,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
|
||||
|
||||
import { artifacts, TestDelegatorRewardsContract } from '../../src';
|
||||
|
||||
blockchainTests('delegator rewards', env => {
|
||||
let testContract: TestDelegatorRewardsContract;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestDelegatorRewardsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestLibFixedMath,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('computeRewardBalanceOfDelegator()', () => {
|
||||
it('does stuff', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
});
|
@ -8,6 +8,7 @@ import {
|
||||
testCombinatoriallyWithReferenceFunc,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { cartesianProduct } from 'js-combinatorics';
|
||||
|
||||
import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src';
|
||||
|
||||
@ -196,25 +197,51 @@ blockchainTests.resets('LibProxy', env => {
|
||||
|
||||
describe('Combinatorial Tests', () => {
|
||||
// Combinatorial Scenarios for `proxyCall()`.
|
||||
const revertRuleScenarios: RevertRule[] = [
|
||||
function getCombinatorialTestDescription(params: [RevertRule, boolean, string, string]): string {
|
||||
const REVERT_RULE_NAMES = [
|
||||
'RevertOnError',
|
||||
'AlwaysRevert',
|
||||
'NeverRevert',
|
||||
];
|
||||
return [
|
||||
`revertRule: ${REVERT_RULE_NAMES[params[0]]}`,
|
||||
`ignoreIngressSelector: ${params[1]}`,
|
||||
`customEgressSelector: ${params[2]}`,
|
||||
`calldata: ${
|
||||
params[3].length / 2 - 2 > 4
|
||||
? // tslint:disable-next-line
|
||||
hexSlice(params[3], 0, 4) + '...'
|
||||
: params[3]
|
||||
}`,
|
||||
].join(', ');
|
||||
}
|
||||
|
||||
const scenarios = [
|
||||
// revertRule
|
||||
[
|
||||
RevertRule.RevertOnError,
|
||||
RevertRule.AlwaysRevert,
|
||||
RevertRule.NeverRevert,
|
||||
];
|
||||
const ignoreIngressScenarios: boolean[] = [false, true];
|
||||
const customEgressScenarios: string[] = [
|
||||
],
|
||||
// ignoreIngressSelector
|
||||
[false, true],
|
||||
// customEgressSelector
|
||||
[
|
||||
constants.NULL_BYTES4,
|
||||
constructRandomFailureCalldata(), // Random failure calldata is used because it is nonzero and won't collide.
|
||||
];
|
||||
const calldataScenarios: string[] = [constructRandomFailureCalldata(), constructRandomSuccessCalldata()];
|
||||
// Random failure calldata is used because it is nonzero and
|
||||
// won't collide.
|
||||
constructRandomFailureCalldata(),
|
||||
],
|
||||
// calldata
|
||||
[
|
||||
constructRandomFailureCalldata(),
|
||||
constructRandomSuccessCalldata(),
|
||||
],
|
||||
] as [RevertRule[], boolean[], string[], string[]];
|
||||
|
||||
// A reference function that returns the expected success and returndata values of a given call to `proxyCall()`.
|
||||
async function referenceFuncAsync(
|
||||
revertRule: RevertRule,
|
||||
customEgressSelector: string,
|
||||
shouldIgnoreIngressSelector: boolean,
|
||||
calldata: string,
|
||||
): Promise<[boolean, string]> {
|
||||
for (const params of cartesianProduct(...scenarios).toArray()) {
|
||||
const [revertRule, shouldIgnoreIngressSelector, customEgressSelector, calldata] = params;
|
||||
it(getCombinatorialTestDescription(params), async () => {
|
||||
// Determine whether or not the call should succeed.
|
||||
let shouldSucceed = true;
|
||||
if (
|
||||
@ -242,33 +269,16 @@ blockchainTests.resets('LibProxy', env => {
|
||||
returnData = hexConcat(customEgressSelector, returnData);
|
||||
}
|
||||
|
||||
// Return the success and return data values.
|
||||
return [shouldSucceed, returnData];
|
||||
}
|
||||
|
||||
// A wrapper for `publicProxyCall()` that allow us to combinatorially test `proxyCall()` for the
|
||||
// scenarios defined above.
|
||||
async function testFuncAsync(
|
||||
revertRule: RevertRule,
|
||||
customEgressSelector: string,
|
||||
shouldIgnoreIngressSelector: boolean,
|
||||
calldata: string,
|
||||
): Promise<[boolean, string]> {
|
||||
return publicProxyCallAsync({
|
||||
const [didSucceed, actualReturnData] = await publicProxyCallAsync({
|
||||
calldata,
|
||||
customEgressSelector,
|
||||
ignoreIngressSelector: shouldIgnoreIngressSelector,
|
||||
revertRule,
|
||||
});
|
||||
expect(didSucceed).to.be.eq(shouldSucceed);
|
||||
expect(actualReturnData).to.be.eq(returnData);
|
||||
});
|
||||
}
|
||||
|
||||
// Combinatorially test proxy call.
|
||||
testCombinatoriallyWithReferenceFunc('proxyCall', referenceFuncAsync, testFuncAsync, [
|
||||
revertRuleScenarios,
|
||||
customEgressScenarios,
|
||||
ignoreIngressScenarios,
|
||||
calldataScenarios,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,28 +1,34 @@
|
||||
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { BlockchainTestsEnvironment, constants } from '@0x/contracts-test-utils';
|
||||
import { BlockchainTestsEnvironment, constants, filterLogsToArguments, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { ContractArtifact, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
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 { StakingParams } from './types';
|
||||
import { EndOfEpochInfo, StakingParams } from './types';
|
||||
|
||||
export class StakingApiWrapper {
|
||||
public stakingContractAddress: string; // The address of the real Staking.sol contract
|
||||
public stakingContract: StakingContract; // The StakingProxy.sol contract wrapped as a StakingContract to borrow API
|
||||
public stakingProxyContract: StakingProxyContract; // The StakingProxy.sol contract as a StakingProxyContract
|
||||
// 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;
|
||||
@ -30,21 +36,53 @@ export class StakingApiWrapper {
|
||||
public utils = {
|
||||
// Epoch Utils
|
||||
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
||||
// increase timestamp of next block
|
||||
const { epochDurationInSeconds } = await this.utils.getParamsAsync();
|
||||
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||
// 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();
|
||||
},
|
||||
|
||||
skipToNextEpochAsync: async (): Promise<TransactionReceiptWithDecodedLogs> => {
|
||||
skipToNextEpochAndFinalizeAsync: async (): Promise<TransactionReceiptWithDecodedLogs> => {
|
||||
await this.utils.fastForwardToNextEpochAsync();
|
||||
// increment epoch in contracts
|
||||
const txReceipt = await this.stakingContract.finalizeFees.awaitTransactionSuccessAsync();
|
||||
logUtils.log(`Finalization costed ${txReceipt.gasUsed} gas`);
|
||||
// mine next block
|
||||
await this._web3Wrapper.mineBlockAsync();
|
||||
return txReceipt;
|
||||
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
|
||||
|
@ -53,6 +53,14 @@ export interface SimulationParams {
|
||||
withdrawByUndelegating: boolean;
|
||||
}
|
||||
|
||||
export interface EndOfEpochInfo {
|
||||
closingEpoch: BigNumber;
|
||||
activePoolIds: string[];
|
||||
rewardsAvailable: BigNumber;
|
||||
totalFeesCollected: BigNumber;
|
||||
totalWeightedStake: BigNumber;
|
||||
}
|
||||
|
||||
export interface StakeBalance {
|
||||
currentEpochBalance: BigNumber;
|
||||
nextEpochBalance: BigNumber;
|
||||
|
Loading…
x
Reference in New Issue
Block a user