Merge pull request #2205 from 0xProject/feature/contracts-staking/rip-mixin-vault-core
Consolidate MixinVaultCore and ZrxVault + unit tests
This commit is contained in:
commit
e4ab832ced
@ -18,28 +18,41 @@
|
|||||||
|
|
||||||
pragma solidity ^0.5.9;
|
pragma solidity ^0.5.9;
|
||||||
|
|
||||||
|
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
|
||||||
|
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||||
import "../interfaces/IZrxVault.sol";
|
import "./libs/LibStakingRichErrors.sol";
|
||||||
import "./MixinVaultCore.sol";
|
import "./interfaces/IZrxVault.sol";
|
||||||
|
|
||||||
|
|
||||||
/// @dev This vault manages Zrx Tokens.
|
/// @dev This vault manages Zrx Tokens.
|
||||||
/// When a user mints stake, their Zrx Tokens are deposited into this vault.
|
/// When a user mints stake, their Zrx Tokens are deposited into this vault.
|
||||||
/// Similarly, when they burn stake, their Zrx Tokens are withdrawn from this vault.
|
/// Similarly, when they burn stake, their Zrx Tokens are withdrawn from this vault.
|
||||||
/// There is a "Catastrophic Failure Mode" that, when invoked, only
|
/// The contract also includes management of the staking contract
|
||||||
/// allows withdrawals to be made. Once this vault is in catastrophic
|
/// and setting the vault to "Catastrophic Failure Mode".
|
||||||
/// failure mode, it cannot be returned to normal mode; this prevents
|
/// Catastrophic Failure Mode should only be set iff there is
|
||||||
/// corruption of related state in the staking contract.
|
/// non-recoverable corruption of the staking contracts. If there is a
|
||||||
|
/// recoverable flaw/bug/vulnerability, simply detach the staking contract
|
||||||
|
/// by setting its address to `address(0)`. In Catastrophic Failure Mode, only withdrawals
|
||||||
|
/// can be made (no deposits). Once Catastrophic Failure Mode is invoked,
|
||||||
|
/// it cannot be returned to normal mode; this prevents corruption of related
|
||||||
|
/// state in the staking contract.
|
||||||
contract ZrxVault is
|
contract ZrxVault is
|
||||||
IZrxVault,
|
Authorizable,
|
||||||
MixinVaultCore
|
IZrxVault
|
||||||
{
|
{
|
||||||
using LibSafeMath for uint256;
|
using LibSafeMath for uint256;
|
||||||
|
|
||||||
// mapping from Owner to ZRX balance
|
// Address of staking proxy contract
|
||||||
|
address payable public stakingProxyAddress;
|
||||||
|
|
||||||
|
// True iff vault has been set to Catastrophic Failure Mode
|
||||||
|
bool public isInCatastrophicFailure;
|
||||||
|
|
||||||
|
// Mapping from staker to ZRX balance
|
||||||
mapping (address => uint256) internal _balances;
|
mapping (address => uint256) internal _balances;
|
||||||
|
|
||||||
// Zrx Asset Proxy
|
// Zrx Asset Proxy
|
||||||
@ -59,7 +72,10 @@ contract ZrxVault is
|
|||||||
address _zrxTokenAddress
|
address _zrxTokenAddress
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
|
Authorizable()
|
||||||
{
|
{
|
||||||
|
_addAuthorizedAddress(owner);
|
||||||
|
|
||||||
zrxAssetProxy = IAssetProxy(_zrxProxyAddress);
|
zrxAssetProxy = IAssetProxy(_zrxProxyAddress);
|
||||||
_zrxToken = IERC20Token(_zrxTokenAddress);
|
_zrxToken = IERC20Token(_zrxTokenAddress);
|
||||||
_zrxAssetData = abi.encodeWithSelector(
|
_zrxAssetData = abi.encodeWithSelector(
|
||||||
@ -68,6 +84,28 @@ contract ZrxVault is
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev Sets the address of the StakingProxy contract.
|
||||||
|
/// Note that only the contract owner can call this function.
|
||||||
|
/// @param _stakingProxyAddress Address of Staking proxy contract.
|
||||||
|
function setStakingProxy(address payable _stakingProxyAddress)
|
||||||
|
external
|
||||||
|
onlyAuthorized
|
||||||
|
{
|
||||||
|
stakingProxyAddress = _stakingProxyAddress;
|
||||||
|
emit StakingProxySet(_stakingProxyAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Vault enters into Catastrophic Failure Mode.
|
||||||
|
/// *** WARNING - ONCE IN CATOSTROPHIC FAILURE MODE, YOU CAN NEVER GO BACK! ***
|
||||||
|
/// Note that only the contract owner can call this function.
|
||||||
|
function enterCatastrophicFailure()
|
||||||
|
external
|
||||||
|
onlyAuthorized
|
||||||
|
{
|
||||||
|
isInCatastrophicFailure = true;
|
||||||
|
emit InCatastrophicFailureMode(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
/// @dev Sets the Zrx proxy.
|
/// @dev Sets the Zrx proxy.
|
||||||
/// Note that only an authorized address can call this function.
|
/// Note that only an authorized address can call this function.
|
||||||
/// Note that this can only be called when *not* in Catastrophic Failure mode.
|
/// Note that this can only be called when *not* in Catastrophic Failure mode.
|
||||||
@ -165,4 +203,48 @@ contract ZrxVault is
|
|||||||
amount
|
amount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modifier onlyStakingProxy() {
|
||||||
|
_assertSenderIsStakingProxy();
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyInCatastrophicFailure() {
|
||||||
|
_assertInCatastrophicFailure();
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyNotInCatastrophicFailure() {
|
||||||
|
_assertNotInCatastrophicFailure();
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertSenderIsStakingProxy()
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
if (msg.sender != stakingProxyAddress) {
|
||||||
|
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByStakingContractError(
|
||||||
|
msg.sender
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertInCatastrophicFailure()
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
if (!isInCatastrophicFailure) {
|
||||||
|
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableIfInCatastrophicFailureError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertNotInCatastrophicFailure()
|
||||||
|
private
|
||||||
|
view
|
||||||
|
{
|
||||||
|
if (isInCatastrophicFailure) {
|
||||||
|
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableIfNotInCatastrophicFailureError());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,53 +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;
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev This mixin contains core logic for vaults.
|
|
||||||
/// This includes management of the staking contract
|
|
||||||
/// and setting the vault to "Catastrophic Failure Mode".
|
|
||||||
/// It's up to the vault how they handle this failure mode; however,
|
|
||||||
/// all vaults should disable all functionality aside from withdrawals.
|
|
||||||
/// Vaults should only be set to Catastrophic Failure Mode iff there is
|
|
||||||
/// non-recoverable corruption of the staking contracts. If there is a
|
|
||||||
/// recoverable flaw/bug/vulnerability, simply detach the staking contract
|
|
||||||
/// by setting its address to `address(0)`. Once in Catastrophic Failure Mode,
|
|
||||||
/// a vault cannot be reset to normal mode; this prevents corruption of related
|
|
||||||
/// state in the staking contract.
|
|
||||||
interface IVaultCore {
|
|
||||||
|
|
||||||
/// @dev Emmitted whenever a StakingProxy is set in a vault.
|
|
||||||
event StakingProxySet(address stakingProxyAddress);
|
|
||||||
|
|
||||||
/// @dev Emitted when the Staking contract is put into Catastrophic Failure Mode
|
|
||||||
/// @param sender Address of sender (`msg.sender`)
|
|
||||||
event InCatastrophicFailureMode(address sender);
|
|
||||||
|
|
||||||
/// @dev Sets the address of the StakingProxy contract.
|
|
||||||
/// Note that this is callable only by an authorized address.
|
|
||||||
/// @param _stakingProxyAddress Address of Staking proxy contract.
|
|
||||||
function setStakingProxy(address payable _stakingProxyAddress)
|
|
||||||
external;
|
|
||||||
|
|
||||||
/// @dev Vault enters into Catastrophic Failure Mode.
|
|
||||||
/// *** WARNING - ONCE IN CATOSTROPHIC FAILURE MODE, YOU CAN NEVER GO BACK! ***
|
|
||||||
/// Note that this is callable only by an authorized address.
|
|
||||||
function enterCatastrophicFailure()
|
|
||||||
external;
|
|
||||||
}
|
|
@ -22,12 +22,24 @@ pragma solidity ^0.5.9;
|
|||||||
/// @dev This vault manages Zrx Tokens.
|
/// @dev This vault manages Zrx Tokens.
|
||||||
/// When a user mints stake, their Zrx Tokens are deposited into this vault.
|
/// When a user mints stake, their Zrx Tokens are deposited into this vault.
|
||||||
/// Similarly, when they burn stake, their Zrx Tokens are withdrawn from this vault.
|
/// Similarly, when they burn stake, their Zrx Tokens are withdrawn from this vault.
|
||||||
/// There is a "Catastrophic Failure Mode" that, when invoked, only
|
/// The contract also includes management of the staking contract
|
||||||
/// allows withdrawals to be made. Once this vault is in catastrophic
|
/// and setting the vault to "Catastrophic Failure Mode".
|
||||||
/// failure mode, it cannot be returned to normal mode; this prevents
|
/// Catastrophic Failure Mode should only be set iff there is
|
||||||
/// corruption of related state in the staking contract.
|
/// non-recoverable corruption of the staking contracts. If there is a
|
||||||
|
/// recoverable flaw/bug/vulnerability, simply detach the staking contract
|
||||||
|
/// by setting its address to `address(0)`. In Catastrophic Failure Mode, only withdrawals
|
||||||
|
/// can be made (no deposits). Once Catastrophic Failure Mode is invoked,
|
||||||
|
/// it cannot be returned to normal mode; this prevents corruption of related
|
||||||
|
/// state in the staking contract.
|
||||||
interface IZrxVault {
|
interface IZrxVault {
|
||||||
|
|
||||||
|
/// @dev Emmitted whenever a StakingProxy is set in a vault.
|
||||||
|
event StakingProxySet(address stakingProxyAddress);
|
||||||
|
|
||||||
|
/// @dev Emitted when the Staking contract is put into Catastrophic Failure Mode
|
||||||
|
/// @param sender Address of sender (`msg.sender`)
|
||||||
|
event InCatastrophicFailureMode(address sender);
|
||||||
|
|
||||||
/// @dev Emitted when Zrx Tokens are deposited into the vault.
|
/// @dev Emitted when Zrx Tokens are deposited into the vault.
|
||||||
/// @param staker of Zrx Tokens.
|
/// @param staker of Zrx Tokens.
|
||||||
/// @param amount of Zrx Tokens deposited.
|
/// @param amount of Zrx Tokens deposited.
|
||||||
@ -47,6 +59,18 @@ interface IZrxVault {
|
|||||||
/// @dev Emitted whenever the ZRX AssetProxy is set.
|
/// @dev Emitted whenever the ZRX AssetProxy is set.
|
||||||
event ZrxProxySet(address zrxProxyAddress);
|
event ZrxProxySet(address zrxProxyAddress);
|
||||||
|
|
||||||
|
/// @dev Sets the address of the StakingProxy contract.
|
||||||
|
/// Note that only the contract staker can call this function.
|
||||||
|
/// @param _stakingProxyAddress Address of Staking proxy contract.
|
||||||
|
function setStakingProxy(address payable _stakingProxyAddress)
|
||||||
|
external;
|
||||||
|
|
||||||
|
/// @dev Vault enters into Catastrophic Failure Mode.
|
||||||
|
/// *** WARNING - ONCE IN CATOSTROPHIC FAILURE MODE, YOU CAN NEVER GO BACK! ***
|
||||||
|
/// Note that only the contract staker can call this function.
|
||||||
|
function enterCatastrophicFailure()
|
||||||
|
external;
|
||||||
|
|
||||||
/// @dev Sets the Zrx proxy.
|
/// @dev Sets the Zrx proxy.
|
||||||
/// Note that only the contract staker can call this.
|
/// Note that only the contract staker can call this.
|
||||||
/// Note that this can only be called when *not* in Catastrophic Failure mode.
|
/// Note that this can only be called when *not* in Catastrophic Failure mode.
|
||||||
|
@ -1,103 +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;
|
|
||||||
|
|
||||||
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
|
|
||||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
|
||||||
import "../libs/LibStakingRichErrors.sol";
|
|
||||||
import "../interfaces/IVaultCore.sol";
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev This mixin contains core logic for vaults.
|
|
||||||
/// This includes management of the staking contract
|
|
||||||
/// and setting the vault to "Catastrophic Failure Mode".
|
|
||||||
/// It's up to the vault how they handle this failure mode; however,
|
|
||||||
/// all vaults should disable all functionality aside from withdrawals.
|
|
||||||
/// Vaults should only be set to Catastrophic Failure Mode iff there is
|
|
||||||
/// non-recoverable corruption of the staking contracts. If there is a
|
|
||||||
/// recoverable flaw/bug/vulnerability, simply detach the staking contract
|
|
||||||
/// by setting its address to `address(0)`. Once in Catastrophic Failure Mode,
|
|
||||||
/// a vault cannot be reset to normal mode; this prevents corruption of related
|
|
||||||
/// status in the staking contract.
|
|
||||||
contract MixinVaultCore is
|
|
||||||
Authorizable,
|
|
||||||
IVaultCore
|
|
||||||
{
|
|
||||||
// Address of staking contract
|
|
||||||
address payable public stakingProxyAddress;
|
|
||||||
|
|
||||||
// True iff vault has been set to Catastrophic Failure Mode
|
|
||||||
bool public isInCatastrophicFailure;
|
|
||||||
|
|
||||||
/// @dev Asserts that the sender (`msg.sender`) is the staking contract.
|
|
||||||
modifier onlyStakingProxy {
|
|
||||||
if (msg.sender != stakingProxyAddress) {
|
|
||||||
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByStakingContractError(
|
|
||||||
msg.sender
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Asserts that this contract *is in* Catastrophic Failure Mode.
|
|
||||||
modifier onlyInCatastrophicFailure {
|
|
||||||
if (!isInCatastrophicFailure) {
|
|
||||||
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableIfInCatastrophicFailureError());
|
|
||||||
}
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Asserts that this contract *is not in* Catastrophic Failure Mode.
|
|
||||||
modifier onlyNotInCatastrophicFailure {
|
|
||||||
if (isInCatastrophicFailure) {
|
|
||||||
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableIfNotInCatastrophicFailureError());
|
|
||||||
}
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sets the vault owner and adds owner as an authorized address.
|
|
||||||
constructor()
|
|
||||||
public
|
|
||||||
Authorizable()
|
|
||||||
{
|
|
||||||
_addAuthorizedAddress(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Sets the address of the StakingProxy contract.
|
|
||||||
/// Note that only an authorized address can call this function.
|
|
||||||
/// @param _stakingProxyAddress Address of Staking proxy contract.
|
|
||||||
function setStakingProxy(address payable _stakingProxyAddress)
|
|
||||||
external
|
|
||||||
onlyAuthorized
|
|
||||||
{
|
|
||||||
stakingProxyAddress = _stakingProxyAddress;
|
|
||||||
emit StakingProxySet(_stakingProxyAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Vault enters into Catastrophic Failure Mode.
|
|
||||||
/// *** WARNING - ONCE IN CATOSTROPHIC FAILURE MODE, YOU CAN NEVER GO BACK! ***
|
|
||||||
/// Note that only an authorized address can call this function.
|
|
||||||
function enterCatastrophicFailure()
|
|
||||||
external
|
|
||||||
onlyAuthorized
|
|
||||||
{
|
|
||||||
isInCatastrophicFailure = true;
|
|
||||||
emit InCatastrophicFailureMode(msg.sender);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +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;
|
|
||||||
|
|
||||||
import "../src/vaults/MixinVaultCore.sol";
|
|
||||||
|
|
||||||
|
|
||||||
// solhint-disable no-empty-blocks
|
|
||||||
contract TestMixinVaultCore is
|
|
||||||
MixinVaultCore
|
|
||||||
{
|
|
||||||
function assertStakingProxy()
|
|
||||||
external
|
|
||||||
view
|
|
||||||
onlyStakingProxy
|
|
||||||
{}
|
|
||||||
|
|
||||||
function assertInCatastrophicFailure()
|
|
||||||
external
|
|
||||||
view
|
|
||||||
onlyInCatastrophicFailure
|
|
||||||
{}
|
|
||||||
|
|
||||||
function assertNotInCatastrophicFailure()
|
|
||||||
external
|
|
||||||
view
|
|
||||||
onlyNotInCatastrophicFailure
|
|
||||||
{}
|
|
||||||
}
|
|
@ -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/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IVaultCore|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|ReadOnlyProxy|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinVaultCore|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStorageLayoutAndConstants|ZrxVault).json"
|
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|ReadOnlyProxy|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStorageLayoutAndConstants|ZrxVault).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -11,7 +11,6 @@ import * as IStakingProxy from '../generated-artifacts/IStakingProxy.json';
|
|||||||
import * as IStorage from '../generated-artifacts/IStorage.json';
|
import * as IStorage from '../generated-artifacts/IStorage.json';
|
||||||
import * as IStorageInit from '../generated-artifacts/IStorageInit.json';
|
import * as IStorageInit from '../generated-artifacts/IStorageInit.json';
|
||||||
import * as IStructs from '../generated-artifacts/IStructs.json';
|
import * as IStructs from '../generated-artifacts/IStructs.json';
|
||||||
import * as IVaultCore from '../generated-artifacts/IVaultCore.json';
|
|
||||||
import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
|
import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
|
||||||
import * as LibCobbDouglas from '../generated-artifacts/LibCobbDouglas.json';
|
import * as LibCobbDouglas from '../generated-artifacts/LibCobbDouglas.json';
|
||||||
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
|
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
|
||||||
@ -34,7 +33,6 @@ import * as MixinStakeStorage from '../generated-artifacts/MixinStakeStorage.jso
|
|||||||
import * as MixinStakingPool from '../generated-artifacts/MixinStakingPool.json';
|
import * as MixinStakingPool from '../generated-artifacts/MixinStakingPool.json';
|
||||||
import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json';
|
import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json';
|
||||||
import * as MixinStorage from '../generated-artifacts/MixinStorage.json';
|
import * as MixinStorage from '../generated-artifacts/MixinStorage.json';
|
||||||
import * as MixinVaultCore from '../generated-artifacts/MixinVaultCore.json';
|
|
||||||
import * as ReadOnlyProxy from '../generated-artifacts/ReadOnlyProxy.json';
|
import * as ReadOnlyProxy from '../generated-artifacts/ReadOnlyProxy.json';
|
||||||
import * as Staking from '../generated-artifacts/Staking.json';
|
import * as Staking from '../generated-artifacts/Staking.json';
|
||||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
||||||
@ -48,7 +46,6 @@ import * as TestLibFixedMath from '../generated-artifacts/TestLibFixedMath.json'
|
|||||||
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
|
import * as TestLibProxy from '../generated-artifacts/TestLibProxy.json';
|
||||||
import * as TestLibProxyReceiver from '../generated-artifacts/TestLibProxyReceiver.json';
|
import * as TestLibProxyReceiver from '../generated-artifacts/TestLibProxyReceiver.json';
|
||||||
import * as TestLibSafeDowncast from '../generated-artifacts/TestLibSafeDowncast.json';
|
import * as TestLibSafeDowncast from '../generated-artifacts/TestLibSafeDowncast.json';
|
||||||
import * as TestMixinVaultCore from '../generated-artifacts/TestMixinVaultCore.json';
|
|
||||||
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
||||||
import * as TestStaking from '../generated-artifacts/TestStaking.json';
|
import * as TestStaking from '../generated-artifacts/TestStaking.json';
|
||||||
import * as TestStakingNoWETH from '../generated-artifacts/TestStakingNoWETH.json';
|
import * as TestStakingNoWETH from '../generated-artifacts/TestStakingNoWETH.json';
|
||||||
@ -59,6 +56,7 @@ export const artifacts = {
|
|||||||
ReadOnlyProxy: ReadOnlyProxy as ContractArtifact,
|
ReadOnlyProxy: ReadOnlyProxy as ContractArtifact,
|
||||||
Staking: Staking as ContractArtifact,
|
Staking: Staking as ContractArtifact,
|
||||||
StakingProxy: StakingProxy as ContractArtifact,
|
StakingProxy: StakingProxy as ContractArtifact,
|
||||||
|
ZrxVault: ZrxVault as ContractArtifact,
|
||||||
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
||||||
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
||||||
MixinConstants: MixinConstants as ContractArtifact,
|
MixinConstants: MixinConstants as ContractArtifact,
|
||||||
@ -70,7 +68,6 @@ export const artifacts = {
|
|||||||
IStorage: IStorage as ContractArtifact,
|
IStorage: IStorage as ContractArtifact,
|
||||||
IStorageInit: IStorageInit as ContractArtifact,
|
IStorageInit: IStorageInit as ContractArtifact,
|
||||||
IStructs: IStructs as ContractArtifact,
|
IStructs: IStructs as ContractArtifact,
|
||||||
IVaultCore: IVaultCore as ContractArtifact,
|
|
||||||
IZrxVault: IZrxVault as ContractArtifact,
|
IZrxVault: IZrxVault as ContractArtifact,
|
||||||
LibCobbDouglas: LibCobbDouglas as ContractArtifact,
|
LibCobbDouglas: LibCobbDouglas as ContractArtifact,
|
||||||
LibFixedMath: LibFixedMath as ContractArtifact,
|
LibFixedMath: LibFixedMath as ContractArtifact,
|
||||||
@ -88,8 +85,6 @@ export const artifacts = {
|
|||||||
MixinFinalizer: MixinFinalizer as ContractArtifact,
|
MixinFinalizer: MixinFinalizer as ContractArtifact,
|
||||||
MixinParams: MixinParams as ContractArtifact,
|
MixinParams: MixinParams as ContractArtifact,
|
||||||
MixinScheduler: MixinScheduler as ContractArtifact,
|
MixinScheduler: MixinScheduler as ContractArtifact,
|
||||||
MixinVaultCore: MixinVaultCore as ContractArtifact,
|
|
||||||
ZrxVault: ZrxVault as ContractArtifact,
|
|
||||||
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
|
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
|
||||||
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
||||||
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
|
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
|
||||||
@ -100,7 +95,6 @@ export const artifacts = {
|
|||||||
TestLibProxy: TestLibProxy as ContractArtifact,
|
TestLibProxy: TestLibProxy as ContractArtifact,
|
||||||
TestLibProxyReceiver: TestLibProxyReceiver as ContractArtifact,
|
TestLibProxyReceiver: TestLibProxyReceiver as ContractArtifact,
|
||||||
TestLibSafeDowncast: TestLibSafeDowncast as ContractArtifact,
|
TestLibSafeDowncast: TestLibSafeDowncast as ContractArtifact,
|
||||||
TestMixinVaultCore: TestMixinVaultCore as ContractArtifact,
|
|
||||||
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
||||||
TestStaking: TestStaking as ContractArtifact,
|
TestStaking: TestStaking as ContractArtifact,
|
||||||
TestStakingNoWETH: TestStakingNoWETH as ContractArtifact,
|
TestStakingNoWETH: TestStakingNoWETH as ContractArtifact,
|
||||||
|
@ -9,7 +9,6 @@ export * from '../generated-wrappers/i_staking_proxy';
|
|||||||
export * from '../generated-wrappers/i_storage';
|
export * from '../generated-wrappers/i_storage';
|
||||||
export * from '../generated-wrappers/i_storage_init';
|
export * from '../generated-wrappers/i_storage_init';
|
||||||
export * from '../generated-wrappers/i_structs';
|
export * from '../generated-wrappers/i_structs';
|
||||||
export * from '../generated-wrappers/i_vault_core';
|
|
||||||
export * from '../generated-wrappers/i_zrx_vault';
|
export * from '../generated-wrappers/i_zrx_vault';
|
||||||
export * from '../generated-wrappers/lib_cobb_douglas';
|
export * from '../generated-wrappers/lib_cobb_douglas';
|
||||||
export * from '../generated-wrappers/lib_fixed_math';
|
export * from '../generated-wrappers/lib_fixed_math';
|
||||||
@ -32,7 +31,6 @@ export * from '../generated-wrappers/mixin_stake_storage';
|
|||||||
export * from '../generated-wrappers/mixin_staking_pool';
|
export * from '../generated-wrappers/mixin_staking_pool';
|
||||||
export * from '../generated-wrappers/mixin_staking_pool_rewards';
|
export * from '../generated-wrappers/mixin_staking_pool_rewards';
|
||||||
export * from '../generated-wrappers/mixin_storage';
|
export * from '../generated-wrappers/mixin_storage';
|
||||||
export * from '../generated-wrappers/mixin_vault_core';
|
|
||||||
export * from '../generated-wrappers/read_only_proxy';
|
export * from '../generated-wrappers/read_only_proxy';
|
||||||
export * from '../generated-wrappers/staking';
|
export * from '../generated-wrappers/staking';
|
||||||
export * from '../generated-wrappers/staking_proxy';
|
export * from '../generated-wrappers/staking_proxy';
|
||||||
@ -46,7 +44,6 @@ export * from '../generated-wrappers/test_lib_fixed_math';
|
|||||||
export * from '../generated-wrappers/test_lib_proxy';
|
export * from '../generated-wrappers/test_lib_proxy';
|
||||||
export * from '../generated-wrappers/test_lib_proxy_receiver';
|
export * from '../generated-wrappers/test_lib_proxy_receiver';
|
||||||
export * from '../generated-wrappers/test_lib_safe_downcast';
|
export * from '../generated-wrappers/test_lib_safe_downcast';
|
||||||
export * from '../generated-wrappers/test_mixin_vault_core';
|
|
||||||
export * from '../generated-wrappers/test_protocol_fees';
|
export * from '../generated-wrappers/test_protocol_fees';
|
||||||
export * from '../generated-wrappers/test_staking';
|
export * from '../generated-wrappers/test_staking';
|
||||||
export * from '../generated-wrappers/test_staking_no_w_e_t_h';
|
export * from '../generated-wrappers/test_staking_no_w_e_t_h';
|
||||||
|
@ -1,340 +0,0 @@
|
|||||||
/*
|
|
||||||
@TODO (hysz) - update once new staking mechanics are merged
|
|
||||||
|
|
||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { Simulation } from './utils/Simulation';
|
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
|
||||||
blockchainTests('End-To-End Simulations', env => {
|
|
||||||
// constants
|
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
const PPM_ONE = 1e6;
|
|
||||||
// tokens & addresses
|
|
||||||
let accounts: string[];
|
|
||||||
let owner: string;
|
|
||||||
let exchange: string;
|
|
||||||
let users: string[];
|
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
|
||||||
let stakingWrapper: StakingWrapper;
|
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
|
||||||
// tests
|
|
||||||
before(async () => {
|
|
||||||
// create accounts
|
|
||||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
|
||||||
owner = accounts[0];
|
|
||||||
exchange = accounts[1];
|
|
||||||
users = accounts.slice(2);
|
|
||||||
users = [...users];
|
|
||||||
|
|
||||||
// deploy erc20 proxy
|
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
|
||||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
|
||||||
});
|
|
||||||
blockchainTests.resets('Simulations', () => {
|
|
||||||
it('Should successfully simulate (no delegators / no shadow balances)', async () => {
|
|
||||||
// @TODO - get computations more accurate
|
|
||||||
const simulationParams = {
|
|
||||||
users,
|
|
||||||
numberOfPools: 3,
|
|
||||||
poolOperatorShares: [100, 100, 100].map(v => (v / 100) * PPM_ONE),
|
|
||||||
stakeByPoolOperator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(42),
|
|
||||||
StakingWrapper.toBaseUnitAmount(84),
|
|
||||||
StakingWrapper.toBaseUnitAmount(97),
|
|
||||||
],
|
|
||||||
numberOfMakers: 6,
|
|
||||||
numberOfMakersPerPool: [1, 2, 3],
|
|
||||||
protocolFeesByMaker: [
|
|
||||||
// pool 1
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
// pool 2
|
|
||||||
StakingWrapper.toBaseUnitAmount(3.2),
|
|
||||||
StakingWrapper.toBaseUnitAmount(12.123258),
|
|
||||||
// pool 3
|
|
||||||
StakingWrapper.toBaseUnitAmount(23.577),
|
|
||||||
StakingWrapper.toBaseUnitAmount(4.54522236),
|
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
|
||||||
],
|
|
||||||
numberOfDelegators: 0,
|
|
||||||
numberOfDelegatorsPerPool: [0, 0, 0],
|
|
||||||
stakeByDelegator: [],
|
|
||||||
delegateInNextEpoch: false, // no shadow eth
|
|
||||||
withdrawByUndelegating: false, // profits are withdrawn without undelegating
|
|
||||||
expectedFeesByPool: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
StakingWrapper.toBaseUnitAmount(15.323258),
|
|
||||||
StakingWrapper.toBaseUnitAmount(28.12222236),
|
|
||||||
],
|
|
||||||
expectedPayoutByPool: [
|
|
||||||
new BigNumber('4.7567723629327287936195903273616'),
|
|
||||||
new BigNumber('16.281305003949353165639885849565'),
|
|
||||||
new BigNumber('20.310284473430148345239837590322'),
|
|
||||||
],
|
|
||||||
expectedPayoutByPoolOperator: [
|
|
||||||
new BigNumber('4.7567723629327287936195903273616'),
|
|
||||||
new BigNumber('16.281305003949353165639885849565'),
|
|
||||||
new BigNumber('20.310284473430148345239837590322'),
|
|
||||||
],
|
|
||||||
expectedMembersPayoutByPool: [new BigNumber('0'), new BigNumber('0'), new BigNumber('0')],
|
|
||||||
expectedPayoutByDelegator: [],
|
|
||||||
exchangeAddress: exchange,
|
|
||||||
};
|
|
||||||
const simulator = new Simulation(stakingWrapper, simulationParams);
|
|
||||||
await simulator.runAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should successfully simulate (delegators withdraw by undelegating / no shadow balances)', async () => {
|
|
||||||
// @TODO - get computations more accurate
|
|
||||||
|
|
||||||
\ // the expected payouts were computed by hand
|
|
||||||
// @TODO - get computations more accurate
|
|
||||||
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Weighted) | Payout
|
|
||||||
0 | 0.304958 | 42 | 0 | 42 | 3.0060373...
|
|
||||||
1 | 15.323258 | 84 | 0 | 84 |
|
|
||||||
3 | 28.12222236 | 97 | 182 | 260.8
|
|
||||||
...
|
|
||||||
Cumulative Fees = 43.75043836
|
|
||||||
Cumulative Weighted Stake = 386.8
|
|
||||||
Total Rewards = 43.75043836
|
|
||||||
|
|
||||||
const simulationParams = {
|
|
||||||
users,
|
|
||||||
numberOfPools: 3,
|
|
||||||
poolOperatorShares: [39, 59, 43].map(v => (v / 100) * PPM_ONE),
|
|
||||||
stakeByPoolOperator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(42),
|
|
||||||
StakingWrapper.toBaseUnitAmount(84),
|
|
||||||
StakingWrapper.toBaseUnitAmount(97),
|
|
||||||
],
|
|
||||||
numberOfMakers: 6,
|
|
||||||
numberOfMakersPerPool: [1, 2, 3],
|
|
||||||
protocolFeesByMaker: [
|
|
||||||
// pool 1
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
// pool 2
|
|
||||||
StakingWrapper.toBaseUnitAmount(3.2),
|
|
||||||
StakingWrapper.toBaseUnitAmount(12.123258),
|
|
||||||
// pool 3
|
|
||||||
StakingWrapper.toBaseUnitAmount(23.577),
|
|
||||||
StakingWrapper.toBaseUnitAmount(4.54522236),
|
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
|
||||||
],
|
|
||||||
numberOfDelegators: 3,
|
|
||||||
numberOfDelegatorsPerPool: [0, 0, 3],
|
|
||||||
stakeByDelegator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
|
||||||
StakingWrapper.toBaseUnitAmount(75),
|
|
||||||
StakingWrapper.toBaseUnitAmount(90),
|
|
||||||
],
|
|
||||||
delegateInNextEpoch: false, // delegated stake is included in payout computation + no shadow ether
|
|
||||||
withdrawByUndelegating: false, // profits are withdrawn without undelegating
|
|
||||||
expectedFeesByPool: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
StakingWrapper.toBaseUnitAmount(15.323258),
|
|
||||||
StakingWrapper.toBaseUnitAmount(28.12222236),
|
|
||||||
],
|
|
||||||
expectedPayoutByPool: [
|
|
||||||
new BigNumber('3.0060373101095302067028699237670'),
|
|
||||||
new BigNumber('10.288953635983966866289393130525'),
|
|
||||||
new BigNumber('29.264731802500529663161540874979'),
|
|
||||||
],
|
|
||||||
expectedPayoutByPoolOperator: [
|
|
||||||
new BigNumber('1.1723545509427168206625812850596'),
|
|
||||||
new BigNumber('6.0704826452305401312658116198463'),
|
|
||||||
new BigNumber('12.583834675075227560217188236544'),
|
|
||||||
],
|
|
||||||
expectedMembersPayoutByPool: [
|
|
||||||
new BigNumber('1.8336827591668133860402886387074'),
|
|
||||||
new BigNumber('4.2184709907534267350235815106787'),
|
|
||||||
new BigNumber('16.680897127425302102944352638435'),
|
|
||||||
],
|
|
||||||
expectedPayoutByDelegator: [
|
|
||||||
// note that the on-chain values may be slightly different due to rounding down on each entry
|
|
||||||
// there is a carry over between calls, which we account for here. the result is that delegators
|
|
||||||
// who withdraw later on will scoop up any rounding spillover from those who have already withdrawn.
|
|
||||||
new BigNumber('1.0163987496997496894870114443624'),
|
|
||||||
new BigNumber('4.4841121310283074536191681368932'),
|
|
||||||
new BigNumber('5.3809345572339689443430017642717'),
|
|
||||||
],
|
|
||||||
exchangeAddress: exchange,
|
|
||||||
};
|
|
||||||
const simulator = new Simulation(stakingWrapper, simulationParams);
|
|
||||||
await simulator.runAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should successfully simulate (delegators withdraw by undelegating / includes shadow balances / delegators enter after reward payouts)', async () => {
|
|
||||||
// @TODO - get computations more accurate
|
|
||||||
|
|
||||||
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled)
|
|
||||||
0 | 0.304958 | 42 | 0 | 42
|
|
||||||
1 | 15.323258 | 84 | 0 | 84
|
|
||||||
3 | 28.12222236 | 97 | 182 | 260.8
|
|
||||||
...
|
|
||||||
Cumulative Fees = 43.75043836
|
|
||||||
Cumulative Weighted Stake = 386.8
|
|
||||||
Total Rewards = 43.75043836
|
|
||||||
|
|
||||||
// In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
|
|
||||||
// The first delegator got to claim it all. This is due to the necessary conservation of payouts.
|
|
||||||
// When a new delegator arrives, their new stake should not affect existing delegator payouts.
|
|
||||||
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
|
|
||||||
|
|
||||||
const simulationParams = {
|
|
||||||
users,
|
|
||||||
numberOfPools: 3,
|
|
||||||
poolOperatorShares: [39, 59, 43].map(v => (v / 100) * PPM_ONE),
|
|
||||||
stakeByPoolOperator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(42),
|
|
||||||
StakingWrapper.toBaseUnitAmount(84),
|
|
||||||
StakingWrapper.toBaseUnitAmount(97),
|
|
||||||
],
|
|
||||||
numberOfMakers: 6,
|
|
||||||
numberOfMakersPerPool: [1, 2, 3],
|
|
||||||
protocolFeesByMaker: [
|
|
||||||
// pool 1
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
// pool 2
|
|
||||||
StakingWrapper.toBaseUnitAmount(3.2),
|
|
||||||
StakingWrapper.toBaseUnitAmount(12.123258),
|
|
||||||
// pool 3
|
|
||||||
StakingWrapper.toBaseUnitAmount(23.577),
|
|
||||||
StakingWrapper.toBaseUnitAmount(4.54522236),
|
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
|
||||||
],
|
|
||||||
numberOfDelegators: 3,
|
|
||||||
numberOfDelegatorsPerPool: [0, 0, 3],
|
|
||||||
stakeByDelegator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
|
||||||
StakingWrapper.toBaseUnitAmount(75),
|
|
||||||
StakingWrapper.toBaseUnitAmount(90),
|
|
||||||
],
|
|
||||||
delegateInNextEpoch: true, // delegated stake is included in payout computation + forces shadow eth
|
|
||||||
withdrawByUndelegating: true, // profits are withdrawn as result of undelegating
|
|
||||||
expectedFeesByPool: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
StakingWrapper.toBaseUnitAmount(15.323258),
|
|
||||||
StakingWrapper.toBaseUnitAmount(28.12222236),
|
|
||||||
],
|
|
||||||
expectedPayoutByPool: [
|
|
||||||
new BigNumber('4.7567723629327287476569912989141'),
|
|
||||||
new BigNumber('16.281305003949352312532097047985'),
|
|
||||||
new BigNumber('20.310284473430147203349271380151'),
|
|
||||||
],
|
|
||||||
expectedPayoutByPoolOperator: [
|
|
||||||
new BigNumber('1.8551412215437642749591650093188'),
|
|
||||||
new BigNumber('9.6059699523301173582693060410895'),
|
|
||||||
new BigNumber('8.7334223235749631621465139389311'),
|
|
||||||
],
|
|
||||||
expectedMembersPayoutByPool: [
|
|
||||||
new BigNumber('2.9016311413889644726978262895953'),
|
|
||||||
new BigNumber('6.6753350516192349542627910068955'),
|
|
||||||
new BigNumber('11.576862149855184041202757441220'),
|
|
||||||
],
|
|
||||||
expectedPayoutByDelegator: [new BigNumber(0), new BigNumber(0), new BigNumber(0)],
|
|
||||||
exchangeAddress: exchange,
|
|
||||||
};
|
|
||||||
const simulator = new Simulation(stakingWrapper, simulationParams);
|
|
||||||
await simulator.runAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should successfully simulate (delegators withdraw without undelegating / includes shadow balances / delegators enter after reward payouts)', async () => {
|
|
||||||
// @TODO - get computations more accurate
|
|
||||||
|
|
||||||
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled)
|
|
||||||
0 | 0.304958 | 42 | 0 | 42
|
|
||||||
1 | 15.323258 | 84 | 0 | 84
|
|
||||||
3 | 28.12222236 | 97 | 182 | 260.8
|
|
||||||
...
|
|
||||||
Cumulative Fees = 43.75043836
|
|
||||||
Cumulative Weighted Stake = 386.8
|
|
||||||
Total Rewards = 43.75043836
|
|
||||||
|
|
||||||
// In this case, there was already a pot of ETH in the delegator pool that nobody had claimed.
|
|
||||||
// The first delegator got to claim it all. This is due to the necessary conservation of payouts.
|
|
||||||
// When a new delegator arrives, their new stake should not affect existing delegator payouts.
|
|
||||||
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
|
|
||||||
|
|
||||||
const simulationParams = {
|
|
||||||
users,
|
|
||||||
numberOfPools: 3,
|
|
||||||
poolOperatorShares: [39, 59, 43].map(v => (v / 100) * PPM_ONE),
|
|
||||||
stakeByPoolOperator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(42),
|
|
||||||
StakingWrapper.toBaseUnitAmount(84),
|
|
||||||
StakingWrapper.toBaseUnitAmount(97),
|
|
||||||
],
|
|
||||||
numberOfMakers: 6,
|
|
||||||
numberOfMakersPerPool: [1, 2, 3],
|
|
||||||
protocolFeesByMaker: [
|
|
||||||
// pool 1
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
// pool 2
|
|
||||||
StakingWrapper.toBaseUnitAmount(3.2),
|
|
||||||
StakingWrapper.toBaseUnitAmount(12.123258),
|
|
||||||
// pool 3
|
|
||||||
StakingWrapper.toBaseUnitAmount(23.577),
|
|
||||||
StakingWrapper.toBaseUnitAmount(4.54522236),
|
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
|
||||||
],
|
|
||||||
numberOfDelegators: 3,
|
|
||||||
numberOfDelegatorsPerPool: [0, 0, 3],
|
|
||||||
stakeByDelegator: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
|
||||||
StakingWrapper.toBaseUnitAmount(75),
|
|
||||||
StakingWrapper.toBaseUnitAmount(90),
|
|
||||||
],
|
|
||||||
delegateInNextEpoch: true, // delegated stake is included in payout computation + forces shadow eth
|
|
||||||
withdrawByUndelegating: false, // profits are withdrawn without undelegating
|
|
||||||
expectedFeesByPool: [
|
|
||||||
StakingWrapper.toBaseUnitAmount(0.304958),
|
|
||||||
StakingWrapper.toBaseUnitAmount(15.323258),
|
|
||||||
StakingWrapper.toBaseUnitAmount(28.12222236),
|
|
||||||
],
|
|
||||||
expectedPayoutByPool: [
|
|
||||||
new BigNumber('4.7567723629327287476569912989141'),
|
|
||||||
new BigNumber('16.281305003949352312532097047985'),
|
|
||||||
new BigNumber('20.310284473430147203349271380151'),
|
|
||||||
],
|
|
||||||
expectedPayoutByPoolOperator: [
|
|
||||||
new BigNumber('1.8551412215437642749591650093188'),
|
|
||||||
new BigNumber('9.6059699523301173582693060410895'),
|
|
||||||
new BigNumber('8.7334223235749631621465139389311'),
|
|
||||||
],
|
|
||||||
expectedMembersPayoutByPool: [
|
|
||||||
new BigNumber('2.9016311413889644726978262895953'),
|
|
||||||
new BigNumber('6.6753350516192349542627910068955'),
|
|
||||||
new BigNumber('11.576862149855184041202757441220'),
|
|
||||||
],
|
|
||||||
expectedPayoutByDelegator: [new BigNumber(0), new BigNumber(0), new BigNumber(0)],
|
|
||||||
exchangeAddress: exchange,
|
|
||||||
};
|
|
||||||
const simulator = new Simulation(stakingWrapper, simulationParams);
|
|
||||||
await simulator.runAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should not be able to record a protocol fee from an unknown exchange', async () => {
|
|
||||||
const makerAddress = users[1];
|
|
||||||
const protocolFee = new BigNumber(1);
|
|
||||||
// TODO(jalextowle) I need to update this test when I make my PR on adding protocol fees to the Staking contracts
|
|
||||||
const revertError = new StakingRevertErrors.OnlyCallableByExchangeError(owner);
|
|
||||||
const tx = stakingWrapper.payProtocolFeeAsync(makerAddress, makerAddress, protocolFee, protocolFee, owner);
|
|
||||||
await expect(tx).to.revertWith(revertError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// tslint:enable:no-unnecessary-type-assertion
|
|
||||||
*/
|
|
@ -25,7 +25,7 @@ import {
|
|||||||
toBaseUnitAmount,
|
toBaseUnitAmount,
|
||||||
} from '../utils/number_utils';
|
} from '../utils/number_utils';
|
||||||
|
|
||||||
blockchainTests.resets('delegator unit rewards', env => {
|
blockchainTests.resets('Delegator rewards unit tests', env => {
|
||||||
let testContract: TestDelegatorRewardsContract;
|
let testContract: TestDelegatorRewardsContract;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
} from '../../src';
|
} from '../../src';
|
||||||
import { assertIntegerRoughlyEquals, getRandomInteger, toBaseUnitAmount } from '../utils/number_utils';
|
import { assertIntegerRoughlyEquals, getRandomInteger, toBaseUnitAmount } from '../utils/number_utils';
|
||||||
|
|
||||||
blockchainTests.resets('finalizer unit tests', env => {
|
blockchainTests.resets('Finalizer unit tests', env => {
|
||||||
const { ZERO_AMOUNT } = constants;
|
const { ZERO_AMOUNT } = constants;
|
||||||
const INITIAL_EPOCH = 0;
|
const INITIAL_EPOCH = 0;
|
||||||
const INITIAL_BALANCE = toBaseUnitAmount(32);
|
const INITIAL_BALANCE = toBaseUnitAmount(32);
|
||||||
|
@ -2,12 +2,12 @@ import { blockchainTests, Numberish } from '@0x/contracts-test-utils';
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts, TestCobbDouglasContract } from '../src/';
|
import { artifacts, TestCobbDouglasContract } from '../../src/';
|
||||||
|
|
||||||
import { assertRoughlyEquals, getRandomInteger, getRandomPortion, toDecimal } from './utils/number_utils';
|
import { assertRoughlyEquals, getRandomInteger, getRandomPortion, toDecimal } from '../utils/number_utils';
|
||||||
|
|
||||||
// tslint:disable: no-unnecessary-type-assertion
|
// tslint:disable: no-unnecessary-type-assertion
|
||||||
blockchainTests('Cobb-Douglas', env => {
|
blockchainTests('LibCobbDouglas unit tests', env => {
|
||||||
const FUZZ_COUNT = 1024;
|
const FUZZ_COUNT = 1024;
|
||||||
const PRECISION = 15;
|
const PRECISION = 15;
|
||||||
|
|
@ -7,7 +7,7 @@ import { artifacts, TestLibFixedMathContract } from '../../src';
|
|||||||
|
|
||||||
import { assertRoughlyEquals, fromFixed, toDecimal, toFixed } from '../utils/number_utils';
|
import { assertRoughlyEquals, fromFixed, toDecimal, toFixed } from '../utils/number_utils';
|
||||||
|
|
||||||
blockchainTests('LibFixedMath', env => {
|
blockchainTests('LibFixedMath unit tests', env => {
|
||||||
let testContract: TestLibFixedMathContract;
|
let testContract: TestLibFixedMathContract;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
@ -4,7 +4,7 @@ import { cartesianProduct } from 'js-combinatorics';
|
|||||||
|
|
||||||
import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src';
|
import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src';
|
||||||
|
|
||||||
blockchainTests.resets('LibProxy', env => {
|
blockchainTests.resets('LibProxy unit tests', env => {
|
||||||
let proxy: TestLibProxyContract;
|
let proxy: TestLibProxyContract;
|
||||||
let receiver: TestLibProxyReceiverContract;
|
let receiver: TestLibProxyReceiverContract;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
|
|||||||
|
|
||||||
import { artifacts, TestLibSafeDowncastContract } from '../../src/';
|
import { artifacts, TestLibSafeDowncastContract } from '../../src/';
|
||||||
|
|
||||||
blockchainTests('LibSafeDowncast', env => {
|
blockchainTests('LibSafeDowncast unit tests', env => {
|
||||||
let testContract: TestLibSafeDowncastContract;
|
let testContract: TestLibSafeDowncastContract;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
import { blockchainTests, expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
|
||||||
import { AuthorizableRevertErrors } from '@0x/utils';
|
|
||||||
|
|
||||||
import { constants } from '../utils/constants';
|
|
||||||
|
|
||||||
import {
|
|
||||||
artifacts,
|
|
||||||
TestMixinVaultCoreContract,
|
|
||||||
TestMixinVaultCoreInCatastrophicFailureModeEventArgs,
|
|
||||||
TestMixinVaultCoreStakingProxySetEventArgs,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
blockchainTests.resets('MixinVaultCore', env => {
|
|
||||||
let owner: string;
|
|
||||||
let nonOwnerAddresses: string[];
|
|
||||||
let testContract: TestMixinVaultCoreContract;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
[owner, ...nonOwnerAddresses] = await env.getAccountAddressesAsync();
|
|
||||||
|
|
||||||
testContract = await TestMixinVaultCoreContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.TestMixinVaultCore,
|
|
||||||
env.provider,
|
|
||||||
env.txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Set staking proxy', () => {
|
|
||||||
async function testAssertStakingProxyAsync(callerAddress: string): Promise<void> {
|
|
||||||
const tx = testContract.assertStakingProxy.callAsync({ from: callerAddress });
|
|
||||||
const expectedError = new StakingRevertErrors.OnlyCallableByStakingContractError(callerAddress);
|
|
||||||
expect(tx).to.revertWith(expectedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Owner can set staking proxy', async () => {
|
|
||||||
const newAddress = nonOwnerAddresses[0];
|
|
||||||
const receipt = await testContract.setStakingProxy.awaitTransactionSuccessAsync(newAddress, {
|
|
||||||
from: owner,
|
|
||||||
});
|
|
||||||
const eventArgs = filterLogsToArguments<TestMixinVaultCoreStakingProxySetEventArgs>(
|
|
||||||
receipt.logs,
|
|
||||||
'StakingProxySet',
|
|
||||||
);
|
|
||||||
expect(eventArgs.length).to.equal(1);
|
|
||||||
expect(eventArgs[0].stakingProxyAddress).to.equal(newAddress);
|
|
||||||
expect(await testContract.stakingProxyAddress.callAsync()).to.equal(newAddress);
|
|
||||||
// The new staking proxy address should be able to pass the modifier check
|
|
||||||
await testContract.assertStakingProxy.callAsync({ from: newAddress });
|
|
||||||
return testAssertStakingProxyAsync(owner);
|
|
||||||
});
|
|
||||||
it('Non-authorized address cannot set staking proxy', async () => {
|
|
||||||
const notAuthorized = nonOwnerAddresses[0];
|
|
||||||
const newAddress = nonOwnerAddresses[1];
|
|
||||||
const tx = testContract.setStakingProxy.awaitTransactionSuccessAsync(newAddress, {
|
|
||||||
from: notAuthorized,
|
|
||||||
});
|
|
||||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
|
||||||
expect(tx).to.revertWith(expectedError);
|
|
||||||
expect(await testContract.stakingProxyAddress.callAsync()).to.equal(constants.NIL_ADDRESS);
|
|
||||||
return testAssertStakingProxyAsync(newAddress);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Catastrophic failure mode', () => {
|
|
||||||
async function testCatastrophicFailureModeAsync(isInCatastrophicFailure: boolean): Promise<void> {
|
|
||||||
const [expectToSucceed, expectToRevert] = isInCatastrophicFailure
|
|
||||||
? [testContract.assertInCatastrophicFailure, testContract.assertNotInCatastrophicFailure]
|
|
||||||
: [testContract.assertNotInCatastrophicFailure, testContract.assertInCatastrophicFailure];
|
|
||||||
const expectedError = isInCatastrophicFailure
|
|
||||||
? new StakingRevertErrors.OnlyCallableIfNotInCatastrophicFailureError()
|
|
||||||
: new StakingRevertErrors.OnlyCallableIfInCatastrophicFailureError();
|
|
||||||
await expectToSucceed.callAsync();
|
|
||||||
expect(expectToRevert.callAsync()).to.revertWith(expectedError);
|
|
||||||
expect(await testContract.isInCatastrophicFailure.callAsync()).to.equal(isInCatastrophicFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Owner can turn on catastrophic failure mode', async () => {
|
|
||||||
await testCatastrophicFailureModeAsync(false);
|
|
||||||
const receipt = await testContract.enterCatastrophicFailure.awaitTransactionSuccessAsync({ from: owner });
|
|
||||||
const eventArgs = filterLogsToArguments<TestMixinVaultCoreInCatastrophicFailureModeEventArgs>(
|
|
||||||
receipt.logs,
|
|
||||||
'InCatastrophicFailureMode',
|
|
||||||
);
|
|
||||||
expect(eventArgs.length).to.equal(1);
|
|
||||||
expect(eventArgs[0].sender).to.equal(owner);
|
|
||||||
return testCatastrophicFailureModeAsync(true);
|
|
||||||
});
|
|
||||||
it('Non-authorized address cannot turn on catastrophic failure mode', async () => {
|
|
||||||
await testCatastrophicFailureModeAsync(false);
|
|
||||||
const tx = testContract.enterCatastrophicFailure.awaitTransactionSuccessAsync({
|
|
||||||
from: nonOwnerAddresses[0],
|
|
||||||
});
|
|
||||||
expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(nonOwnerAddresses[0]));
|
|
||||||
return testCatastrophicFailureModeAsync(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,10 +3,10 @@ import { AuthorizableRevertErrors, BigNumber } from '@0x/utils';
|
|||||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from '../src/';
|
import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from '../../src/';
|
||||||
|
|
||||||
import { constants as stakingConstants } from './utils/constants';
|
import { constants as stakingConstants } from '../utils/constants';
|
||||||
import { StakingParams } from './utils/types';
|
import { StakingParams } from '../utils/types';
|
||||||
|
|
||||||
blockchainTests('Configurable Parameters unit tests', env => {
|
blockchainTests('Configurable Parameters unit tests', env => {
|
||||||
let testContract: MixinParamsContract;
|
let testContract: MixinParamsContract;
|
@ -19,11 +19,11 @@ import {
|
|||||||
TestProtocolFeesContract,
|
TestProtocolFeesContract,
|
||||||
TestProtocolFeesERC20ProxyTransferFromEventArgs,
|
TestProtocolFeesERC20ProxyTransferFromEventArgs,
|
||||||
TestProtocolFeesEvents,
|
TestProtocolFeesEvents,
|
||||||
} from '../src';
|
} from '../../src';
|
||||||
|
|
||||||
import { getRandomInteger } from './utils/number_utils';
|
import { getRandomInteger } from '../utils/number_utils';
|
||||||
|
|
||||||
blockchainTests('Protocol Fee Unit Tests', env => {
|
blockchainTests('Protocol Fees unit tests', env => {
|
||||||
let ownerAddress: string;
|
let ownerAddress: string;
|
||||||
let exchangeAddress: string;
|
let exchangeAddress: string;
|
||||||
let notExchangeAddress: string;
|
let notExchangeAddress: string;
|
438
contracts/staking/test/unit_tests/zrx_vault_test.ts
Normal file
438
contracts/staking/test/unit_tests/zrx_vault_test.ts
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
|
import {
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
expectTransactionFailedAsync,
|
||||||
|
filterLogsToArguments,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { assetDataUtils, StakingRevertErrors } from '@0x/order-utils';
|
||||||
|
import { RevertReason } from '@0x/types';
|
||||||
|
import { AuthorizableRevertErrors, BigNumber, SafeMathRevertErrors } from '@0x/utils';
|
||||||
|
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { constants as stakingConstants } from '../utils/constants';
|
||||||
|
|
||||||
|
import {
|
||||||
|
artifacts,
|
||||||
|
ZrxVaultContract,
|
||||||
|
ZrxVaultDepositEventArgs,
|
||||||
|
ZrxVaultInCatastrophicFailureModeEventArgs,
|
||||||
|
ZrxVaultStakingProxySetEventArgs,
|
||||||
|
ZrxVaultWithdrawEventArgs,
|
||||||
|
ZrxVaultZrxProxySetEventArgs,
|
||||||
|
} from '../../src';
|
||||||
|
|
||||||
|
blockchainTests.resets('ZrxVault unit tests', env => {
|
||||||
|
let accounts: string[];
|
||||||
|
let owner: string;
|
||||||
|
let nonOwnerAddresses: string[];
|
||||||
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
|
let zrxVault: ZrxVaultContract;
|
||||||
|
let zrxAssetData: string;
|
||||||
|
let zrxProxyAddress: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
// create accounts
|
||||||
|
accounts = await env.getAccountAddressesAsync();
|
||||||
|
[owner, ...nonOwnerAddresses] = accounts;
|
||||||
|
|
||||||
|
// set up ERC20Wrapper
|
||||||
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
|
// deploy erc20 proxy
|
||||||
|
const erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||||
|
zrxProxyAddress = erc20ProxyContract.address;
|
||||||
|
// deploy zrx token
|
||||||
|
const [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, constants.DUMMY_TOKEN_DECIMALS);
|
||||||
|
zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenContract.address);
|
||||||
|
|
||||||
|
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||||
|
|
||||||
|
zrxVault = await ZrxVaultContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.ZrxVault,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
zrxProxyAddress,
|
||||||
|
zrxTokenContract.address,
|
||||||
|
);
|
||||||
|
|
||||||
|
// configure erc20 proxy to accept calls from zrx vault
|
||||||
|
await erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(zrxVault.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
enum ZrxTransfer {
|
||||||
|
Deposit,
|
||||||
|
Withdrawal,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyTransferPostconditionsAsync(
|
||||||
|
transferType: ZrxTransfer,
|
||||||
|
staker: string,
|
||||||
|
amount: BigNumber,
|
||||||
|
initialVaultBalance: BigNumber,
|
||||||
|
initialTokenBalance: BigNumber,
|
||||||
|
receipt: TransactionReceiptWithDecodedLogs,
|
||||||
|
): Promise<void> {
|
||||||
|
const eventArgs =
|
||||||
|
transferType === ZrxTransfer.Deposit
|
||||||
|
? filterLogsToArguments<ZrxVaultDepositEventArgs>(receipt.logs, 'Deposit')
|
||||||
|
: filterLogsToArguments<ZrxVaultWithdrawEventArgs>(receipt.logs, 'Withdraw');
|
||||||
|
expect(eventArgs.length).to.equal(1);
|
||||||
|
expect(eventArgs[0].staker).to.equal(staker);
|
||||||
|
expect(eventArgs[0].amount).to.bignumber.equal(amount);
|
||||||
|
|
||||||
|
const newVaultBalance = await zrxVault.balanceOf.callAsync(staker);
|
||||||
|
const newTokenBalance = await erc20Wrapper.getBalanceAsync(staker, zrxAssetData);
|
||||||
|
const [expectedVaultBalance, expectedTokenBalance] =
|
||||||
|
transferType === ZrxTransfer.Deposit
|
||||||
|
? [initialVaultBalance.plus(amount), initialTokenBalance.minus(amount)]
|
||||||
|
: [initialVaultBalance.minus(amount), initialTokenBalance.plus(amount)];
|
||||||
|
expect(newVaultBalance).to.bignumber.equal(expectedVaultBalance);
|
||||||
|
expect(newTokenBalance).to.bignumber.equal(expectedTokenBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Normal operation', () => {
|
||||||
|
describe('Setting proxies', () => {
|
||||||
|
async function verifyStakingProxySetAsync(
|
||||||
|
receipt: TransactionReceiptWithDecodedLogs,
|
||||||
|
newProxy: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const eventArgs = filterLogsToArguments<ZrxVaultStakingProxySetEventArgs>(
|
||||||
|
receipt.logs,
|
||||||
|
'StakingProxySet',
|
||||||
|
);
|
||||||
|
expect(eventArgs.length).to.equal(1);
|
||||||
|
expect(eventArgs[0].stakingProxyAddress).to.equal(newProxy);
|
||||||
|
const actualAddress = await zrxVault.stakingProxyAddress.callAsync();
|
||||||
|
expect(actualAddress).to.equal(newProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Owner can set the ZRX proxy', async () => {
|
||||||
|
const newProxy = nonOwnerAddresses[0];
|
||||||
|
const receipt = await zrxVault.setZrxProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: owner,
|
||||||
|
});
|
||||||
|
const eventArgs = filterLogsToArguments<ZrxVaultZrxProxySetEventArgs>(receipt.logs, 'ZrxProxySet');
|
||||||
|
expect(eventArgs.length).to.equal(1);
|
||||||
|
expect(eventArgs[0].zrxProxyAddress).to.equal(newProxy);
|
||||||
|
});
|
||||||
|
it('Authorized address can set the ZRX proxy', async () => {
|
||||||
|
const [authorized, newProxy] = nonOwnerAddresses;
|
||||||
|
await zrxVault.addAuthorizedAddress.awaitTransactionSuccessAsync(authorized, { from: owner });
|
||||||
|
const receipt = await zrxVault.setZrxProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: authorized,
|
||||||
|
});
|
||||||
|
const eventArgs = filterLogsToArguments<ZrxVaultZrxProxySetEventArgs>(receipt.logs, 'ZrxProxySet');
|
||||||
|
expect(eventArgs.length).to.equal(1);
|
||||||
|
expect(eventArgs[0].zrxProxyAddress).to.equal(newProxy);
|
||||||
|
});
|
||||||
|
it('Non-authorized address cannot set the ZRX proxy', async () => {
|
||||||
|
const [notAuthorized, newProxy] = nonOwnerAddresses;
|
||||||
|
const tx = zrxVault.setZrxProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: notAuthorized,
|
||||||
|
});
|
||||||
|
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
});
|
||||||
|
it('Owner can set the staking proxy', async () => {
|
||||||
|
const newProxy = nonOwnerAddresses[0];
|
||||||
|
const receipt = await zrxVault.setStakingProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: owner,
|
||||||
|
});
|
||||||
|
await verifyStakingProxySetAsync(receipt, newProxy);
|
||||||
|
});
|
||||||
|
it('Authorized address can set the staking proxy', async () => {
|
||||||
|
const [authorized, newProxy] = nonOwnerAddresses;
|
||||||
|
await zrxVault.addAuthorizedAddress.awaitTransactionSuccessAsync(authorized, { from: owner });
|
||||||
|
const receipt = await zrxVault.setStakingProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: authorized,
|
||||||
|
});
|
||||||
|
await verifyStakingProxySetAsync(receipt, newProxy);
|
||||||
|
});
|
||||||
|
it('Non-authorized address cannot set the staking proxy', async () => {
|
||||||
|
const [notAuthorized, newProxy] = nonOwnerAddresses;
|
||||||
|
const tx = zrxVault.setStakingProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: notAuthorized,
|
||||||
|
});
|
||||||
|
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
const actualAddress = await zrxVault.stakingProxyAddress.callAsync();
|
||||||
|
expect(actualAddress).to.equal(stakingConstants.NIL_ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ZRX management', () => {
|
||||||
|
let staker: string;
|
||||||
|
let stakingProxy: string;
|
||||||
|
let initialVaultBalance: BigNumber;
|
||||||
|
let initialTokenBalance: BigNumber;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[staker, stakingProxy] = nonOwnerAddresses;
|
||||||
|
await zrxVault.setStakingProxy.awaitTransactionSuccessAsync(stakingProxy, { from: owner });
|
||||||
|
await zrxVault.depositFrom.awaitTransactionSuccessAsync(staker, new BigNumber(10), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
initialVaultBalance = await zrxVault.balanceOf.callAsync(staker);
|
||||||
|
initialTokenBalance = await erc20Wrapper.getBalanceAsync(staker, zrxAssetData);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Deposit', () => {
|
||||||
|
it('Staking proxy can deposit zero amount on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.depositFrom.awaitTransactionSuccessAsync(
|
||||||
|
staker,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
{
|
||||||
|
from: stakingProxy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Deposit,
|
||||||
|
staker,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Staking proxy can deposit nonzero amount on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.depositFrom.awaitTransactionSuccessAsync(staker, new BigNumber(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Deposit,
|
||||||
|
staker,
|
||||||
|
new BigNumber(1),
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Staking proxy can deposit entire ZRX balance on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.depositFrom.awaitTransactionSuccessAsync(
|
||||||
|
staker,
|
||||||
|
initialTokenBalance,
|
||||||
|
{
|
||||||
|
from: stakingProxy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Deposit,
|
||||||
|
staker,
|
||||||
|
initialTokenBalance,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Reverts if attempting to deposit more than staker's ZRX balance", async () => {
|
||||||
|
const tx = zrxVault.depositFrom.sendTransactionAsync(staker, initialTokenBalance.plus(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
expectTransactionFailedAsync(tx, RevertReason.TransferFailed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Withdrawal', () => {
|
||||||
|
it('Staking proxy can withdraw zero amount on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawFrom.awaitTransactionSuccessAsync(
|
||||||
|
staker,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
{
|
||||||
|
from: stakingProxy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
constants.ZERO_AMOUNT,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Staking proxy can withdraw nonzero amount on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawFrom.awaitTransactionSuccessAsync(staker, new BigNumber(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
new BigNumber(1),
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Staking proxy can withdraw entire vault balance on behalf of staker', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawFrom.awaitTransactionSuccessAsync(
|
||||||
|
staker,
|
||||||
|
initialVaultBalance,
|
||||||
|
{
|
||||||
|
from: stakingProxy,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("Reverts if attempting to withdraw more than staker's vault balance", async () => {
|
||||||
|
const tx = zrxVault.withdrawFrom.awaitTransactionSuccessAsync(staker, initialVaultBalance.plus(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
const expectedError = new SafeMathRevertErrors.Uint256BinOpError(
|
||||||
|
SafeMathRevertErrors.BinOpErrorCodes.SubtractionUnderflow,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialVaultBalance.plus(1),
|
||||||
|
);
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Catastrophic Failure Mode', () => {
|
||||||
|
describe('Authorization', () => {
|
||||||
|
async function verifyCatastrophicFailureModeAsync(
|
||||||
|
sender: string,
|
||||||
|
receipt: TransactionReceiptWithDecodedLogs,
|
||||||
|
): Promise<void> {
|
||||||
|
const eventArgs = filterLogsToArguments<ZrxVaultInCatastrophicFailureModeEventArgs>(
|
||||||
|
receipt.logs,
|
||||||
|
'InCatastrophicFailureMode',
|
||||||
|
);
|
||||||
|
expect(eventArgs.length).to.equal(1);
|
||||||
|
expect(eventArgs[0].sender).to.equal(sender);
|
||||||
|
expect(await zrxVault.isInCatastrophicFailure.callAsync()).to.be.true();
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Owner can turn on Catastrophic Failure Mode', async () => {
|
||||||
|
const receipt = await zrxVault.enterCatastrophicFailure.awaitTransactionSuccessAsync({ from: owner });
|
||||||
|
await verifyCatastrophicFailureModeAsync(owner, receipt);
|
||||||
|
});
|
||||||
|
it('Authorized address can turn on Catastrophic Failure Mode', async () => {
|
||||||
|
const authorized = nonOwnerAddresses[0];
|
||||||
|
await zrxVault.addAuthorizedAddress.awaitTransactionSuccessAsync(authorized, { from: owner });
|
||||||
|
const receipt = await zrxVault.enterCatastrophicFailure.awaitTransactionSuccessAsync({
|
||||||
|
from: authorized,
|
||||||
|
});
|
||||||
|
await verifyCatastrophicFailureModeAsync(authorized, receipt);
|
||||||
|
});
|
||||||
|
it('Non-authorized address cannot turn on Catastrophic Failure Mode', async () => {
|
||||||
|
const notAuthorized = nonOwnerAddresses[0];
|
||||||
|
const tx = zrxVault.enterCatastrophicFailure.awaitTransactionSuccessAsync({
|
||||||
|
from: notAuthorized,
|
||||||
|
});
|
||||||
|
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
expect(await zrxVault.isInCatastrophicFailure.callAsync()).to.be.false();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Affected functionality', () => {
|
||||||
|
let staker: string;
|
||||||
|
let stakingProxy: string;
|
||||||
|
let initialVaultBalance: BigNumber;
|
||||||
|
let initialTokenBalance: BigNumber;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[staker, stakingProxy, ...nonOwnerAddresses] = nonOwnerAddresses;
|
||||||
|
await zrxVault.setStakingProxy.awaitTransactionSuccessAsync(stakingProxy, { from: owner });
|
||||||
|
await zrxVault.depositFrom.awaitTransactionSuccessAsync(staker, new BigNumber(10), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
await zrxVault.enterCatastrophicFailure.awaitTransactionSuccessAsync({ from: owner });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
initialVaultBalance = await zrxVault.balanceOf.callAsync(staker);
|
||||||
|
initialTokenBalance = await erc20Wrapper.getBalanceAsync(staker, zrxAssetData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Owner cannot set the ZRX proxy', async () => {
|
||||||
|
const newProxy = nonOwnerAddresses[0];
|
||||||
|
const tx = zrxVault.setZrxProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: owner,
|
||||||
|
});
|
||||||
|
const expectedError = new StakingRevertErrors.OnlyCallableIfNotInCatastrophicFailureError();
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
const actualAddress = await zrxVault.zrxAssetProxy.callAsync();
|
||||||
|
expect(actualAddress).to.equal(zrxProxyAddress);
|
||||||
|
});
|
||||||
|
it('Authorized address cannot set the ZRX proxy', async () => {
|
||||||
|
const [authorized, newProxy] = nonOwnerAddresses;
|
||||||
|
await zrxVault.addAuthorizedAddress.awaitTransactionSuccessAsync(authorized, { from: owner });
|
||||||
|
const tx = zrxVault.setZrxProxy.awaitTransactionSuccessAsync(newProxy, {
|
||||||
|
from: authorized,
|
||||||
|
});
|
||||||
|
const expectedError = new StakingRevertErrors.OnlyCallableIfNotInCatastrophicFailureError();
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
const actualAddress = await zrxVault.zrxAssetProxy.callAsync();
|
||||||
|
expect(actualAddress).to.equal(zrxProxyAddress);
|
||||||
|
});
|
||||||
|
it('Staking proxy cannot deposit ZRX', async () => {
|
||||||
|
const tx = zrxVault.depositFrom.awaitTransactionSuccessAsync(staker, new BigNumber(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
const expectedError = new StakingRevertErrors.OnlyCallableIfNotInCatastrophicFailureError();
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Withdrawal', () => {
|
||||||
|
it('Staking proxy cannot call `withdrawFrom`', async () => {
|
||||||
|
const tx = zrxVault.withdrawFrom.awaitTransactionSuccessAsync(staker, new BigNumber(1), {
|
||||||
|
from: stakingProxy,
|
||||||
|
});
|
||||||
|
const expectedError = new StakingRevertErrors.OnlyCallableIfNotInCatastrophicFailureError();
|
||||||
|
expect(tx).to.revertWith(expectedError);
|
||||||
|
});
|
||||||
|
it('Staker can withdraw all their ZRX', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawAllFrom.awaitTransactionSuccessAsync(staker, {
|
||||||
|
from: staker,
|
||||||
|
});
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Owner can withdraw ZRX on behalf of a staker', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawAllFrom.awaitTransactionSuccessAsync(staker, {
|
||||||
|
from: owner,
|
||||||
|
});
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('Non-owner address can withdraw ZRX on behalf of a staker', async () => {
|
||||||
|
const receipt = await zrxVault.withdrawAllFrom.awaitTransactionSuccessAsync(staker, {
|
||||||
|
from: nonOwnerAddresses[0],
|
||||||
|
});
|
||||||
|
await verifyTransferPostconditionsAsync(
|
||||||
|
ZrxTransfer.Withdrawal,
|
||||||
|
staker,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialVaultBalance,
|
||||||
|
initialTokenBalance,
|
||||||
|
receipt,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,279 +0,0 @@
|
|||||||
/*
|
|
||||||
@TODO (hysz) - update once new staking mechanics are merged
|
|
||||||
|
|
||||||
import { expect } from '@0x/contracts-test-utils';
|
|
||||||
import { chaiSetup } from '@0x/contracts-test-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import { MakerActor } from '../actors/maker_actor';
|
|
||||||
import { PoolOperatorActor } from '../actors/pool_operator_actor';
|
|
||||||
|
|
||||||
import { Queue } from './queue';
|
|
||||||
import { StakingWrapper } from './staking_wrapper';
|
|
||||||
import { SimulationParams } from './types';
|
|
||||||
|
|
||||||
const REWARD_PRECISION = 12;
|
|
||||||
|
|
||||||
export class Simulation {
|
|
||||||
private readonly _stakingWrapper: StakingWrapper;
|
|
||||||
private readonly _p: SimulationParams;
|
|
||||||
private _userQueue: Queue<string>;
|
|
||||||
private readonly _poolOperators: PoolOperatorActor[];
|
|
||||||
private readonly _poolOperatorsAsDelegators: DelegatorActor[];
|
|
||||||
private readonly _poolIds: string[];
|
|
||||||
private readonly _makers: MakerActor[];
|
|
||||||
private readonly _delegators: DelegatorActor[];
|
|
||||||
|
|
||||||
private static _assertRewardsEqual(actual: BigNumber, expected: BigNumber, message?: string): void {
|
|
||||||
expect(
|
|
||||||
StakingWrapper.trimFloat(StakingWrapper.toFloatingPoint(actual, 18), REWARD_PRECISION),
|
|
||||||
message,
|
|
||||||
).to.be.bignumber.equal(StakingWrapper.trimFloat(expected, REWARD_PRECISION));
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(stakingWrapper: StakingWrapper, simulationParams: SimulationParams) {
|
|
||||||
this._stakingWrapper = stakingWrapper;
|
|
||||||
this._p = simulationParams;
|
|
||||||
this._userQueue = new Queue<string>();
|
|
||||||
this._poolOperators = [];
|
|
||||||
this._poolOperatorsAsDelegators = [];
|
|
||||||
this._poolIds = [];
|
|
||||||
this._makers = [];
|
|
||||||
this._delegators = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async runAsync(): Promise<void> {
|
|
||||||
this._userQueue = new Queue<string>(this._p.users);
|
|
||||||
await this._stakingWrapper.addExchangeAddressAsync(this._p.exchangeAddress);
|
|
||||||
await this._setupPoolsAsync(this._p);
|
|
||||||
await this._setupMakersAsync(this._p);
|
|
||||||
await this._payProtocolFeesAsync(this._p);
|
|
||||||
if (this._p.delegateInNextEpoch) {
|
|
||||||
// this property forces the staking contracts to use shadow ether
|
|
||||||
await this._stakingWrapper.skipToNextEpochAsync();
|
|
||||||
}
|
|
||||||
await this._setupDelegatorsAsync(this._p);
|
|
||||||
await this._stakingWrapper.skipToNextEpochAsync();
|
|
||||||
// everyone has been paid out into the vault. check balances.
|
|
||||||
await this._assertVaultBalancesAsync(this._p);
|
|
||||||
await this._withdrawRewardForStakingPoolMemberForOperatorsAsync(this._p);
|
|
||||||
if (this._p.withdrawByUndelegating) {
|
|
||||||
await this._withdrawRewardForStakingPoolMemberForDelegatorsAsync(this._p);
|
|
||||||
} else {
|
|
||||||
await this._withdrawRewardForStakingPoolMemberForDelegatorsByUndelegatingAsync(this._p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @TODO cleanup status and verify the staking contract is empty
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _withdrawRewardForStakingPoolMemberForDelegatorsByUndelegatingAsync(
|
|
||||||
p: SimulationParams,
|
|
||||||
): Promise<void> {
|
|
||||||
let delegatorIdx = 0;
|
|
||||||
let poolIdx = 0;
|
|
||||||
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
|
|
||||||
const poolId = this._poolIds[poolIdx];
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const j of _.range(numberOfDelegatorsInPool)) {
|
|
||||||
const delegator = this._delegators[delegatorIdx];
|
|
||||||
const delegatorAddress = delegator.getOwner();
|
|
||||||
const amountOfStakeDelegated = p.stakeByDelegator[delegatorIdx];
|
|
||||||
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
|
|
||||||
await delegator.deactivateAndTimeLockDelegatedStakeAsync(poolId, amountOfStakeDelegated);
|
|
||||||
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
|
|
||||||
const reward = finalEthBalance.minus(initEthBalance);
|
|
||||||
const expectedReward = p.expectedPayoutByDelegator[delegatorIdx];
|
|
||||||
Simulation._assertRewardsEqual(
|
|
||||||
reward,
|
|
||||||
expectedReward,
|
|
||||||
`reward withdrawn from pool ${poolId} for delegator ${delegatorAddress}`,
|
|
||||||
);
|
|
||||||
delegatorIdx += 1;
|
|
||||||
}
|
|
||||||
poolIdx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _withdrawRewardForStakingPoolMemberForDelegatorsAsync(p: SimulationParams): Promise<void> {
|
|
||||||
let delegatorIdx = 0;
|
|
||||||
let poolIdx = 0;
|
|
||||||
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
|
|
||||||
const poolId = this._poolIds[poolIdx];
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const j of _.range(numberOfDelegatorsInPool)) {
|
|
||||||
const delegator = this._delegators[delegatorIdx];
|
|
||||||
const delegatorAddress = delegator.getOwner();
|
|
||||||
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
|
|
||||||
await this._stakingWrapper.withdrawTotalRewardForStakingPoolMemberAsync(poolId, delegatorAddress);
|
|
||||||
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(delegatorAddress);
|
|
||||||
const reward = finalEthBalance.minus(initEthBalance);
|
|
||||||
const expectedReward = p.expectedPayoutByDelegator[delegatorIdx];
|
|
||||||
Simulation._assertRewardsEqual(
|
|
||||||
reward,
|
|
||||||
expectedReward,
|
|
||||||
`reward withdrawn from pool ${poolId} for delegator ${delegatorAddress}`,
|
|
||||||
);
|
|
||||||
delegatorIdx += 1;
|
|
||||||
}
|
|
||||||
poolIdx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _setupPoolsAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(p.numberOfPools)) {
|
|
||||||
// create operator
|
|
||||||
const poolOperatorAddress = this._userQueue.popFront();
|
|
||||||
const poolOperator = new PoolOperatorActor(poolOperatorAddress, this._stakingWrapper);
|
|
||||||
this._poolOperators.push(poolOperator);
|
|
||||||
// create a pool id for this operator
|
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(p.poolOperatorShares[i], false);
|
|
||||||
this._poolIds.push(poolId);
|
|
||||||
// each pool operator can also be a staker/delegator
|
|
||||||
const poolOperatorAsDelegator = new DelegatorActor(poolOperatorAddress, this._stakingWrapper);
|
|
||||||
this._poolOperatorsAsDelegators.push(poolOperatorAsDelegator);
|
|
||||||
// add stake to the operator's pool
|
|
||||||
const amountOfStake = p.stakeByPoolOperator[i];
|
|
||||||
await poolOperatorAsDelegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amountOfStake);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _setupMakersAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// create makers
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(p.numberOfMakers)) {
|
|
||||||
const makerAddress = this._userQueue.popFront();
|
|
||||||
const maker = new MakerActor(makerAddress, this._stakingWrapper);
|
|
||||||
this._makers.push(maker);
|
|
||||||
}
|
|
||||||
// add each maker to their respective pool
|
|
||||||
let makerIdx = 0;
|
|
||||||
let poolIdx = 0;
|
|
||||||
for (const numberOfMakersInPool of p.numberOfMakersPerPool) {
|
|
||||||
const poolId = this._poolIds[poolIdx];
|
|
||||||
const poolOperator = this._poolOperators[poolIdx];
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const j of _.range(numberOfMakersInPool)) {
|
|
||||||
const maker = this._makers[makerIdx];
|
|
||||||
await maker.joinStakingPoolAsMakerAsync(poolId);
|
|
||||||
const makerAddress = maker.getOwner();
|
|
||||||
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
|
|
||||||
makerIdx += 1;
|
|
||||||
}
|
|
||||||
poolIdx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _setupDelegatorsAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// create delegators
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(p.numberOfDelegators)) {
|
|
||||||
const delegatorAddress = this._userQueue.popFront();
|
|
||||||
const delegator = new DelegatorActor(delegatorAddress, this._stakingWrapper);
|
|
||||||
this._delegators.push(delegator);
|
|
||||||
}
|
|
||||||
// delegate to pools
|
|
||||||
// currently each actor delegates to a single pool
|
|
||||||
let delegatorIdx = 0;
|
|
||||||
let poolIdx = 0;
|
|
||||||
for (const numberOfDelegatorsInPool of p.numberOfDelegatorsPerPool) {
|
|
||||||
const poolId = this._poolIds[poolIdx];
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const j of _.range(numberOfDelegatorsInPool)) {
|
|
||||||
const delegator = this._delegators[delegatorIdx];
|
|
||||||
const amount = p.stakeByDelegator[delegatorIdx];
|
|
||||||
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amount);
|
|
||||||
delegatorIdx += 1;
|
|
||||||
}
|
|
||||||
poolIdx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _payProtocolFeesAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// pay fees
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(this._makers.length)) {
|
|
||||||
const maker = this._makers[i];
|
|
||||||
const makerAddress = maker.getOwner();
|
|
||||||
const feeAmount = p.protocolFeesByMaker[i];
|
|
||||||
// TODO(jalextowle): I'll need to fix this once I make my PR on protocol fees. The arguments
|
|
||||||
// I'm adding are just placeholders for now.
|
|
||||||
await this._stakingWrapper.payProtocolFeeAsync(
|
|
||||||
makerAddress,
|
|
||||||
makerAddress,
|
|
||||||
feeAmount,
|
|
||||||
feeAmount,
|
|
||||||
p.exchangeAddress,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// validate fees per pool
|
|
||||||
let expectedTotalFeesThisEpoch = new BigNumber(0);
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(this._poolIds.length)) {
|
|
||||||
const poolId = this._poolIds[i];
|
|
||||||
const expectedFees = p.expectedFeesByPool[i];
|
|
||||||
const fees = await this._stakingWrapper.getProtocolFeesThisEpochByPoolAsync(poolId);
|
|
||||||
expect(fees, `fees for pool ${poolId}`).to.be.bignumber.equal(expectedFees);
|
|
||||||
expectedTotalFeesThisEpoch = expectedTotalFeesThisEpoch.plus(fees);
|
|
||||||
}
|
|
||||||
// validate total fees
|
|
||||||
const totalFeesThisEpoch = await this._stakingWrapper.getTotalProtocolFeesThisEpochAsync();
|
|
||||||
expect(expectedTotalFeesThisEpoch, 'total fees earned').to.be.bignumber.equal(totalFeesThisEpoch);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _assertVaultBalancesAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(p.numberOfPools)) {
|
|
||||||
// @TODO - we trim balances in here because payouts are accurate only to REWARD_PRECISION decimal places.
|
|
||||||
// update once more accurate.
|
|
||||||
// check pool balance in vault
|
|
||||||
const poolId = this._poolIds[i];
|
|
||||||
const rewardVaultBalance = await this._stakingWrapper.rewardVaultBalanceOfAsync(poolId);
|
|
||||||
const expectedRewardBalance = p.expectedPayoutByPool[i];
|
|
||||||
Simulation._assertRewardsEqual(
|
|
||||||
rewardVaultBalance,
|
|
||||||
expectedRewardBalance,
|
|
||||||
`expected balance in vault for pool with id ${poolId}`,
|
|
||||||
);
|
|
||||||
// check operator's balance
|
|
||||||
const poolOperatorVaultBalance = await this._stakingWrapper.getRewardBalanceOfStakingPoolOperatorAsync(
|
|
||||||
poolId,
|
|
||||||
);
|
|
||||||
const expectedPoolOperatorVaultBalance = p.expectedPayoutByPoolOperator[i];
|
|
||||||
Simulation._assertRewardsEqual(
|
|
||||||
poolOperatorVaultBalance,
|
|
||||||
expectedPoolOperatorVaultBalance,
|
|
||||||
`operator balance in vault for pool with id ${poolId}`,
|
|
||||||
);
|
|
||||||
// check balance of pool members
|
|
||||||
const membersVaultBalance = await this._stakingWrapper.getRewardBalanceOfStakingPoolMembersAsync(poolId);
|
|
||||||
const expectedMembersVaultBalance = p.expectedMembersPayoutByPool[i];
|
|
||||||
Simulation._assertRewardsEqual(
|
|
||||||
membersVaultBalance,
|
|
||||||
expectedMembersVaultBalance,
|
|
||||||
`members balance in vault for pool with id ${poolId}`,
|
|
||||||
);
|
|
||||||
// @TODO compute balance of each member
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _withdrawRewardForStakingPoolMemberForOperatorsAsync(p: SimulationParams): Promise<void> {
|
|
||||||
// tslint:disable-next-line no-unused-variable
|
|
||||||
for (const i of _.range(p.numberOfPools)) {
|
|
||||||
// @TODO - we trim balances in here because payouts are accurate only to REWARD_PRECISION decimal places.
|
|
||||||
// update once more accurate.
|
|
||||||
// check pool balance in vault
|
|
||||||
const poolId = this._poolIds[i];
|
|
||||||
const poolOperator = this._poolOperators[i];
|
|
||||||
const poolOperatorAddress = poolOperator.getOwner();
|
|
||||||
const initEthBalance = await this._stakingWrapper.getEthBalanceAsync(poolOperatorAddress);
|
|
||||||
await this._stakingWrapper.withdrawTotalRewardForStakingPoolOperatorAsync(poolId, poolOperatorAddress);
|
|
||||||
const finalEthBalance = await this._stakingWrapper.getEthBalanceAsync(poolOperatorAddress);
|
|
||||||
const reward = finalEthBalance.minus(initEthBalance);
|
|
||||||
const expectedReward = p.expectedPayoutByPoolOperator[i];
|
|
||||||
Simulation._assertRewardsEqual(reward, expectedReward, `reward withdrawn from pool ${poolId} for operator`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
@ -9,7 +9,6 @@
|
|||||||
"generated-artifacts/IStorage.json",
|
"generated-artifacts/IStorage.json",
|
||||||
"generated-artifacts/IStorageInit.json",
|
"generated-artifacts/IStorageInit.json",
|
||||||
"generated-artifacts/IStructs.json",
|
"generated-artifacts/IStructs.json",
|
||||||
"generated-artifacts/IVaultCore.json",
|
|
||||||
"generated-artifacts/IZrxVault.json",
|
"generated-artifacts/IZrxVault.json",
|
||||||
"generated-artifacts/LibCobbDouglas.json",
|
"generated-artifacts/LibCobbDouglas.json",
|
||||||
"generated-artifacts/LibFixedMath.json",
|
"generated-artifacts/LibFixedMath.json",
|
||||||
@ -32,7 +31,6 @@
|
|||||||
"generated-artifacts/MixinStakingPool.json",
|
"generated-artifacts/MixinStakingPool.json",
|
||||||
"generated-artifacts/MixinStakingPoolRewards.json",
|
"generated-artifacts/MixinStakingPoolRewards.json",
|
||||||
"generated-artifacts/MixinStorage.json",
|
"generated-artifacts/MixinStorage.json",
|
||||||
"generated-artifacts/MixinVaultCore.json",
|
|
||||||
"generated-artifacts/ReadOnlyProxy.json",
|
"generated-artifacts/ReadOnlyProxy.json",
|
||||||
"generated-artifacts/Staking.json",
|
"generated-artifacts/Staking.json",
|
||||||
"generated-artifacts/StakingProxy.json",
|
"generated-artifacts/StakingProxy.json",
|
||||||
@ -46,7 +44,6 @@
|
|||||||
"generated-artifacts/TestLibProxy.json",
|
"generated-artifacts/TestLibProxy.json",
|
||||||
"generated-artifacts/TestLibProxyReceiver.json",
|
"generated-artifacts/TestLibProxyReceiver.json",
|
||||||
"generated-artifacts/TestLibSafeDowncast.json",
|
"generated-artifacts/TestLibSafeDowncast.json",
|
||||||
"generated-artifacts/TestMixinVaultCore.json",
|
|
||||||
"generated-artifacts/TestProtocolFees.json",
|
"generated-artifacts/TestProtocolFees.json",
|
||||||
"generated-artifacts/TestStaking.json",
|
"generated-artifacts/TestStaking.json",
|
||||||
"generated-artifacts/TestStakingNoWETH.json",
|
"generated-artifacts/TestStakingNoWETH.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user