@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 "../libs/LibFixedMath.sol";
|
||||||
import "../immutable/MixinStorage.sol";
|
import "../immutable/MixinStorage.sol";
|
||||||
import "../immutable/MixinConstants.sol";
|
import "../immutable/MixinConstants.sol";
|
||||||
|
import "../immutable/MixinDeploymentConstants.sol";
|
||||||
import "../interfaces/IStakingEvents.sol";
|
import "../interfaces/IStakingEvents.sol";
|
||||||
import "../interfaces/IStructs.sol";
|
import "../interfaces/IStructs.sol";
|
||||||
import "../stake/MixinStakeBalances.sol";
|
import "../stake/MixinStakeBalances.sol";
|
||||||
@ -47,6 +48,7 @@ import "./MixinExchangeManager.sol";
|
|||||||
contract MixinExchangeFees is
|
contract MixinExchangeFees is
|
||||||
IStakingEvents,
|
IStakingEvents,
|
||||||
MixinConstants,
|
MixinConstants,
|
||||||
|
MixinDeploymentConstants,
|
||||||
Ownable,
|
Ownable,
|
||||||
MixinStorage,
|
MixinStorage,
|
||||||
MixinZrxVault,
|
MixinZrxVault,
|
||||||
@ -111,7 +113,7 @@ contract MixinExchangeFees is
|
|||||||
if (poolStake >= minimumPoolStake) {
|
if (poolStake >= minimumPoolStake) {
|
||||||
// Credit the pool.
|
// Credit the pool.
|
||||||
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
|
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
|
||||||
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);
|
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);
|
||||||
if (_feesCollectedThisEpoch == 0) {
|
if (_feesCollectedThisEpoch == 0) {
|
||||||
activePoolsThisEpoch.push(poolId);
|
activePoolsThisEpoch.push(poolId);
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,4 @@ contract MixinConstants
|
|||||||
uint64 constant internal INITIAL_EPOCH = 0;
|
uint64 constant internal INITIAL_EPOCH = 0;
|
||||||
|
|
||||||
uint256 constant internal MIN_TOKEN_VALUE = 10**18;
|
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;
|
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 {
|
contract MixinDeploymentConstants {
|
||||||
|
|
||||||
|
using LibBytes for bytes;
|
||||||
|
|
||||||
// @TODO SET THESE VALUES FOR DEPLOYMENT
|
// @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
|
// Mainnet WETH9 Address
|
||||||
address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
|
address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
|
||||||
|
|
||||||
@ -49,4 +48,14 @@ contract MixinDeploymentConstants {
|
|||||||
|
|
||||||
// Ropsten & Rinkeby Weth Asset Data
|
// Ropsten & Rinkeby Weth Asset Data
|
||||||
// bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c778417e063141139fce010982780140aa0cd5ab";
|
// 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;
|
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-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/LibRichErrors.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||||
import "./MixinConstants.sol";
|
import "./MixinConstants.sol";
|
||||||
@ -36,20 +34,6 @@ contract MixinStorage is
|
|||||||
MixinConstants,
|
MixinConstants,
|
||||||
Ownable
|
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
|
// WETH Asset Proxy
|
||||||
IAssetProxy internal wethAssetProxy;
|
IAssetProxy internal wethAssetProxy;
|
||||||
|
@ -53,14 +53,14 @@ interface IStakingEvents {
|
|||||||
uint256 earliestEndTimeInSeconds
|
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 epochDurationInSeconds Minimum seconds between epochs.
|
||||||
/// @param rewardDelegatedStakeWeight How much delegated stake is weighted vs operator stake, in ppm.
|
/// @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 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 maximumMakersInPool Maximum number of maker addresses allowed to be registered to a pool.
|
||||||
/// @param cobbDouglasAlphaNumerator Numerator for cobb douglas alpha factor.
|
/// @param cobbDouglasAlphaNumerator Numerator for cobb douglas alpha factor.
|
||||||
/// @param cobbDouglasAlphaDenomintor Denominator for cobb douglas alpha factor.
|
/// @param cobbDouglasAlphaDenomintor Denominator for cobb douglas alpha factor.
|
||||||
event Tuned(
|
event ParamsChanged(
|
||||||
uint256 epochDurationInSeconds,
|
uint256 epochDurationInSeconds,
|
||||||
uint32 rewardDelegatedStakeWeight,
|
uint32 rewardDelegatedStakeWeight,
|
||||||
uint256 minimumPoolStake,
|
uint256 minimumPoolStake,
|
||||||
|
@ -65,7 +65,7 @@ contract MixinParams is
|
|||||||
cobbDouglasAlphaNumerator = _cobbDouglasAlphaNumerator;
|
cobbDouglasAlphaNumerator = _cobbDouglasAlphaNumerator;
|
||||||
cobbDouglasAlphaDenomintor = _cobbDouglasAlphaDenomintor;
|
cobbDouglasAlphaDenomintor = _cobbDouglasAlphaDenomintor;
|
||||||
|
|
||||||
emit Tuned(
|
emit ParamsChanged(
|
||||||
epochDurationInSeconds,
|
epochDurationInSeconds,
|
||||||
rewardDelegatedStakeWeight,
|
rewardDelegatedStakeWeight,
|
||||||
minimumPoolStake,
|
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
|
contract TestProtocolFees is
|
||||||
Staking
|
Staking
|
||||||
{
|
{
|
||||||
function setWethProxy(address wethProxyAddress)
|
struct TestPool {
|
||||||
external
|
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);
|
wethAssetProxy = IAssetProxy(wethProxyAddress);
|
||||||
|
_initMixinParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMakerToPool(bytes32 poolId, address makerAddress)
|
function addMakerToPool(bytes32 poolId, address makerAddress)
|
||||||
@ -39,6 +47,10 @@ contract TestProtocolFees is
|
|||||||
poolJoinedByMakerAddress[makerAddress].confirmed = true;
|
poolJoinedByMakerAddress[makerAddress].confirmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWethAssetData() external pure returns (bytes memory) {
|
||||||
|
return WETH_ASSET_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
function getActivePoolsByEpoch()
|
function getActivePoolsByEpoch()
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
@ -46,4 +58,42 @@ contract TestProtocolFees is
|
|||||||
{
|
{
|
||||||
return activePoolsThisEpoch;
|
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
|
// solhint-disable no-empty-blocks
|
||||||
constructor(address _stakingContract)
|
constructor(address _stakingContract)
|
||||||
public
|
public
|
||||||
StakingProxy(_stakingContract, address(0))
|
StakingProxy(_stakingContract, address(0), address(0))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
function getAttachedContract() external view returns (address) {
|
function getAttachedContract() external view returns (address) {
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -21,6 +21,7 @@ import * as LibProxy from '../generated-artifacts/LibProxy.json';
|
|||||||
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
|
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
|
||||||
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
|
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
|
||||||
import * as MixinConstants from '../generated-artifacts/MixinConstants.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 MixinEthVault from '../generated-artifacts/MixinEthVault.json';
|
||||||
import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json';
|
import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json';
|
||||||
import * as MixinExchangeManager from '../generated-artifacts/MixinExchangeManager.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 StakingPoolRewardVault from '../generated-artifacts/StakingPoolRewardVault.json';
|
||||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
||||||
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.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 TestInitTarget from '../generated-artifacts/TestInitTarget.json';
|
||||||
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
|
import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json';
|
||||||
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
||||||
@ -56,6 +56,7 @@ export const artifacts = {
|
|||||||
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
||||||
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
||||||
MixinConstants: MixinConstants as ContractArtifact,
|
MixinConstants: MixinConstants as ContractArtifact,
|
||||||
|
MixinDeploymentConstants: MixinDeploymentConstants as ContractArtifact,
|
||||||
MixinStorage: MixinStorage as ContractArtifact,
|
MixinStorage: MixinStorage as ContractArtifact,
|
||||||
IEthVault: IEthVault as ContractArtifact,
|
IEthVault: IEthVault as ContractArtifact,
|
||||||
IStaking: IStaking as ContractArtifact,
|
IStaking: IStaking as ContractArtifact,
|
||||||
@ -86,7 +87,6 @@ export const artifacts = {
|
|||||||
StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
|
StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
|
||||||
ZrxVault: ZrxVault as ContractArtifact,
|
ZrxVault: ZrxVault as ContractArtifact,
|
||||||
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
||||||
TestExchangeFees: TestExchangeFees as ContractArtifact,
|
|
||||||
TestInitTarget: TestInitTarget as ContractArtifact,
|
TestInitTarget: TestInitTarget as ContractArtifact,
|
||||||
TestLibFixedMath: TestLibFixedMath as ContractArtifact,
|
TestLibFixedMath: TestLibFixedMath as ContractArtifact,
|
||||||
TestProtocolFees: TestProtocolFees 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_safe_downcast';
|
||||||
export * from '../generated-wrappers/lib_staking_rich_errors';
|
export * from '../generated-wrappers/lib_staking_rich_errors';
|
||||||
export * from '../generated-wrappers/mixin_constants';
|
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_eth_vault';
|
||||||
export * from '../generated-wrappers/mixin_exchange_fees';
|
export * from '../generated-wrappers/mixin_exchange_fees';
|
||||||
export * from '../generated-wrappers/mixin_exchange_manager';
|
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_pool_reward_vault';
|
||||||
export * from '../generated-wrappers/staking_proxy';
|
export * from '../generated-wrappers/staking_proxy';
|
||||||
export * from '../generated-wrappers/test_cobb_douglas';
|
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_init_target';
|
||||||
export * from '../generated-wrappers/test_lib_fixed_math';
|
export * from '../generated-wrappers/test_lib_fixed_math';
|
||||||
export * from '../generated-wrappers/test_protocol_fees';
|
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', () => {
|
describe('Epochs & TimeLocks', () => {
|
||||||
it('basic epochs & timeLock periods', async () => {
|
it('basic epochs & timeLock periods', async () => {
|
||||||
///// 1/3 Validate Assumptions /////
|
///// 1/3 Validate Assumptions /////
|
||||||
expect(await stakingApiWrapper.stakingContract.getEpochDurationInSeconds.callAsync()).to.be.bignumber.equal(
|
expect((await stakingApiWrapper.utils.getParamsAsync()).epochDurationInSeconds).to.be.bignumber.equal(
|
||||||
stakingConstants.DEFAULT_HYPER_PARAMETERS.epochDurationInSeconds,
|
stakingConstants.DEFAULT_PARAMS.epochDurationInSeconds,
|
||||||
);
|
);
|
||||||
///// 2/3 Validate Initial Epoch & TimeLock Period /////
|
///// 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 { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import { BigNumber, OwnableRevertErrors, StringRevertError } from '@0x/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 => {
|
blockchainTests('Migration tests', env => {
|
||||||
let ownerAddress: string;
|
let ownerAddress: string;
|
||||||
@ -99,6 +105,21 @@ blockchainTests('Migration tests', env => {
|
|||||||
await assertInitStateAsync(proxyContract);
|
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 () => {
|
it('reverts if init() reverts', async () => {
|
||||||
await enableInitRevertsAsync();
|
await enableInitRevertsAsync();
|
||||||
const tx = proxyContract.attachStakingContract.awaitTransactionSuccessAsync(initTargetContract.address);
|
const tx = proxyContract.attachStakingContract.awaitTransactionSuccessAsync(initTargetContract.address);
|
||||||
|
@ -2,7 +2,7 @@ import { blockchainTests, constants, expect, filterLogsToArguments, Numberish }
|
|||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
|
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
|
||||||
|
|
||||||
import { artifacts, IStakingEventsTunedEventArgs, MixinParamsContract } from '../src/';
|
import { artifacts, IStakingEventsParamsChangedEventArgs, MixinParamsContract } from '../src/';
|
||||||
|
|
||||||
blockchainTests('Configurable Parameters', env => {
|
blockchainTests('Configurable Parameters', env => {
|
||||||
let testContract: MixinParamsContract;
|
let testContract: MixinParamsContract;
|
||||||
@ -20,7 +20,7 @@ blockchainTests('Configurable Parameters', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
blockchainTests.resets('setParams()', () => {
|
blockchainTests.resets('setParams()', () => {
|
||||||
interface HyperParameters {
|
interface Params {
|
||||||
epochDurationInSeconds: Numberish;
|
epochDurationInSeconds: Numberish;
|
||||||
rewardDelegatedStakeWeight: Numberish;
|
rewardDelegatedStakeWeight: Numberish;
|
||||||
minimumPoolStake: Numberish;
|
minimumPoolStake: Numberish;
|
||||||
@ -34,13 +34,13 @@ blockchainTests('Configurable Parameters', env => {
|
|||||||
const DEFAULT_PARAMS = {
|
const DEFAULT_PARAMS = {
|
||||||
epochDurationInSeconds: TWO_WEEKS,
|
epochDurationInSeconds: TWO_WEEKS,
|
||||||
rewardDelegatedStakeWeight: PPM_90_PERCENT,
|
rewardDelegatedStakeWeight: PPM_90_PERCENT,
|
||||||
minimumPoolStake: '100e18',
|
minimumPoolStake: constants.DUMMY_TOKEN_DECIMALS.times(100),
|
||||||
maximumMakersInPool: 10,
|
maximumMakersInPool: 10,
|
||||||
cobbDouglasAlphaNumerator: 1,
|
cobbDouglasAlphaNumerator: 1,
|
||||||
cobbDouglasAlphaDenomintor: 2,
|
cobbDouglasAlphaDenomintor: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function setParamsAndAssertAsync(params: Partial<HyperParameters>, from?: string): Promise<void> {
|
async function setParamsAndAssertAsync(params: Partial<Params>, from?: string): Promise<void> {
|
||||||
const _params = {
|
const _params = {
|
||||||
...DEFAULT_PARAMS,
|
...DEFAULT_PARAMS,
|
||||||
...params,
|
...params,
|
||||||
@ -56,7 +56,7 @@ blockchainTests('Configurable Parameters', env => {
|
|||||||
);
|
);
|
||||||
// Assert event.
|
// Assert event.
|
||||||
expect(receipt.logs.length).to.eq(1);
|
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.epochDurationInSeconds).to.bignumber.eq(_params.epochDurationInSeconds);
|
||||||
expect(event.rewardDelegatedStakeWeight).to.bignumber.eq(_params.rewardDelegatedStakeWeight);
|
expect(event.rewardDelegatedStakeWeight).to.bignumber.eq(_params.rewardDelegatedStakeWeight);
|
||||||
expect(event.minimumPoolStake).to.bignumber.eq(_params.minimumPoolStake);
|
expect(event.minimumPoolStake).to.bignumber.eq(_params.minimumPoolStake);
|
||||||
|
@ -321,10 +321,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
|
|
||||||
const makerAddresses = users.slice(
|
const makerAddresses = users.slice(1, stakingConstants.DEFAULT_PARAMS.maximumMakersInPool.toNumber() + 2);
|
||||||
1,
|
|
||||||
stakingConstants.DEFAULT_PARAMS.maximumMakersInPool.toNumber() + 2,
|
|
||||||
);
|
|
||||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
|
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
|
||||||
|
|
||||||
// create pool
|
// 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 { 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 => {
|
blockchainTests('Protocol Fee Unit Tests', env => {
|
||||||
// The accounts that will be used during testing.
|
let ownerAddress: string;
|
||||||
let owner: string;
|
let exchangeAddress: string;
|
||||||
let exchange: string;
|
let notExchangeAddress: string;
|
||||||
let nonExchange: string;
|
let testContract: TestProtocolFeesContract;
|
||||||
let makerAddress: string;
|
let wethAssetData: 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';
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// Get accounts to represent the exchange and an address that is not a registered exchange.
|
[ownerAddress, exchangeAddress, notExchangeAddress] = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||||
[
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Deploy the erc20Proxy for testing.
|
// Deploy the erc20Proxy for testing.
|
||||||
const proxy = await TestProtocolFeesERC20ProxyContract.deployFrom0xArtifactAsync(
|
const proxy = await TestProtocolFeesERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||||
@ -52,187 +31,349 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register the test ERC20Proxy in the exchange.
|
// Deploy the protocol fees contract.
|
||||||
await protocolFees.setWethProxy.awaitTransactionSuccessAsync(proxy.address);
|
testContract = await TestProtocolFeesContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestProtocolFees,
|
||||||
|
env.provider,
|
||||||
|
{
|
||||||
|
...env.txDefaults,
|
||||||
|
from: ownerAddress,
|
||||||
|
},
|
||||||
|
artifacts,
|
||||||
|
exchangeAddress,
|
||||||
|
proxy.address,
|
||||||
|
);
|
||||||
|
|
||||||
// Register an exchange in the protocol fee contract.
|
wethAssetData = await testContract.getWethAssetData.callAsync();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
blockchainTests.resets('payProtocolFee', () => {
|
async function createTestPoolAsync(stake: BigNumber, makers: string[]): Promise<string> {
|
||||||
it('should revert if called by a non-exchange', async () => {
|
const poolId = hexRandom();
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
await testContract.createTestPool.awaitTransactionSuccessAsync(poolId, stake, makers);
|
||||||
poolId: DEFAULT_POOL_ID,
|
return poolId;
|
||||||
makerAddress,
|
}
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
blockchainTests.resets('payProtocolFee()', () => {
|
||||||
from: nonExchange,
|
const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH);
|
||||||
value: constants.ZERO_AMOUNT,
|
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 () => {
|
||||||
|
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
payerAddress,
|
||||||
|
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 () => {
|
||||||
|
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
payerAddress,
|
||||||
|
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 () => {
|
||||||
|
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
payerAddress,
|
||||||
|
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 < than the provided message value', async () => {
|
||||||
|
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
payerAddress,
|
||||||
|
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 revert if `protocolFeePaid` is > than the provided message value', async () => {
|
||||||
|
const tx = testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
makerAddress,
|
||||||
|
payerAddress,
|
||||||
|
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 revert if `protocolFeePaid` is zero with zero value sent', async () => {
|
describe('ETH fees', () => {
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
function assertNoWETHTransferLogs(logs: LogEntry[]): void {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const logsArgs = filterLogsToArguments<TestProtocolFeesERC20ProxyTransferFromCalledEventArgs>(
|
||||||
makerAddress,
|
logs,
|
||||||
payerAddress,
|
'TransferFromCalled',
|
||||||
protocolFeePaid: constants.ZERO_AMOUNT,
|
);
|
||||||
from: exchange,
|
expect(logsArgs).to.deep.eq([]);
|
||||||
value: constants.ZERO_AMOUNT,
|
}
|
||||||
|
|
||||||
|
it('should not transfer WETH if value is sent', async () => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 revert if `protocolFeePaid` is zero with non-zero value sent', async () => {
|
describe('WETH fees', () => {
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
function assertWETHTransferLogs(logs: LogEntry[], fromAddress: string, amount: BigNumber): void {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const logsArgs = filterLogsToArguments<TestProtocolFeesERC20ProxyTransferFromCalledEventArgs>(
|
||||||
makerAddress,
|
logs,
|
||||||
payerAddress,
|
'TransferFromCalled',
|
||||||
protocolFeePaid: constants.ZERO_AMOUNT,
|
);
|
||||||
from: exchange,
|
expect(logsArgs.length).to.eq(1);
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
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,
|
||||||
|
DEFAULT_PROTOCOL_FEE_PAID,
|
||||||
|
{ from: exchangeAddress, value: ZERO_AMOUNT },
|
||||||
|
);
|
||||||
|
assertWETHTransferLogs(receipt.logs, payerAddress, DEFAULT_PROTOCOL_FEE_PAID);
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 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 revert if `protocolFeePaid` is different than the provided message value', async () => {
|
describe('Multiple makers', () => {
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
it('fees paid to different makers in the same pool go to that pool', async () => {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const otherMakerAddress = randomAddress();
|
||||||
makerAddress,
|
const poolId = await createTestPoolAsync(minimumStake, [makerAddress, otherMakerAddress]);
|
||||||
payerAddress,
|
const payAsync = async (_makerAddress: string) => {
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID.minus(50),
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
from: exchange,
|
_makerAddress,
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
payerAddress,
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
_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 call `transferFrom` in the proxy if no value is sent and the maker is not in a pool', async () => {
|
describe('Dust stake', () => {
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
it('credits pools with stake > minimum', async () => {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const poolId = await createTestPoolAsync(minimumStake.plus(1), [makerAddress]);
|
||||||
makerAddress: payerAddress, // This is an unregistered maker address
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
payerAddress,
|
makerAddress,
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
constants.NULL_ADDRESS,
|
||||||
from: exchange,
|
DEFAULT_PROTOCOL_FEE_PAID,
|
||||||
value: constants.ZERO_AMOUNT,
|
{ 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 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,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: constants.ZERO_AMOUNT,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
it('credits pools with stake == minimum', async () => {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||||
makerAddress,
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
payerAddress,
|
makerAddress,
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
constants.NULL_ADDRESS,
|
||||||
from: exchange,
|
DEFAULT_PROTOCOL_FEE_PAID,
|
||||||
value: 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 and then ETH', async () => {
|
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
|
||||||
poolId: DEFAULT_POOL_ID,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: constants.ZERO_AMOUNT,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
it('does not credit pools with stake < minimum', async () => {
|
||||||
poolId: DEFAULT_POOL_ID,
|
const poolId = await createTestPoolAsync(minimumStake.minus(1), [makerAddress]);
|
||||||
makerAddress,
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
payerAddress,
|
makerAddress,
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
constants.NULL_ADDRESS,
|
||||||
from: exchange,
|
DEFAULT_PROTOCOL_FEE_PAID,
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
{ from: exchangeAddress, value: DEFAULT_PROTOCOL_FEE_PAID },
|
||||||
});
|
);
|
||||||
});
|
const feesCredited = await testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||||
|
expect(feesCredited).to.bignumber.eq(0);
|
||||||
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,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
});
|
|
||||||
|
|
||||||
await protocolFeeActor.payProtocolFeeAsync({
|
|
||||||
poolId: DEFAULT_POOL_ID,
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid: DEFAULT_PROTOCOL_FEE_PAID,
|
|
||||||
from: exchange,
|
|
||||||
value: constants.ZERO_AMOUNT,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// tslint:enable:no-unnecessary-type-assertion
|
|
||||||
|
@ -41,8 +41,8 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||||
// set up hyper-parameters
|
// set up staking parameters
|
||||||
await stakingApiWrapper.stakingContract.setParamsAsync({
|
await stakingApiWrapper.utils.setParamsAsync({
|
||||||
minimumPoolStake: new BigNumber(0),
|
minimumPoolStake: new BigNumber(0),
|
||||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||||
cobbDouglasAlphaDenomintor: new BigNumber(6),
|
cobbDouglasAlphaDenomintor: new BigNumber(6),
|
||||||
|
@ -16,6 +16,9 @@ import {
|
|||||||
ZrxVaultContract,
|
ZrxVaultContract,
|
||||||
} from '../../src';
|
} from '../../src';
|
||||||
|
|
||||||
|
import { constants as stakingConstants } from './constants';
|
||||||
|
import { StakingParams } from './types';
|
||||||
|
|
||||||
export class StakingApiWrapper {
|
export class StakingApiWrapper {
|
||||||
public stakingContractAddress: string; // The address of the real Staking.sol contract
|
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 stakingContract: StakingContract; // The StakingProxy.sol contract wrapped as a StakingContract to borrow API
|
||||||
@ -28,7 +31,7 @@ export class StakingApiWrapper {
|
|||||||
// Epoch Utils
|
// Epoch Utils
|
||||||
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
||||||
// increase timestamp of next block
|
// increase timestamp of next block
|
||||||
const epochDurationInSeconds = await this.stakingContract.getEpochDurationInSeconds.callAsync();
|
const { epochDurationInSeconds } = await this.utils.getParamsAsync();
|
||||||
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||||
// mine next block
|
// mine next block
|
||||||
await this._web3Wrapper.mineBlockAsync();
|
await this._web3Wrapper.mineBlockAsync();
|
||||||
@ -63,6 +66,35 @@ export class StakingApiWrapper {
|
|||||||
getZrxTokenBalanceOfZrxVaultAsync: async (): Promise<BigNumber> => {
|
getZrxTokenBalanceOfZrxVaultAsync: async (): Promise<BigNumber> => {
|
||||||
return this.zrxTokenContract.balanceOf.callAsync(this.zrxVaultContract.address);
|
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;
|
private readonly _web3Wrapper: Web3Wrapper;
|
||||||
|
@ -10,7 +10,6 @@ export const constants = {
|
|||||||
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
NIL_POOL_ID: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||||
INITIAL_EPOCH: new BigNumber(0),
|
INITIAL_EPOCH: new BigNumber(0),
|
||||||
CHAIN_ID: 1,
|
|
||||||
DEFAULT_PARAMS: {
|
DEFAULT_PARAMS: {
|
||||||
epochDurationInSeconds: new BigNumber(TWO_WEEKS),
|
epochDurationInSeconds: new BigNumber(TWO_WEEKS),
|
||||||
rewardDelegatedStakeWeight: new BigNumber(0.9 * 1e6), // 90%
|
rewardDelegatedStakeWeight: new BigNumber(0.9 * 1e6), // 90%
|
||||||
|
@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils';
|
|||||||
|
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
|
|
||||||
export interface HyperParameters {
|
export interface StakingParams {
|
||||||
epochDurationInSeconds: BigNumber;
|
epochDurationInSeconds: BigNumber;
|
||||||
rewardDelegatedStakeWeight: BigNumber;
|
rewardDelegatedStakeWeight: BigNumber;
|
||||||
minimumPoolStake: BigNumber;
|
minimumPoolStake: BigNumber;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"generated-artifacts/LibSafeDowncast.json",
|
"generated-artifacts/LibSafeDowncast.json",
|
||||||
"generated-artifacts/LibStakingRichErrors.json",
|
"generated-artifacts/LibStakingRichErrors.json",
|
||||||
"generated-artifacts/MixinConstants.json",
|
"generated-artifacts/MixinConstants.json",
|
||||||
|
"generated-artifacts/MixinDeploymentConstants.json",
|
||||||
"generated-artifacts/MixinEthVault.json",
|
"generated-artifacts/MixinEthVault.json",
|
||||||
"generated-artifacts/MixinExchangeFees.json",
|
"generated-artifacts/MixinExchangeFees.json",
|
||||||
"generated-artifacts/MixinExchangeManager.json",
|
"generated-artifacts/MixinExchangeManager.json",
|
||||||
@ -38,7 +39,6 @@
|
|||||||
"generated-artifacts/StakingPoolRewardVault.json",
|
"generated-artifacts/StakingPoolRewardVault.json",
|
||||||
"generated-artifacts/StakingProxy.json",
|
"generated-artifacts/StakingProxy.json",
|
||||||
"generated-artifacts/TestCobbDouglas.json",
|
"generated-artifacts/TestCobbDouglas.json",
|
||||||
"generated-artifacts/TestExchangeFees.json",
|
|
||||||
"generated-artifacts/TestInitTarget.json",
|
"generated-artifacts/TestInitTarget.json",
|
||||||
"generated-artifacts/TestLibFixedMath.json",
|
"generated-artifacts/TestLibFixedMath.json",
|
||||||
"generated-artifacts/TestProtocolFees.json",
|
"generated-artifacts/TestProtocolFees.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user