move operator from staking logic to vault, allow operator to decrease operatorShare

This commit is contained in:
Michael Zhu
2019-08-28 18:23:44 -07:00
parent 91cee9c648
commit 1d6406bbd6
14 changed files with 293 additions and 187 deletions

View File

@@ -254,7 +254,7 @@ contract MixinExchangeFees is
// compute weighted stake
uint256 totalStakeDelegatedToPool = getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
uint256 stakeHeldByPoolOperator = getStakeDelegatedToPoolByOwner(getStakingPoolOperator(poolId), poolId).currentEpochBalance;
uint256 stakeHeldByPoolOperator = getStakeDelegatedToPoolByOwner(rewardVault.operatorOf(poolId), poolId).currentEpochBalance;
uint256 weightedStake = stakeHeldByPoolOperator.safeAdd(
totalStakeDelegatedToPool
.safeSub(stakeHeldByPoolOperator)
@@ -300,7 +300,7 @@ contract MixinExchangeFees is
);
// record reward in vault
(, uint256 poolPortion) = rewardVault.recordDepositFor(
(, uint256 membersPortion) = rewardVault.recordDepositFor(
activePools[i].poolId,
reward,
activePools[i].delegatedStake == 0 // true -> reward is for operator only
@@ -308,10 +308,10 @@ contract MixinExchangeFees is
totalRewardsPaid = totalRewardsPaid.safeAdd(reward);
// sync cumulative rewards, if necessary.
if (poolPortion > 0) {
if (membersPortion > 0) {
_recordRewardForDelegators(
activePools[i].poolId,
poolPortion,
membersPortion,
activePools[i].delegatedStake,
currentEpoch
);

View File

@@ -90,9 +90,6 @@ contract MixinStorage is
// tracking Pool Id
bytes32 internal nextPoolId = INITIAL_POOL_ID;
// mapping from Pool Id to Pool
mapping (bytes32 => IStructs.Pool) internal poolById;
// mapping from Maker Address to a struct representing the pool the maker has joined and
// whether the operator of that pool has subsequently added the maker.
mapping (address => IStructs.MakerPoolJoinStatus) internal poolJoinedByMakerAddress;

View File

@@ -27,16 +27,17 @@ pragma solidity ^0.5.9;
/// corruption of related state in the staking contract.
interface IStakingPoolRewardVault {
/// @dev Holds the balance for a staking pool.
/// @dev Holds the balances and other data for a staking pool.
/// @param initialzed True iff the balance struct is initialized.
/// @param operatorShare Fraction of the total balance owned by the operator, in ppm.
/// @param operatorBalance Balance in ETH of the operator.
/// @param membersBalance Balance in ETH co-owned by the pool members.
struct Balance {
struct Pool {
bool initialized;
uint32 operatorShare;
uint96 operatorBalance;
uint96 membersBalance;
address payable operatorAddress;
}
/// @dev Emitted when the eth vault is changed
@@ -81,6 +82,16 @@ interface IStakingPoolRewardVault {
uint32 operatorShare
);
/// @dev Emitted when a staking pool's operator share is decreased.
/// @param poolId Unique Id of pool that was registered.
/// @param oldOperatorShare Previous share of rewards owned by operator.
/// @param newOperatorShare Newly decreased share of rewards owned by operator.
event OperatorShareDecreased(
bytes32 poolId,
uint32 oldOperatorShare,
uint32 newOperatorShare
);
/// @dev Fallback function.
/// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode.
@@ -138,10 +149,31 @@ interface IStakingPoolRewardVault {
/// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode.
/// @param poolId Unique Id of pool.
/// @param poolOperatorShare Share of rewards given to the pool operator, in ppm.
function registerStakingPool(bytes32 poolId, uint32 poolOperatorShare)
/// @param operatorAddress Address of the pool operator.
/// @param operatorShare Share of rewards given to the pool operator, in ppm.
function registerStakingPool(
bytes32 poolId,
address payable operatorAddress,
uint32 operatorShare
)
external;
/// @dev Decreases the operator share for the given pool (i.e. increases pool rewards for members).
/// Note that this is only callable by the staking contract, and will revert if the new operator
/// share value is greater than the old value.
/// @param poolId Unique Id of pool.
/// @param newOperatorShare The newly decresaed percentage of any rewards owned by the operator.
function decreaseOperatorShare(bytes32 poolId, uint32 newOperatorShare)
external;
/// @dev Returns the address of the operator of a given pool
/// @param poolId Unique id of pool
/// @return operatorAddress Operator of the pool
function operatorOf(bytes32 poolId)
external
view
returns (address payable);
/// @dev Returns the total balance of a pool.
/// @param poolId Unique Id of pool.
/// @return Balance in ETH.

View File

@@ -21,24 +21,6 @@ pragma solidity ^0.5.9;
interface IStructs {
/// @dev Allowed signature types.
enum SignatureType {
Illegal, // 0x00, default value
Invalid, // 0x01
EIP712, // 0x02
EthSign, // 0x03
Wallet, // 0x04
NSignatureTypes // 0x05, number of signature types. Always leave at end.
}
/// @dev Status for Staking Pools (see MixinStakingPool).
/// @param operatorAddress Address of pool operator.
/// @param operatorShare Portion of pool rewards owned by operator, in ppm.
struct Pool {
address payable operatorAddress;
uint32 operatorShare;
}
/// @dev Status for a pool that actively traded during the current epoch.
/// (see MixinExchangeFees).
/// @param poolId Unique Id of staking pool.

View File

@@ -84,9 +84,9 @@ library LibStakingRichErrors {
bytes4 internal constant AMOUNT_EXCEEDS_BALANCE_OF_POOL_ERROR_SELECTOR =
0x4c5c09dd;
// bytes4(keccak256("InvalidPoolOperatorShareError(bytes32,uint32)"))
bytes4 internal constant INVALID_POOL_OPERATOR_SHARE_ERROR_SELECTOR =
0x70f55b5a;
// bytes4(keccak256("OperatorShareError(uint8,bytes32,uint32)"))
bytes4 internal constant OPERATOR_SHARE_ERROR_SELECTOR =
0x22df9597;
// bytes4(keccak256("PoolAlreadyExistsError(bytes32)"))
bytes4 internal constant POOL_ALREADY_EXISTS_ERROR_SELECTOR =
@@ -112,13 +112,6 @@ library LibStakingRichErrors {
bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL =
hex"01ecebea";
enum MakerPoolAssignmentErrorCodes {
MAKER_ADDRESS_ALREADY_REGISTERED,
MAKER_ADDRESS_NOT_REGISTERED,
MAKER_ADDRESS_NOT_PENDING_ADD,
POOL_IS_FULL
}
// bytes4(keccak256("InvalidProtocolFeePaymentError(uint8,uint256,uint256)"))
bytes4 internal constant INVALID_PROTOCOL_FEE_PAYMENT_ERROR_SELECTOR =
0xefd6cb33;
@@ -127,6 +120,18 @@ library LibStakingRichErrors {
bytes internal constant INVALID_WETH_ASSET_DATA_ERROR =
hex"24bf322c";
enum MakerPoolAssignmentErrorCodes {
MAKER_ADDRESS_ALREADY_REGISTERED,
MAKER_ADDRESS_NOT_REGISTERED,
MAKER_ADDRESS_NOT_PENDING_ADD,
POOL_IS_FULL
}
enum OperatorShareErrorCodes {
OPERATOR_SHARE_MUST_BE_BETWEEN_0_AND_100,
CAN_ONLY_DECREASE_OPERATOR_SHARE
}
// solhint-disable func-name-mixedcase
function MiscalculatedRewardsError(
uint256 totalRewardsPaid,
@@ -320,18 +325,20 @@ library LibStakingRichErrors {
);
}
function InvalidPoolOperatorShareError(
function OperatorShareError(
OperatorShareErrorCodes errorCode,
bytes32 poolId,
uint32 poolOperatorShare
uint32 operatorShare
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INVALID_POOL_OPERATOR_SHARE_ERROR_SELECTOR,
OPERATOR_SHARE_ERROR_SELECTOR,
errorCode,
poolId,
poolOperatorShare
operatorShare
);
}

View File

@@ -24,7 +24,6 @@ import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../interfaces/IStakingEvents.sol";
import "./MixinZrxVault.sol";
import "../staking_pools/MixinStakingPoolRewardVault.sol";
import "../staking_pools/MixinStakingPoolRewards.sol";
import "../sys/MixinScheduler.sol";
import "../libs/LibStakingRichErrors.sol";

View File

@@ -26,8 +26,6 @@ import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../sys/MixinScheduler.sol";
import "./MixinStakingPoolRewardVault.sol";
import "./MixinStakingPoolRewards.sol";
@@ -62,38 +60,6 @@ contract MixinStakingPool is
{
using LibSafeMath for uint256;
/// @dev Asserts that the sender is the operator of the input pool.
/// @param poolId Pool sender must be operator of.
modifier onlyStakingPoolOperator(bytes32 poolId) {
address poolOperator = getStakingPoolOperator(poolId);
if (msg.sender != poolOperator) {
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByPoolOperatorError(
msg.sender,
poolOperator
));
}
_;
}
/// @dev Asserts that the sender is the operator of the input pool or the input maker.
/// @param poolId Pool sender must be operator of.
/// @param makerAddress Address of a maker in the pool.
modifier onlyStakingPoolOperatorOrMaker(bytes32 poolId, address makerAddress) {
address poolOperator = getStakingPoolOperator(poolId);
if (msg.sender != poolOperator && msg.sender != makerAddress) {
LibRichErrors.rrevert(
LibStakingRichErrors.OnlyCallableByPoolOperatorOrMakerError(
msg.sender,
poolOperator,
makerAddress
)
);
}
_;
}
/// @dev Create a new staking pool. The sender will be the operator of this pool.
/// Note that an operator must be payable.
/// @param operatorShare Portion of rewards owned by the operator, in ppm.
@@ -110,19 +76,12 @@ contract MixinStakingPool is
poolId = nextPoolId;
nextPoolId = _computeNextStakingPoolId(poolId);
// store metadata about this pool
IStructs.Pool memory pool = IStructs.Pool({
operatorAddress: operatorAddress,
operatorShare: operatorShare
});
poolById[poolId] = pool;
// initialize cumulative rewards for this pool;
// this is used to track rewards earned by delegators.
_initializeCumulativeRewards(poolId);
// register pool in reward vault
rewardVault.registerStakingPool(poolId, operatorShare);
rewardVault.registerStakingPool(poolId, operatorAddress, operatorShare);
// Staking pool has been created
emit StakingPoolCreated(poolId, operatorAddress, operatorShare);
@@ -319,30 +278,6 @@ contract MixinStakingPool is
return nextPoolId;
}
/// @dev Returns the pool operator
/// @param poolId Unique id of pool
/// @return operatorAddress Operator of the pool
function getStakingPoolOperator(bytes32 poolId)
public
view
returns (address operatorAddress)
{
operatorAddress = poolById[poolId].operatorAddress;
return operatorAddress;
}
/// @dev Convenience function for loading information on a pool.
/// @param poolId Unique id of pool.
/// @return pool Pool info.
function _getStakingPool(bytes32 poolId)
internal
view
returns (IStructs.Pool memory pool)
{
pool = poolById[poolId];
return pool;
}
/// @dev Computes the unique id that comes after the input pool id.
/// @param poolId Unique id of pool.
/// @return Next pool id after input pool.

View File

@@ -18,10 +18,11 @@
pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "../libs/LibStakingRichErrors.sol";
import "../interfaces/IStakingEvents.sol";
import "../interfaces/IStakingPoolRewardVault.sol";
import "../immutable/MixinStorage.sol";
import "../libs/LibStakingRichErrors.sol";
/// @dev This mixin contains logic for interfacing with the Staking Pool Reward Vault (vaults/StakingPoolRewardVault.sol)
@@ -32,6 +33,41 @@ contract MixinStakingPoolRewardVault is
MixinStorage
{
/// @dev Asserts that the sender is the operator of the input pool.
/// @param poolId Pool sender must be operator of.
modifier onlyStakingPoolOperator(bytes32 poolId) {
address poolOperator = rewardVault.operatorOf(poolId);
if (msg.sender != poolOperator) {
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByPoolOperatorError(
msg.sender,
poolOperator
));
}
_;
}
/// @dev Asserts that the sender is the operator of the input pool or the input maker.
/// @param poolId Pool sender must be operator of.
/// @param makerAddress Address of a maker in the pool.
modifier onlyStakingPoolOperatorOrMaker(bytes32 poolId, address makerAddress) {
address poolOperator;
if (
msg.sender != makerAddress &&
msg.sender != (poolOperator = rewardVault.operatorOf(poolId))
) {
LibRichErrors.rrevert(
LibStakingRichErrors.OnlyCallableByPoolOperatorOrMakerError(
msg.sender,
poolOperator,
makerAddress
)
);
}
_;
}
/// @dev Sets the address of the reward vault.
/// This can only be called by the owner of this contract.
function setStakingPoolRewardVault(address payable rewardVaultAddress)
@@ -52,16 +88,16 @@ contract MixinStakingPoolRewardVault is
return address(rewardVault);
}
/// @dev Registers a staking pool in the reward vault.
/// @param poolId Unique id of pool.
/// @param operatorShare Portion of rewards owned by the operator, in ppm.
function _registerStakingPoolInRewardVault(bytes32 poolId, uint32 operatorShare)
internal
/// @dev Decreases the operator share for the given pool (i.e. increases pool rewards for members).
/// Note that this is only callable by the pool operator, and will revert if the new operator
/// share value is greater than the old value.
/// @param poolId Unique Id of pool.
/// @param newOperatorShare The newly decreased percentage of any rewards owned by the operator.
function decreaseStakingPoolOperatorShare(bytes32 poolId, uint32 newOperatorShare)
public
onlyStakingPoolOperator(poolId)
{
rewardVault.registerStakingPool(
poolId,
operatorShare
);
rewardVault.decreaseOperatorShare(poolId, newOperatorShare);
}
/// @dev Deposits an amount in ETH into the reward vault.

View File

@@ -17,6 +17,7 @@
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
@@ -46,8 +47,8 @@ contract StakingPoolRewardVault is
using LibSafeMath for uint256;
using LibSafeDowncast for uint256;
// mapping from Pool to Reward Balance in ETH
mapping (bytes32 => Balance) internal balanceByPoolId;
// mapping from poolId to Pool metadata
mapping (bytes32 => Pool) internal poolById;
// address of ether vault
IEthVault internal ethVault;
@@ -81,7 +82,7 @@ contract StakingPoolRewardVault is
/// @param amount Amount in ETH to record.
/// @param operatorOnly Only attribute amount to operator.
/// @return operatorPortion Portion of amount attributed to the operator.
/// @return poolPortion Portion of amount attributed to the pool.
/// @return membersPortion Portion of amount attributed to the pool.
function recordDepositFor(
bytes32 poolId,
uint256 amount,
@@ -91,14 +92,14 @@ contract StakingPoolRewardVault is
onlyStakingContract
returns (
uint256 operatorPortion,
uint256 poolPortion
uint256 membersPortion
)
{
// update balance of pool
Balance memory balance = balanceByPoolId[poolId];
(operatorPortion, poolPortion) = _incrementBalanceStruct(balance, amount, operatorOnly);
balanceByPoolId[poolId] = balance;
return (operatorPortion, poolPortion);
Pool memory pool = poolById[poolId];
(operatorPortion, membersPortion) = _incrementPoolBalances(pool, amount, operatorOnly);
poolById[poolId] = pool;
return (operatorPortion, membersPortion);
}
/// @dev Withdraw some amount in ETH of an operator's reward.
@@ -118,16 +119,16 @@ contract StakingPoolRewardVault is
}
// sanity check - sufficient balance?
uint256 operatorBalance = uint256(balanceByPoolId[poolId].operatorBalance);
uint256 operatorBalance = uint256(poolById[poolId].operatorBalance);
if (amount > operatorBalance) {
LibRichErrors.rrevert(LibStakingRichErrors.AmountExceedsBalanceOfPoolError(
amount,
balanceByPoolId[poolId].operatorBalance
poolById[poolId].operatorBalance
));
}
// update balance and transfer `amount` in ETH to staking contract
balanceByPoolId[poolId].operatorBalance = operatorBalance.safeSub(amount).downcastToUint96();
poolById[poolId].operatorBalance = operatorBalance.safeSub(amount).downcastToUint96();
_transferToEthVault(operator, amount);
// notify
@@ -152,16 +153,16 @@ contract StakingPoolRewardVault is
}
// sanity check - sufficient balance?
uint256 membersBalance = uint256(balanceByPoolId[poolId].membersBalance);
uint256 membersBalance = uint256(poolById[poolId].membersBalance);
if (amount > membersBalance) {
LibRichErrors.rrevert(LibStakingRichErrors.AmountExceedsBalanceOfPoolError(
amount,
balanceByPoolId[poolId].membersBalance
poolById[poolId].membersBalance
));
}
// update balance and transfer `amount` in ETH to staking contract
balanceByPoolId[poolId].membersBalance = membersBalance.safeSub(amount).downcastToUint96();
poolById[poolId].membersBalance = membersBalance.safeSub(amount).downcastToUint96();
_transferToEthVault(member, amount);
// notify
@@ -172,35 +173,76 @@ contract StakingPoolRewardVault is
/// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode.
/// @param poolId Unique Id of pool.
/// @param poolOperatorShare Fraction of rewards given to the pool operator, in ppm.
function registerStakingPool(bytes32 poolId, uint32 poolOperatorShare)
/// @param operatorShare Fraction of rewards given to the pool operator, in ppm.
function registerStakingPool(
bytes32 poolId,
address payable operatorAddress,
uint32 operatorShare
)
external
onlyStakingContract
onlyNotInCatastrophicFailure
{
// operator share must be a valid fraction
if (poolOperatorShare > PPM_DENOMINATOR) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidPoolOperatorShareError(
if (operatorShare > PPM_DENOMINATOR) {
LibRichErrors.rrevert(LibStakingRichErrors.OperatorShareError(
LibStakingRichErrors.OperatorShareErrorCodes.OPERATOR_SHARE_MUST_BE_BETWEEN_0_AND_100,
poolId,
poolOperatorShare
operatorShare
));
}
// pool must not exist
Balance memory balance = balanceByPoolId[poolId];
if (balance.initialized) {
Pool memory pool = poolById[poolId];
if (pool.initialized) {
LibRichErrors.rrevert(LibStakingRichErrors.PoolAlreadyExistsError(
poolId
));
}
// set initial balance
balance.initialized = true;
balance.operatorShare = poolOperatorShare;
balanceByPoolId[poolId] = balance;
// initialize pool
pool.initialized = true;
pool.operatorAddress = operatorAddress;
pool.operatorShare = operatorShare;
poolById[poolId] = pool;
// notify
emit StakingPoolRegistered(poolId, poolOperatorShare);
emit StakingPoolRegistered(poolId, operatorShare);
}
/// @dev Decreases the operator share for the given pool (i.e. increases pool rewards for members).
/// Note that this is only callable by the staking contract, and will revert if the new operator
/// share value is greater than the old value.
/// @param poolId Unique Id of pool.
/// @param newOperatorShare The newly decreased percentage of any rewards owned by the operator.
function decreaseOperatorShare(bytes32 poolId, uint32 newOperatorShare)
external
onlyStakingContract
onlyNotInCatastrophicFailure
{
uint32 oldOperatorShare = poolById[poolId].operatorShare;
if (newOperatorShare >= oldOperatorShare) {
LibRichErrors.rrevert(LibStakingRichErrors.OperatorShareError(
LibStakingRichErrors.OperatorShareErrorCodes.CAN_ONLY_DECREASE_OPERATOR_SHARE,
poolId,
newOperatorShare
));
} else {
poolById[poolId].operatorShare = newOperatorShare;
emit OperatorShareDecreased(poolId, oldOperatorShare, newOperatorShare);
}
}
/// @dev Returns the address of the operator of a given pool
/// @param poolId Unique id of pool
/// @return operatorAddress Operator of the pool
function operatorOf(bytes32 poolId)
external
view
returns (address payable)
{
return poolById[poolId].operatorAddress;
}
/// @dev Returns the total balance of a pool.
@@ -211,8 +253,7 @@ contract StakingPoolRewardVault is
view
returns (uint256)
{
Balance memory balance = balanceByPoolId[poolId];
return balance.operatorBalance + balance.membersBalance;
return poolById[poolId].operatorBalance + poolById[poolId].membersBalance;
}
/// @dev Returns the balance of a pool operator.
@@ -223,7 +264,7 @@ contract StakingPoolRewardVault is
view
returns (uint256)
{
return balanceByPoolId[poolId].operatorBalance;
return poolById[poolId].operatorBalance;
}
/// @dev Returns the balance co-owned by members of a pool.
@@ -234,7 +275,7 @@ contract StakingPoolRewardVault is
view
returns (uint256)
{
return balanceByPoolId[poolId].membersBalance;
return poolById[poolId].membersBalance;
}
/// @dev Returns the operator share of a pool's balance.
@@ -245,42 +286,42 @@ contract StakingPoolRewardVault is
view
returns (uint256)
{
return balanceByPoolId[poolId].operatorShare;
return poolById[poolId].operatorShare;
}
/// @dev Increments a balance struct, splitting the input amount between the
/// @dev Increments a balances in a Pool struct, splitting the input amount between the
/// pool operator and members of the pool based on the pool operator's share.
/// @param balance Balance struct to increment.
/// @param pool Pool struct with the balances to increment.
/// @param amount Amount to add to balance.
/// @param operatorOnly Only give this balance to the operator.
/// @return portion of amount given to operator and delegators, respectively.
function _incrementBalanceStruct(Balance memory balance, uint256 amount, bool operatorOnly)
function _incrementPoolBalances(Pool memory pool, uint256 amount, bool operatorOnly)
private
pure
returns (uint256 operatorPortion, uint256 poolPortion)
returns (uint256 operatorPortion, uint256 membersPortion)
{
// compute portions. One of the two must round down: the operator always receives the leftover from rounding.
operatorPortion = operatorOnly
? amount
: LibMath.getPartialAmountCeil(
uint256(balance.operatorShare),
uint256(pool.operatorShare),
PPM_DENOMINATOR,
amount
);
poolPortion = amount.safeSub(operatorPortion);
membersPortion = amount.safeSub(operatorPortion);
// compute new balances
uint256 newOperatorBalance = uint256(balance.operatorBalance).safeAdd(operatorPortion);
uint256 newMembersBalance = uint256(balance.membersBalance).safeAdd(poolPortion);
uint256 newOperatorBalance = uint256(pool.operatorBalance).safeAdd(operatorPortion);
uint256 newMembersBalance = uint256(pool.membersBalance).safeAdd(membersPortion);
// save new balances
balance.operatorBalance = newOperatorBalance.downcastToUint96();
balance.membersBalance = newMembersBalance.downcastToUint96();
pool.operatorBalance = newOperatorBalance.downcastToUint96();
pool.membersBalance = newMembersBalance.downcastToUint96();
return (
operatorPortion,
poolPortion
membersPortion
);
}

View File

@@ -73,9 +73,6 @@ contract TestStorageLayout is
if sub(nextPoolId_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(poolById_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(poolJoinedByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)

View File

@@ -86,4 +86,26 @@ export class PoolOperatorActor extends BaseActor {
);
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
}
public async decreaseStakingPoolOperatorShareAsync(
poolId: string,
newOperatorShare: number,
revertError?: RevertError,
): Promise<void> {
// decrease operator share
const txReceiptPromise = this._stakingApiWrapper.stakingContract.decreaseStakingPoolOperatorShare.awaitTransactionSuccessAsync(
poolId,
newOperatorShare,
{ from: this._owner },
);
if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError);
return;
}
await txReceiptPromise;
// Check operator share
const decreasedOperatorShare = await this._stakingApiWrapper.rewardVaultContract.getOperatorShare.callAsync(
poolId,
);
expect(decreasedOperatorShare, 'updated operator share').to.be.bignumber.equal(newOperatorShare);
}
}

View File

@@ -43,10 +43,24 @@ blockchainTests('Staking Pool Management', env => {
const nextPoolId = await stakingApiWrapper.stakingContract.getNextStakingPoolId.callAsync();
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
});
it('Should fail to create a pool with operator share > 100', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (101 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const revertError = new StakingRevertErrors.OperatorShareError(
StakingRevertErrors.OperatorShareErrorCodes.OperatorShareMustBeBetween0And100,
stakingConstants.INITIAL_POOL_ID,
operatorShare,
);
// create pool
await poolOperator.createStakingPoolAsync(operatorShare, false, revertError);
});
it('Should successfully create a pool and add owner as a maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
@@ -55,7 +69,7 @@ blockchainTests('Staking Pool Management', env => {
const nextPoolId = await stakingApiWrapper.stakingContract.getNextStakingPoolId.callAsync();
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
});
it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
it('Should throw if operatorShare is > PPM_DENOMINATOR', async () => {
// test parameters
const operatorAddress = users[0];
// tslint:disable-next-line
@@ -64,13 +78,17 @@ blockchainTests('Staking Pool Management', env => {
// create pool
const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
const expectedError = new StakingRevertErrors.InvalidPoolOperatorShareError(expectedPoolId, operatorShare);
const expectedError = new StakingRevertErrors.OperatorShareError(
StakingRevertErrors.OperatorShareErrorCodes.OperatorShareMustBeBetween0And100,
expectedPoolId,
operatorShare,
);
return expect(tx).to.revertWith(expectedError);
});
it('Should successfully add/remove a maker to a pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
@@ -87,7 +105,7 @@ blockchainTests('Staking Pool Management', env => {
it('Maker should successfully remove themselves from a pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
@@ -104,7 +122,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should successfully add/remove multiple makers to the same pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddresses = users.slice(1, 4);
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
@@ -136,7 +154,7 @@ blockchainTests('Staking Pool Management', env => {
});
it('Should fail if maker already assigned to another pool tries to join', async () => {
// test parameters
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);
@@ -165,8 +183,9 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to add maker to pool if the maker has not joined any pools', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
// create pool
@@ -183,7 +202,7 @@ blockchainTests('Staking Pool Management', env => {
});
it('Should fail to add maker to pool if the maker joined a different pool', async () => {
// test parameters
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);
@@ -210,7 +229,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to add the same maker twice', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
@@ -231,7 +250,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to remove a maker that does not exist', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
// create pool
@@ -248,7 +267,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to add a maker when called by someone other than the pool operator', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
@@ -272,7 +291,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
@@ -299,7 +318,7 @@ blockchainTests('Staking Pool Management', env => {
it('Should fail to add a maker if the pool is full', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = 39;
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddresses = users.slice(1, stakingConstants.MAX_MAKERS_IN_POOL + 2);
@@ -330,6 +349,38 @@ blockchainTests('Staking Pool Management', env => {
);
await poolOperator.addMakerToStakingPoolAsync(poolId, lastMakerAddress, revertError);
});
it('Operator should successfully decrease their share of rewards', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// decrease operator share
await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1);
});
it('Should fail if operator tries to increase their share of rewards', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
const increasedOperatorShare = operatorShare + 1;
const revertError = new StakingRevertErrors.OperatorShareError(
StakingRevertErrors.OperatorShareErrorCodes.CanOnlyDecreaseOperatorShare,
poolId,
increasedOperatorShare,
);
// decrease operator share
await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, increasedOperatorShare, revertError);
});
});
});
// tslint:enable:no-unnecessary-type-assertion

View File

@@ -37,6 +37,7 @@ blockchainTests('Staking Vaults', env => {
let revertError = new StakingRevertErrors.PoolAlreadyExistsError(poolId);
let tx = stakingApiWrapper.rewardVaultContract.registerStakingPool.awaitTransactionSuccessAsync(
poolId,
poolOperator,
operatorShare,
{ from: stakingApiWrapper.stakingContractAddress },
);
@@ -45,6 +46,7 @@ blockchainTests('Staking Vaults', env => {
revertError = new StakingRevertErrors.OnlyCallableByStakingContractError(notStakingContractAddress);
tx = stakingApiWrapper.rewardVaultContract.registerStakingPool.awaitTransactionSuccessAsync(
poolId,
poolOperator,
operatorShare,
{ from: notStakingContractAddress },
);

View File

@@ -9,6 +9,11 @@ export enum MakerPoolAssignmentErrorCodes {
PoolIsFull,
}
export enum OperatorShareErrorCodes {
OperatorShareMustBeBetween0And100,
CanOnlyDecreaseOperatorShare,
}
export enum ProtocolFeePaymentErrorCodes {
ZeroProtocolFeePaid,
MismatchedFeeAndPayment,
@@ -141,13 +146,13 @@ export class AmountExceedsBalanceOfPoolError extends RevertError {
}
}
export class InvalidPoolOperatorShareError extends RevertError {
constructor(poolId?: string, poolOperatorShare?: BigNumber | number | string) {
super(
'InvalidPoolOperatorShareError',
'InvalidPoolOperatorShareError(bytes32 poolId, uint32 poolOperatorShare)',
{ poolId, poolOperatorShare },
);
export class OperatorShareError extends RevertError {
constructor(error?: OperatorShareErrorCodes, poolId?: string, operatorShare?: BigNumber | number | string) {
super('OperatorShareError', 'OperatorShareError(uint8 error, bytes32 poolId, uint32 operatorShare)', {
error,
poolId,
operatorShare,
});
}
}
@@ -213,7 +218,7 @@ const types = [
OnlyCallableIfInCatastrophicFailureError,
OnlyCallableIfNotInCatastrophicFailureError,
AmountExceedsBalanceOfPoolError,
InvalidPoolOperatorShareError,
OperatorShareError,
PoolAlreadyExistsError,
InvalidCobbDouglasAlphaError,
EthVaultNotSetError,