diff --git a/contracts/staking/contracts/src/vaults/ZrxVault.sol b/contracts/staking/contracts/src/ZrxVault.sol similarity index 62% rename from contracts/staking/contracts/src/vaults/ZrxVault.sol rename to contracts/staking/contracts/src/ZrxVault.sol index 3f2652d13e..7945568ae3 100644 --- a/contracts/staking/contracts/src/vaults/ZrxVault.sol +++ b/contracts/staking/contracts/src/ZrxVault.sol @@ -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()); + } + } } diff --git a/contracts/staking/contracts/src/interfaces/IVaultCore.sol b/contracts/staking/contracts/src/interfaces/IVaultCore.sol deleted file mode 100644 index fcae080d59..0000000000 --- a/contracts/staking/contracts/src/interfaces/IVaultCore.sol +++ /dev/null @@ -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; -} diff --git a/contracts/staking/contracts/src/interfaces/IZrxVault.sol b/contracts/staking/contracts/src/interfaces/IZrxVault.sol index e9cc497f5b..4d6c7ba5c4 100644 --- a/contracts/staking/contracts/src/interfaces/IZrxVault.sol +++ b/contracts/staking/contracts/src/interfaces/IZrxVault.sol @@ -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. diff --git a/contracts/staking/contracts/src/vaults/MixinVaultCore.sol b/contracts/staking/contracts/src/vaults/MixinVaultCore.sol deleted file mode 100644 index 728a69fc16..0000000000 --- a/contracts/staking/contracts/src/vaults/MixinVaultCore.sol +++ /dev/null @@ -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); - } -} diff --git a/contracts/staking/contracts/test/TestMixinVaultCore.sol b/contracts/staking/contracts/test/TestMixinVaultCore.sol deleted file mode 100644 index 56a4ccd6c1..0000000000 --- a/contracts/staking/contracts/test/TestMixinVaultCore.sol +++ /dev/null @@ -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 - {} -} diff --git a/contracts/staking/package.json b/contracts/staking/package.json index 8514ce007b..0889a633e4 100644 --- a/contracts/staking/package.json +++ b/contracts/staking/package.json @@ -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", diff --git a/contracts/staking/src/artifacts.ts b/contracts/staking/src/artifacts.ts index 62ab2cb6e8..c7c7930e16 100644 --- a/contracts/staking/src/artifacts.ts +++ b/contracts/staking/src/artifacts.ts @@ -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, diff --git a/contracts/staking/src/wrappers.ts b/contracts/staking/src/wrappers.ts index 4758a2f5f8..a36e5b2651 100644 --- a/contracts/staking/src/wrappers.ts +++ b/contracts/staking/src/wrappers.ts @@ -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'; diff --git a/contracts/staking/test/codesize.ts b/contracts/staking/test/codesize_test.ts similarity index 100% rename from contracts/staking/test/codesize.ts rename to contracts/staking/test/codesize_test.ts diff --git a/contracts/staking/test/layout_and_constant_regression_prevention.ts b/contracts/staking/test/layout_and_constant_regression_test.ts similarity index 100% rename from contracts/staking/test/layout_and_constant_regression_prevention.ts rename to contracts/staking/test/layout_and_constant_regression_test.ts diff --git a/contracts/staking/test/migration.ts b/contracts/staking/test/migration_test.ts similarity index 100% rename from contracts/staking/test/migration.ts rename to contracts/staking/test/migration_test.ts diff --git a/contracts/staking/test/simulations_test.ts b/contracts/staking/test/simulations_test.ts deleted file mode 100644 index e32224998e..0000000000 --- a/contracts/staking/test/simulations_test.ts +++ /dev/null @@ -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 -*/ diff --git a/contracts/staking/test/unit_tests/delegator_reward_test.ts b/contracts/staking/test/unit_tests/delegator_reward_test.ts index 9c316a8ebb..0113edc223 100644 --- a/contracts/staking/test/unit_tests/delegator_reward_test.ts +++ b/contracts/staking/test/unit_tests/delegator_reward_test.ts @@ -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 () => { diff --git a/contracts/staking/test/unit_tests/finalizer_test.ts b/contracts/staking/test/unit_tests/finalizer_test.ts index ea86acb7f3..50448f5063 100644 --- a/contracts/staking/test/unit_tests/finalizer_test.ts +++ b/contracts/staking/test/unit_tests/finalizer_test.ts @@ -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); diff --git a/contracts/staking/test/cobb_douglas.ts b/contracts/staking/test/unit_tests/lib_cobb_douglas_test.ts similarity index 98% rename from contracts/staking/test/cobb_douglas.ts rename to contracts/staking/test/unit_tests/lib_cobb_douglas_test.ts index 4981882e04..e0080e560e 100644 --- a/contracts/staking/test/cobb_douglas.ts +++ b/contracts/staking/test/unit_tests/lib_cobb_douglas_test.ts @@ -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; diff --git a/contracts/staking/test/unit_tests/lib_fixed_math.ts b/contracts/staking/test/unit_tests/lib_fixed_math_test.ts similarity index 99% rename from contracts/staking/test/unit_tests/lib_fixed_math.ts rename to contracts/staking/test/unit_tests/lib_fixed_math_test.ts index a9bd912c86..5e1eae7aff 100644 --- a/contracts/staking/test/unit_tests/lib_fixed_math.ts +++ b/contracts/staking/test/unit_tests/lib_fixed_math_test.ts @@ -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 () => { diff --git a/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts b/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts index 34e425c1da..4efaa03b8b 100644 --- a/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts +++ b/contracts/staking/test/unit_tests/lib_proxy_unit_test.ts @@ -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; diff --git a/contracts/staking/test/unit_tests/lib_safe_downcast_test.ts b/contracts/staking/test/unit_tests/lib_safe_downcast_test.ts index 359450e29b..9a3ac8931e 100644 --- a/contracts/staking/test/unit_tests/lib_safe_downcast_test.ts +++ b/contracts/staking/test/unit_tests/lib_safe_downcast_test.ts @@ -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 () => { diff --git a/contracts/staking/test/unit_tests/mixin_vault_core_test.ts b/contracts/staking/test/unit_tests/mixin_vault_core_test.ts deleted file mode 100644 index 5c25aac1a6..0000000000 --- a/contracts/staking/test/unit_tests/mixin_vault_core_test.ts +++ /dev/null @@ -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 { - 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( - 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 { - 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( - 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); - }); - }); -}); diff --git a/contracts/staking/test/params.ts b/contracts/staking/test/unit_tests/params_test.ts similarity index 96% rename from contracts/staking/test/params.ts rename to contracts/staking/test/unit_tests/params_test.ts index bca685c1f3..82b1d62c92 100644 --- a/contracts/staking/test/params.ts +++ b/contracts/staking/test/unit_tests/params_test.ts @@ -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; diff --git a/contracts/staking/test/protocol_fees.ts b/contracts/staking/test/unit_tests/protocol_fees_test.ts similarity index 99% rename from contracts/staking/test/protocol_fees.ts rename to contracts/staking/test/unit_tests/protocol_fees_test.ts index c77c863f75..a89a0f4d74 100644 --- a/contracts/staking/test/protocol_fees.ts +++ b/contracts/staking/test/unit_tests/protocol_fees_test.ts @@ -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; diff --git a/contracts/staking/test/unit_tests/zrx_vault_test.ts b/contracts/staking/test/unit_tests/zrx_vault_test.ts new file mode 100644 index 0000000000..84c0292b6d --- /dev/null +++ b/contracts/staking/test/unit_tests/zrx_vault_test.ts @@ -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 { + const eventArgs = + transferType === ZrxTransfer.Deposit + ? filterLogsToArguments(receipt.logs, 'Deposit') + : filterLogsToArguments(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 { + const eventArgs = filterLogsToArguments( + 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(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(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 { + const eventArgs = filterLogsToArguments( + 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, + ); + }); + }); + }); + }); +}); diff --git a/contracts/staking/test/utils/Simulation.ts b/contracts/staking/test/utils/Simulation.ts deleted file mode 100644 index 806c24dfb0..0000000000 --- a/contracts/staking/test/utils/Simulation.ts +++ /dev/null @@ -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; - 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(); - this._poolOperators = []; - this._poolOperatorsAsDelegators = []; - this._poolIds = []; - this._makers = []; - this._delegators = []; - } - - public async runAsync(): Promise { - this._userQueue = new Queue(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 { - 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 { - 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 { - // 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 { - // 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 { - // 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 { - // 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 { - // 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 { - // 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`); - } - } -} -*/ diff --git a/contracts/staking/tsconfig.json b/contracts/staking/tsconfig.json index e713b1ba5a..466ca080b3 100644 --- a/contracts/staking/tsconfig.json +++ b/contracts/staking/tsconfig.json @@ -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",