Merge pull request #2178 from 0xProject/feat/3.0/upgradability-controls
Upgradability controls
This commit is contained in:
@@ -54,7 +54,7 @@ contract Staking is
|
||||
address _zrxVaultAddress
|
||||
)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
// DANGER! When performing upgrades, take care to modify this logic
|
||||
// to prevent accidentally clearing prior state.
|
||||
|
||||
@@ -90,7 +90,7 @@ contract StakingProxy is
|
||||
address _zrxVaultAddress
|
||||
)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
_attachStakingContract(
|
||||
_stakingContract,
|
||||
@@ -105,7 +105,7 @@ contract StakingProxy is
|
||||
/// Note that this is callable only by this contract's owner.
|
||||
function detachStakingContract()
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
stakingContract = NIL_ADDRESS;
|
||||
emit StakingContractDetachedFromProxy();
|
||||
@@ -114,7 +114,7 @@ contract StakingProxy is
|
||||
/// @dev Set read-only mode (state cannot be changed).
|
||||
function setReadOnlyMode(bool readOnlyMode)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
if (readOnlyMode) {
|
||||
stakingContract = readOnlyProxy;
|
||||
@@ -164,6 +164,79 @@ contract StakingProxy is
|
||||
return batchReturnData;
|
||||
}
|
||||
|
||||
/// @dev Asserts that an epoch is between 5 and 30 days long.
|
||||
// Asserts that 0 < cobb douglas alpha value <= 1.
|
||||
// Asserts that a stake weight is <= 100%.
|
||||
// Asserts that pools allow >= 1 maker.
|
||||
// Asserts that all addresses are initialized.
|
||||
function _assertValidStorageParams()
|
||||
internal
|
||||
view
|
||||
{
|
||||
// Epoch length must be between 5 and 30 days long
|
||||
uint256 _epochDurationInSeconds = epochDurationInSeconds;
|
||||
if (_epochDurationInSeconds < 5 days || _epochDurationInSeconds > 30 days) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidEpochDuration
|
||||
));
|
||||
}
|
||||
|
||||
// Alpha must be 0 < x <= 1
|
||||
uint32 _cobbDouglasAlphaDenominator = cobbDouglasAlphaDenominator;
|
||||
if (cobbDouglasAlphaNumerator > _cobbDouglasAlphaDenominator || _cobbDouglasAlphaDenominator == 0) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha
|
||||
));
|
||||
}
|
||||
|
||||
// Weight of delegated stake must be <= 100%
|
||||
if (rewardDelegatedStakeWeight > PPM_DENOMINATOR) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidRewardDelegatedStakeWeight
|
||||
));
|
||||
}
|
||||
|
||||
// Pools must allow at least one maker
|
||||
if (maximumMakersInPool == 0) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidMaximumMakersInPool
|
||||
));
|
||||
}
|
||||
|
||||
// ERC20Proxy and Vault contract addresses must always be initialized
|
||||
if (address(wethAssetProxy) == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidWethProxyAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (address(ethVault) == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidEthVaultAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (address(rewardVault) == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidRewardVaultAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (address(zrxVault) == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidZrxVaultAddress
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Attach a staking contract; future calls will be delegated to the staking contract.
|
||||
/// @param _stakingContract Address of staking contract.
|
||||
/// @param _wethProxyAddress The address that can transfer WETH for fees.
|
||||
@@ -177,7 +250,7 @@ contract StakingProxy is
|
||||
address _rewardVaultAddress,
|
||||
address _zrxVaultAddress
|
||||
)
|
||||
private
|
||||
internal
|
||||
{
|
||||
// Attach the staking contract
|
||||
stakingContract = readOnlyProxyCallee = _stakingContract;
|
||||
@@ -198,5 +271,8 @@ contract StakingProxy is
|
||||
revert(add(initReturnData, 0x20), mload(initReturnData))
|
||||
}
|
||||
}
|
||||
|
||||
// Assert initialized storage values are valid
|
||||
_assertValidStorageParams();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ contract MixinExchangeManager is
|
||||
/// @param addr Address of exchange contract to add
|
||||
function addExchangeAddress(address addr)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
if (validExchanges[addr]) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.ExchangeAddressAlreadyRegisteredError(
|
||||
@@ -61,7 +61,7 @@ contract MixinExchangeManager is
|
||||
/// @param addr Address of exchange contract to remove
|
||||
function removeExchangeAddress(address addr)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
if (!validExchanges[addr]) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.ExchangeAddressNotRegisteredError(
|
||||
|
||||
@@ -21,7 +21,7 @@ pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
|
||||
import "./MixinConstants.sol";
|
||||
import "../interfaces/IZrxVault.sol";
|
||||
import "../interfaces/IEthVault.sol";
|
||||
@@ -33,7 +33,7 @@ import "../libs/LibStakingRichErrors.sol";
|
||||
// solhint-disable max-states-count, no-empty-blocks
|
||||
contract MixinStorage is
|
||||
MixinConstants,
|
||||
Ownable
|
||||
Authorizable
|
||||
{
|
||||
// WETH Asset Proxy
|
||||
IAssetProxy public wethAssetProxy;
|
||||
@@ -134,4 +134,12 @@ contract MixinStorage is
|
||||
|
||||
// Denominator for cobb douglas alpha factor.
|
||||
uint32 public cobbDouglasAlphaDenominator;
|
||||
|
||||
/// @dev Adds owner as an authorized address.
|
||||
constructor()
|
||||
public
|
||||
Authorizable()
|
||||
{
|
||||
_addAuthorizedAddress(owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ library LibStakingRichErrors {
|
||||
InvalidWethProxyAddress,
|
||||
InvalidEthVaultAddress,
|
||||
InvalidRewardVaultAddress,
|
||||
InvalidZrxVaultAddress
|
||||
InvalidZrxVaultAddress,
|
||||
InvalidEpochDuration
|
||||
}
|
||||
|
||||
enum MakerPoolAssignmentErrorCodes {
|
||||
|
||||
@@ -177,7 +177,9 @@ contract MixinStakingPoolMakers is
|
||||
}
|
||||
|
||||
// Is the pool already full?
|
||||
if (pool.numberOfMakers == maximumMakersInPool) {
|
||||
// NOTE: If maximumMakersInPool is decreased below the number of makers currently in a pool,
|
||||
// the pool will no longer be able to add more makers.
|
||||
if (pool.numberOfMakers >= maximumMakersInPool) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
|
||||
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.PoolIsFull,
|
||||
makerAddress,
|
||||
|
||||
@@ -56,7 +56,7 @@ contract MixinParams is
|
||||
address _zrxVaultAddress
|
||||
)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
_setParams(
|
||||
_epochDurationInSeconds,
|
||||
@@ -130,7 +130,7 @@ contract MixinParams is
|
||||
// Set up defaults.
|
||||
// These cannot be set to variables, or we go over the stack variable limit.
|
||||
_setParams(
|
||||
2 weeks, // epochDurationInSeconds
|
||||
10 days, // epochDurationInSeconds
|
||||
(90 * PPM_DENOMINATOR) / 100, // rewardDelegatedStakeWeight
|
||||
100 * MIN_TOKEN_VALUE, // minimumPoolStake
|
||||
10, // maximumMakersInPool
|
||||
@@ -192,20 +192,6 @@ contract MixinParams is
|
||||
)
|
||||
private
|
||||
{
|
||||
_assertValidRewardDelegatedStakeWeight(_rewardDelegatedStakeWeight);
|
||||
_assertValidMaximumMakersInPool(_maximumMakersInPool);
|
||||
_assertValidCobbDouglasAlpha(
|
||||
_cobbDouglasAlphaNumerator,
|
||||
_cobbDouglasAlphaDenominator
|
||||
);
|
||||
_assertValidAddresses(
|
||||
_wethProxyAddress,
|
||||
_ethVaultAddress,
|
||||
_rewardVaultAddress,
|
||||
_zrxVaultAddress
|
||||
);
|
||||
// TODO: set boundaries on some of these params
|
||||
|
||||
epochDurationInSeconds = _epochDurationInSeconds;
|
||||
rewardDelegatedStakeWeight = _rewardDelegatedStakeWeight;
|
||||
minimumPoolStake = _minimumPoolStake;
|
||||
@@ -230,98 +216,4 @@ contract MixinParams is
|
||||
_zrxVaultAddress
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Asserts that cobb douglas alpha values are valid.
|
||||
/// @param numerator Numerator for cobb douglas alpha factor.
|
||||
/// @param denominator Denominator for cobb douglas alpha factor.
|
||||
function _assertValidCobbDouglasAlpha(
|
||||
uint32 numerator,
|
||||
uint32 denominator
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
// Alpha must be 0 < x < 1
|
||||
if (numerator > denominator || denominator == 0) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Asserts that a stake weight is valid.
|
||||
/// @param weight How much delegated stake is weighted vs operator stake, in ppm.
|
||||
function _assertValidRewardDelegatedStakeWeight(
|
||||
uint32 weight
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
if (weight > PPM_DENOMINATOR) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidRewardDelegatedStakeWeight
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Asserts that a maximum makers value is valid.
|
||||
/// @param amount Maximum number of maker addresses allowed to be registered to a pool.
|
||||
function _assertValidMaximumMakersInPool(
|
||||
uint256 amount
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
if (amount == 0) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidMaximumMakersInPool
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Asserts that passed in addresses are non-zero.
|
||||
/// @param _wethProxyAddress The address that can transfer WETH for fees.
|
||||
/// @param _ethVaultAddress Address of the EthVault contract.
|
||||
/// @param _rewardVaultAddress Address of the StakingPoolRewardVault contract.
|
||||
/// @param _zrxVaultAddress Address of the ZrxVault contract.
|
||||
function _assertValidAddresses(
|
||||
address _wethProxyAddress,
|
||||
address _ethVaultAddress,
|
||||
address _rewardVaultAddress,
|
||||
address _zrxVaultAddress
|
||||
)
|
||||
private
|
||||
pure
|
||||
{
|
||||
if (_wethProxyAddress == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidWethProxyAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (_ethVaultAddress == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidEthVaultAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (_rewardVaultAddress == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidRewardVaultAddress
|
||||
));
|
||||
}
|
||||
|
||||
if (_zrxVaultAddress == NIL_ADDRESS) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidParamValueError(
|
||||
LibStakingRichErrors.InvalidParamValueErrorCode.InvalidZrxVaultAddress
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "../libs/LibStakingRichErrors.sol";
|
||||
import "../interfaces/IVaultCore.sol";
|
||||
@@ -36,7 +36,7 @@ import "../interfaces/IVaultCore.sol";
|
||||
/// a vault cannot be reset to normal mode; this prevents corruption of related
|
||||
/// status in the staking contract.
|
||||
contract MixinVaultCore is
|
||||
Ownable,
|
||||
Authorizable,
|
||||
IVaultCore
|
||||
{
|
||||
// Address of staking contract
|
||||
@@ -71,12 +71,20 @@ contract MixinVaultCore is
|
||||
_;
|
||||
}
|
||||
|
||||
/// @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 the contract owner can call this function.
|
||||
/// @param _stakingProxyAddress Address of Staking proxy contract.
|
||||
function setStakingProxy(address payable _stakingProxyAddress)
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
stakingProxyAddress = _stakingProxyAddress;
|
||||
emit StakingProxySet(_stakingProxyAddress);
|
||||
@@ -87,7 +95,7 @@ contract MixinVaultCore is
|
||||
/// Note that only the contract owner can call this function.
|
||||
function enterCatastrophicFailure()
|
||||
external
|
||||
onlyOwner
|
||||
onlyAuthorized
|
||||
{
|
||||
isInCatastrophicFailure = true;
|
||||
emit InCatastrophicFailureMode(msg.sender);
|
||||
|
||||
71
contracts/staking/contracts/test/TestAssertStorageParams.sol
Normal file
71
contracts/staking/contracts/test/TestAssertStorageParams.sol
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/StakingProxy.sol";
|
||||
|
||||
|
||||
contract TestAssertStorageParams is
|
||||
StakingProxy
|
||||
{
|
||||
struct StorageParams {
|
||||
uint256 epochDurationInSeconds;
|
||||
uint32 rewardDelegatedStakeWeight;
|
||||
uint256 minimumPoolStake;
|
||||
uint256 maximumMakersInPool;
|
||||
uint32 cobbDouglasAlphaNumerator;
|
||||
uint32 cobbDouglasAlphaDenominator;
|
||||
address wethProxyAddress;
|
||||
address ethVaultAddress;
|
||||
address rewardVaultAddress;
|
||||
address zrxVaultAddress;
|
||||
}
|
||||
|
||||
constructor()
|
||||
public
|
||||
StakingProxy(
|
||||
NIL_ADDRESS,
|
||||
NIL_ADDRESS,
|
||||
NIL_ADDRESS,
|
||||
NIL_ADDRESS,
|
||||
NIL_ADDRESS,
|
||||
NIL_ADDRESS
|
||||
)
|
||||
{}
|
||||
|
||||
function setAndAssertParams(StorageParams memory params)
|
||||
public
|
||||
{
|
||||
epochDurationInSeconds = params.epochDurationInSeconds;
|
||||
rewardDelegatedStakeWeight = params.rewardDelegatedStakeWeight;
|
||||
minimumPoolStake = params.minimumPoolStake;
|
||||
maximumMakersInPool = params.maximumMakersInPool;
|
||||
cobbDouglasAlphaNumerator = params.cobbDouglasAlphaNumerator;
|
||||
cobbDouglasAlphaDenominator = params.cobbDouglasAlphaDenominator;
|
||||
wethAssetProxy = IAssetProxy(params.wethProxyAddress);
|
||||
ethVault = IEthVault(params.ethVaultAddress);
|
||||
rewardVault = IStakingPoolRewardVault(params.rewardVaultAddress);
|
||||
zrxVault = IZrxVault(params.zrxVaultAddress);
|
||||
_assertValidStorageParams();
|
||||
}
|
||||
|
||||
function _attachStakingContract(address, address, address, address, address)
|
||||
internal
|
||||
{}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ contract TestInitTarget is
|
||||
external
|
||||
{
|
||||
if (SHOULD_REVERT_ADDRESS.balance != 0) {
|
||||
revert("FORCED_REVERT");
|
||||
revert("FORCED_INIT_REVERT");
|
||||
}
|
||||
_initCounter += 1;
|
||||
_initSender = msg.sender;
|
||||
|
||||
@@ -24,6 +24,8 @@ import "../src/StakingProxy.sol";
|
||||
contract TestStakingProxy is
|
||||
StakingProxy
|
||||
{
|
||||
address public constant SHOULD_REVERT_STAKING_ADDRESS = 0x5ed6A38c6bEcEd15b0AB58566b6fD7A00463d2F7;
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
constructor(address _stakingContract)
|
||||
public
|
||||
@@ -50,4 +52,14 @@ contract TestStakingProxy is
|
||||
rewardVault = IStakingPoolRewardVault(_rewardVaultAddress);
|
||||
zrxVault = IZrxVault(_zrxVaultAddress);
|
||||
}
|
||||
|
||||
function _assertValidStorageParams()
|
||||
internal
|
||||
view
|
||||
{
|
||||
require(
|
||||
stakingContract != SHOULD_REVERT_STAKING_ADDRESS,
|
||||
"FORCED_STORAGE_PARAMS_REVERT"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"config": {
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStorage|IStorageInit|IStructs|IVaultCore|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolMakers|MixinStakingPoolModifiers|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestCumulativeRewardTracking|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinVaultCore|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStakingProxy|TestStorageLayout|ZrxVault).json"
|
||||
"abis": "./generated-artifacts/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStorage|IStorageInit|IStructs|IVaultCore|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolMakers|MixinStakingPoolModifiers|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|ReadOnlyProxy|Staking|StakingPoolRewardVault|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinVaultCore|TestProtocolFees|TestProtocolFeesERC20Proxy|TestStaking|TestStakingProxy|TestStorageLayout|ZrxVault).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -42,6 +42,7 @@ import * as ReadOnlyProxy from '../generated-artifacts/ReadOnlyProxy.json';
|
||||
import * as Staking from '../generated-artifacts/Staking.json';
|
||||
import * as StakingPoolRewardVault from '../generated-artifacts/StakingPoolRewardVault.json';
|
||||
import * as StakingProxy from '../generated-artifacts/StakingProxy.json';
|
||||
import * as TestAssertStorageParams from '../generated-artifacts/TestAssertStorageParams.json';
|
||||
import * as TestCobbDouglas from '../generated-artifacts/TestCobbDouglas.json';
|
||||
import * as TestCumulativeRewardTracking from '../generated-artifacts/TestCumulativeRewardTracking.json';
|
||||
import * as TestInitTarget from '../generated-artifacts/TestInitTarget.json';
|
||||
@@ -95,6 +96,7 @@ export const artifacts = {
|
||||
MixinVaultCore: MixinVaultCore as ContractArtifact,
|
||||
StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
|
||||
ZrxVault: ZrxVault as ContractArtifact,
|
||||
TestAssertStorageParams: TestAssertStorageParams as ContractArtifact,
|
||||
TestCobbDouglas: TestCobbDouglas as ContractArtifact,
|
||||
TestCumulativeRewardTracking: TestCumulativeRewardTracking as ContractArtifact,
|
||||
TestInitTarget: TestInitTarget as ContractArtifact,
|
||||
|
||||
@@ -40,6 +40,7 @@ export * from '../generated-wrappers/read_only_proxy';
|
||||
export * from '../generated-wrappers/staking';
|
||||
export * from '../generated-wrappers/staking_pool_reward_vault';
|
||||
export * from '../generated-wrappers/staking_proxy';
|
||||
export * from '../generated-wrappers/test_assert_storage_params';
|
||||
export * from '../generated-wrappers/test_cobb_douglas';
|
||||
export * from '../generated-wrappers/test_cumulative_reward_tracking';
|
||||
export * from '../generated-wrappers/test_init_target';
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import { blockchainTests, constants, expect, filterLogsToArguments, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber, OwnableRevertErrors, StringRevertError } from '@0x/utils';
|
||||
import { AuthorizableRevertErrors, BigNumber, StringRevertError } from '@0x/utils';
|
||||
|
||||
import {
|
||||
artifacts,
|
||||
StakingContract,
|
||||
StakingProxyContract,
|
||||
TestAssertStorageParamsContract,
|
||||
TestInitTargetContract,
|
||||
TestInitTargetInitAddressesEventArgs,
|
||||
TestStakingProxyContract,
|
||||
TestStakingProxyStakingContractAttachedToProxyEventArgs,
|
||||
} from '../src/';
|
||||
|
||||
import { constants as stakingConstants } from './utils/constants';
|
||||
|
||||
blockchainTests('Migration tests', env => {
|
||||
let ownerAddress: string;
|
||||
let notOwnerAddress: string;
|
||||
let authorizedAddress: string;
|
||||
let notAuthorizedAddress: string;
|
||||
|
||||
let stakingContract: StakingContract;
|
||||
|
||||
before(async () => {
|
||||
[ownerAddress, notOwnerAddress] = await env.getAccountAddressesAsync();
|
||||
[authorizedAddress, notAuthorizedAddress] = await env.getAccountAddressesAsync();
|
||||
stakingContract = await StakingContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Staking,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('StakingProxy', () => {
|
||||
const REVERT_ERROR = new StringRevertError('FORCED_REVERT');
|
||||
const INIT_REVERT_ERROR = new StringRevertError('FORCED_INIT_REVERT');
|
||||
const STORAGE_PARAMS_REVERT_ERROR = new StringRevertError('FORCED_STORAGE_PARAMS_REVERT');
|
||||
let initTargetContract: TestInitTargetContract;
|
||||
let revertAddress: string;
|
||||
|
||||
async function deployStakingProxyAsync(stakingContractAddress?: string): Promise<TestStakingProxyContract> {
|
||||
return TestStakingProxyContract.deployFrom0xArtifactAsync(
|
||||
@@ -34,23 +48,23 @@ blockchainTests('Migration tests', env => {
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
[ownerAddress, notOwnerAddress] = await env.getAccountAddressesAsync();
|
||||
[authorizedAddress, notAuthorizedAddress] = await env.getAccountAddressesAsync();
|
||||
initTargetContract = await TestInitTargetContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestInitTarget,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
revertAddress = await initTargetContract.SHOULD_REVERT_ADDRESS.callAsync();
|
||||
});
|
||||
|
||||
async function enableInitRevertsAsync(): Promise<void> {
|
||||
const revertAddress = await initTargetContract.SHOULD_REVERT_ADDRESS.callAsync();
|
||||
// Deposit some ether into `revertAddress` to signal `initTargetContract`
|
||||
// to fail.
|
||||
await env.web3Wrapper.awaitTransactionMinedAsync(
|
||||
await env.web3Wrapper.sendTransactionAsync({
|
||||
...env.txDefaults,
|
||||
from: ownerAddress,
|
||||
from: authorizedAddress,
|
||||
to: revertAddress,
|
||||
data: constants.NULL_BYTES,
|
||||
value: new BigNumber(1),
|
||||
@@ -62,7 +76,7 @@ blockchainTests('Migration tests', env => {
|
||||
const [senderAddress, thisAddress] = await initTargetContract.getInitState.callAsync({
|
||||
to: proxyContract.address,
|
||||
});
|
||||
expect(senderAddress).to.eq(ownerAddress);
|
||||
expect(senderAddress).to.eq(authorizedAddress);
|
||||
expect(thisAddress).to.eq(proxyContract.address);
|
||||
const attachedAddress = await proxyContract.stakingContract.callAsync();
|
||||
expect(attachedAddress).to.eq(initTargetContract.address);
|
||||
@@ -77,7 +91,49 @@ blockchainTests('Migration tests', env => {
|
||||
it('reverts if init() reverts', async () => {
|
||||
await enableInitRevertsAsync();
|
||||
const tx = deployStakingProxyAsync(initTargetContract.address);
|
||||
return expect(tx).to.revertWith(REVERT_ERROR);
|
||||
return expect(tx).to.revertWith(INIT_REVERT_ERROR);
|
||||
});
|
||||
|
||||
it('reverts if assertValidStorageParams() fails', async () => {
|
||||
const tx = deployStakingProxyAsync(revertAddress);
|
||||
return expect(tx).to.revertWith(STORAGE_PARAMS_REVERT_ERROR);
|
||||
});
|
||||
|
||||
it('should set the correct initial params', async () => {
|
||||
const wethProxyAddress = randomAddress();
|
||||
const ethVaultAddress = randomAddress();
|
||||
const rewardVaultAddress = randomAddress();
|
||||
const zrxVaultAddress = randomAddress();
|
||||
|
||||
const stakingProxyContractAddress = (await StakingProxyContract.deployFrom0xArtifactAsync(
|
||||
artifacts.StakingProxy,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
stakingContract.address,
|
||||
stakingContract.address,
|
||||
wethProxyAddress,
|
||||
ethVaultAddress,
|
||||
rewardVaultAddress,
|
||||
zrxVaultAddress,
|
||||
)).address;
|
||||
|
||||
const stakingProxyContract = new StakingContract(
|
||||
stakingProxyContractAddress,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
);
|
||||
const params = await stakingProxyContract.getParams.callAsync();
|
||||
expect(params[0]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.epochDurationInSeconds);
|
||||
expect(params[1]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.rewardDelegatedStakeWeight);
|
||||
expect(params[2]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.minimumPoolStake);
|
||||
expect(params[3]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.maximumMakersInPool);
|
||||
expect(params[4]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator);
|
||||
expect(params[5]).to.bignumber.eq(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaDenominator);
|
||||
expect(params[6]).to.eq(wethProxyAddress);
|
||||
expect(params[7]).to.eq(ethVaultAddress);
|
||||
expect(params[8]).to.eq(rewardVaultAddress);
|
||||
expect(params[9]).to.eq(zrxVaultAddress);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +144,7 @@ blockchainTests('Migration tests', env => {
|
||||
proxyContract = await deployStakingProxyAsync();
|
||||
});
|
||||
|
||||
it('throws if not called by owner', async () => {
|
||||
it('throws if not called by an authorized address', async () => {
|
||||
const tx = proxyContract.attachStakingContract.awaitTransactionSuccessAsync(
|
||||
initTargetContract.address,
|
||||
constants.NULL_ADDRESS,
|
||||
@@ -96,10 +152,10 @@ blockchainTests('Migration tests', env => {
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
{
|
||||
from: notOwnerAddress,
|
||||
from: notAuthorizedAddress,
|
||||
},
|
||||
);
|
||||
const expectedError = new OwnableRevertErrors.OnlyOwnerError(notOwnerAddress, ownerAddress);
|
||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorizedAddress);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
@@ -142,7 +198,7 @@ blockchainTests('Migration tests', env => {
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
return expect(tx).to.revertWith(REVERT_ERROR);
|
||||
return expect(tx).to.revertWith(INIT_REVERT_ERROR);
|
||||
});
|
||||
|
||||
it('calls init with initialized addresses if passed in args are null', async () => {
|
||||
@@ -197,6 +253,17 @@ blockchainTests('Migration tests', env => {
|
||||
expect(args.zrxVaultAddress).to.eq(zrxVaultAddress);
|
||||
}
|
||||
});
|
||||
|
||||
it('reverts if assertValidStorageParams() fails', async () => {
|
||||
const tx = proxyContract.attachStakingContract.awaitTransactionSuccessAsync(
|
||||
revertAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
return expect(tx).to.revertWith(STORAGE_PARAMS_REVERT_ERROR);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('upgrades', async () => {
|
||||
@@ -216,28 +283,17 @@ blockchainTests('Migration tests', env => {
|
||||
});
|
||||
|
||||
blockchainTests.resets('Staking.init()', async () => {
|
||||
let stakingContract: StakingContract;
|
||||
|
||||
before(async () => {
|
||||
stakingContract = await StakingContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Staking,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if not called by owner', async () => {
|
||||
it('throws if not called by an authorized address', async () => {
|
||||
const tx = stakingContract.init.awaitTransactionSuccessAsync(
|
||||
randomAddress(),
|
||||
randomAddress(),
|
||||
randomAddress(),
|
||||
randomAddress(),
|
||||
{
|
||||
from: notOwnerAddress,
|
||||
from: notAuthorizedAddress,
|
||||
},
|
||||
);
|
||||
const expectedError = new OwnableRevertErrors.OnlyOwnerError(notOwnerAddress, ownerAddress);
|
||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorizedAddress);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
@@ -258,5 +314,163 @@ blockchainTests('Migration tests', env => {
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('assertValidStorageParams', async () => {
|
||||
let proxyContract: TestAssertStorageParamsContract;
|
||||
const fiveDays = new BigNumber(5 * 24 * 60 * 60);
|
||||
const thirtyDays = new BigNumber(30 * 24 * 60 * 60);
|
||||
before(async () => {
|
||||
proxyContract = await TestAssertStorageParamsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestAssertStorageParams,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
it('succeeds if all params are valid', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync(stakingConstants.DEFAULT_PARAMS);
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
|
||||
it('reverts if epoch duration is < 5 days', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
epochDurationInSeconds: fiveDays.minus(1),
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidEpochDuration,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if epoch duration is > 30 days', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
epochDurationInSeconds: thirtyDays.plus(1),
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidEpochDuration,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('succeeds if epoch duration is 5 days', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
epochDurationInSeconds: fiveDays,
|
||||
});
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
it('succeeds if epoch duration is 30 days', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
epochDurationInSeconds: thirtyDays,
|
||||
});
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
it('reverts if alpha denominator is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
cobbDouglasAlphaDenominator: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if alpha > 1', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
cobbDouglasAlphaNumerator: new BigNumber(101),
|
||||
cobbDouglasAlphaDenominator: new BigNumber(100),
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('succeeds if alpha == 1', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||
cobbDouglasAlphaDenominator: new BigNumber(1),
|
||||
});
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
it('succeeds if alpha == 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
cobbDouglasAlphaNumerator: constants.ZERO_AMOUNT,
|
||||
cobbDouglasAlphaDenominator: new BigNumber(1),
|
||||
});
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
it('reverts if delegation weight is > 100%', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
rewardDelegatedStakeWeight: new BigNumber(stakingConstants.PPM).plus(1),
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidRewardDelegatedStakeWeight,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('succeeds if delegation weight is 100%', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
rewardDelegatedStakeWeight: new BigNumber(stakingConstants.PPM),
|
||||
});
|
||||
expect(tx).to.be.fulfilled('');
|
||||
});
|
||||
it('reverts if max makers in pool is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
maximumMakersInPool: constants.ZERO_AMOUNT,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidMaximumMakersInPool,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if wethAssetProxy is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
wethProxyAddress: constants.NULL_ADDRESS,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidWethProxyAddress,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if ethVault is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
ethVaultAddress: constants.NULL_ADDRESS,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidEthVaultAddress,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if rewardVault is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
rewardVaultAddress: constants.NULL_ADDRESS,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidRewardVaultAddress,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
it('reverts if zrxVault is 0', async () => {
|
||||
const tx = proxyContract.setAndAssertParams.awaitTransactionSuccessAsync({
|
||||
...stakingConstants.DEFAULT_PARAMS,
|
||||
zrxVaultAddress: constants.NULL_ADDRESS,
|
||||
});
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidZrxVaultAddress,
|
||||
);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { blockchainTests, constants, expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber, OwnableRevertErrors } from '@0x/utils';
|
||||
import { blockchainTests, expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||
import { AuthorizableRevertErrors, BigNumber } from '@0x/utils';
|
||||
|
||||
import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from '../src/';
|
||||
|
||||
@@ -9,11 +8,11 @@ import { StakingParams } from './utils/types';
|
||||
|
||||
blockchainTests('Configurable Parameters', env => {
|
||||
let testContract: MixinParamsContract;
|
||||
let ownerAddress: string;
|
||||
let notOwnerAddress: string;
|
||||
let authorizedAddress: string;
|
||||
let notAuthorizedAddress: string;
|
||||
|
||||
before(async () => {
|
||||
[ownerAddress, notOwnerAddress] = await env.getAccountAddressesAsync();
|
||||
[authorizedAddress, notAuthorizedAddress] = await env.getAccountAddressesAsync();
|
||||
testContract = await MixinParamsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.MixinParams,
|
||||
env.provider,
|
||||
@@ -68,91 +67,15 @@ blockchainTests('Configurable Parameters', env => {
|
||||
expect(actual[9]).to.eq(_params.zrxVaultAddress);
|
||||
}
|
||||
|
||||
it('throws if not called by owner', async () => {
|
||||
const tx = setParamsAndAssertAsync({}, notOwnerAddress);
|
||||
const expectedError = new OwnableRevertErrors.OnlyOwnerError(notOwnerAddress, ownerAddress);
|
||||
it('throws if not called by an authorized address', async () => {
|
||||
const tx = setParamsAndAssertAsync({}, notAuthorizedAddress);
|
||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorizedAddress);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('works if called by owner', async () => {
|
||||
return setParamsAndAssertAsync({});
|
||||
});
|
||||
|
||||
describe('rewardDelegatedStakeWeight', () => {
|
||||
it('throws when > PPM_100_PERCENT', async () => {
|
||||
const params = {
|
||||
rewardDelegatedStakeWeight: constants.PPM_100_PERCENT + 1,
|
||||
};
|
||||
const tx = setParamsAndAssertAsync(params);
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidRewardDelegatedStakeWeight,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('maximumMakersInPool', () => {
|
||||
it('throws when == 0', async () => {
|
||||
const params = {
|
||||
maximumMakersInPool: constants.ZERO_AMOUNT,
|
||||
};
|
||||
const tx = setParamsAndAssertAsync(params);
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidMaximumMakersInPool,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cobb-douglas alpha', () => {
|
||||
it('throws with denominator == 0', async () => {
|
||||
const params = {
|
||||
cobbDouglasAlphaNumerator: 0,
|
||||
cobbDouglasAlphaDenominator: 0,
|
||||
};
|
||||
const tx = setParamsAndAssertAsync(params);
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('throws with numerator > denominator', async () => {
|
||||
const params = {
|
||||
cobbDouglasAlphaNumerator: 2,
|
||||
cobbDouglasAlphaDenominator: 1,
|
||||
};
|
||||
const tx = setParamsAndAssertAsync(params);
|
||||
const expectedError = new StakingRevertErrors.InvalidParamValueError(
|
||||
StakingRevertErrors.InvalidParamValueErrorCode.InvalidCobbDouglasAlpha,
|
||||
);
|
||||
return expect(tx).to.revertWith(expectedError);
|
||||
});
|
||||
|
||||
it('accepts numerator == denominator', async () => {
|
||||
const params = {
|
||||
cobbDouglasAlphaNumerator: 1,
|
||||
cobbDouglasAlphaDenominator: 1,
|
||||
};
|
||||
return setParamsAndAssertAsync(params);
|
||||
});
|
||||
|
||||
it('accepts numerator < denominator', async () => {
|
||||
const params = {
|
||||
cobbDouglasAlphaNumerator: 1,
|
||||
cobbDouglasAlphaDenominator: 2,
|
||||
};
|
||||
return setParamsAndAssertAsync(params);
|
||||
});
|
||||
|
||||
it('accepts numerator == 0', async () => {
|
||||
const params = {
|
||||
cobbDouglasAlphaNumerator: 0,
|
||||
cobbDouglasAlphaDenominator: 1,
|
||||
};
|
||||
return setParamsAndAssertAsync(params);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { blockchainTests, expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { OwnableRevertErrors } from '@0x/utils';
|
||||
import { AuthorizableRevertErrors } from '@0x/utils';
|
||||
|
||||
import { constants } from '../utils/constants';
|
||||
|
||||
@@ -50,13 +50,13 @@ blockchainTests.resets('MixinVaultCore', env => {
|
||||
await testContract.assertStakingProxy.callAsync({ from: newAddress });
|
||||
return testAssertStakingProxyAsync(owner);
|
||||
});
|
||||
it('Non-owner address cannot set staking proxy', async () => {
|
||||
const notOwner = nonOwnerAddresses[0];
|
||||
it('Non-authorized address cannot set staking proxy', async () => {
|
||||
const notAuthorized = nonOwnerAddresses[0];
|
||||
const newAddress = nonOwnerAddresses[1];
|
||||
const tx = testContract.setStakingProxy.awaitTransactionSuccessAsync(newAddress, {
|
||||
from: notOwner,
|
||||
from: notAuthorized,
|
||||
});
|
||||
const expectedError = new OwnableRevertErrors.OnlyOwnerError(notOwner);
|
||||
const expectedError = new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthorized);
|
||||
expect(tx).to.revertWith(expectedError);
|
||||
expect(await testContract.stakingProxyAddress.callAsync()).to.equal(constants.NIL_ADDRESS);
|
||||
return testAssertStakingProxyAsync(newAddress);
|
||||
@@ -87,12 +87,12 @@ blockchainTests.resets('MixinVaultCore', env => {
|
||||
expect(eventArgs[0].sender).to.equal(owner);
|
||||
return testCatastrophicFailureModeAsync(true);
|
||||
});
|
||||
it('Non-owner cannot turn on catastrophic failure mode', async () => {
|
||||
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 OwnableRevertErrors.OnlyOwnerError());
|
||||
expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(nonOwnerAddresses[0]));
|
||||
return testCatastrophicFailureModeAsync(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { constants as testConstants, randomAddress } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
const TWO_WEEKS = 14 * 24 * 60 * 60;
|
||||
const PPM_90_PERCENT = 10 ** 6 * 0.9;
|
||||
const TEN_DAYS = 10 * 24 * 60 * 60;
|
||||
const PPM = 10 ** 6;
|
||||
export const constants = {
|
||||
TOKEN_MULTIPLIER: testConstants.DUMMY_TOKEN_DECIMALS,
|
||||
INITIAL_POOL_ID: '0x0000000000000000000000000000000100000000000000000000000000000000',
|
||||
@@ -11,9 +11,9 @@ export const constants = {
|
||||
NIL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
INITIAL_EPOCH: new BigNumber(0),
|
||||
DEFAULT_PARAMS: {
|
||||
epochDurationInSeconds: new BigNumber(TWO_WEEKS),
|
||||
rewardDelegatedStakeWeight: new BigNumber(PPM_90_PERCENT),
|
||||
minimumPoolStake: testConstants.DUMMY_TOKEN_DECIMALS.times(100),
|
||||
epochDurationInSeconds: new BigNumber(TEN_DAYS),
|
||||
rewardDelegatedStakeWeight: new BigNumber(PPM * 0.9),
|
||||
minimumPoolStake: new BigNumber(10).pow(testConstants.DUMMY_TOKEN_DECIMALS).times(100),
|
||||
maximumMakersInPool: new BigNumber(10),
|
||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||
cobbDouglasAlphaDenominator: new BigNumber(2),
|
||||
@@ -22,4 +22,5 @@ export const constants = {
|
||||
rewardVaultAddress: randomAddress(),
|
||||
zrxVaultAddress: randomAddress(),
|
||||
},
|
||||
PPM,
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"generated-artifacts/Staking.json",
|
||||
"generated-artifacts/StakingPoolRewardVault.json",
|
||||
"generated-artifacts/StakingProxy.json",
|
||||
"generated-artifacts/TestAssertStorageParams.json",
|
||||
"generated-artifacts/TestCobbDouglas.json",
|
||||
"generated-artifacts/TestCumulativeRewardTracking.json",
|
||||
"generated-artifacts/TestInitTarget.json",
|
||||
|
||||
@@ -24,6 +24,7 @@ import "./LibRichErrors.sol";
|
||||
import "./Ownable.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract Authorizable is
|
||||
Ownable,
|
||||
IAuthorizable
|
||||
@@ -37,25 +38,19 @@ contract Authorizable is
|
||||
mapping (address => bool) public authorized;
|
||||
address[] public authorities;
|
||||
|
||||
/// @dev Initializes the `owner` address.
|
||||
constructor()
|
||||
public
|
||||
Ownable()
|
||||
{}
|
||||
|
||||
/// @dev Authorizes an address.
|
||||
/// @param target Address to authorize.
|
||||
function addAuthorizedAddress(address target)
|
||||
external
|
||||
onlyOwner
|
||||
{
|
||||
// Ensure that the target is not the zero address.
|
||||
if (target == address(0)) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.ZeroCantBeAuthorizedError());
|
||||
}
|
||||
|
||||
// Ensure that the target is not already authorized.
|
||||
if (authorized[target]) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetAlreadyAuthorizedError(target));
|
||||
}
|
||||
|
||||
authorized[target] = true;
|
||||
authorities.push(target);
|
||||
emit AuthorizedAddressAdded(target, msg.sender);
|
||||
_addAuthorizedAddress(target);
|
||||
}
|
||||
|
||||
/// @dev Removes authorizion of an address.
|
||||
@@ -67,16 +62,12 @@ contract Authorizable is
|
||||
if (!authorized[target]) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target));
|
||||
}
|
||||
|
||||
delete authorized[target];
|
||||
for (uint256 i = 0; i < authorities.length; i++) {
|
||||
if (authorities[i] == target) {
|
||||
authorities[i] = authorities[authorities.length - 1];
|
||||
authorities.length -= 1;
|
||||
_removeAuthorizedAddressAtIndex(target, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit AuthorizedAddressRemoved(target, msg.sender);
|
||||
}
|
||||
|
||||
/// @dev Removes authorizion of an address.
|
||||
@@ -89,26 +80,7 @@ contract Authorizable is
|
||||
external
|
||||
onlyOwner
|
||||
{
|
||||
if (!authorized[target]) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target));
|
||||
}
|
||||
if (index >= authorities.length) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.IndexOutOfBoundsError(
|
||||
index,
|
||||
authorities.length
|
||||
));
|
||||
}
|
||||
if (authorities[index] != target) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.AuthorizedAddressMismatchError(
|
||||
authorities[index],
|
||||
target
|
||||
));
|
||||
}
|
||||
|
||||
delete authorized[target];
|
||||
authorities[index] = authorities[authorities.length - 1];
|
||||
authorities.length -= 1;
|
||||
emit AuthorizedAddressRemoved(target, msg.sender);
|
||||
_removeAuthorizedAddressAtIndex(target, index);
|
||||
}
|
||||
|
||||
/// @dev Gets all authorized addresses.
|
||||
@@ -130,4 +102,55 @@ contract Authorizable is
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.SenderNotAuthorizedError(msg.sender));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Authorizes an address.
|
||||
/// @param target Address to authorize.
|
||||
function _addAuthorizedAddress(address target)
|
||||
internal
|
||||
{
|
||||
// Ensure that the target is not the zero address.
|
||||
if (target == address(0)) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.ZeroCantBeAuthorizedError());
|
||||
}
|
||||
|
||||
// Ensure that the target is not already authorized.
|
||||
if (authorized[target]) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetAlreadyAuthorizedError(target));
|
||||
}
|
||||
|
||||
authorized[target] = true;
|
||||
authorities.push(target);
|
||||
emit AuthorizedAddressAdded(target, msg.sender);
|
||||
}
|
||||
|
||||
/// @dev Removes authorizion of an address.
|
||||
/// @param target Address to remove authorization from.
|
||||
/// @param index Index of target in authorities array.
|
||||
function _removeAuthorizedAddressAtIndex(
|
||||
address target,
|
||||
uint256 index
|
||||
)
|
||||
internal
|
||||
{
|
||||
if (!authorized[target]) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target));
|
||||
}
|
||||
if (index >= authorities.length) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.IndexOutOfBoundsError(
|
||||
index,
|
||||
authorities.length
|
||||
));
|
||||
}
|
||||
if (authorities[index] != target) {
|
||||
LibRichErrors.rrevert(LibAuthorizableRichErrors.AuthorizedAddressMismatchError(
|
||||
authorities[index],
|
||||
target
|
||||
));
|
||||
}
|
||||
|
||||
delete authorized[target];
|
||||
authorities[index] = authorities[authorities.length - 1];
|
||||
authorities.length -= 1;
|
||||
emit AuthorizedAddressRemoved(target, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ export enum InvalidParamValueErrorCode {
|
||||
InvalidCobbDouglasAlpha,
|
||||
InvalidRewardDelegatedStakeWeight,
|
||||
InvalidMaximumMakersInPool,
|
||||
InvalidWethProxyAddress,
|
||||
InvalidEthVaultAddress,
|
||||
InvalidRewardVaultAddress,
|
||||
InvalidZrxVaultAddress,
|
||||
InvalidEpochDuration,
|
||||
}
|
||||
|
||||
export enum InitializationErrorCode {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class IndexOutOfBoundsError extends RevertError {
|
||||
|
||||
export class SenderNotAuthorizedError extends RevertError {
|
||||
constructor(sender?: string) {
|
||||
super('SenderNotAuthorizedError', 'SenderNotAuthorizedError()', { sender });
|
||||
super('SenderNotAuthorizedError', 'SenderNotAuthorizedError(address sender)', { sender });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user