Merge pull request #2190 from 0xProject/feat/3.0/consolidate-pool-mixins

Consolidate staking pool mixins
This commit is contained in:
Amir Bandeali 2019-09-24 14:00:28 -07:00 committed by GitHub
commit 0c5f0271c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 371 additions and 430 deletions

View File

@ -1,4 +1,4 @@
import { chaiSetup, getCodesizeFromArtifact } from '@0x/contracts-test-utils'; import { chaiSetup, constants, getCodesizeFromArtifact } from '@0x/contracts-test-utils';
import * as chai from 'chai'; import * as chai from 'chai';
chaiSetup.configure(); chaiSetup.configure();
@ -7,12 +7,10 @@ const expect = chai.expect;
import { artifacts } from '../src'; import { artifacts } from '../src';
describe('Contract Size Checks', () => { describe('Contract Size Checks', () => {
const MAX_CODE_SIZE = 24576;
describe('Exchange', () => { describe('Exchange', () => {
it('should have a codesize less than the maximum', async () => { it('should have a codesize less than the maximum', async () => {
const actualSize = getCodesizeFromArtifact(artifacts.Exchange); const actualSize = getCodesizeFromArtifact(artifacts.Exchange);
expect(actualSize).to.be.lt(MAX_CODE_SIZE); expect(actualSize).to.be.lt(constants.MAX_CODE_SIZE);
}); });
}); });
}); });

View File

@ -34,12 +34,10 @@ contract Staking is
MixinConstants, MixinConstants,
Ownable, Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager, MixinExchangeManager,
MixinScheduler, MixinScheduler,
MixinParams, MixinParams,
MixinStakeStorage, MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards, MixinCumulativeRewards,
MixinStakingPoolRewards, MixinStakingPoolRewards,
@ -214,8 +212,8 @@ contract Staking is
slot := add(slot, 0x1) slot := add(slot, 0x1)
assertSlotAndOffset( assertSlotAndOffset(
poolJoinedByMakerAddress_slot, _poolJoinedByMakerAddress_slot,
poolJoinedByMakerAddress_offset, _poolJoinedByMakerAddress_offset,
slot, slot,
offset offset
) )
@ -378,8 +376,8 @@ contract Staking is
slot := add(slot, 0x1) slot := add(slot, 0x1)
assertSlotAndOffset( assertSlotAndOffset(
_wethReservedForPoolRewards_slot, wethReservedForPoolRewards_slot,
_wethReservedForPoolRewards_offset, wethReservedForPoolRewards_offset,
slot, slot,
offset offset
) )

View File

