Rebased and addressed PR comments

This commit is contained in:
Greg Hysen
2019-09-17 14:31:38 -07:00
parent db97fe8164
commit 768387fea9
18 changed files with 125 additions and 253 deletions

View File

@@ -29,8 +29,8 @@ import "./fees/MixinExchangeFees.sol";
contract Staking is
IStaking,
MixinParams,
MixinStake,
MixinStakingPool,
MixinStake,
MixinExchangeFees
{
// this contract can receive ETH

View File

@@ -78,7 +78,7 @@ contract MixinStorage is
mapping (address => IStructs.MakerPoolJoinStatus) public poolJoinedByMakerAddress;
// mapping from Pool Id to Pool
mapping (bytes32 => IStructs.Pool) internal poolById;
mapping (bytes32 => IStructs.Pool) public poolById;
// current epoch
uint256 public currentEpoch = INITIAL_EPOCH;

View File

@@ -105,11 +105,11 @@ interface IStakingEvents {
/// @dev Emitted by MixinStakingPool when a new pool is created.
/// @param poolId Unique id generated for pool.
/// @param operatorAddress Address of creator/operator of pool.
/// @param operator The operator (creator) of pool.
/// @param operatorShare The share of rewards given to the operator, in ppm.
event StakingPoolCreated(
bytes32 poolId,
address operatorAddress,
address operator,
uint32 operatorShare
);
@@ -148,7 +148,7 @@ interface IStakingEvents {
/// @param oldOperatorShare Previous share of rewards owned by operator.
/// @param newOperatorShare Newly decreased share of rewards owned by operator.
event OperatorShareDecreased(
bytes32 poolId,
bytes32 indexed poolId,
uint32 oldOperatorShare,
uint32 newOperatorShare
);

View File

@@ -38,23 +38,11 @@ interface IStakingPoolRewardVault {
/// @param member of the pool.
/// @param poolId The pool the reward was deposited for.
event PoolRewardTransferredToEthVault(
bytes32 poolId,
address member,
bytes32 indexed poolId,
address indexed member,
uint256 amount
);
/// @dev Emitted when the eth vault is changed
/// @param newEthVault address of new Eth vault.
event EthVaultChanged(
address newEthVault
);
/// @dev Sets the Eth Vault.
/// Note that only the contract owner can call this.
/// @param ethVaultAddress Address of the Eth Vault.
function setEthVault(address ethVaultAddress)
external;
/// @dev Deposit an amount of ETH (`msg.value`) for `poolId` into the vault.
/// Note that this is only callable by the staking contract.
/// @param poolId that owns the ETH.
@@ -65,11 +53,14 @@ interface IStakingPoolRewardVault {
/// @dev Withdraw some amount in ETH of a pool member.
/// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool.
/// @param member of pool to transfer funds to.
/// @param amount Amount in ETH to transfer.
/// @param ethVaultAddress address of Eth Vault to send rewards to.
function transferToEthVault(
bytes32 poolId,
address member,
uint256 amount
uint256 amount,
address ethVaultAddress
)
external;

View File

@@ -96,7 +96,7 @@ interface IStructs {
IStructs.Fraction cumulativeReward;
}
/// @dev Holds the balances and other data for a staking pool.
/// @dev Holds the metadata for a staking pool.
/// @param initialzed True iff the balance struct is initialized.
/// @param operator of the pool.
/// @param operatorShare Fraction of the total balance owned by the operator, in ppm.

View File

@@ -233,7 +233,7 @@ library LibStakingRichErrors {
function OnlyCallableByPoolOperatorError(
address senderAddress,
address poolOperatorAddress
address operator
)
internal
pure
@@ -242,13 +242,13 @@ library LibStakingRichErrors {
return abi.encodeWithSelector(
ONLY_CALLABLE_BY_POOL_OPERATOR_ERROR_SELECTOR,
senderAddress,
poolOperatorAddress
operator
);
}
function OnlyCallableByPoolOperatorOrMakerError(
address senderAddress,
address poolOperatorAddress,
address operator,
address makerAddress
)
internal
@@ -258,7 +258,7 @@ library LibStakingRichErrors {
return abi.encodeWithSelector(
ONLY_CALLABLE_BY_POOL_OPERATOR_OR_MAKER_ERROR_SELECTOR,
senderAddress,
poolOperatorAddress,
operator,
makerAddress
);
}

View File

@@ -21,12 +21,17 @@ pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../staking_pools/MixinStakingPoolRewards.sol";
import "../staking_pools/MixinStakingPool.sol";
import "../libs/LibStakingRichErrors.sol";
/// @dev This mixin contains logic for managing ZRX tokens and Stake.
contract MixinStake is
MixinStakingPoolRewards
MixinStorage,
MixinStakingPoolMakers,
MixinStakingPoolRewards,
MixinStakingPool
{
using LibSafeMath for uint256;
@@ -162,15 +167,8 @@ contract MixinStake is
)
private
{
// revert if pool with given poolId doesn't exist
if (rewardVault.operatorOf(poolId) == NIL_ADDRESS) {
LibRichErrors.rrevert(
LibStakingRichErrors.PoolExistenceError(
poolId,
false
)
);
}
// sanity check the pool we're delegating to exists
_assertStakingPoolExists(poolId);
// cache amount delegated to pool by owner
IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]);
@@ -197,15 +195,8 @@ contract MixinStake is
)
private
{
// revert if pool with given poolId doesn't exist
if (rewardVault.operatorOf(poolId) == NIL_ADDRESS) {
LibRichErrors.rrevert(
LibStakingRichErrors.PoolExistenceError(
poolId,
false
)
);
}
// sanity check the pool we're undelegating from exists
_assertStakingPoolExists(poolId);
// cache amount delegated to pool by owner
IStructs.StoredBalance memory initDelegatedStakeToPoolByOwner = _loadUnsyncedBalance(_delegatedStakeToPoolByOwner[owner][poolId]);

View File

@@ -1,81 +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;
import "../interfaces/IStakingEvents.sol";
import "../interfaces/IEthVault.sol";
import "../immutable/MixinStorage.sol";
/// @dev This mixin contains logic for managing and interfacing with the Eth Vault.
/// (see vaults/EthVault.sol).
contract MixinEthVault is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage
{
/// @dev Set the Eth Vault.
/// @param ethVaultAddress Address of the Eth Vault.
function setEthVault(address ethVaultAddress)
external
onlyOwner
{
ethVault = IEthVault(ethVaultAddress);
}
/// @dev Return the current Eth Vault
/// @return Eth Vault
function getEthVault()
public
view
returns (address)
{
return address(ethVault);
}
/// @dev Transfers operator reward to the ETH vault.
/// @param poolId Unique Id of pool to transfer reward for,
/// @param operator of the pool.
/// @param amount of ETH to transfer.
function _transferOperatorRewardToEthVault(
bytes32 poolId,
address operator,
uint256 amount
)
internal
{
// sanity check on eth vault
IEthVault _ethVault = ethVault;
if (address(_ethVault) == address(0)) {
LibRichErrors.rrevert(
LibStakingRichErrors.EthVaultNotSetError()
);
}
// perform transfer
_ethVault.depositFor.value(amount)(operator);
emit OperatorRewardTransferredToEthVault(
poolId,
operator,
amount
);
}
}

View File

@@ -28,6 +28,8 @@ import "./MixinStakingPoolMakers.sol";
contract MixinStakingPool is
MixinStorage,
MixinStakingPoolMakers,
MixinStakingPoolRewards
{
using LibSafeMath for uint256;
@@ -85,10 +87,10 @@ contract MixinStakingPool is
external
{
// load pool and assert that we can decrease
IStructs.Pool memory pool = poolById[poolId];
uint32 currentOperatorShare = poolById[poolId].operatorShare;
_assertNewOperatorShare(
poolId,
pool.operatorShare,
currentOperatorShare,
newOperatorShare
);
@@ -96,22 +98,11 @@ contract MixinStakingPool is
poolById[poolId].operatorShare = newOperatorShare;
emit OperatorShareDecreased(
poolId,
pool.operatorShare,
currentOperatorShare,
newOperatorShare
);
}
/// @dev Returns the staking pool with `poolId`
/// @param poolId Unique id of pool
/// @return operator Operator of the pool
function getStakingPool(bytes32 poolId)
external
view
returns (IStructs.Pool memory)
{
return poolById[poolId];
}
/// @dev Returns the unique id that will be assigned to the next pool that is created.
/// @return Pool id.
function getNextStakingPoolId()
@@ -122,6 +113,14 @@ contract MixinStakingPool is
return nextPoolId;
}
function getStakingPool(bytes32 poolId)
public
view
returns (IStructs.Pool memory)
{
return poolById[poolId];
}
/// @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.
@@ -133,6 +132,22 @@ contract MixinStakingPool is
return bytes32(uint256(poolId).safeAdd(POOL_ID_INCREMENT_AMOUNT));
}
function _assertStakingPoolExists(bytes32 poolId)
internal
view
returns (bool)
{
if (poolById[poolId].operator == NIL_ADDRESS) {
// we use the pool's operator as a proxy for its existence
LibRichErrors.rrevert(
LibStakingRichErrors.PoolExistenceError(
poolId,
false
)
);
}
}
function _assertNewOperatorShare(
bytes32 poolId,
uint32 currentOperatorShare,

View File

@@ -19,34 +19,27 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
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/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "./MixinStakingPoolRewards.sol";
import "./MixinStakingPoolModifiers.sol";
/// @dev This mixin contains logic for staking pools.
contract MixinStakingPoolMakers is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinZrxVault,
MixinStakingPoolRewardVault,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances,
MixinStakingPoolRewards,
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
)

View File

@@ -19,39 +19,21 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "./MixinStakingPoolRewards.sol";
contract MixinStakingPoolModifiers is
IStakingEvents,
MixinConstants,
Ownable,
MixinStorage,
MixinZrxVault,
MixinStakingPoolRewardVault,
MixinScheduler,
MixinStakeStorage,
MixinStakeBalances,
MixinStakingPoolRewards
MixinStorage
{
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 = poolById[poolId].operator;
if (msg.sender != poolOperator) {
address operator = poolById[poolId].operator;
if (msg.sender != operator) {
LibRichErrors.rrevert(LibStakingRichErrors.OnlyCallableByPoolOperatorError(
msg.sender,
poolOperator
operator
));
}
@@ -62,15 +44,15 @@ contract MixinStakingPoolModifiers is
/// @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;
address operator;
if (
msg.sender != makerAddress &&
msg.sender != (poolOperator = poolById[poolId].operator)
msg.sender != (operator = poolById[poolId].operator)
) {
LibRichErrors.rrevert(
LibStakingRichErrors.OnlyCallableByPoolOperatorOrMakerError(
msg.sender,
poolOperator,
operator,
makerAddress
)
);

View File

@@ -58,15 +58,8 @@ contract MixinStakingPoolRewardVault is
function _depositIntoStakingPoolRewardVault(uint256 amount)
internal
{
// cast to payable and sanity check
// cast to payable then transfer
address payable rewardVaultAddress = address(uint160(address(rewardVault)));
if (rewardVaultAddress == NIL_ADDRESS) {
LibRichErrors.rrevert(
LibStakingRichErrors.RewardVaultNotSetError()
);
}
// perform transfer
rewardVaultAddress.transfer(amount);
}
@@ -81,15 +74,11 @@ contract MixinStakingPoolRewardVault is
)
internal
{
// sanity check
IStakingPoolRewardVault _rewardVault = rewardVault;
if (address(_rewardVault) == NIL_ADDRESS) {
LibRichErrors.rrevert(
LibStakingRichErrors.RewardVaultNotSetError()
);
}
// perform transfer
_rewardVault.transferToEthVault(poolId, member, amount);
rewardVault.transferToEthVault(
poolId,
member,
amount,
address(ethVault)
);
}
}

View File

@@ -19,10 +19,10 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
import "@0x/contracts-utils/contracts/src/LibFractions.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "./MixinCumulativeRewards.sol";
import "./MixinEthVault.sol";
contract MixinStakingPoolRewards is
@@ -38,16 +38,16 @@ contract MixinStakingPoolRewards is
{
address member = msg.sender;
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(delegatedStakeToPoolByOwner[member][poolId]);
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(_delegatedStakeToPoolByOwner[member][poolId]);
_syncRewardsForDelegator(
poolId,
member,
_loadUnsyncedBalance(delegatedStakeToPoolByOwner[member][poolId]), // initial balance
_loadUnsyncedBalance(_delegatedStakeToPoolByOwner[member][poolId]), // initial balance
finalDelegatedStakeToPoolByOwner
);
// update stored balance with synchronized version; this prevents redundant withdrawals.
delegatedStakeToPoolByOwner[member][poolId] = finalDelegatedStakeToPoolByOwner;
_delegatedStakeToPoolByOwner[member][poolId] = finalDelegatedStakeToPoolByOwner;
}
/// @dev Computes the reward balance in ETH of a specific member of a pool.
@@ -104,10 +104,12 @@ contract MixinStakingPoolRewards is
);
}
/// @dev Records a reward for delegators. This adds to the `_cumulativeRewardsByPool`.
/// @dev Handles a pool's reward. This will deposit the operator's reward into the Eth Vault and
/// the members' reward into the Staking Pool Vault. It also records the cumulative reward, which
/// is used to compute each delegator's portion of the members' reward.
/// @param poolId Unique Id of pool.
/// @param reward to record for delegators.
/// @param amountOfDelegatedStake the amount of delegated stake that will split this reward.
/// @param reward received by the pool.
/// @param amountOfDelegatedStake the amount of delegated stake that will split the reward.
/// @param epoch at which this was earned.
function _handleStakingPoolReward(
bytes32 poolId,
@@ -289,4 +291,24 @@ contract MixinStakingPoolRewards is
);
}
}
/// @dev Transfers operator reward to the ETH vault.
/// @param poolId Unique Id of pool to transfer reward for,
/// @param operator of the pool.
/// @param amount of ETH to transfer.
function _transferOperatorRewardToEthVault(
bytes32 poolId,
address operator,
uint256 amount
)
private
{
// perform transfer and notify
ethVault.depositFor.value(amount)(operator);
emit OperatorRewardTransferredToEthVault(
poolId,
operator,
amount
);
}
}

