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;
|
||||
|
||||
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-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
|
||||
import "../interfaces/IZrxVault.sol";
|
||||
import "./MixinVaultCore.sol";
|
||||
import "./libs/LibStakingRichErrors.sol";
|
||||
import "./interfaces/IZrxVault.sol";
|
||||
|
||||
|
||||
/// @dev This vault manages Zrx Tokens.
|
||||
/// 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.
|
||||
/// There is a "Catastrophic Failure Mode" that, when invoked, only
|
||||
/// allows withdrawals to be made. Once this vault is in catastrophic
|
||||
/// failure mode, it cannot be returned to normal mode; this prevents
|
||||
/// corruption of related state in the staking contract.
|
||||
/// The contract also includes management of the staking contract
|
||||
/// and setting the vault to "Catastrophic Failure Mode".
|
||||
/// Catastrophic Failure Mode should only be set 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)`. 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
|
||||
IZrxVault,
|
||||
MixinVaultCore
|
||||
Authorizable,
|
||||
IZrxVault
|
||||
{
|
||||
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;
|
||||
|
||||
// Zrx Asset Proxy
|
||||
@ -59,7 +72,10 @@ contract ZrxVault is
|
||||
address _zrxTokenAddress
|
||||
)
|
||||
public
|
||||
Authorizable()
|
||||
{
|
||||
_addAuthorizedAddress(owner);
|
||||
|
||||
zrxAssetProxy = IAssetProxy(_zrxProxyAddress);
|
||||
_zrxToken = IERC20Token(_zrxTokenAddress);
|
||||
_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.
|
||||
/// Note that only an authorized address can call this function.
|
||||
/// Note that this can only be called when *not* in Catastrophic Failure mode.
|
||||
@ -165,4 +203,48 @@ contract ZrxVault is
|
||||
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.
|
||||
/// 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.
|
||||
/// There is a "Catastrophic Failure Mode" that, when invoked, only
|
||||
/// allows withdrawals to be made. Once this vault is in catastrophic
|
||||
/// failure mode, it cannot be returned to normal mode; this prevents
|
||||
/// corruption of related state in the staking contract.
|
||||
/// The contract also includes management of the staking contract
|
||||
/// and setting the vault to "Catastrophic Failure Mode".
|
||||
/// Catastrophic Failure Mode should only be set 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)`. 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 {
|
||||
|
||||
/// @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.
|
||||
/// @param staker of Zrx Tokens.
|
||||
/// @param amount of Zrx Tokens deposited.
|
||||
@ -47,6 +59,18 @@ interface IZrxVault {
|
||||
/// @dev Emitted whenever the ZRX AssetProxy is set.
|
||||
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.
|
||||
/// Note that only the contract staker can call this.
|
||||
/// 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": {
|
||||
"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": {
|
||||
"type": "git",
|
||||
|
@ -11,7 +11,6 @@ import * as IStakingProxy from '../generated-artifacts/IStakingProxy.json';
|
||||
import * as IStorage from '../generated-artifacts/IStorage.json';
|
||||
import * as IStorageInit from '../generated-artifacts/IStorageInit.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 LibCobbDouglas from '../generated-artifacts/LibCobbDouglas.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 MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.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 Staking from '../generated-artifacts/Staking.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 TestLibProxyReceiver from '../generated-artifacts/TestLibProxyReceiver.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 TestStaking from '../generated-artifacts/TestStaking.json';
|
||||
import * as TestStakingNoWETH from '../generated-artifacts/TestStakingNoWETH.json';
|
||||
@ -59,6 +56,7 @@ export const artifacts = {
|
||||
ReadOnlyProxy: ReadOnlyProxy as ContractArtifact,
|
||||
Staking: Staking as ContractArtifact,
|
||||
StakingProxy: StakingProxy as ContractArtifact,
|
||||
ZrxVault: ZrxVault as ContractArtifact,
|
||||
MixinExchangeFees: MixinExchangeFees as ContractArtifact,
|
||||
MixinExchangeManager: MixinExchangeManager as ContractArtifact,
|
||||
MixinConstants: MixinConstants as ContractArtifact,
|
||||
@ -70,7 +68,6 @@ export const artifacts = {
|
||||
IStorage: IStorage as ContractArtifact,
|
||||
IStorageInit: IStorageInit as ContractArtifact,
|
||||
IStructs: IStructs as ContractArtifact,
|
||||
IVaultCore: IVaultCore as ContractArtifact,
|
||||
IZrxVault: IZrxVault as ContractArtifact,
|
||||
LibCobbDouglas: LibCobbDouglas as ContractArtifact,
|
||||
LibFixedMath: LibFixedMath as ContractArtifact,
|
||||
@ -88,8 +85,6 @@ export const artifacts = {
|
||||
MixinFinalizer: MixinFinalizer as ContractArtifact,
|
||||
MixinParams: MixinParams as ContractArtifact,
|
||||
MixinScheduler: MixinScheduler as ContractArtifact,
|
||||
MixinVaultCore: MixinVaultCore as ContractArtifact,
|
||||
ZrxVault: ZrxVault as ContractArtifact,
|
||||
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
|
||||
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
||||
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
|
||||
@ -100,7 +95,6 @@ export const artifacts = {
|
||||
TestLibProxy: TestLibProxy as ContractArtifact,
|
||||
TestLibProxyReceiver: TestLibProxyReceiver as ContractArtifact,
|
||||
TestLibSafeDowncast: TestLibSafeDowncast as ContractArtifact,
|
||||
TestMixinVaultCore: TestMixinVaultCore as ContractArtifact,
|
||||
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
||||
TestStaking: TestStaking 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_init';
|
||||
export * from '../generated-wrappers/i_structs';
|
||||
export * from '../generated-wrappers/i_vault_core';
|
||||
export * from '../generated-wrappers/i_zrx_vault';
|
||||
export * from '../generated-wrappers/lib_cobb_douglas';
|
||||
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_rewards';
|
||||
export * from '../generated-wrappers/mixin_storage';
|
||||
export * from '../generated-wrappers/mixin_vault_core';
|
||||
export * from '../generated-wrappers/read_only_proxy';
|
||||
export * from '../generated-wrappers/staking';
|
||||
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_receiver';
|
||||
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_staking';
|
||||
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,
|
||||
} from '../utils/number_utils';
|
||||
|
||||
blockchainTests.resets('delegator unit rewards', env => {
|
||||
blockchainTests.resets('Delegator rewards unit tests', env => {
|
||||
let testContract: TestDelegatorRewardsContract;
|
||||
|
||||
before(async () => {
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
} from '../../src';
|
||||
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 INITIAL_EPOCH = 0;
|
||||
const INITIAL_BALANCE = toBaseUnitAmount(32);
|
||||
|
@ -2,12 +2,12 @@ import { blockchainTests, Numberish } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
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
|
||||
blockchainTests('Cobb-Douglas', env => {
|
||||
blockchainTests('LibCobbDouglas unit tests', env => {
|
||||
const FUZZ_COUNT = 1024;
|
||||
const PRECISION = 15;
|
||||
|
@ -7,7 +7,7 @@ import { artifacts, TestLibFixedMathContract } from '../../src';
|
||||
|
||||
import { assertRoughlyEquals, fromFixed, toDecimal, toFixed } from '../utils/number_utils';
|
||||
|
||||
blockchainTests('LibFixedMath', env => {
|
||||
blockchainTests('LibFixedMath unit tests', env => {
|
||||
let testContract: TestLibFixedMathContract;
|
||||
|
||||
before(async () => {
|
@ -4,7 +4,7 @@ import { cartesianProduct } from 'js-combinatorics';
|
||||
|
||||
import { artifacts, TestLibProxyContract, TestLibProxyReceiverContract } from '../../src';
|
||||
|
||||
blockchainTests.resets('LibProxy', env => {
|
||||
blockchainTests.resets('LibProxy unit tests', env => {
|
||||
let proxy: TestLibProxyContract;
|
||||
let receiver: TestLibProxyReceiverContract;
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { BigNumber, SafeMathRevertErrors } from '@0x/utils';
|
||||
|
||||
import { artifacts, TestLibSafeDowncastContract } from '../../src/';
|
||||
|
||||
blockchainTests('LibSafeDowncast', env => {
|
||||
blockchainTests('LibSafeDowncast unit tests', env => {
|
||||
let testContract: TestLibSafeDowncastContract;
|
||||
|
||||
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 * as _ from 'lodash';
|
||||
|
||||
import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from '../src/';
|
||||
import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from '../../src/';
|
||||
|
||||
import { constants as stakingConstants } from './utils/constants';
|
||||
import { StakingParams } from './utils/types';
|
||||
import { constants as stakingConstants } from '../utils/constants';
|
||||
import { StakingParams } from '../utils/types';
|
||||
|
||||
blockchainTests('Configurable Parameters unit tests', env => {
|
||||
let testContract: MixinParamsContract;
|
@ -19,11 +19,11 @@ import {
|
||||
TestProtocolFeesContract,
|
||||
TestProtocolFeesERC20ProxyTransferFromEventArgs,
|
||||
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 exchangeAddress: 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/IStorageInit.json",
|
||||
"generated-artifacts/IStructs.json",
|
||||
"generated-artifacts/IVaultCore.json",
|
||||
"generated-artifacts/IZrxVault.json",
|
||||
"generated-artifacts/LibCobbDouglas.json",
|
||||
"generated-artifacts/LibFixedMath.json",
|
||||
@ -32,7 +31,6 @@
|
||||
"generated-artifacts/MixinStakingPool.json",
|
||||
"generated-artifacts/MixinStakingPoolRewards.json",
|
||||
"generated-artifacts/MixinStorage.json",
|
||||
"generated-artifacts/MixinVaultCore.json",
|
||||
"generated-artifacts/ReadOnlyProxy.json",
|
||||
"generated-artifacts/Staking.json",
|
||||
"generated-artifacts/StakingProxy.json",
|
||||
@ -46,7 +44,6 @@
|
||||
"generated-artifacts/TestLibProxy.json",
|
||||
"generated-artifacts/TestLibProxyReceiver.json",
|
||||
"generated-artifacts/TestLibSafeDowncast.json",
|
||||
"generated-artifacts/TestMixinVaultCore.json",
|
||||
"generated-artifacts/TestProtocolFees.json",
|
||||
"generated-artifacts/TestStaking.json",
|
||||
"generated-artifacts/TestStakingNoWETH.json",
|
||||
|
Loading…
x
Reference in New Issue
Block a user