@ -38,11 +38,9 @@ contract MixinExchangeFees is
MixinConstants, MixinConstants,
Ownable, Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinExchangeManager, MixinExchangeManager,
MixinScheduler, MixinScheduler,
MixinStakeStorage, MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards, MixinCumulativeRewards,
MixinStakingPoolRewards, MixinStakingPoolRewards,
@ -126,20 +124,6 @@ contract MixinExchangeFees is
activePoolsThisEpoch[poolId] = pool; activePoolsThisEpoch[poolId] = pool;
} }
/// @dev Returns the total balance of this contract, including WETH,
/// minus any WETH that has been reserved for rewards.
/// @return totalBalance Total balance.
function getAvailableRewardsBalance()
external
view
returns (uint256 totalBalance)
{
totalBalance = address(this).balance.safeAdd(
_getAvailableWethBalance()
);
return totalBalance;
}
/// @dev Get information on an active staking pool in this epoch. /// @dev Get information on an active staking pool in this epoch.
/// @param poolId Pool Id to query. /// @param poolId Pool Id to query.
/// @return pool ActivePool struct. /// @return pool ActivePool struct.
@ -170,6 +154,7 @@ contract MixinExchangeFees is
_poolById[poolId].operator, _poolById[poolId].operator,
poolId poolId
).currentEpochBalance; ).currentEpochBalance;
membersStake = totalStake.safeSub(operatorStake); membersStake = totalStake.safeSub(operatorStake);
weightedStake = operatorStake.safeAdd( weightedStake = operatorStake.safeAdd(
LibMath.getPartialAmountFloor( LibMath.getPartialAmountFloor(

View File

@ -77,7 +77,8 @@ contract MixinStorage is
// mapping from Maker Address to a struct representing the pool the maker has joined and // 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. // whether the operator of that pool has subsequently added the maker.
mapping (address => IStructs.MakerPoolJoinStatus) public poolJoinedByMakerAddress; // (access externally using `getStakingPoolIdOfMaker`)
mapping (address => IStructs.MakerPoolJoinStatus) internal _poolJoinedByMakerAddress;
// mapping from Pool Id to Pool // mapping from Pool Id to Pool
mapping (bytes32 => IStructs.Pool) internal _poolById; mapping (bytes32 => IStructs.Pool) internal _poolById;
@ -144,7 +145,7 @@ contract MixinStorage is
IStructs.UnfinalizedState public unfinalizedState; IStructs.UnfinalizedState public unfinalizedState;
/// @dev The WETH balance of this contract that is reserved for pool reward payouts. /// @dev The WETH balance of this contract that is reserved for pool reward payouts.
uint256 _wethReservedForPoolRewards; uint256 public wethReservedForPoolRewards;
/// @dev Adds owner as an authorized address. /// @dev Adds owner as an authorized address.
constructor() constructor()

View File

@ -51,11 +51,6 @@ interface IStorage {
view view
returns (bytes32); returns (bytes32);
function poolJoinedByMakerAddress(address makerAddress)
external
view
returns (IStructs.MakerPoolJoinStatus memory);
function numMakersByPoolId(bytes32 poolId) function numMakersByPoolId(bytes32 poolId)
external external
view view

View File

@ -75,13 +75,9 @@ library LibStakingRichErrors {
bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR = bytes4 internal constant INSUFFICIENT_BALANCE_ERROR_SELECTOR =
0x84c8b7c9; 0x84c8b7c9;
// bytes4(keccak256("OnlyCallableByPoolOperatorError(address,address)")) // bytes4(keccak256("OnlyCallableByPoolOperatorOrMakerError(address,bytes32)"))
bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_ERROR_SELECTOR =
0x6cfa0c22;
// bytes4(keccak256("OnlyCallableByPoolOperatorOrMakerError(address,address,address)"))
bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR = bytes4 internal constant ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR =
0x7d9e1c10; 0x7677eb13;
// bytes4(keccak256("MakerPoolAssignmentError(uint8,address,bytes32)")) // bytes4(keccak256("MakerPoolAssignmentError(uint8,address,bytes32)"))
bytes4 internal constant MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR = bytes4 internal constant MAKER_POOL_ASSIGNMENT_ERROR_SELECTOR =
@ -217,25 +213,9 @@ library LibStakingRichErrors {
); );
} }
function OnlyCallableByPoolOperatorError(
address senderAddress,
address operator
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
ONLY_CALLABLE_BY_POOL_OPERATOR_ERROR_SELECTOR,
senderAddress,
operator
);
}
function OnlyCallableByPoolOperatorOrMakerError( function OnlyCallableByPoolOperatorOrMakerError(
address senderAddress, address senderAddress,
address operator, bytes32 poolId
address makerAddress
) )
internal internal
pure pure
@ -244,8 +224,7 @@ library LibStakingRichErrors {
return abi.encodeWithSelector( return abi.encodeWithSelector(
ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR, ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR,
senderAddress, senderAddress,
operator, poolId
makerAddress
); );
} }

View File

@ -32,10 +32,8 @@ contract MixinStake is
MixinConstants, MixinConstants,
Ownable, Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinScheduler, MixinScheduler,
MixinStakeStorage, MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards, MixinCumulativeRewards,
MixinStakingPoolRewards, MixinStakingPoolRewards,

View File

@ -24,7 +24,6 @@ import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol"; import "../libs/LibStakingRichErrors.sol";
import "../interfaces/IStructs.sol"; import "../interfaces/IStructs.sol";
import "./MixinStakingPoolRewards.sol"; import "./MixinStakingPoolRewards.sol";
import "./MixinStakingPoolMakers.sol";
contract MixinStakingPool is contract MixinStakingPool is
@ -33,15 +32,21 @@ contract MixinStakingPool is
MixinConstants, MixinConstants,
Ownable, Ownable,
MixinStorage, MixinStorage,
MixinStakingPoolModifiers,
MixinScheduler, MixinScheduler,
MixinStakeStorage, MixinStakeStorage,
MixinStakingPoolMakers,
MixinStakeBalances, MixinStakeBalances,
MixinCumulativeRewards, MixinCumulativeRewards,
MixinStakingPoolRewards MixinStakingPoolRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
using LibSafeDowncast for uint256;
/// @dev Asserts that the sender is the operator of the input pool or the input maker.
/// @param poolId Pool sender must be operator of.
modifier onlyStakingPoolOperatorOrMaker(bytes32 poolId) {
_assertSenderIsPoolOperatorOrMaker(poolId);
_;
}
/// @dev Create a new staking pool. The sender will be the operator of this pool. /// @dev Create a new staking pool. The sender will be the operator of this pool.
/// Note that an operator must be payable. /// Note that an operator must be payable.
@ -94,6 +99,7 @@ contract MixinStakingPool is
/// @param newOperatorShare The newly decreased percentage of any rewards owned by the operator. /// @param newOperatorShare The newly decreased percentage of any rewards owned by the operator.
function decreaseStakingPoolOperatorShare(bytes32 poolId, uint32 newOperatorShare) function decreaseStakingPoolOperatorShare(bytes32 poolId, uint32 newOperatorShare)
external external
onlyStakingPoolOperatorOrMaker(poolId)
{ {
// load pool and assert that we can decrease // load pool and assert that we can decrease
uint32 currentOperatorShare = _poolById[poolId].operatorShare; uint32 currentOperatorShare = _poolById[poolId].operatorShare;
@ -112,6 +118,94 @@ contract MixinStakingPool is
); );
} }
/// @dev Allows caller to join a staking pool if already assigned.
/// @param poolId Unique id of pool.
function joinStakingPoolAsMaker(bytes32 poolId)
external
{
// Is the maker already in a pool?
address makerAddress = msg.sender;
IStructs.MakerPoolJoinStatus memory poolJoinStatus = _poolJoinedByMakerAddress[makerAddress];
if (poolJoinStatus.confirmed) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
poolJoinStatus.poolId
));
}
poolJoinStatus.poolId = poolId;
_poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
// Maker has joined to the pool, awaiting operator confirmation
emit PendingAddMakerToPool(
poolId,
makerAddress
);
}
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function addMakerToStakingPool(
bytes32 poolId,
address makerAddress
)
external
onlyStakingPoolOperatorOrMaker(poolId)
{
_addMakerToStakingPool(poolId, makerAddress);
}
/// @dev Removes a maker from a staking pool. Note that this is only callable by the pool operator or maker.
/// Note also that the maker does not have to *agree* to leave the pool; this action is
/// at the sole discretion of the pool operator.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function removeMakerFromStakingPool(
bytes32 poolId,
address makerAddress
)
external
onlyStakingPoolOperatorOrMaker(poolId)
{
bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress);
if (makerPoolId != poolId) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
makerAddress,
makerPoolId
));
}
// remove the pool and confirmation from the maker status
delete _poolJoinedByMakerAddress[makerAddress];
_poolById[poolId].numberOfMakers = uint256(_poolById[poolId].numberOfMakers).safeSub(1).downcastToUint32();
// Maker has been removed from the pool`
emit MakerRemovedFromStakingPool(
poolId,
makerAddress
);
}
/// @dev Returns the pool id of the input maker.
/// @param makerAddress Address of maker
/// @return Pool id, nil if maker is not yet assigned to a pool.
function getStakingPoolIdOfMaker(address makerAddress)
public
view
returns (bytes32)
{
IStructs.MakerPoolJoinStatus memory poolJoinStatus = _poolJoinedByMakerAddress[makerAddress];
if (poolJoinStatus.confirmed) {
return poolJoinStatus.poolId;
} else {
return NIL_POOL_ID;
}
}
/// @dev Returns a staking pool /// @dev Returns a staking pool
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
function getStakingPool(bytes32 poolId) function getStakingPool(bytes32 poolId)
@ -122,6 +216,65 @@ contract MixinStakingPool is
return _poolById[poolId]; return _poolById[poolId];
} }
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function _addMakerToStakingPool(
bytes32 poolId,
address makerAddress
)
internal
{
// cache pool and join status for use throughout this function
IStructs.Pool memory pool = _poolById[poolId];
IStructs.MakerPoolJoinStatus memory poolJoinStatus = _poolJoinedByMakerAddress[makerAddress];
// Is the maker already in a pool?
if (poolJoinStatus.confirmed) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
poolJoinStatus.poolId
));
}
// Is the maker trying to join this pool; or are they the operator?
bytes32 makerPendingPoolId = poolJoinStatus.poolId;
if (makerPendingPoolId != poolId && makerAddress != pool.operator) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
makerAddress,
makerPendingPoolId
));
}
// Is the pool already full?
// 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,
poolId
));
}
// Add maker to pool
poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: true
});
_poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
_poolById[poolId].numberOfMakers = uint256(pool.numberOfMakers).safeAdd(1).downcastToUint32();
// Maker has been added to the pool
emit MakerAddedToStakingPool(
poolId,
makerAddress
);
}
/// @dev Computes the unique id that comes after the input pool id. /// @dev Computes the unique id that comes after the input pool id.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @return Next pool id after input pool. /// @return Next pool id after input pool.
@ -180,4 +333,24 @@ contract MixinStakingPool is
)); ));
} }
} }
/// @dev Asserts that the sender is the operator of the input pool or the input maker.
/// @param poolId Pool sender must be operator of.
function _assertSenderIsPoolOperatorOrMaker(bytes32 poolId)
private
view
{
address operator = _poolById[poolId].operator;
if (
msg.sender != operator &&
getStakingPoolIdOfMaker(msg.sender) != poolId
) {
LibRichErrors.rrevert(
LibStakingRichErrors.OnlyCallableByPoolOperatorOrMakerError(
msg.sender,
poolId
)
);
}
}
} }

