Merge pull request #2178 from 0xProject/feat/3.0/upgradability-controls

Upgradability controls
This commit is contained in:
Amir Bandeali
2019-09-20 17:47:36 -07:00
committed by GitHub
22 changed files with 531 additions and 291 deletions

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -45,7 +45,8 @@ library LibStakingRichErrors {
InvalidWethProxyAddress,
InvalidEthVaultAddress,
InvalidRewardVaultAddress,
InvalidZrxVaultAddress
InvalidZrxVaultAddress,
InvalidEpochDuration
}
enum MakerPoolAssignmentErrorCodes {

View File

@@ -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,

View File

@@ -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
));
}
}
}

View File

@@ -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);

View 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
{}
}

View File

@@ -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;

View File

@@ -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"
);
}
}

View File

@@ -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",

View File

@@ -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,

View File

@@ -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';

View File

@@ -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

View File

@@ -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

View File

@@ -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);
});
});

View File

@@ -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,
};

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -23,6 +23,11 @@ export enum InvalidParamValueErrorCode {
InvalidCobbDouglasAlpha,
InvalidRewardDelegatedStakeWeight,
InvalidMaximumMakersInPool,
InvalidWethProxyAddress,
InvalidEthVaultAddress,
InvalidRewardVaultAddress,
InvalidZrxVaultAddress,
InvalidEpochDuration,
}
export enum InitializationErrorCode {

View File

@@ -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 });
}
}