View File

@@ -38,21 +38,7 @@ contract StakingPoolRewardVault is
using LibSafeMath for uint256;
// mapping from poolId to Pool metadata
mapping (bytes32 => uint256) internal balanceByPoolId;
// address of ether vault
IEthVault internal _ethVault;
/// @dev Sets the Eth Vault.
/// Note that only the contract owner can call this.
/// @param ethVaultAddress Address of the Eth Vault.
function setEthVault(address ethVaultAddress)
external
onlyOwner
{
_ethVault = IEthVault(ethVaultAddress);
emit EthVaultChanged(ethVaultAddress);
}
mapping (bytes32 => uint256) internal _balanceByPoolId;
/// @dev Deposit an amount of ETH (`msg.value`) for `poolId` into the vault.
/// Note that this is only callable by the staking contract.
@@ -60,35 +46,29 @@ contract StakingPoolRewardVault is
function depositFor(bytes32 poolId)
external
payable
onlyStakingContract
onlyStakingProxy
{
balanceByPoolId[poolId] = balanceByPoolId[poolId].safeAdd(msg.value);
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeAdd(msg.value);
emit EthDepositedIntoVault(msg.sender, poolId, msg.value);
}
/// @dev Withdraw some amount in ETH of a pool member.
/// Note that this is only callable by the staking contract.
/// @param poolId Unique Id of pool.
/// @param member of pool to transfer funds to.
/// @param amount Amount in ETH to transfer.
/// @param ethVaultAddress address of Eth Vault to send rewards to.
function transferToEthVault(
bytes32 poolId,
address member,
uint256 amount
uint256 amount,
address ethVaultAddress
)
external
onlyStakingProxy
{
// sanity check on eth vault
IEthVault _ethVault = ethVault;
if (address(_ethVault) == address(0)) {
LibRichErrors.rrevert(
LibStakingRichErrors.EthVaultNotSetError()
);
}
// perform transfer
balanceByPoolId[poolId] = balanceByPoolId[poolId].safeSub(amount);
_ethVault.depositFor.value(amount)(member);
_balanceByPoolId[poolId] = _balanceByPoolId[poolId].safeSub(amount);
IEthVault(ethVaultAddress).depositFor.value(amount)(member);
emit PoolRewardTransferredToEthVault(
poolId,
member,
@@ -103,6 +83,6 @@ contract StakingPoolRewardVault is
view
returns (uint256)
{
return balanceByPoolId[poolId];
return _balanceByPoolId[poolId];
}
}

View File

@@ -35,10 +35,10 @@ export class PoolOperatorActor extends BaseActor {
);
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
// check the number of makers in the pool
const numMakersAfterRemoving = await this._stakingApiWrapper.stakingContract.numMakersByPoolId.callAsync(
const pool = await this._stakingApiWrapper.stakingContract.getStakingPool.callAsync(
poolId,
);
expect(numMakersAfterRemoving, 'number of makers in pool').to.be.bignumber.equal(1);
expect(pool.numberOfMakers, 'number of makers in pool').to.be.bignumber.equal(1);
}
return poolId;
}