View File

@ -1,206 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSafeDowncast.sol";
import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinStorage.sol";
import "./MixinStakingPoolModifiers.sol";
/// @dev This mixin contains logic for staking pools.
contract MixinStakingPoolMakers is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinStakingPoolModifiers
{
using LibSafeMath for uint256;
using LibSafeDowncast for uint256;
/// @dev Allows caller to join a staking pool if already assigned.
/// @param poolId Unique id of pool.
function joinStakingPoolAsMaker(
bytes32 poolId
)
external
{
// Is the maker already in a pool?
address makerAddress = msg.sender;
if (isMakerAssignedToStakingPool(makerAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
getStakingPoolIdOfMaker(makerAddress)
));
}
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: false
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
// Maker has joined to the pool, awaiting operator confirmation
emit PendingAddMakerToPool(
poolId,
makerAddress
);
}
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function addMakerToStakingPool(
bytes32 poolId,
address makerAddress
)
external
onlyStakingPoolOperator(poolId)
{
_addMakerToStakingPool(poolId, makerAddress);
}
/// @dev Removes a maker from a staking pool. Note that this is only callable by the pool operator or maker.
/// Note also that the maker does not have to *agree* to leave the pool; this action is
/// at the sole discretion of the pool operator.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function removeMakerFromStakingPool(
bytes32 poolId,
address makerAddress
)
external
onlyStakingPoolOperatorOrMaker(poolId, makerAddress)
{
bytes32 makerPoolId = getStakingPoolIdOfMaker(makerAddress);
if (makerPoolId != poolId) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
makerAddress,
makerPoolId
));
}
// remove the pool and confirmation from the maker status
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: NIL_POOL_ID,
confirmed: false
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
_poolById[poolId].numberOfMakers = uint256(_poolById[poolId].numberOfMakers).safeSub(1).downcastToUint32();
// Maker has been removed from the pool`
emit MakerRemovedFromStakingPool(
poolId,
makerAddress
);
}
/// @dev Returns the pool id of the input maker.
/// @param makerAddress Address of maker
/// @return Pool id, nil if maker is not yet assigned to a pool.
function getStakingPoolIdOfMaker(address makerAddress)
public
view
returns (bytes32)
{
if (isMakerAssignedToStakingPool(makerAddress)) {
return poolJoinedByMakerAddress[makerAddress].poolId;
} else {
return NIL_POOL_ID;
}
}
/// @dev Returns true iff the maker is assigned to a staking pool.
/// @param makerAddress Address of maker
/// @return True iff assigned.
function isMakerAssignedToStakingPool(address makerAddress)
public
view
returns (bool)
{
return poolJoinedByMakerAddress[makerAddress].confirmed;
}
/// @dev Adds a maker to a staking pool. Note that this is only callable by the pool operator.
/// Note also that the maker must have previously called joinStakingPoolAsMaker.
/// @param poolId Unique id of pool.
/// @param makerAddress Address of maker.
function _addMakerToStakingPool(
bytes32 poolId,
address makerAddress
)
internal
{
// cache pool for use throughout this function
IStructs.Pool memory pool = _poolById[poolId];
// Is the maker already in a pool?
if (isMakerAssignedToStakingPool(makerAddress)) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
makerAddress,
getStakingPoolIdOfMaker(makerAddress)
));
}
// Is the maker trying to join this pool; or are they the operator?
bytes32 makerPendingPoolId = poolJoinedByMakerAddress[makerAddress].poolId;
if (makerPendingPoolId != poolId && makerAddress != pool.operator) {
LibRichErrors.rrevert(LibStakingRichErrors.MakerPoolAssignmentError(
LibStakingRichErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
makerAddress,
makerPendingPoolId
));
}
// Is the pool already full?
// 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,
poolId
));
}
// Add maker to pool
IStructs.MakerPoolJoinStatus memory poolJoinStatus = IStructs.MakerPoolJoinStatus({
poolId: poolId,
confirmed: true
});
poolJoinedByMakerAddress[makerAddress] = poolJoinStatus;
_poolById[poolId].numberOfMakers = uint256(pool.numberOfMakers).safeAdd(1).downcastToUint32();
// Maker has been added to the pool
emit MakerAddedToStakingPool(
poolId,
makerAddress
);
}
}

