move operator from staking logic to vault, allow operator to decrease operatorShare
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user