View File

@@ -138,8 +138,8 @@ blockchainTests('Staking Pool Management', env => {
);
// check the number of makers in the pool
let numMakers = await stakingApiWrapper.stakingContract.numMakersByPoolId.callAsync(poolId);
expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
let pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
expect(pool.numberOfMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
// remove maker from pool
await Promise.all(
@@ -149,8 +149,8 @@ blockchainTests('Staking Pool Management', env => {
);
// check the number of makers in the pool
numMakers = await stakingApiWrapper.stakingContract.numMakersByPoolId.callAsync(poolId);
expect(numMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
expect(pool.numberOfMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
});
it('Should fail if maker already assigned to another pool tries to join', async () => {
// test parameters
@@ -337,8 +337,8 @@ blockchainTests('Staking Pool Management', env => {
);
// check the number of makers in the pool
const numMakers = await stakingApiWrapper.stakingContract.numMakersByPoolId.callAsync(poolId);
expect(numMakers, 'number of makers in pool').to.be.bignumber.equal(
const pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
expect(pool.numberOfMakers, 'number of makers in pool').to.be.bignumber.equal(
stakingConstants.DEFAULT_PARAMS.maximumMakersInPool,
);

View File

@@ -113,10 +113,12 @@ blockchainTests.resets('Testing Rewards', env => {
? _expectedEndBalances.poolRewardVaultBalance
: ZERO,
};
const pool = await stakingApiWrapper.rewardVaultContract.poolById.callAsync(poolId);
const operatorBalance = pool[1];
const membersBalance = pool[2];
/*
const pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
const operatorBalance = pool[2];
const membersBalance = pool[3];
const poolBalances = { poolBalance: operatorBalance.plus(membersBalance), operatorBalance, membersBalance };
*/
const finalEndBalancesAsArray = await Promise.all([
// staker 1
stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator.callAsync(

View File

@@ -208,16 +208,6 @@ export async function deployAndConfigureContractsAsync(
rewardVaultContract.address,
zrxVaultContract.address,
);
// set eth vault in staking contract
const setEthVaultCalldata = stakingContract.setEthVault.getABIEncodedTransactionData(ethVaultContract.address);
const setEthVaultCalldataTxData = {
from: ownerAddress,
to: stakingProxyContract.address,
data: setEthVaultCalldata,
};
await env.web3Wrapper.awaitTransactionSuccessAsync(
await env.web3Wrapper.sendTransactionAsync(setEthVaultCalldataTxData),
);
// configure erc20 proxy to accept calls from zrx vault
await erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(zrxVaultContract.address);
@@ -225,8 +215,6 @@ export async function deployAndConfigureContractsAsync(
await zrxVaultContract.setStakingProxy.awaitTransactionSuccessAsync(stakingProxyContract.address);
// set staking proxy contract in reward vault
await rewardVaultContract.setStakingProxy.awaitTransactionSuccessAsync(stakingProxyContract.address);
// set the eth vault in the reward vault
await rewardVaultContract.setEthVault.awaitTransactionSuccessAsync(ethVaultContract.address);
return new StakingApiWrapper(
env,
ownerAddress,