View File

@ -1,65 +0,0 @@
/*
Copyright 2019 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../immutable/MixinStorage.sol";
contract MixinStakingPoolModifiers is
MixinConstants,
Ownable,
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 operator = _poolById[poolId].operator;
if (msg.sender != operator) {
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByPoolOperatorError(
msg.sender,
operator
));
}
_;
}
/// @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 operator = _poolById[poolId].operator;
if (
msg.sender != operator &&
msg.sender != makerAddress
) {
LibRichErrors.rrevert(
LibStakingRichErrors.OnlyCallableByPoolOperatorOrMakerError(
msg.sender,
operator,
makerAddress
)
);
}
_;
}
}

View File

@ -188,8 +188,8 @@ contract MixinStakingPoolRewards is
} }
if (membersReward > 0) { if (membersReward > 0) {
// Increment the balance of the pool // Increase the balance of the pool
_incrementPoolRewards(poolId, membersReward); _increasePoolRewards(poolId, membersReward);
// Fetch the last epoch at which we stored an entry for this pool; // Fetch the last epoch at which we stored an entry for this pool;
// this is the most up-to-date cumulative rewards for this pool. // this is the most up-to-date cumulative rewards for this pool.
@ -279,8 +279,8 @@ contract MixinStakingPoolRewards is
return; return;
} }
// Decrement the balance of the pool // Decrease the balance of the pool
_decrementPoolRewards(poolId, balance); _decreasePoolRewards(poolId, balance);
// Withdraw the member's WETH balance // Withdraw the member's WETH balance
_getWethContract().transfer(member, balance); _getWethContract().transfer(member, balance);
@ -421,23 +421,23 @@ contract MixinStakingPoolRewards is
); );
} }
/// @dev Increments rewards for a pool. /// @dev Increases rewards for a pool.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param amount Amount to increment rewards by. /// @param amount Amount to increment rewards by.
function _incrementPoolRewards(bytes32 poolId, uint256 amount) function _increasePoolRewards(bytes32 poolId, uint256 amount)
private private
{ {
rewardsByPoolId[poolId] = rewardsByPoolId[poolId].safeAdd(amount); rewardsByPoolId[poolId] = rewardsByPoolId[poolId].safeAdd(amount);
_wethReservedForPoolRewards = _wethReservedForPoolRewards.safeAdd(amount); wethReservedForPoolRewards = wethReservedForPoolRewards.safeAdd(amount);
} }
/// @dev Decrements rewards for a pool. /// @dev Decreases rewards for a pool.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param amount Amount to decrement rewards by. /// @param amount Amount to decrement rewards by.
function _decrementPoolRewards(bytes32 poolId, uint256 amount) function _decreasePoolRewards(bytes32 poolId, uint256 amount)
private private
{ {
rewardsByPoolId[poolId] = rewardsByPoolId[poolId].safeSub(amount); rewardsByPoolId[poolId] = rewardsByPoolId[poolId].safeSub(amount);
_wethReservedForPoolRewards = _wethReservedForPoolRewards.safeSub(amount); wethReservedForPoolRewards = wethReservedForPoolRewards.safeSub(amount);
} }
} }

View File

@ -257,7 +257,7 @@ contract MixinFinalizer is
returns (uint256 wethBalance) returns (uint256 wethBalance)
{ {
wethBalance = _getWethContract().balanceOf(address(this)) wethBalance = _getWethContract().balanceOf(address(this))
.safeSub(_wethReservedForPoolRewards); .safeSub(wethReservedForPoolRewards);
return wethBalance; return wethBalance;
} }

View File

@ -56,8 +56,8 @@ contract TestProtocolFees is
function addMakerToPool(bytes32 poolId, address makerAddress) function addMakerToPool(bytes32 poolId, address makerAddress)
external external
{ {
poolJoinedByMakerAddress[makerAddress].poolId = poolId; _poolJoinedByMakerAddress[makerAddress].poolId = poolId;
poolJoinedByMakerAddress[makerAddress].confirmed = true; _poolJoinedByMakerAddress[makerAddress].confirmed = true;
} }
function advanceEpoch() function advanceEpoch()

View File

@ -37,7 +37,7 @@
}, },
"config": { "config": {
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IVaultCore|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolMakers|MixinStakingPoolModifiers|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|ReadOnlyProxy|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinVaultCore|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|ZrxVault).json" "abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IVaultCore|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibProxy|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|ReadOnlyProxy|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibProxy|TestLibProxyReceiver|TestLibSafeDowncast|TestMixinVaultCore|TestProtocolFees|TestStaking|TestStakingNoWETH|TestStakingProxy|ZrxVault).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -32,8 +32,6 @@ import * as MixinStake from '../generated-artifacts/MixinStake.json';
import * as MixinStakeBalances from '../generated-artifacts/MixinStakeBalances.json'; import * as MixinStakeBalances from '../generated-artifacts/MixinStakeBalances.json';
import * as MixinStakeStorage from '../generated-artifacts/MixinStakeStorage.json'; import * as MixinStakeStorage from '../generated-artifacts/MixinStakeStorage.json';
import * as MixinStakingPool from '../generated-artifacts/MixinStakingPool.json'; import * as MixinStakingPool from '../generated-artifacts/MixinStakingPool.json';
import * as MixinStakingPoolMakers from '../generated-artifacts/MixinStakingPoolMakers.json';
import * as MixinStakingPoolModifiers from '../generated-artifacts/MixinStakingPoolModifiers.json';
import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json'; import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json';
import * as MixinStorage from '../generated-artifacts/MixinStorage.json'; import * as MixinStorage from '../generated-artifacts/MixinStorage.json';
import * as MixinVaultCore from '../generated-artifacts/MixinVaultCore.json'; import * as MixinVaultCore from '../generated-artifacts/MixinVaultCore.json';
@ -84,8 +82,6 @@ export const artifacts = {
MixinStakeStorage: MixinStakeStorage as ContractArtifact, MixinStakeStorage: MixinStakeStorage as ContractArtifact,
MixinCumulativeRewards: MixinCumulativeRewards as ContractArtifact, MixinCumulativeRewards: MixinCumulativeRewards as ContractArtifact,
MixinStakingPool: MixinStakingPool as ContractArtifact, MixinStakingPool: MixinStakingPool as ContractArtifact,
MixinStakingPoolMakers: MixinStakingPoolMakers as ContractArtifact,
MixinStakingPoolModifiers: MixinStakingPoolModifiers as ContractArtifact,
MixinStakingPoolRewards: MixinStakingPoolRewards as ContractArtifact, MixinStakingPoolRewards: MixinStakingPoolRewards as ContractArtifact,
MixinAbstract: MixinAbstract as ContractArtifact, MixinAbstract: MixinAbstract as ContractArtifact,
MixinFinalizer: MixinFinalizer as ContractArtifact, MixinFinalizer: MixinFinalizer as ContractArtifact,

View File

@ -30,8 +30,6 @@ export * from '../generated-wrappers/mixin_stake';
export * from '../generated-wrappers/mixin_stake_balances'; export * from '../generated-wrappers/mixin_stake_balances';
export * from '../generated-wrappers/mixin_stake_storage'; export * from '../generated-wrappers/mixin_stake_storage';
export * from '../generated-wrappers/mixin_staking_pool'; export * from '../generated-wrappers/mixin_staking_pool';
export * from '../generated-wrappers/mixin_staking_pool_makers';
export * from '../generated-wrappers/mixin_staking_pool_modifiers';
export * from '../generated-wrappers/mixin_staking_pool_rewards'; export * from '../generated-wrappers/mixin_staking_pool_rewards';
export * from '../generated-wrappers/mixin_storage'; export * from '../generated-wrappers/mixin_storage';
export * from '../generated-wrappers/mixin_vault_core'; export * from '../generated-wrappers/mixin_vault_core';

View File

@ -241,7 +241,7 @@ export class FinalizerActor extends BaseActor {
this._stakingApiWrapper.stakingContract.getActiveStakingPoolThisEpoch.callAsync(poolId), this._stakingApiWrapper.stakingContract.getActiveStakingPoolThisEpoch.callAsync(poolId),
), ),
); );
const totalRewards = await this._stakingApiWrapper.stakingContract.getAvailableRewardsBalance.callAsync(); const totalRewards = await this._stakingApiWrapper.utils.getAvailableRewardsBalanceAsync();
const totalFeesCollected = BigNumber.sum(...activePools.map(p => p.feesCollected)); const totalFeesCollected = BigNumber.sum(...activePools.map(p => p.feesCollected));
const totalWeightedStake = BigNumber.sum(...activePools.map(p => p.weightedStake)); const totalWeightedStake = BigNumber.sum(...activePools.map(p => p.weightedStake));
if (totalRewards.eq(0) || totalFeesCollected.eq(0) || totalWeightedStake.eq(0)) { if (totalRewards.eq(0) || totalFeesCollected.eq(0) || totalWeightedStake.eq(0)) {

View File

@ -4,9 +4,9 @@ import * as _ from 'lodash';
import { constants as stakingConstants } from '../utils/constants'; import { constants as stakingConstants } from '../utils/constants';
import { BaseActor } from './base_actor'; import { PoolOperatorActor } from './pool_operator_actor';
export class MakerActor extends BaseActor { export class MakerActor extends PoolOperatorActor {
public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> { public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
// Join pool // Join pool
const txReceiptPromise = this._stakingApiWrapper.stakingContract.joinStakingPoolAsMaker.awaitTransactionSuccessAsync( const txReceiptPromise = this._stakingApiWrapper.stakingContract.joinStakingPoolAsMaker.awaitTransactionSuccessAsync(
@ -26,29 +26,4 @@ export class MakerActor extends BaseActor {
); );
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID); expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
} }
public async removeMakerFromStakingPoolAsync(
poolId: string,
makerAddress: string,
revertError?: RevertError,
): Promise<void> {
// remove maker (should fail if makerAddress !== this._owner)
const txReceiptPromise = this._stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
poolId,
makerAddress,
{ from: this._owner },
);
if (revertError !== undefined) {
await expect(txReceiptPromise).to.revertWith(revertError);
return;
}
await txReceiptPromise;
// check the pool id of the maker
const poolIdOfMakerAfterRemoving = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
this._owner,
);
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
}
} }

View File

@ -0,0 +1,18 @@
import { constants, expect, getCodesizeFromArtifact } from '@0x/contracts-test-utils';
import { artifacts } from '../src';
describe.skip('Contract Size Checks', () => {
describe('Staking', () => {
it('should have a codesize less than the maximum', async () => {
const actualSize = getCodesizeFromArtifact(artifacts.Staking);
expect(actualSize).to.be.lt(constants.MAX_CODE_SIZE);
});
});
describe('StakingProxy', () => {
it('should have a codesize less than the maximum', async () => {
const actualSize = getCodesizeFromArtifact(artifacts.StakingProxy);
expect(actualSize).to.be.lt(constants.MAX_CODE_SIZE);
});
});
});

View File

@ -9,6 +9,7 @@ import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api
import { constants as stakingConstants } from './utils/constants'; import { constants as stakingConstants } from './utils/constants';
// tslint:disable:no-unnecessary-type-assertion // tslint:disable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count
blockchainTests('Staking Pool Management', env => { blockchainTests('Staking Pool Management', env => {
// constants // constants
const { PPM_100_PERCENT, PPM_DENOMINATOR } = constants; const { PPM_100_PERCENT, PPM_DENOMINATOR } = constants;
@ -23,8 +24,7 @@ blockchainTests('Staking Pool Management', env => {
before(async () => { before(async () => {
// create accounts // create accounts
accounts = await env.getAccountAddressesAsync(); accounts = await env.getAccountAddressesAsync();
owner = accounts[0]; [owner, ...users] = accounts;
users = accounts.slice(1);
// set up ERC20Wrapper // set up ERC20Wrapper
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner); erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
// deploy staking contracts // deploy staking contracts
@ -102,6 +102,65 @@ blockchainTests('Staking Pool Management', env => {
// operator removes maker from pool // operator removes maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress); await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
}); });
it('Should successfully add/remove a maker to a pool if approved by maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const maker1Address = users[1];
const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
const maker2Address = users[2];
const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker1.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, maker1Address);
// maker joins pool
await maker2.joinStakingPoolAsMakerAsync(poolId);
// approved maker adds new maker to pool
await maker1.addMakerToStakingPoolAsync(poolId, maker2Address);
});
it('should fail to add a maker to a pool if called by pending maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker.joinStakingPoolAsMakerAsync(poolId);
await maker.addMakerToStakingPoolAsync(
poolId,
makerAddress,
new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(makerAddress, poolId),
);
});
it('should fail to add a maker to a pool if not called by operator/registered maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const maker1Address = users[1];
const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
const maker2Address = users[2];
const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker1.joinStakingPoolAsMakerAsync(poolId);
await maker2.addMakerToStakingPoolAsync(
poolId,
maker1Address,
new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(maker2Address, poolId),
);
});
it('Maker should successfully remove themselves from a pool', async () => { it('Maker should successfully remove themselves from a pool', async () => {
// test parameters // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
@ -119,6 +178,23 @@ blockchainTests('Staking Pool Management', env => {
// maker removes themselves from pool // maker removes themselves from pool
await maker.removeMakerFromStakingPoolAsync(poolId, makerAddress); await maker.removeMakerFromStakingPoolAsync(poolId, makerAddress);
}); });
it('operator can remove a maker from their pool', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// operator removes maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
});
it('Should successfully add/remove multiple makers to the same pool', async () => { it('Should successfully add/remove multiple makers to the same pool', async () => {
// test parameters // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
@ -152,6 +228,28 @@ blockchainTests('Staking Pool Management', env => {
pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId); pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
expect(pool.numberOfMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0); expect(pool.numberOfMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
}); });
it('should fail to remove a maker from a pool if not called by operator/registered maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const maker1Address = users[1];
const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
const maker2Address = users[2];
const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker1.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, maker1Address);
await maker2.removeMakerFromStakingPoolAsync(
poolId,
maker1Address,
new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(maker2Address, poolId),
);
});
it('Should fail if maker already assigned to another pool tries to join', async () => { it('Should fail if maker already assigned to another pool tries to join', async () => {
// test parameters // test parameters
const operatorShare = (39 / 100) * PPM_DENOMINATOR; const operatorShare = (39 / 100) * PPM_DENOMINATOR;
@ -264,30 +362,6 @@ blockchainTests('Staking Pool Management', env => {
// remove non-existent maker from pool // remove non-existent maker from pool
await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError); await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError);
}); });
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 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
const notOperatorAddress = users[2];
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// add maker to pool
await maker.joinStakingPoolAsMakerAsync(poolId);
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorError(
notOperatorAddress,
operatorAddress,
);
const tx = stakingApiWrapper.stakingContract.addMakerToStakingPool.awaitTransactionSuccessAsync(
poolId,
makerAddress,
{ from: notOperatorAddress },
);
await expect(tx).to.revertWith(revertError);
});
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => { it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
// test parameters // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
@ -305,8 +379,7 @@ blockchainTests('Staking Pool Management', env => {
// try to remove the maker address from an address other than the operator // try to remove the maker address from an address other than the operator
const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError( const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(
neitherOperatorNorMakerAddress, neitherOperatorNorMakerAddress,
operatorAddress, poolId,
makerAddress,
); );
const tx = stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync( const tx = stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
poolId, poolId,
@ -364,6 +437,24 @@ blockchainTests('Staking Pool Management', env => {
// decrease operator share // decrease operator share
await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1); await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1);
}); });
it('Maker should successfuly decrease their share of rewards', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
// maker joins pool
await maker.joinStakingPoolAsMakerAsync(poolId);
// operator adds maker to pool
await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
// decrease operator share
await maker.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1);
});
it('Should fail if operator tries to increase their share of rewards', async () => { it('Should fail if operator tries to increase their share of rewards', async () => {
// test parameters // test parameters
const operatorAddress = users[0]; const operatorAddress = users[0];
@ -401,6 +492,22 @@ blockchainTests('Staking Pool Management', env => {
// decrease operator share // decrease operator share
await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare, revertError); await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare, revertError);
}); });
it('should fail to decrease operator share if not called by operator/registered maker', async () => {
// test parameters
const operatorAddress = users[0];
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
const makerAddress = users[1];
const maker = new MakerActor(makerAddress, stakingApiWrapper);
// create pool
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
await maker.decreaseStakingPoolOperatorShareAsync(
poolId,
operatorShare - 1,
new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(makerAddress, poolId),
);
});
}); });
}); });
// tslint:enable:no-unnecessary-type-assertion // tslint:enable:no-unnecessary-type-assertion

View File

@ -122,10 +122,13 @@ export class StakingApiWrapper {
); );
}, },
getEthAndWethBalanceOfAsync: async (address: string): Promise<BigNumber> => { getAvailableRewardsBalanceAsync: async (): Promise<BigNumber> => {
const ethBalance = await this._web3Wrapper.getBalanceInWeiAsync(address); const [ethBalance, wethBalance, reservedRewards] = await Promise.all([
const wethBalance = await this.wethContract.balanceOf.callAsync(address); this._web3Wrapper.getBalanceInWeiAsync(this.stakingProxyContract.address),
return BigNumber.sum(ethBalance, wethBalance); this.wethContract.balanceOf.callAsync(this.stakingProxyContract.address),
this.stakingContract.wethReservedForPoolRewards.callAsync(),
]);
return BigNumber.sum(ethBalance, wethBalance).minus(reservedRewards);
}, },
getParamsAsync: async (): Promise<StakingParams> => { getParamsAsync: async (): Promise<StakingParams> => {

View File

@ -30,8 +30,6 @@
"generated-artifacts/MixinStakeBalances.json", "generated-artifacts/MixinStakeBalances.json",
"generated-artifacts/MixinStakeStorage.json", "generated-artifacts/MixinStakeStorage.json",
"generated-artifacts/MixinStakingPool.json", "generated-artifacts/MixinStakingPool.json",
"generated-artifacts/MixinStakingPoolMakers.json",
"generated-artifacts/MixinStakingPoolModifiers.json",
"generated-artifacts/MixinStakingPoolRewards.json", "generated-artifacts/MixinStakingPoolRewards.json",
"generated-artifacts/MixinStorage.json", "generated-artifacts/MixinStorage.json",
"generated-artifacts/MixinVaultCore.json", "generated-artifacts/MixinVaultCore.json",

View File

@ -83,4 +83,5 @@ export const constants = {
NUM_TEST_ACCOUNTS: 20, NUM_TEST_ACCOUNTS: 20,
PPM_DENOMINATOR: 1e6, PPM_DENOMINATOR: 1e6,
PPM_100_PERCENT: 1e6, PPM_100_PERCENT: 1e6,
MAX_CODE_SIZE: 24576,
}; };

View File

@ -77,22 +77,12 @@ export class InsufficientBalanceError extends RevertError {
} }
} }
export class OnlyCallableByPoolOperatorError extends RevertError {
constructor(senderAddress?: string, poolOperatorAddress?: string) {
super(
'OnlyCallableByPoolOperatorError',
'OnlyCallableByPoolOperatorError(address senderAddress, address poolOperatorAddress)',
{ senderAddress, poolOperatorAddress },
);
}
}
export class OnlyCallableByPoolOperatorOrMakerError extends RevertError { export class OnlyCallableByPoolOperatorOrMakerError extends RevertError {
constructor(senderAddress?: string, poolOperatorAddress?: string, makerAddress?: string) { constructor(senderAddress?: string, poolId?: string) {
super( super(
'OnlyCallableByPoolOperatorOrMakerError', 'OnlyCallableByPoolOperatorOrMakerError',
'OnlyCallableByPoolOperatorOrMakerError(address senderAddress, address poolOperatorAddress, address makerAddress)', 'OnlyCallableByPoolOperatorOrMakerError(address senderAddress, bytes32 poolId)',
{ senderAddress, poolOperatorAddress, makerAddress }, { senderAddress, poolId },
); );
} }
} }
@ -243,7 +233,6 @@ const types = [
MakerPoolAssignmentError, MakerPoolAssignmentError,
MiscalculatedRewardsError, MiscalculatedRewardsError,
OnlyCallableByExchangeError, OnlyCallableByExchangeError,
OnlyCallableByPoolOperatorError,
OnlyCallableByPoolOperatorOrMakerError, OnlyCallableByPoolOperatorOrMakerError,
OnlyCallableByStakingContractError, OnlyCallableByStakingContractError,
OnlyCallableIfInCatastrophicFailureError, OnlyCallableIfInCatastrophicFailureError,