@0x/contracts-staking
: Rename Tuned
event to ParamsChanged
.
`@0x/contracts-staking`: Merge `exchange_fees` unit tests into `protocol_fees` unit tests. `@0x/contracts-staking`: Remove `ProtocolFeeActor` and any use of it. `@0x/contracts-staking`: Remove unused constants. `@0x/contracts-staking`: Move WETH assertion constructor into `MixinDeploymentConstants`. `@0x/contracts-staking`: Add more unit tests.
This commit is contained in:
parent
d8d791e4f0
commit
2ed39cd18d
@ -26,6 +26,7 @@ import "../libs/LibStakingRichErrors.sol";
|
||||
import "../libs/LibFixedMath.sol";
|
||||
import "../immutable/MixinStorage.sol";
|
||||
import "../immutable/MixinConstants.sol";
|
||||
import "../immutable/MixinDeploymentConstants.sol";
|
||||
import "../interfaces/IStakingEvents.sol";
|
||||
import "../interfaces/IStructs.sol";
|
||||
import "../stake/MixinStakeBalances.sol";
|
||||
@ -47,6 +48,7 @@ import "./MixinExchangeManager.sol";
|
||||
contract MixinExchangeFees is
|
||||
IStakingEvents,
|
||||
MixinConstants,
|
||||
MixinDeploymentConstants,
|
||||
Ownable,
|
||||
MixinStorage,
|
||||
MixinZrxVault,
|
||||
@ -111,7 +113,7 @@ contract MixinExchangeFees is
|
||||
if (poolStake >= minimumPoolStake) {
|
||||
// Credit the pool.
|
||||
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
|
||||
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);
|
||||
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
|
||||
if (_feesCollectedThisEpoch == 0) {
|
||||
activePoolsThisEpoch.push(poolId);
|
||||
}
|
||||
|
@ -39,7 +39,4 @@ contract MixinConstants
|
||||
uint64 constant internal INITIAL_EPOCH = 0;
|
||||
|
||||
uint256 constant internal MIN_TOKEN_VALUE = 10**18;
|
||||
|
||||
// TODO(dorothy-zbornak): Remove when signatures are removed from maker handshake.
|
||||
uint256 constant internal CHAIN_ID = 1;
|
||||
}
|
||||
|
@ -18,20 +18,19 @@
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "../libs/LibStakingRichErrors.sol";
|
||||
|
||||
|
||||
// solhint-disable separate-by-one-line-in-contract
|
||||
contract MixinDeploymentConstants {
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
// @TODO SET THESE VALUES FOR DEPLOYMENT
|
||||
|
||||
uint256 constant internal EPOCH_DURATION_IN_SECONDS = 1000;
|
||||
|
||||
uint256 constant internal TIMELOCK_DURATION_IN_EPOCHS = 3;
|
||||
|
||||
// How much delegated stake is weighted vs operator stake, in ppm.
|
||||
uint32 constant internal REWARD_DELEGATED_STAKE_WEIGHT = 900000; // 90%
|
||||
|
||||
uint256 constant internal CHAIN_ID = 1;
|
||||
|
||||
// Mainnet WETH9 Address
|
||||
address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
|
||||
|
||||
@ -49,4 +48,14 @@ contract MixinDeploymentConstants {
|
||||
|
||||
// Ropsten & Rinkeby Weth Asset Data
|
||||
// bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c778417e063141139fce010982780140aa0cd5ab";
|
||||
|
||||
/// @dev Ensures that the WETH_ASSET_DATA is correct.
|
||||
constructor() public {
|
||||
// Ensure that the WETH_ASSET_DATA is correct.
|
||||
if (!WETH_ASSET_DATA.equals(
|
||||
abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS)
|
||||
)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.InvalidWethAssetDataError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,7 @@
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||
import "./MixinConstants.sol";
|
||||
@ -36,20 +34,6 @@ contract MixinStorage is
|
||||
MixinConstants,
|
||||
Ownable
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
|
||||
/// @dev Ensures that the WETH_ASSET_DATA is correct.
|
||||
constructor()
|
||||
public
|
||||
Ownable()
|
||||
{
|
||||
// Ensure that the WETH_ASSET_DATA from MixinDeploymentConstants is correct.
|
||||
if (!WETH_ASSET_DATA.equals(
|
||||
abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS)
|
||||
)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.InvalidWethAssetDataError());
|
||||
}
|
||||
}
|
||||
|
||||
// WETH Asset Proxy
|
||||
IAssetProxy internal wethAssetProxy;
|
||||
|
@ -53,14 +53,14 @@ interface IStakingEvents {
|
||||
uint256 earliestEndTimeInSeconds
|
||||
);
|
||||
|
||||
/// @dev Emitted whenever hyperparameters are changed via the `tune()` function.
|
||||
/// @dev Emitted whenever staking parameters are changed via the `setParams()` function.
|
||||
/// @param epochDurationInSeconds Minimum seconds between epochs.
|
||||
/// @param rewardDelegatedStakeWeight How much delegated stake is weighted vs operator stake, in ppm.
|
||||
/// @param minimumPoolStake Minimum amount of stake required in a pool to collect rewards.
|
||||
/// @param maximumMakersInPool Maximum number of maker addresses allowed to be registered to a pool.
|
||||
/// @param cobbDouglasAlphaNumerator Numerator for cobb douglas alpha factor.
|
||||
/// @param cobbDouglasAlphaDenomintor Denominator for cobb douglas alpha factor.
|
||||
event Tuned(
|
||||
event ParamsChanged(
|
||||
uint256 epochDurationInSeconds,
|
||||
uint32 rewardDelegatedStakeWeight,
|
||||
uint256 minimumPoolStake,
|
||||
|
@ -65,7 +65,7 @@ contract MixinParams is
|
||||
cobbDouglasAlphaNumerator = _cobbDouglasAlphaNumerator;
|
||||
cobbDouglasAlphaDenomintor = _cobbDouglasAlphaDenomintor;
|
||||
|
||||
emit Tuned(
|
||||
emit ParamsChanged(
|
||||
epochDurationInSeconds,
|
||||
rewardDelegatedStakeWeight,
|
||||
minimumPoolStake,
|
||||
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
|
||||
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 TestExchangeFees is
|
||||
Staking
|
||||
{
|
||||
|
||||
struct TestPool {
|
||||
uint256 stake;
|
||||
mapping(address => bool) isMaker;
|
||||
}
|
||||
|
||||
mapping(bytes32 => TestPool) private _testPools;
|
||||
mapping(address => bytes32) private _makersToTestPoolIds;
|
||||
|
||||
constructor(address exchangeAddress) public {
|
||||
validExchanges[exchangeAddress] = true;
|
||||
_initMixinParams();
|
||||
}
|
||||
|
||||
/// @dev Create a test pool.
|
||||
function createTestPool(
|
||||
bytes32 poolId,
|
||||
uint256 stake,
|
||||
address[] memory makerAddresses
|
||||
)
|
||||
public
|
||||
{
|
||||
TestPool storage pool = _testPools[poolId];
|
||||
pool.stake = stake;
|
||||
for (uint256 i = 0; i < makerAddresses.length; ++i) {
|
||||
pool.isMaker[makerAddresses[i]] = true;
|
||||
_makersToTestPoolIds[makerAddresses[i]] = poolId;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getStakingPoolIdOfMaker(address makerAddress)
|
||||
public
|
||||
view
|
||||
returns (bytes32)
|
||||
{
|
||||
return _makersToTestPoolIds[makerAddress];
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getTotalStakeDelegatedToPool(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
uint256 stake = _testPools[poolId].stake;
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: stake,
|
||||
nextEpochBalance: stake
|
||||
});
|
||||
}
|
||||
}
|
@ -26,10 +26,18 @@ import "../src/Staking.sol";
|
||||
contract TestProtocolFees is
|
||||
Staking
|
||||
{
|
||||
function setWethProxy(address wethProxyAddress)
|
||||
external
|
||||
{
|
||||
struct TestPool {
|
||||
uint256 stake;
|
||||
mapping(address => bool) isMaker;
|
||||
}
|
||||
|
||||
mapping(bytes32 => TestPool) private _testPools;
|
||||
mapping(address => bytes32) private _makersToTestPoolIds;
|
||||
|
||||
constructor(address exchangeAddress, address wethProxyAddress) public {
|
||||
validExchanges[exchangeAddress] = true;
|
||||
wethAssetProxy = IAssetProxy(wethProxyAddress);
|
||||
_initMixinParams();
|
||||
}
|
||||
|
||||
function addMakerToPool(bytes32 poolId, address makerAddress)
|
||||
@ -39,6 +47,10 @@ contract TestProtocolFees is
|
||||
poolJoinedByMakerAddress[makerAddress].confirmed = true;
|
||||
}
|
||||
|
||||
function getWethAssetData() external pure returns (bytes memory) {
|
||||
return WETH_ASSET_DATA;
|
||||
}
|
||||
|
||||
function getActivePoolsByEpoch()
|
||||
external
|
||||
view
|
||||
@ -46,4 +58,42 @@ contract TestProtocolFees is
|
||||
{
|
||||
return activePoolsThisEpoch;
|
||||
}
|
||||
|
||||
/// @dev Create a test pool.
|
||||
function createTestPool(
|
||||
bytes32 poolId,
|
||||
uint256 stake,
|
||||
address[] memory makerAddresses
|
||||
)
|
||||
public
|
||||
{
|
||||
TestPool storage pool = _testPools[poolId];
|
||||
pool.stake = stake;
|
||||
for (uint256 i = 0; i < makerAddresses.length; ++i) {
|
||||
pool.isMaker[makerAddresses[i]] = true;
|
||||
_makersToTestPoolIds[makerAddresses[i]] = poolId;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getStakingPoolIdOfMaker(address makerAddress)
|
||||
public
|
||||
view
|
||||
returns (bytes32)
|
||||
{
|
||||
return _makersToTestPoolIds[makerAddress];
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getTotalStakeDelegatedToPool(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
uint256 stake = _testPools[poolId].stake;
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: stake,
|
||||
nextEpochBalance: stake
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ contract TestStakingProxy is
|
||||
// solhint-disable no-empty-blocks
|
||||
constructor(address _stakingContract)
|
||||
public
|
||||
StakingProxy(_stakingContract, address(0))
|
||||
StakingProxy(_stakingContract, address(0), address(0))
|
||||
{}
|
||||
|
||||
function getAttachedContract() external view returns (address) {
|
||||
|
@ -37,7 +37,7 @@
|
||||
},
|
||||
"config": {
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStorageLayout|ZrxVault).json"
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStorageInit|IStructs|IVaultCore|IZrxVault|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestInitTarget|TestLibFixedMath|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStakingProxy|TestStorageLayout|ZrxVault).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -21,6 +21,7 @@ import * as LibProxy from '../generated-artifacts/LibProxy.json';
|
||||
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
|
||||
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
|
||||
import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
|
||||
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
|
||||
import * as MixinEthVault from '../generated-artifacts/MixinEthVault.json';
|
||||
import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json';
|
||||
import * as MixinExchangeManager from '../generated-artifacts/MixinExchangeManager.json';
|
||||
@ -40,7 +41,6 @@ import * as Staking from '../generated-artifacts/Staking.json';
|
||||
import * as StakingPoolRewardVault from '../generated-artifacts/StakingPoolRewardVault.json';
|
||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
||||
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json';
|
||||
import * as TestExchangeFees from '../generated-artifacts/TestExchangeFees.json';
|
||||
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
|
||||
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
|
||||
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
||||
@ -56,6 +56,7 @@ export const artifacts = {
|
||||
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
||||
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
||||
MixinConstants: MixinConstants as ContractArtifact,
|
||||
MixinDeploymentConstants: MixinDeploymentConstants as ContractArtifact,
|
||||
MixinStorage: MixinStorage as ContractArtifact,
|
||||
IEthVault: IEthVault as ContractArtifact,
|
||||
IStaking: IStaking as ContractArtifact,
|
||||
@ -86,7 +87,6 @@ export const artifacts = {
|
||||
StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
|
||||
ZrxVault: ZrxVault as ContractArtifact,
|
||||
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
||||
TestExchangeFees: TestExchangeFees as ContractArtifact,
|
||||
TestInitTarget: TestInitTarget as ContractArtifact,
|
||||
TestLibFixedMath: TestLibFixedMath as ContractArtifact,
|
||||
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
||||
|
@ -19,6 +19,7 @@ export * from '../generated-wrappers/lib_proxy';
|
||||
export * from '../generated-wrappers/lib_safe_downcast';
|
||||
export * from '../generated-wrappers/lib_staking_rich_errors';
|
||||
export * from '../generated-wrappers/mixin_constants';
|
||||
export * from '../generated-wrappers/mixin_deployment_constants';
|
||||
export * from '../generated-wrappers/mixin_eth_vault';
|
||||
export * from '../generated-wrappers/mixin_exchange_fees';
|
||||
export * from '../generated-wrappers/mixin_exchange_manager';
|
||||
@ -38,7 +39,6 @@ export * from '../generated-wrappers/staking';
|
||||
export * from '../generated-wrappers/staking_pool_reward_vault';
|
||||
export * from '../generated-wrappers/staking_proxy';
|
||||
export * from '../generated-wrappers/test_cobb_douglas';
|
||||
export * from '../generated-wrappers/test_exchange_fees';
|
||||
export * from '../generated-wrappers/test_init_target';
|
||||
export * from '../generated-wrappers/test_lib_fixed_math';
|
||||
export * from '../generated-wrappers/test_protocol_fees';
|
||||
|
@ -1,129 +0,0 @@
|
||||
import { constants, expect } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { TestProtocolFeesContract, TestProtocolFeesERC20ProxyTransferFromCalledEventArgs } from '../../src';
|
||||
|
||||
export interface PayProtocolFeeArgs {
|
||||
poolId: string;
|
||||
makerAddress: string;
|
||||
payerAddress: string;
|
||||
protocolFeePaid: BigNumber;
|
||||
from: string;
|
||||
value: BigNumber;
|
||||
}
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
export class ProtocolFeeActor {
|
||||
private readonly _exchanges: string[];
|
||||
private readonly _registered_makers: string[];
|
||||
private readonly _protocolFees: TestProtocolFeesContract;
|
||||
private readonly _wethAssetData = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
|
||||
constructor(exchanges: string[], makers: string[], protocolFees: TestProtocolFeesContract) {
|
||||
this._exchanges = exchanges;
|
||||
this._protocolFees = protocolFees;
|
||||
this._registered_makers = makers;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will test the `payProtocolFee()` function, and will revert if the behavior deviates
|
||||
* whatsoever from the expected behavior.
|
||||
* @param makerAddress The address of the order's maker.
|
||||
* @param payerAddress The address that is responsible for paying the protocol fee.
|
||||
* @param protocolFeePaid The fee that should be paid to the staking contract.
|
||||
* @param from The address that should send the transaction.
|
||||
* @param value The amount of value that should be sent in the transaction.
|
||||
*/
|
||||
public async payProtocolFeeAsync(args: PayProtocolFeeArgs): Promise<void> {
|
||||
// Get the original state to compare with afterwards
|
||||
const originalActivePools = await this._protocolFees.getActivePoolsByEpoch.callAsync();
|
||||
const originalProtocolFeesCollected = await this._protocolFees.getProtocolFeesThisEpochByPool.callAsync(
|
||||
args.poolId,
|
||||
);
|
||||
|
||||
// If the poolId is already registered, it should not be added to the active pools list. Otherwise, it should be added.
|
||||
const shouldBeAdded = !originalActivePools.includes(args.poolId);
|
||||
|
||||
// Handle all of the failure cases.
|
||||
const tx = this._protocolFees.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
args.makerAddress,
|
||||
args.payerAddress,
|
||||
args.protocolFeePaid,
|
||||
{ from: args.from, value: args.value },
|
||||
);
|
||||
if (!this._exchanges.includes(args.from)) {
|
||||
const expectedError = new StakingRevertErrors.OnlyCallableByExchangeError(args.from);
|
||||
return expect(tx, 'should revert when the `from` address is not a registered exchange').to.revertWith(
|
||||
expectedError,
|
||||
);
|
||||
} else if (args.protocolFeePaid.eq(0)) {
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid,
|
||||
constants.ZERO_AMOUNT,
|
||||
new BigNumber(args.value),
|
||||
);
|
||||
return expect(tx, 'should revert when the `protocolFeePaid` is zero').to.revertWith(expectedError);
|
||||
} else if (!args.protocolFeePaid.eq(args.value) && !args.value.eq(0)) {
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
|
||||
args.protocolFeePaid,
|
||||
new BigNumber(args.value),
|
||||
);
|
||||
return expect(tx, 'should revert when the `protocolFeePaid` and the value are mismatched').to.revertWith(
|
||||
expectedError,
|
||||
);
|
||||
}
|
||||
|
||||
// Call the transaction and collect the logs.
|
||||
const receipt = await tx;
|
||||
|
||||
// If WETH should have been paid, an event should be logged. Otherwise, no event should have been logged.
|
||||
if (args.value.eq(0)) {
|
||||
// Ensure that one log was recorded.
|
||||
expect(receipt.logs.length, 'log length should be one').to.be.eq(1);
|
||||
|
||||
// Ensure that the correct log was recorded.
|
||||
const log = receipt.logs[0] as LogWithDecodedArgs<TestProtocolFeesERC20ProxyTransferFromCalledEventArgs>;
|
||||
expect(log.event, 'log event should be `TransferFromCalled`').to.be.eq('TransferFromCalled');
|
||||
expect(log.args.assetData, 'log `assetData` should be `wethAssetData`').to.be.eq(this._wethAssetData);
|
||||
expect(log.args.amount, 'log `amount` should be `protocolFeePaid`').bignumber.to.be.eq(
|
||||
args.protocolFeePaid,
|
||||
);
|
||||
expect(log.args.from, 'log `from` should be `payerAddress`').to.be.eq(args.payerAddress);
|
||||
expect(log.args.to).to.be.eq(this._protocolFees.address);
|
||||
} else {
|
||||
expect(receipt.logs.length, 'log length should be zero').to.be.eq(0);
|
||||
}
|
||||
|
||||
// Get the final state.
|
||||
const finalActivePools = await this._protocolFees.getActivePoolsByEpoch.callAsync();
|
||||
const finalProtocolFeesCollected = await this._protocolFees.getProtocolFeesThisEpochByPool.callAsync(
|
||||
args.poolId,
|
||||
);
|
||||
|
||||
// Check that the active pools list was updated appropriately.
|
||||
if (shouldBeAdded && this._registered_makers.includes(args.makerAddress)) {
|
||||
// Check that the pool id was added to the list of active pools for this epoch.
|
||||
expect(finalActivePools.length, 'final active pools should have been updated').to.be.eq(
|
||||
originalActivePools.length + 1,
|
||||
);
|
||||
expect(finalActivePools.includes(args.poolId), 'final active pools should contain pool id').to.be.true();
|
||||
} else {
|
||||
// Check that active pools list was not altered.
|
||||
expect(finalActivePools, 'final active pools should be identical to original active pools').to.be.deep.eq(
|
||||
originalActivePools,
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the pool has the correct amount of fees attributed to it for this epoch.
|
||||
if (this._registered_makers.includes(args.makerAddress)) {
|
||||
expect(
|
||||
finalProtocolFeesCollected,
|
||||
'final protocol fees should be the original protocol fees plus the fee paid',
|
||||
).bignumber.to.be.eq(originalProtocolFeesCollected.plus(args.protocolFeePaid));
|
||||
}
|
||||
}
|
||||
}
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
@ -28,8 +28,8 @@ blockchainTests('Epochs', env => {
|
||||
describe('Epochs & TimeLocks', () => {
|
||||
it('basic epochs & timeLock periods', async () => {
|
||||
///// 1/3 Validate Assumptions /////
|
||||
expect(await stakingApiWrapper.stakingContract.getEpochDurationInSeconds.callAsync()).to.be.bignumber.equal(
|
||||
stakingConstants.DEFAULT_HYPER_PARAMETERS.epochDurationInSeconds,
|
||||
expect((await stakingApiWrapper.utils.getParamsAsync()).epochDurationInSeconds).to.be.bignumber.equal(
|
||||
stakingConstants.DEFAULT_PARAMS.epochDurationInSeconds,
|
||||
);
|
||||
///// 2/3 Validate Initial Epoch & TimeLock Period /////
|
||||
{
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { artifacts, TestExchangeFeesContract } from '../src/';
|
||||
|
||||
blockchainTests.resets('Fees tests', env => {
|
||||
let testContract: TestExchangeFeesContract;
|
||||
|
||||
before(async () => {
|
||||
const [ownerAddress] = await env.getAccountAddressesAsync();
|
||||
testContract = await TestExchangeFeesContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestExchangeFees,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
ownerAddress,
|
||||
);
|
||||
});
|
||||
|
||||
describe('payProtocolFee()', () => {
|
||||
let minimumStake: BigNumber;
|
||||
|
||||
function randomAddress(): string {
|
||||
return hexRandom(constants.ADDRESS_LENGTH);
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
minimumStake = (await testContract.getParams.callAsync())[2];
|
||||
});
|
||||
|
||||
it('credits pools with stake == minimum', async () => {
|
||||
const makerAddress = randomAddress();
|
||||
const feePaid = new BigNumber(1e18);
|
||||
const poolId = hexRandom();
|
||||
await testContract.createTestPool.awaitTransactionSuccessAsync(poolId, minimumStake, [makerAddress]);
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
feePaid,
|
||||
{ value: feePaid },
|
||||
);
|
||||
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(feesCredited).to.bignumber.eq(feePaid);
|
||||
});
|
||||
|
||||
it('does not credit pools with stake < minimum', async () => {
|
||||
const stake = minimumStake.minus(1);
|
||||
const makerAddress = randomAddress();
|
||||
const feePaid = new BigNumber(1e18);
|
||||
const poolId = hexRandom();
|
||||
await testContract.createTestPool.awaitTransactionSuccessAsync(poolId, stake, [makerAddress]);
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
feePaid,
|
||||
{ value: feePaid },
|
||||
);
|
||||
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(feesCredited).to.bignumber.eq(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
@ -1,8 +1,14 @@
|
||||
import { blockchainTests, constants, expect, hexRandom } from '@0x/contracts-test-utils';
|
||||
import { blockchainTests, constants, expect, filterLogsToArguments, hexRandom } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber, OwnableRevertErrors, StringRevertError } from '@0x/utils';
|
||||
|
||||
import { artifacts, StakingContract, TestInitTargetContract, TestStakingProxyContract } from '../src/';
|
||||
import {
|
||||
artifacts,
|
||||
StakingContract,
|
||||
TestInitTargetContract,
|
||||
TestStakingProxyContract,
|
||||
TestStakingProxyStakingContractAttachedToProxyEventArgs,
|
||||
} from '../src/';
|
||||
|
||||
blockchainTests('Migration tests', env => {
|
||||
let ownerAddress: string;
|
||||
@ -99,6 +105,21 @@ blockchainTests('Migration tests', env => {
|
||||
await assertInitStateAsync(proxyContract);
|
||||
});
|
||||
|
||||
it('emits a `StakingContractAttached` event', async () => {
|
||||
const receipt = await proxyContract.attachStakingContract.awaitTransactionSuccessAsync(
|
||||
initTargetContract.address,
|
||||
);
|
||||
const logsArgs = filterLogsToArguments<TestStakingProxyStakingContractAttachedToProxyEventArgs>(
|
||||
receipt.logs,
|
||||
'StakingContractAttached',
|
||||
);
|
||||
expect(logsArgs.length).to.eq(1);
|
||||
for (const args of logsArgs) {
|
||||
expect(args.newStakingContractAddress).to.eq(initTargetContract.address);
|
||||
}
|
||||
await assertInitStateAsync(proxyContract);
|
||||
});
|
||||
|
||||
it('reverts if init() reverts', async () => {
|
||||
await enableInitRevertsAsync();
|
||||
const tx = proxyContract.attachStakingContract.awaitTransactionSuccessAsync(initTargetContract.address);
|
||||
|
@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, filterLogsToArguments, Numberish }
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
|
||||
|
||||
import { artifacts, IStakingEventsTunedEventArgs, MixinParamsContract } from '../src/';
|
||||
import { artifacts, IStakingEventsParamsChangedEventArgs, MixinParamsContract } from '../src/';
|
||||
|
||||
blockchainTests('Configurable Parameters', env => {
|
||||
let testContract: MixinParamsContract;
|
||||
@ -20,7 +20,7 @@ blockchainTests('Configurable Parameters', env => {
|
||||
});
|
||||
|
||||
blockchainTests.resets('setParams()', () => {
|
||||
interface HyperParameters {
|
||||
interface Params {
|
||||
epochDurationInSeconds: Numberish;
|
||||
rewardDelegatedStakeWeight: Numberish;
|
||||
minimumPoolStake: Numberish;
|
||||
@ -34,13 +34,13 @@ blockchainTests('Configurable Parameters', env => {
|
||||
const DEFAULT_PARAMS = {
|
||||
epochDurationInSeconds: TWO_WEEKS,
|
||||
rewardDelegatedStakeWeight: PPM_90_PERCENT,
|
||||
minimumPoolStake: '100e18',
|
||||
minimumPoolStake: constants.DUMMY_TOKEN_DECIMALS.times(100),
|
||||
maximumMakersInPool: 10,
|
||||
cobbDouglasAlphaNumerator: 1,
|
||||
cobbDouglasAlphaDenomintor: 2,
|
||||
};
|
||||
|
||||
async function setParamsAndAssertAsync(params: Partial<HyperParameters>, from?: string): Promise<void> {
|
||||
async function setParamsAndAssertAsync(params: Partial<Params>, from?: string): Promise<void> {
|
||||
const _params = {
|
||||
...DEFAULT_PARAMS,
|
||||
...params,
|
||||
@ -56,7 +56,7 @@ blockchainTests('Configurable Parameters', env => {
|
||||
);
|
||||
// Assert event.
|
||||
expect(receipt.logs.length).to.eq(1);
|
||||
const event = filterLogsToArguments<IStakingEventsTunedEventArgs>(receipt.logs, 'Tuned')[0];
|
||||
const event = filterLogsToArguments<IStakingEventsParamsChangedEventArgs>(receipt.logs, 'ParamsChanged')[0];
|
||||
expect(event.epochDurationInSeconds).to.bignumber.eq(_params.epochDurationInSeconds);
|
||||
expect(event.rewardDelegatedStakeWeight).to.bignumber.eq(_params.rewardDelegatedStakeWeight);
|
||||
expect(event.minimumPoolStake).to.bignumber.eq(_params.minimumPoolStake);
|
||||
|
@ -321,10 +321,7 @@ blockchainTests('Staking Pool Management', env => {
|
||||
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||
|
||||
const makerAddresses = users.slice(
|
||||
1,
|
||||
stakingConstants.DEFAULT_PARAMS.maximumMakersInPool.toNumber() + 2,
|
||||
);
|
||||
const makerAddresses = users.slice(1, stakingConstants.DEFAULT_PARAMS.maximumMakersInPool.toNumber() + 2);
|
||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
|
||||
|
||||
// create pool
|
||||
|
@ -1,48 +1,27 @@
|
||||
import { blockchainTests, constants } from '@0x/contracts-test-utils';
|
||||
import { blockchainTests, constants, expect, filterLogsToArguments, hexRandom } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { LogEntry } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, TestProtocolFeesContract, TestProtocolFeesERC20ProxyContract } from '../src';
|
||||
import {
|
||||
artifacts,
|
||||
TestProtocolFeesContract,
|
||||
TestProtocolFeesERC20ProxyContract,
|
||||
TestProtocolFeesERC20ProxyTransferFromCalledEventArgs,
|
||||
} from '../src';
|
||||
|
||||
import { ProtocolFeeActor } from './actors/protocol_fee_actor';
|
||||
import { getRandomPortion } from './utils/number_utils';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
// The accounts that will be used during testing.
|
||||
let owner: string;
|
||||
let exchange: string;
|
||||
let nonExchange: string;
|
||||
let makerAddress: string;
|
||||
let payerAddress: string;
|
||||
|
||||
// The actor that will be used for testng `payProtocolFee` and `_unwrapETH`.
|
||||
let protocolFeeActor: ProtocolFeeActor;
|
||||
|
||||
// The default protocol fee that will be paid -- a somewhat realistic value.
|
||||
const DEFAULT_PROTOCOL_FEE_PAID = new BigNumber(150000).times(10000000);
|
||||
|
||||
// The default pool Id that will be used.
|
||||
const DEFAULT_POOL_ID = '0x0000000000000000000000000000000000000000000000000000000000000001';
|
||||
let ownerAddress: string;
|
||||
let exchangeAddress: string;
|
||||
let notExchangeAddress: string;
|
||||
let testContract: TestProtocolFeesContract;
|
||||
let wethAssetData: string;
|
||||
|
||||
before(async () => {
|
||||
// Get accounts to represent the exchange and an address that is not a registered exchange.
|
||||
[
|
||||
owner,
|
||||
exchange,
|
||||
nonExchange,
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
] = (await env.web3Wrapper.getAvailableAddressesAsync()).slice(0, 6);
|
||||
|
||||
// Deploy the protocol fees contract.
|
||||
const protocolFees = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestProtocolFees,
|
||||
env.provider,
|
||||
{
|
||||
...env.txDefaults,
|
||||
from: owner,
|
||||
},
|
||||
artifacts,
|
||||
);
|
||||
[ownerAddress, exchangeAddress, notExchangeAddress] = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
|
||||
// Deploy the erc20Proxy for testing.
|
||||
const proxy = await TestProtocolFeesERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
@ -52,187 +31,349 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
{},
|
||||
);
|
||||
|
||||
// Register the test ERC20Proxy in the exchange.
|
||||
await protocolFees.setWethProxy.awaitTransactionSuccessAsync(proxy.address);
|
||||
// Deploy the protocol fees contract.
|
||||
testContract = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestProtocolFees,
|
||||
env.provider,
|
||||
{
|
||||
...env.txDefaults,
|
||||
from: ownerAddress,
|
||||
},
|
||||
artifacts,
|
||||
exchangeAddress,
|
||||
proxy.address,
|
||||
);
|
||||
|
||||
// Register an exchange in the protocol fee contract.
|
||||
await protocolFees.addExchangeAddress.awaitTransactionSuccessAsync(exchange, { from: owner });
|
||||
|
||||
// "Register" the makerAddress in the default pool.
|
||||
await protocolFees.addMakerToPool.awaitTransactionSuccessAsync(DEFAULT_POOL_ID, makerAddress);
|
||||
|
||||
// Initialize the protocol fee actor.
|
||||
protocolFeeActor = new ProtocolFeeActor([exchange], [makerAddress], protocolFees);
|
||||
wethAssetData = await testContract.getWethAssetData.callAsync();
|
||||
});
|
||||
|
||||
blockchainTests.resets('payProtocolFee', () => {
|
||||
async function createTestPoolAsync(stake: BigNumber, makers: string[]): Promise<string> {
|
||||
const poolId = hexRandom();
|
||||
await testContract.createTestPool.awaitTransactionSuccessAsync(poolId, stake, makers);
|
||||
return poolId;
|
||||
}
|
||||
|
||||
blockchainTests.resets('payProtocolFee()', () => {
|
||||
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
|
||||
const DEFAULT_PROTOCOL_FEE_PAID = new BigNumber(150e3).times(1e9);
|
||||
const { ZERO_AMOUNT } = constants;
|
||||
const makerAddress = randomAddress();
|
||||
const payerAddress = randomAddress();
|
||||
let minimumStake: BigNumber;
|
||||
|
||||
before(async () => {
|
||||
minimumStake = (await testContract.getParams.callAsync())[2];
|
||||
});
|
||||
|
||||
describe('forbidden actions', () => {
|
||||
it('should revert if called by a non-exchange', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: nonExchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
});
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: notExchangeAddress },
|
||||
);
|
||||
const expectedError = new StakingRevertErrors.OnlyCallableByExchangeError(notExchangeAddress);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if `protocolFeePaid` is zero with zero value sent', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: constants.ZERO_AMOUNT,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
});
|
||||
ZERO_AMOUNT,
|
||||
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||
);
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid,
|
||||
ZERO_AMOUNT,
|
||||
ZERO_AMOUNT,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if `protocolFeePaid` is zero with non-zero value sent', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: constants.ZERO_AMOUNT,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
});
|
||||
ZERO_AMOUNT,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid,
|
||||
ZERO_AMOUNT,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should revert if `protocolFeePaid` is different than the provided message value', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('should revert if `protocolFeePaid` is < than the provided message value', async () => {
|
||||
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID.minus(50),
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
});
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID.minus(1) },
|
||||
);
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
DEFAULT_PROTOCOL_FEE_PAID.minus(1),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('should call `transferFrom` in the proxy if no value is sent and the maker is not in a pool', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
makerAddress: payerAddress, // This is an unregistered maker address
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call `transferFrom` in the proxy and update `protocolFeesThisEpochByPool` if no value is sent and the maker is in a pool', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('should revert if `protocolFeePaid` is > than the provided message value', async () => {
|
||||
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID.plus(1) },
|
||||
);
|
||||
const expectedError = new StakingRevertErrors.InvalidProtocolFeePaymentError(
|
||||
StakingRevertErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
DEFAULT_PROTOCOL_FEE_PAID.plus(1),
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call `transferFrom` in the proxy and should not update `protocolFeesThisEpochByPool` if value is sent and the maker is not in a pool', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
makerAddress: payerAddress, // This is an unregistered maker address
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
});
|
||||
});
|
||||
describe('ETH fees', () => {
|
||||
function assertNoWETHTransferLogs(logs: LogEntry[]): void {
|
||||
const logsArgs = filterLogsToArguments<TestProtocolFeesERC20ProxyTransferFromCalledEventArgs>(
|
||||
logs,
|
||||
'TransferFromCalled',
|
||||
);
|
||||
expect(logsArgs).to.deep.eq([]);
|
||||
}
|
||||
|
||||
it('should not call `transferFrom` in the proxy and should update `protocolFeesThisEpochByPool` if value is sent and the maker is in a pool', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('should not transfer WETH if value is sent', async () => {
|
||||
await createTestPoolAsync(minimumStake, []);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
assertNoWETHTransferLogs(receipt.logs);
|
||||
});
|
||||
|
||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
assertNoWETHTransferLogs(receipt.logs);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, []);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
assertNoWETHTransferLogs(receipt.logs);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('fees paid to the same maker should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const payAsync = async () => {
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
assertNoWETHTransferLogs(receipt.logs);
|
||||
};
|
||||
await payAsync();
|
||||
await payAsync();
|
||||
const expectedTotalFees = DEFAULT_PROTOCOL_FEE_PAID.times(2);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only have one active pool if a fee is paid on behalf of one maker ETH twice', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
describe('WETH fees', () => {
|
||||
function assertWETHTransferLogs(logs: LogEntry[], fromAddress: string, amount: BigNumber): void {
|
||||
const logsArgs = filterLogsToArguments<TestProtocolFeesERC20ProxyTransferFromCalledEventArgs>(
|
||||
logs,
|
||||
'TransferFromCalled',
|
||||
);
|
||||
expect(logsArgs.length).to.eq(1);
|
||||
for (const args of logsArgs) {
|
||||
expect(args.assetData).to.eq(wethAssetData);
|
||||
expect(args.from).to.eq(fromAddress);
|
||||
expect(args.to).to.eq(testContract.address);
|
||||
expect(args.amount).to.bignumber.eq(amount);
|
||||
}
|
||||
}
|
||||
|
||||
it('should transfer WETH if no value is sent and the maker is not in a pool', async () => {
|
||||
await createTestPoolAsync(minimumStake, []);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||
);
|
||||
assertWETHTransferLogs(receipt.logs, payerAddress, DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||
);
|
||||
assertWETHTransferLogs(receipt.logs, payerAddress, DEFAULT_PROTOCOL_FEE_PAID);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, []);
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||
);
|
||||
assertWETHTransferLogs(receipt.logs, payerAddress, DEFAULT_PROTOCOL_FEE_PAID);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('fees paid to the same maker should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const payAsync = async () => {
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||
);
|
||||
assertWETHTransferLogs(receipt.logs, payerAddress, DEFAULT_PROTOCOL_FEE_PAID);
|
||||
};
|
||||
await payAsync();
|
||||
await payAsync();
|
||||
const expectedTotalFees = DEFAULT_PROTOCOL_FEE_PAID.times(2);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
||||
});
|
||||
|
||||
it('fees paid to the same maker in WETH then ETH should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const payAsync = async (inWETH: boolean) => {
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{
|
||||
from: exchangeAddress,
|
||||
value: inWETH ? ZERO_AMOUNT : DEFAULT_PROTOCOL_FEE_PAID,
|
||||
},
|
||||
);
|
||||
};
|
||||
await payAsync(true);
|
||||
await payAsync(false);
|
||||
const expectedTotalFees = DEFAULT_PROTOCOL_FEE_PAID.times(2);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only have one active pool if a fee is paid on behalf of one maker in WETH and then ETH', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
makerAddress,
|
||||
describe('Multiple makers', () => {
|
||||
it('fees paid to different makers in the same pool go to that pool', async () => {
|
||||
const otherMakerAddress = randomAddress();
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress, otherMakerAddress]);
|
||||
const payAsync = async (_makerAddress: string) => {
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
_makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
};
|
||||
await payAsync(makerAddress);
|
||||
await payAsync(otherMakerAddress);
|
||||
const expectedTotalFees = DEFAULT_PROTOCOL_FEE_PAID.times(2);
|
||||
const poolFees = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(poolFees).to.bignumber.eq(expectedTotalFees);
|
||||
});
|
||||
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
makerAddress,
|
||||
it('fees paid to makers in different pools go to their respective pools', async () => {
|
||||
const [fee, otherFee] = _.times(2, () => getRandomPortion(DEFAULT_PROTOCOL_FEE_PAID));
|
||||
const otherMakerAddress = randomAddress();
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const otherPoolId = await createTestPoolAsync(minimumStake, [otherMakerAddress]);
|
||||
const payAsync = async (_poolId: string, _makerAddress: string, _fee: BigNumber) => {
|
||||
// prettier-ignore
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
_makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
_fee,
|
||||
{ from: exchangeAddress, value: _fee },
|
||||
);
|
||||
};
|
||||
await payAsync(poolId, makerAddress, fee);
|
||||
await payAsync(otherPoolId, otherMakerAddress, otherFee);
|
||||
const [poolFees, otherPoolFees] = await Promise.all([
|
||||
testContract.getProtocolFeesThisEpochByPool.callAsync(poolId),
|
||||
testContract.getProtocolFeesThisEpochByPool.callAsync(otherPoolId),
|
||||
]);
|
||||
expect(poolFees).to.bignumber.eq(fee);
|
||||
expect(otherPoolFees).to.bignumber.eq(otherFee);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only have one active pool if a fee is paid on behalf of one maker in ETH and then WETH', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
describe('Dust stake', () => {
|
||||
it('credits pools with stake > minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake.plus(1), [makerAddress]);
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
constants.NULL_ADDRESS,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(feesCredited).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('credits pools with stake == minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
});
|
||||
constants.NULL_ADDRESS,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(feesCredited).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
it('should only have one active pool if a fee is paid on behalf of one maker in WETH twice', async () => {
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
it('does not credit pools with stake < minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake.minus(1), [makerAddress]);
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
});
|
||||
|
||||
await protocolFeeActor.payProtocolFeeAsync({
|
||||
poolId: DEFAULT_POOL_ID,
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
||||
from: exchange,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
constants.NULL_ADDRESS,
|
||||
DEFAULT_PROTOCOL_FEE_PAID,
|
||||
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||
);
|
||||
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
expect(feesCredited).to.bignumber.eq(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
@ -41,8 +41,8 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||
// deploy staking contracts
|
||||
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||
// set up hyper-parameters
|
||||
await stakingApiWrapper.stakingContract.setParamsAsync({
|
||||
// set up staking parameters
|
||||
await stakingApiWrapper.utils.setParamsAsync({
|
||||
minimumPoolStake: new BigNumber(0),
|
||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||
cobbDouglasAlphaDenomintor: new BigNumber(6),
|
||||
|
@ -16,6 +16,9 @@ import {
|
||||
ZrxVaultContract,
|
||||
} from '../../src';
|
||||
|
||||
import { constants as stakingConstants } from './constants';
|
||||
import { 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
|
||||
@ -28,7 +31,7 @@ export class StakingApiWrapper {
|
||||
// Epoch Utils
|
||||
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
||||
// increase timestamp of next block
|
||||
const epochDurationInSeconds = await this.stakingContract.getEpochDurationInSeconds.callAsync();
|
||||
const { epochDurationInSeconds } = await this.utils.getParamsAsync();
|
||||
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||
// mine next block
|
||||
await this._web3Wrapper.mineBlockAsync();
|
||||
@ -63,6 +66,35 @@ export class StakingApiWrapper {
|
||||
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.cobbDouglasAlphaDenomintor,
|
||||
);
|
||||
},
|
||||
|
||||
getParamsAsync: async (): Promise<StakingParams> => {
|
||||
return (_.zipObject(
|
||||
[
|
||||
'epochDurationInSeconds',
|
||||
'rewardDelegatedStakeWeight',
|
||||
'minimumPoolStake',
|
||||
'maximumMakersInPool',
|
||||
'cobbDouglasAlphaNumerator',
|
||||
'cobbDouglasAlphaDenomintor',
|
||||
],
|
||||
await this.stakingContract.getParams.callAsync(),
|
||||
) as any) as StakingParams;
|
||||
},
|
||||
};
|
||||
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
|
@ -10,7 +10,6 @@ export const constants = {
|
||||
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
INITIAL_EPOCH: new BigNumber(0),
|
||||
CHAIN_ID: 1,
|
||||
DEFAULT_PARAMS: {
|
||||
epochDurationInSeconds: new BigNumber(TWO_WEEKS),
|
||||
rewardDelegatedStakeWeight: new BigNumber(0.9 * 1e6), // 90%
|
||||
|
@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
export interface HyperParameters {
|
||||
export interface StakingParams {
|
||||
epochDurationInSeconds: BigNumber;
|
||||
rewardDelegatedStakeWeight: BigNumber;
|
||||
minimumPoolStake: BigNumber;
|
||||
|
@ -19,6 +19,7 @@
|
||||
"generated-artifacts/LibSafeDowncast.json",
|
||||
"generated-artifacts/LibStakingRichErrors.json",
|
||||
"generated-artifacts/MixinConstants.json",
|
||||
"generated-artifacts/MixinDeploymentConstants.json",
|
||||
"generated-artifacts/MixinEthVault.json",
|
||||
"generated-artifacts/MixinExchangeFees.json",
|
||||
"generated-artifacts/MixinExchangeManager.json",
|
||||
@ -38,7 +39,6 @@
|
||||
"generated-artifacts/StakingPoolRewardVault.json",
|
||||
"generated-artifacts/StakingProxy.json",
|
||||
"generated-artifacts/TestCobbDouglas.json",
|
||||
"generated-artifacts/TestExchangeFees.json",
|
||||
"generated-artifacts/TestInitTarget.json",
|
||||
"generated-artifacts/TestLibFixedMath.json",
|
||||
"generated-artifacts/TestProtocolFees.json",
|
||||
|
Loading…
x
Reference in New Issue
Block a user