New staking mechanics: delay on entry opposed to exit

This commit is contained in:
Greg Hysen 2019-08-26 11:04:28 -07:00
parent 88e56356c4
commit eb6ad7d29d
39 changed files with 1392 additions and 1509 deletions

View File

@ -17,6 +17,7 @@
*/ */
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "./interfaces/IStaking.sol"; import "./interfaces/IStaking.sol";
import "./fees/MixinExchangeManager.sol"; import "./fees/MixinExchangeManager.sol";
@ -24,9 +25,7 @@ import "./stake/MixinZrxVault.sol";
import "./staking_pools/MixinStakingPoolRewardVault.sol"; import "./staking_pools/MixinStakingPoolRewardVault.sol";
import "./sys/MixinScheduler.sol"; import "./sys/MixinScheduler.sol";
import "./stake/MixinStakeBalances.sol"; import "./stake/MixinStakeBalances.sol";
import "./stake/MixinTimeLockedStake.sol";
import "./stake/MixinStake.sol"; import "./stake/MixinStake.sol";
import "./stake/MixinDelegatedStake.sol";
import "./staking_pools/MixinStakingPool.sol"; import "./staking_pools/MixinStakingPool.sol";
import "./fees/MixinExchangeFees.sol"; import "./fees/MixinExchangeFees.sol";
import "./staking_pools/MixinStakingPoolRewards.sol"; import "./staking_pools/MixinStakingPoolRewards.sol";
@ -36,19 +35,19 @@ contract Staking is
IStaking, IStaking,
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinExchangeManager, MixinExchangeManager,
MixinScheduler, MixinScheduler,
MixinStakingPoolRewardVault, MixinStakingPoolRewardVault,
MixinZrxVault, MixinStakeStorage,
MixinStakingPool,
MixinTimeLockedStake,
MixinStakeBalances, MixinStakeBalances,
MixinStake,
MixinStakingPoolRewards, MixinStakingPoolRewards,
MixinExchangeFees, MixinStake,
MixinDelegatedStake MixinStakingPool,
MixinExchangeFees
{ {
// this contract can receive ETH // this contract can receive ETH
// solhint-disable no-empty-blocks // solhint-disable no-empty-blocks

View File

@ -17,6 +17,7 @@
*/ */
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
@ -45,14 +46,17 @@ import "./MixinExchangeManager.sol";
contract MixinExchangeFees is contract MixinExchangeFees is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinExchangeManager, MixinExchangeManager,
MixinScheduler, MixinScheduler,
MixinStakingPoolRewardVault, MixinStakingPoolRewardVault,
MixinStakingPool, MixinStakeStorage,
MixinTimeLockedStake, MixinStakeBalances,
MixinStakeBalances MixinStakingPoolRewards,
MixinStakingPool
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@ -205,8 +209,8 @@ contract MixinExchangeFees is
bytes32 poolId = activePoolsThisEpoch[i]; bytes32 poolId = activePoolsThisEpoch[i];
// compute weighted stake // compute weighted stake
uint256 totalStakeDelegatedToPool = getTotalStakeDelegatedToPool(poolId); uint256 totalStakeDelegatedToPool = getTotalStakeDelegatedToPool(poolId).current;
uint256 stakeHeldByPoolOperator = getStakeDelegatedToPoolByOwner(getStakingPoolOperator(poolId), poolId); uint256 stakeHeldByPoolOperator = getStakeDelegatedToPoolByOwner(getStakingPoolOperator(poolId), poolId).current; // @TODO Update
uint256 weightedStake = stakeHeldByPoolOperator.safeAdd( uint256 weightedStake = stakeHeldByPoolOperator.safeAdd(
totalStakeDelegatedToPool totalStakeDelegatedToPool
.safeSub(stakeHeldByPoolOperator) .safeSub(stakeHeldByPoolOperator)
@ -218,6 +222,7 @@ contract MixinExchangeFees is
activePools[i].poolId = poolId; activePools[i].poolId = poolId;
activePools[i].feesCollected = protocolFeesThisEpochByPool[poolId]; activePools[i].feesCollected = protocolFeesThisEpochByPool[poolId];
activePools[i].weightedStake = weightedStake; activePools[i].weightedStake = weightedStake;
activePools[i].delegatedStake = totalStakeDelegatedToPool;
// update cumulative amounts // update cumulative amounts
totalFeesCollected = totalFeesCollected.safeAdd(activePools[i].feesCollected); totalFeesCollected = totalFeesCollected.safeAdd(activePools[i].feesCollected);
@ -251,9 +256,20 @@ contract MixinExchangeFees is
); );
// record reward in vault // record reward in vault
_recordDepositInStakingPoolRewardVault(activePools[i].poolId, reward); bool rewardForOperatorOnly = activePools[i].delegatedStake == 0;
(, uint256 poolPortion) = rewardVault.recordDepositFor(activePools[i].poolId, reward, rewardForOperatorOnly);
totalRewardsPaid = totalRewardsPaid.safeAdd(reward); totalRewardsPaid = totalRewardsPaid.safeAdd(reward);
// sync cumulative rewards, if necessary.
if (poolPortion > 0) {
_recordRewardForDelegators(
activePools[i].poolId,
poolPortion,
activePools[i].delegatedStake,
currentEpoch
);
}
// clear state for gas refunds // clear state for gas refunds
protocolFeesThisEpochByPool[activePools[i].poolId] = 0; protocolFeesThisEpochByPool[activePools[i].poolId] = 0;
activePoolsThisEpoch[i] = 0; activePoolsThisEpoch[i] = 0;

View File

@ -32,6 +32,7 @@ import "../immutable/MixinStorage.sol";
contract MixinExchangeManager is contract MixinExchangeManager is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage MixinStorage
{ {

View File

@ -41,4 +41,6 @@ contract MixinConstants is
uint64 constant internal INITIAL_EPOCH = 0; uint64 constant internal INITIAL_EPOCH = 0;
uint64 constant internal INITIAL_TIMELOCK_PERIOD = INITIAL_EPOCH; uint64 constant internal INITIAL_TIMELOCK_PERIOD = INITIAL_EPOCH;
uint256 constant internal MIN_TOKEN_VALUE = 1000000000000000000; // 10**18
} }

View File

@ -21,6 +21,7 @@ pragma solidity ^0.5.9;
import "@0x/contracts-utils/contracts/src/Ownable.sol"; import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "./MixinConstants.sol"; import "./MixinConstants.sol";
import "../interfaces/IZrxVault.sol"; import "../interfaces/IZrxVault.sol";
import "../interfaces/IEthVault.sol";
import "../interfaces/IStakingPoolRewardVault.sol"; import "../interfaces/IStakingPoolRewardVault.sol";
import "../interfaces/IStructs.sol"; import "../interfaces/IStructs.sol";
@ -28,8 +29,8 @@ import "../interfaces/IStructs.sol";
// solhint-disable max-states-count, no-empty-blocks // solhint-disable max-states-count, no-empty-blocks
contract MixinStorage is contract MixinStorage is
MixinDeploymentConstants, MixinDeploymentConstants,
MixinConstants, Ownable,
Ownable MixinConstants
{ {
constructor() constructor()
@ -40,26 +41,23 @@ contract MixinStorage is
// address of staking contract // address of staking contract
address internal stakingContract; address internal stakingContract;
// mapping from Owner to Amount Staked // mapping from Owner to Amount of Active Stake
mapping (address => uint256) internal stakeByOwner; mapping (address => IStructs.DelayedBalance) internal activeStakeByOwner;
// mapping from Owner to Amount of Instactive Stake // mapping from Owner to Amount of Inactive Stake
mapping (address => uint256) internal activatedStakeByOwner; mapping (address => IStructs.DelayedBalance) internal inactiveStakeByOwner;
// mapping from Owner to Amount TimeLocked
mapping (address => IStructs.TimeLock) internal timeLockedStakeByOwner;
// mapping from Owner to Amount Delegated // mapping from Owner to Amount Delegated
mapping (address => uint256) internal delegatedStakeByOwner; mapping (address => IStructs.DelayedBalance) internal delegatedStakeByOwner;
// mapping from Owner to Pool Id to Amount Delegated // mapping from Owner to Pool Id to Amount Delegated
mapping (address => mapping (bytes32 => uint256)) internal delegatedStakeToPoolByOwner; mapping (address => mapping (bytes32 => IStructs.DelayedBalance)) internal delegatedStakeToPoolByOwner;
// mapping from Pool Id to Amount Delegated // mapping from Pool Id to Amount Delegated
mapping (bytes32 => uint256) internal delegatedStakeByPoolId; mapping (bytes32 => IStructs.DelayedBalance) internal delegatedStakeByPoolId;
// total activated stake in the system // mapping from Owner to Amount of Withdrawable Stake
uint256 internal totalActivatedStake; mapping (address => uint256) internal withdrawableStakeByOwner;
// tracking Pool Id // tracking Pool Id
bytes32 internal nextPoolId = INITIAL_POOL_ID; bytes32 internal nextPoolId = INITIAL_POOL_ID;
@ -80,23 +78,16 @@ contract MixinStorage is
// current epoch start time // current epoch start time
uint256 internal currentEpochStartTimeInSeconds; uint256 internal currentEpochStartTimeInSeconds;
// current withdrawal period
uint256 internal currentTimeLockPeriod = INITIAL_TIMELOCK_PERIOD;
// current epoch start time
uint256 internal currentTimeLockPeriodStartEpoch = INITIAL_EPOCH;
// fees collected this epoch // fees collected this epoch
mapping (bytes32 => uint256) internal protocolFeesThisEpochByPool; mapping (bytes32 => uint256) internal protocolFeesThisEpochByPool;
// pools that were active in the current epoch // pools that were active in the current epoch
bytes32[] internal activePoolsThisEpoch; bytes32[] internal activePoolsThisEpoch;
// mapping from POol Id to Shadow Rewards // reward ratios by epoch
mapping (bytes32 => uint256) internal shadowRewardsByPoolId; mapping (bytes32 => mapping (uint256 => IStructs.Fraction)) internal cumulativeRewardsByPool;
// shadow balances by mapping (bytes32 => uint256) internal cumulativeRewardsByPoolLastStored;
mapping (address => mapping (bytes32 => uint256)) internal shadowRewardsInPoolByOwner;
// registered 0x Exchange contracts // registered 0x Exchange contracts
mapping (address => bool) internal validExchanges; mapping (address => bool) internal validExchanges;
@ -104,6 +95,9 @@ contract MixinStorage is
// ZRX vault // ZRX vault
IZrxVault internal zrxVault; IZrxVault internal zrxVault;
// Rebate Vault
IEthVault internal ethVault;
// Rebate Vault // Rebate Vault
IStakingPoolRewardVault internal rewardVault; IStakingPoolRewardVault internal rewardVault;
@ -113,3 +107,4 @@ contract MixinStorage is
// Denominator for cobb douglas alpha factor. // Denominator for cobb douglas alpha factor.
uint256 internal cobbDouglasAlphaDenomintor = 6; uint256 internal cobbDouglasAlphaDenomintor = 6;
} }

View File

@ -0,0 +1,71 @@
/*
Copyright 2018 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;
/// @dev This vault manages Ether.
interface IEthVault {
/// @dev Emitted when Ether are deposited into the vault.
/// @param sender Address of sender (`msg.sender`).
/// @param owner of Ether.
/// @param amount of Ether deposited.
event EthDepositedIntoVault(
address indexed sender,
address indexed owner,
uint256 amount
);
/// @dev Emitted when Ether are withdrawn from the vault.
/// @param sender Address of sender (`msg.sender`).
/// @param owner of Ether.
/// @param amount of Ether withdrawn.
event EthWithdrawnFromVault(
address indexed sender,
address indexed owner,
uint256 amount
);
/// @dev Deposit an `amount` of ETH from `owner` into the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param owner of ETH Tokens.
function depositFor(address owner)
external
payable;
/// @dev Withdraw an `amount` of ETH to `msg.sender` from the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param amount of ETH to withdraw.
function withdraw(uint256 amount)
external;
/// @dev Withdraw ALL ETH to `msg.sender` from the vault.
function withdrawAll()
external
returns (uint256);
/// @dev Returns the balance in ETH of the `owner`
/// @return Balance in ETH.
function balanceOf(address owner)
external
view
returns (uint256);
}

View File

@ -3,22 +3,34 @@ pragma solidity ^0.5.9;
interface IStakingEvents { interface IStakingEvents {
/// @dev Emitted by MixinStake when new Stake is minted. /// @dev Emitted by MixinStake when ZRX is staked.
/// @param owner of Stake. /// @param owner of ZRX.
/// @param amount of Stake minted. /// @param amount of ZRX staked.
event StakeMinted( event Stake(
address owner, address indexed owner,
uint256 amount uint256 amount
); );
/// @dev Emitted by MixinStake when Stake is burned. /// @dev Emitted by MixinStake when ZRX is unstaked.
/// @param owner of Stake. /// @param owner of ZRX.
/// @param amount of Stake burned. /// @param amount of ZRX unstaked.
event StakeBurned( event Unstake(
address owner, address indexed owner,
uint256 amount uint256 amount
); );
/// @dev Emitted by MixinStake when ZRX is unstaked.
/// @param owner of ZRX.
/// @param amount of ZRX unstaked.
event MoveStake(
address indexed owner,
uint256 amount,
uint8 fromState,
bytes32 indexed fromPool,
uint8 toState,
bytes32 indexed toProol
);
/// @dev Emitted by MixinExchangeManager when an exchange is added. /// @dev Emitted by MixinExchangeManager when an exchange is added.
/// @param exchangeAddress Address of new exchange. /// @param exchangeAddress Address of new exchange.
event ExchangeAdded( event ExchangeAdded(

View File

@ -39,6 +39,12 @@ interface IStakingPoolRewardVault {
uint96 membersBalance; uint96 membersBalance;
} }
/// @dev Emitted when the eth vault is changed
/// @param newEthVault address of new rth vault.
event EthVaultChanged(
address newEthVault
);
/// @dev Emitted when reward is deposited. /// @dev Emitted when reward is deposited.
/// @param poolId The pool the reward was deposited for. /// @param poolId The pool the reward was deposited for.
/// Note that a poolId of "0" means "unknown" at time of deposit. /// Note that a poolId of "0" means "unknown" at time of deposit.
@ -75,20 +81,15 @@ interface IStakingPoolRewardVault {
uint32 operatorShare uint32 operatorShare
); );
/// @dev Default constructor. /// @dev Fallback function.
/// Note that this is only callable by the staking contract, and when /// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
function () function ()
external external
payable; payable;
/// @dev Deposit a reward in ETH for a specific pool. function setEthVault(address ethVaultAddress)
/// Note that this is only callable by the staking contract, and when external;
/// not in catastrophic failure mode.
/// @param poolId Unique Id of pool.
function depositFor(bytes32 poolId)
external
payable;
/// @dev Record a deposit for a pool. This deposit should be in the same transaction, /// @dev Record a deposit for a pool. This deposit should be in the same transaction,
/// which is enforced by the staking contract. We do not enforce it here to save (a lot of) gas. /// which is enforced by the staking contract. We do not enforce it here to save (a lot of) gas.
@ -96,23 +97,41 @@ interface IStakingPoolRewardVault {
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. /// @param amount Amount in ETH to record.
function recordDepositFor(bytes32 poolId, uint256 amount) /// @param operatorOnly Only attribute amount to operator.
external; /// @return operatorPortion Portion of amount attributed to the operator.
/// @return operatorPortion Portion of amount attributed to the delegators.
function recordDepositFor(
bytes32 poolId,
uint256 amount,
bool operatorOnly
)
external
returns (
uint256 operatorPortion,
uint256 delegatorsPortion
);
/// @dev Withdraw some amount in ETH of an operator's reward. /// @dev Withdraw some amount in ETH of an operator's reward.
/// Note that this is only callable by the staking contract, and when /// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. function transferOperatorBalanceToEthVault(
function withdrawForOperator(bytes32 poolId, uint256 amount) bytes32 poolId,
address operator,
uint256 amount
)
external; external;
/// @dev Withdraw some amount in ETH of a pool member. /// @dev Withdraw some amount in ETH of a pool member.
/// Note that this is only callable by the staking contract, and when /// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. /// @param amount Amount in ETH to transfer.
function withdrawForMember(bytes32 poolId, uint256 amount) function transferMemberBalanceToEthVault(
bytes32 poolId,
address member,
uint256 amount
)
external; external;
/// @dev Register a new staking pool. /// @dev Register a new staking pool.

View File

@ -56,15 +56,44 @@ interface IStructs {
bytes32 poolId; bytes32 poolId;
uint256 feesCollected; uint256 feesCollected;
uint256 weightedStake; uint256 weightedStake;
uint256 delegatedStake;
} }
/// @dev Tracks timeLocked stake (see MixinTimeLockedStake). /// @dev A delayed balance allows values to be computed
/// @param lockedAt The TimeLock Period that stake was most recently locked at. struct DelayedBalance {
/// @param total Amount of stake that is timeLocked. uint96 current;
/// @param pending Stake pending to be un-TimeLocked next TimeLock Period. uint96 next;
struct TimeLock { uint64 lastStored;
uint64 lockedAt; }
uint96 total;
uint96 pending; /// @dev Balance struct for stake.
/// @param current Balance in the current epoch.
/// @param next Balance in the next epoch.
struct StakeBalance {
uint256 current;
uint256 next;
}
/// @dev States that stake can exist in.
enum StakeState {
ACTIVE,
INACTIVE,
DELEGATED
}
/// @dev Info used to describe a state.
/// @param state of the stake.
/// @param poolId Unique Id of pool. This is set when state=DELEGATED.
struct StakeStateInfo {
StakeState state;
bytes32 poolId;
}
/// @dev Struct to represent a fraction.
/// @param numerator of fraction.
/// @param denominator of fraction.
struct Fraction {
uint256 numerator;
uint256 denominator;
} }
} }

View File

@ -1,127 +0,0 @@
/*
Copyright 2018 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
/// @dev This library contains logic for computing the reward balances of staking pool members.
/// *** READ MixinStakingPoolRewards BEFORE CONTINUING ***
library LibRewardMath {
using LibSafeMath for uint256;
/// @dev Computes a member's payout denominated in the real asset (ETH).
/// Use this function when a member is liquidating their position in the pool (undelegating all their stake);
/// their shadow balance must be reset to zero so there is no need to compute it here.
/// @param amountDelegatedByOwner Amount of Stake delegated by the member to the staking pool.
/// @param totalAmountDelegated Total amount of Stake delegated by all members of the staking pool.
/// @param amountOfShadowAssetHeldByOwner The shadow balance of the member.
/// @param totalAmountOfShadowAsset The sum total of shadow balances across all members of the pool.
/// @param totalAmountOfRealAsset The total amount of ETH shared by members of the pool.
function _computePayoutDenominatedInRealAsset(
uint256 amountDelegatedByOwner,
uint256 totalAmountDelegated,
uint256 amountOfShadowAssetHeldByOwner,
uint256 totalAmountOfShadowAsset,
uint256 totalAmountOfRealAsset
)
internal
pure
returns (uint256)
{
uint256 combinedPayout = amountDelegatedByOwner
.safeMul(totalAmountOfShadowAsset.safeAdd(totalAmountOfRealAsset))
.safeDiv(totalAmountDelegated);
// we round up the amount of shadow assets when computing buy-ins.
// the result is that sometimes the amount of actual assets in the pool
// is less than the shadow eth. in this case, we'll end up with a floating imbalance.
uint256 payoutInRealAsset = combinedPayout < amountOfShadowAssetHeldByOwner ?
0 :
combinedPayout - amountOfShadowAssetHeldByOwner;
return payoutInRealAsset;
}
/// @dev Computes a member's payout denominated in the real asset (ETH).
/// Use this function when a member is undelegating a portion (but not all) of their stake.
/// @param partialAmountDelegatedByOwner Amount of Stake being undelegated by the member to the staking pool.
/// @param amountDelegatedByOwner Amount of Stake delegated by the member to the staking pool.
/// This includes `partialAmountDelegatedByOwner`.
/// @param totalAmountDelegated Total amount of Stake delegated by all members of the staking pool.
/// @param amountOfShadowAssetHeldByOwner The shadow balance of the member.
/// @param totalAmountOfShadowAsset The sum total of shadow balances across all members of the pool.
/// @param totalAmountOfRealAsset The total amount of ETH shared by members of the pool.
function _computePartialPayout(
uint256 partialAmountDelegatedByOwner,
uint256 amountDelegatedByOwner,
uint256 totalAmountDelegated,
uint256 amountOfShadowAssetHeldByOwner,
uint256 totalAmountOfShadowAsset,
uint256 totalAmountOfRealAsset
)
internal
pure
returns (
uint256 payoutInRealAsset,
uint256 payoutInShadowAsset
)
{
payoutInShadowAsset = amountOfShadowAssetHeldByOwner
.safeMul(partialAmountDelegatedByOwner)
.safeDiv(amountDelegatedByOwner);
payoutInRealAsset = _computePayoutDenominatedInRealAsset(
partialAmountDelegatedByOwner,
totalAmountDelegated,
payoutInShadowAsset,
totalAmountOfShadowAsset,
totalAmountOfRealAsset
);
return (payoutInRealAsset, payoutInShadowAsset);
}
/// @dev Computes how much shadow asset to mint a member who wants to
/// join (or delegate more stake to) a staking pool.
/// See MixinStakingPoolRewards for more information on shadow assets.
/// @param amountToDelegateByOwner Amount of Stake the new member would delegate.
/// @param totalAmountDelegated Total amount currently delegated to the pool.
/// This does *not* include `amountToDelegateByOwner`.
/// @param totalAmountOfShadowAsset The sum total of shadow balances across all members of the pool.
/// @param totalAmountOfRealAsset The total amount of ETH shared by members of the pool.
function _computeBuyInDenominatedInShadowAsset(
uint256 amountToDelegateByOwner,
uint256 totalAmountDelegated,
uint256 totalAmountOfShadowAsset,
uint256 totalAmountOfRealAsset
)
internal
pure
returns (uint256)
{
if (totalAmountDelegated == 0) {
return 0;
}
return amountToDelegateByOwner
.safeMul(totalAmountOfShadowAsset.safeAdd(totalAmountOfRealAsset))
.safeAdd(totalAmountDelegated.safeSub(1)) // we round up when computing shadow asset
.safeDiv(totalAmountDelegated);
}
}

View File

@ -1,167 +0,0 @@
/*
Copyright 2018 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../interfaces/IStakingEvents.sol";
import "./MixinZrxVault.sol";
import "../staking_pools/MixinStakingPoolRewardVault.sol";
import "../sys/MixinScheduler.sol";
import "./MixinStakeBalances.sol";
import "./MixinTimeLockedStake.sol";
import "./MixinStake.sol";
import "../staking_pools/MixinStakingPoolRewards.sol";
/// @dev This mixin contains logic for managing delegated stake.
/// **** Read MixinStake before continuing ****
/// Stake can be delegated to staking pools in order to trustlessly
/// leverage the weight of several stakers. The meaning of this
/// leverage depends on the context in which stake the is being utilized.
/// For example, the amount of fee-based rewards a market maker receives
/// is correlated to how much stake has been delegated to their pool (see MixinExchangeFees).
contract MixinDelegatedStake is
IStakingEvents,
MixinDeploymentConstants,
MixinConstants,
MixinStorage,
MixinScheduler,
MixinStakingPoolRewardVault,
MixinZrxVault,
MixinStakingPool,
MixinTimeLockedStake,
MixinStakeBalances,
MixinStake,
MixinStakingPoolRewards
{
using LibSafeMath for uint256;
/// @dev Deposit Zrx and mint stake in the "Activated & Delegated" state.
/// Note that the sender must be payable, as they may receive rewards in ETH from their staking pool.
/// @param poolId Unique Id of staking pool to delegate stake to.
/// @param amount of Zrx to deposit / Stake to mint.
function depositZrxAndDelegateToStakingPool(bytes32 poolId, uint256 amount)
external
{
address payable owner = msg.sender;
_mintStake(owner, amount);
activateStake(amount);
_delegateStake(owner, poolId, amount);
}
/// @dev Activates stake that is presently in the Deactivated & Withdrawable state.
/// Note that the sender must be payable, as they may receive rewards in ETH from their staking pool.
/// The newly activated stake is then delegated to a staking pool.
/// @param poolId Unique Id of staking pool to delegate stake to.
/// @param amount of Stake to activate & delegate.
function activateAndDelegateStake(
bytes32 poolId,
uint256 amount
)
public
{
activateStake(amount);
address payable owner = msg.sender;
_delegateStake(owner, poolId, amount);
}
/// @dev Deactivate & TimeLock stake that is currently in the Activated & Delegated state.
/// Note that the sender must be payable, as they may receive rewards in ETH from their staking pool.
/// @param poolId Unique Id of staking pool that the Stake is currently delegated to.
/// @param amount of Stake to deactivate and timeLock.
function deactivateAndTimeLockDelegatedStake(bytes32 poolId, uint256 amount)
public
{
deactivateAndTimeLockStake(amount);
address payable owner = msg.sender;
_undelegateStake(owner, poolId, amount);
}
/// @dev Delegates stake from `owner` to the staking pool with id `poolId`
/// @param owner of Stake
/// @param poolId Unique Id of staking pool to delegate stake to.
/// @param amount of Stake to delegate.
function _delegateStake(
address payable owner,
bytes32 poolId,
uint256 amount
)
private
{
// take snapshot of parameters before any computation
uint256 _delegatedStakeByOwner = delegatedStakeByOwner[owner];
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
// join staking pool
_joinStakingPool(
poolId,
owner,
amount,
_delegatedStakeByPoolId
);
// increment how much stake the owner has delegated
delegatedStakeByOwner[owner] = _delegatedStakeByOwner.safeAdd(amount);
// increment how much stake the owner has delegated to the input pool
delegatedStakeToPoolByOwner[owner][poolId] = _delegatedStakeToPoolByOwner.safeAdd(amount);
// increment how much stake has been delegated to pool
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId.safeAdd(amount);
}
/// @dev Undelegates stake of `owner` from the staking pool with id `poolId`
/// @param owner of Stake
/// @param poolId Unique Id of staking pool to undelegate stake from.
/// @param amount of Stake to undelegate.
function _undelegateStake(
address payable owner,
bytes32 poolId,
uint256 amount
)
private
{
// take snapshot of parameters before any computation
uint256 _delegatedStakeByOwner = delegatedStakeByOwner[owner];
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
// leave the staking pool
_leaveStakingPool(
poolId,
owner,
amount,
_delegatedStakeToPoolByOwner,
_delegatedStakeByPoolId
);
// decrement how much stake the owner has delegated
delegatedStakeByOwner[owner] = _delegatedStakeByOwner.safeSub(amount);
// decrement how much stake the owner has delegated to the input pool
delegatedStakeToPoolByOwner[owner][poolId] = _delegatedStakeToPoolByOwner.safeSub(amount);
// decrement how much stake has been delegated to pool
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId.safeSub(amount);
}
}

View File

@ -17,188 +17,217 @@
*/ */
pragma solidity ^0.5.9; 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibRewardMath.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol"; import "../immutable/MixinStorage.sol";
import "../interfaces/IStakingEvents.sol"; import "../interfaces/IStakingEvents.sol";
import "./MixinZrxVault.sol"; import "./MixinZrxVault.sol";
import "../staking_pools/MixinStakingPoolRewardVault.sol"; import "../staking_pools/MixinStakingPoolRewardVault.sol";
import "../staking_pools/MixinStakingPoolRewards.sol";
import "../sys/MixinScheduler.sol"; import "../sys/MixinScheduler.sol";
import "./MixinStakeBalances.sol"; import "./MixinStakeBalances.sol";
import "./MixinTimeLockedStake.sol"; import "./MixinStakeStorage.sol";
/// @dev This mixin contains logic for managing ZRX tokens and Stake. /// @dev This mixin contains logic for managing ZRX tokens and Stake.
/// Stake is minted when ZRX is deposited and burned when ZRX is withdrawn.
/// Stake can exist in one of many states:
/// 1. Activated
/// 2. Activated & Delegated
/// 3. Deactivated & TimeLocked
/// 4. Deactivated & Withdrawable
///
/// -- State Definitions --
/// Activated Stake
/// Stake in this state can be used as a utility within the 0x ecosystem.
/// For example, it carries weight when computing fee-based rewards (see MixinExchangeFees).
/// In the future, it may be used to participate in the 0x governance system.
///
/// Activated & Delegated Stake
/// Stake in this state also serves as a utility that is shared between the delegator and delegate.
/// For example, if delegated to a staking pool then it carries weight when computing fee-based rewards for
/// the staking pool; however, in this case, delegated stake carries less weight that regular stake (see MixinStakingPool).
///
/// Deactivated & TimeLocked Stake
/// Stake in this state cannot be used as a utility within the 0x ecosystem.
/// Stake is timeLocked when it moves out of activated states (Activated / Activated & Delagated).
/// By limiting the portability of stake, we mitigate undesirable behavior such as switching staking pools
/// in the middle of an epoch.
///
/// Deactivated & Withdrawable
/// Stake in this state cannot be used as a utility with in the 0x ecosystem.
/// This stake can, however, be burned and withdrawn as Zrx tokens.
/// ----------------------------
///
/// -- Valid State Transtions --
/// Activated -> Deactivated & TimeLocked
///
/// Activated & Delegated -> Deactivated & TimeLocked
///
/// Deactivated & TimeLocked -> Deactivated & Withdrawable
///
/// Deactivated & Withdrawable -> Activated
/// Deactivated & Withdrawable -> Activated & Delegated
/// Deactivated & Withdrawable -> Deactivated & Withdrawable
/// ----------------------------
///
/// Freshly minted stake is in the "Deactvated & Withdrawable" State, so it can
/// either be activated, delegated or withdrawn.
/// See MixinDelegatedStake and MixinTimeLockedStake for more on respective state transitions.
contract MixinStake is contract MixinStake is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinScheduler, MixinScheduler,
MixinStakingPoolRewardVault, MixinStakingPoolRewardVault,
MixinZrxVault, MixinStakeStorage,
MixinTimeLockedStake, MixinStakeBalances,
MixinStakeBalances MixinStakingPoolRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
/// @dev Deposit Zrx. This mints stake for the sender that is in the "Deactivated & Withdrawable" state. /// @dev Stake ZRX tokens. Tokens are deposited into the ZRX Vault. Unstake to retrieve the ZRX.
/// @param amount of Zrx to deposit / Stake to mint. /// Stake is in the 'Active' state.
function depositZrxAndMintDeactivatedStake(uint256 amount) /// @param amount of ZRX to stake.
function stake(uint256 amount)
external external
{ {
_mintStake(msg.sender, amount); address payable owner = msg.sender;
}
/// @dev Deposit Zrx and mint stake in the activated stake.
/// This is a convenience function, and can be used in-place of
/// calling `depositZrxAndMintDeactivatedStake` and `activateStake`.
/// This mints stake for the sender that is in the "Activated" state.
/// @param amount of Zrx to deposit / Stake to mint.
function depositZrxAndMintActivatedStake(uint256 amount)
external
{
_mintStake(msg.sender, amount);
activateStake(amount);
}
/// @dev Burns deactivated stake and withdraws the corresponding amount of Zrx.
/// @param amount of Stake to burn / Zrx to withdraw
function burnDeactivatedStakeAndWithdrawZrx(uint256 amount)
external
{
address owner = msg.sender;
_syncTimeLockedStake(owner);
if (amount > getDeactivatedStake(owner)) {
LibRichErrors.rrevert(LibStakingRichErrors.InsufficientBalanceError(
amount,
getDeactivatedStake(owner)
));
}
_burnStake(owner, amount);
}
/// @dev Activates stake that is presently in the Deactivated & Withdrawable state.
/// @param amount of Stake to activate.
function activateStake(uint256 amount)
public
{
address owner = msg.sender;
_syncTimeLockedStake(owner);
if (amount > getActivatableStake(owner)) {
LibRichErrors.rrevert(LibStakingRichErrors.InsufficientBalanceError(
amount,
getActivatableStake(owner)
));
}
activatedStakeByOwner[owner] = activatedStakeByOwner[owner].safeAdd(amount);
totalActivatedStake = totalActivatedStake.safeAdd(amount);
}
/// @dev Deactivate & TimeLock stake that is currently in the Activated state.
/// @param amount of Stake to deactivate and timeLock.
function deactivateAndTimeLockStake(uint256 amount)
public
{
address owner = msg.sender;
_syncTimeLockedStake(owner);
if (amount > getActivatedStake(owner)) {
LibRichErrors.rrevert(LibStakingRichErrors.InsufficientBalanceError(
amount,
getActivatedStake(owner)
));
}
activatedStakeByOwner[owner] = activatedStakeByOwner[owner].safeSub(amount);
totalActivatedStake = totalActivatedStake.safeSub(amount);
_timeLockStake(owner, amount);
}
/// @dev Mints Stake in the Deactivated & Withdrawable state.
/// @param owner to mint Stake for.
/// @param amount of Stake to mint.
function _mintStake(address owner, uint256 amount)
internal
{
// deposit equivalent amount of ZRX into vault // deposit equivalent amount of ZRX into vault
zrxVault.depositFrom(owner, amount); _depositFromOwnerIntoZrxVault(owner, amount);
// mint stake // mint stake
stakeByOwner[owner] = stakeByOwner[owner].safeAdd(amount); _mintBalance(activeStakeByOwner[owner], amount);
// emit stake event // notify
emit StakeMinted( emit Stake(
owner, owner,
amount amount
); );
} }
/// @dev Burns Stake in the Deactivated & Withdrawable state. /// @dev Unstake. Tokens are withdrawn from the ZRX Vault and returned to the owner.
/// @param owner to mint Stake for. /// Stake must be in the 'inactive' state for at least one full epoch to unstake.
/// @param amount of Stake to mint. /// @param amount of ZRX to unstake.
function _burnStake(address owner, uint256 amount) function unstake(uint256 amount)
internal external
{ {
// burn stake address payable owner = msg.sender;
stakeByOwner[owner] = stakeByOwner[owner].safeSub(amount);
// sanity check
uint256 currentWithdrawableStake = getWithdrawableStake(owner);
require(
amount <= currentWithdrawableStake,
"INSUFFICIENT_FUNDS"
);
// burn inactive stake
_burnBalance(inactiveStakeByOwner[owner], amount);
// update withdrawable field
withdrawableStakeByOwner[owner] = currentWithdrawableStake.safeSub(amount);
// withdraw equivalent amount of ZRX from vault // withdraw equivalent amount of ZRX from vault
zrxVault.withdrawFrom(owner, amount); _withdrawToOwnerFromZrxVault(owner, amount);
// emit stake event // emit stake event
emit StakeBurned( emit Unstake(
owner, owner,
amount amount
); );
} }
/// @dev Moves stake between states: 'active', 'inactive' or 'delegated'.
/// This change comes into effect next epoch.
/// @param from state to move stake out of.
/// @param to state to move stake into.
/// @param amount of stake to move.
function moveStake(IStructs.StakeStateInfo calldata from, IStructs.StakeStateInfo calldata to, uint256 amount)
external
{
// sanity check - do nothing if moving stake between the same state
if (from.state != IStructs.StakeState.DELEGATED && from.state == to.state) {
return;
} else if (from.state == IStructs.StakeState.DELEGATED && from.poolId == to.poolId) {
return;
}
address payable owner = msg.sender;
// handle delegation; this must be done before moving stake as the current
// (out-of-sync) state is used during delegation.
if (from.state == IStructs.StakeState.DELEGATED) {
_undelegateStake(
from.poolId,
owner,
amount
);
}
if (to.state == IStructs.StakeState.DELEGATED) {
_delegateStake(
to.poolId,
owner,
amount
);
}
// cache the current withdrawal state if we're moving out of the inactive state.
uint256 cachedWithdrawableStakeByOwner = (from.state == IStructs.StakeState.INACTIVE)
? getWithdrawableStake(owner)
: 0;
// execute move
IStructs.DelayedBalance storage fromPtr = _getBalancePtrFromState(from);
IStructs.DelayedBalance storage toPtr = _getBalancePtrFromState(to);
_moveStake(fromPtr, toPtr, amount);
// update withdrawable field, if necessary
if (from.state == IStructs.StakeState.INACTIVE) {
withdrawableStakeByOwner[owner] = _computeWithdrawableStake(owner, cachedWithdrawableStakeByOwner);
}
// notify
emit MoveStake(
owner,
amount,
uint8(from.state),
from.poolId,
uint8(to.state),
to.poolId
);
}
/// @dev Delegates an owners stake to a staking pool.
/// @param poolId Id of pool to delegate to.
/// @param owner of stake to delegate.
/// @param amount of stake to delegate.
function _delegateStake(
bytes32 poolId,
address payable owner,
uint256 amount
)
private
{
// transfer any rewards from the transient pool vault to the eth vault;
// this must be done before we can modify the staker's portion of the delegator pool.
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, owner);
// sync cumulative rewards that we'll need for future computations
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
// decrement how much stake the owner has delegated to the input pool
_incrementBalance(delegatedStakeToPoolByOwner[owner][poolId], amount);
// increment how much stake has been delegated to pool
_incrementBalance(delegatedStakeByPoolId[poolId], amount);
}
/// @dev Delegates an owners stake to a staking pool.
/// @param poolId Id of pool to delegate to.
/// @param owner of stake to delegate.
/// @param amount of stake to delegate.
function _undelegateStake(
bytes32 poolId,
address payable owner,
uint256 amount
)
private
{
// transfer any rewards from the transient pool vault to the eth vault;
// this must be done before we can modify the staker's portion of the delegator pool.
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, owner);
// sync cumulative rewards that we'll need for future computations
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
// decrement how much stake the owner has delegated to the input pool
_decrementBalance(delegatedStakeToPoolByOwner[owner][poolId], amount);
// decrement how much stake has been delegated to pool
_decrementBalance(delegatedStakeByPoolId[poolId], amount);
}
/// @dev Returns a storage pointer to a user's stake in a given state.
/// @param state of user's stake to lookup.
/// @return a storage pointer to the corresponding stake stake
function _getBalancePtrFromState(IStructs.StakeStateInfo memory state)
private
returns (IStructs.DelayedBalance storage)
{
// lookup state
address owner = msg.sender;
if (state.state == IStructs.StakeState.ACTIVE) {
return activeStakeByOwner[owner];
} else if (state.state == IStructs.StakeState.INACTIVE) {
return inactiveStakeByOwner[owner];
} else if (state.state == IStructs.StakeState.DELEGATED) {
return delegatedStakeByOwner[owner];
}
// not found
revert("Unrecognized State");
}
} }

View File

@ -24,7 +24,8 @@ import "../interfaces/IStructs.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol"; import "../immutable/MixinStorage.sol";
import "../sys/MixinScheduler.sol"; import "../sys/MixinScheduler.sol";
import "./MixinTimeLockedStake.sol"; import "./MixinZrxVault.sol";
import "./MixinStakeStorage.sol";
/// @dev This mixin contains logic for querying stake balances. /// @dev This mixin contains logic for querying stake balances.
@ -32,157 +33,132 @@ import "./MixinTimeLockedStake.sol";
contract MixinStakeBalances is contract MixinStakeBalances is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinScheduler, MixinScheduler,
MixinTimeLockedStake MixinStakeStorage
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
/// @dev Returns the total activated stake across all owners.
/// This stake is in the "Activated" OR "Activated & Delegated" states.
/// @return Total active stake.
function getActivatedStakeAcrossAllOwners()
public
view
returns (uint256)
{
return totalActivatedStake;
}
/// @dev Returns the total stake for a given owner. /// @dev Returns the total stake for a given owner.
/// This stake can be in any state. /// @param owner of stake.
/// @param owner to query.
/// @return Total active stake for owner. /// @return Total active stake for owner.
function getTotalStake(address owner) function getTotalStake(address owner)
public public
view view
returns (uint256) returns (uint256)
{ {
return stakeByOwner[owner]; return _balanceOfOwnerInZrxVault(owner);
} }
/// @dev Returns the activated stake for a given owner. /// @dev Returns the active stake for a given owner.
/// This stake is in the "Activated" OR "Activated & Delegated" states. /// @param owner of stake.
/// @param owner to query. /// @return Active stake for owner.
/// @return Activated stake for owner. function getActiveStake(address owner)
function getActivatedStake(address owner)
public public
view view
returns (uint256) returns (IStructs.StakeBalance memory balance)
{ {
return activatedStakeByOwner[owner]; IStructs.DelayedBalance memory storedBalance = _syncBalanceDestructive(activeStakeByOwner[owner]);
return IStructs.StakeBalance({
current: storedBalance.current,
next: storedBalance.next
});
} }
/// @dev Returns the deactivated stake for a given owner. /// @dev Returns the inactive stake for a given owner.
/// This stake is in the "Deactivated & TimeLocked" OR "Deactivated & Withdrawable" states. /// @param owner of stake.
/// @param owner to query. /// @return Inactive stake for owner.
/// @return Deactivated stake for owner. function getInactiveStake(address owner)
function getDeactivatedStake(address owner)
public public
view view
returns (uint256) returns (IStructs.StakeBalance memory balance)
{ {
return getTotalStake(owner).safeSub(getActivatedStake(owner)); IStructs.DelayedBalance memory storedBalance = _syncBalanceDestructive(inactiveStakeByOwner[owner]);
return IStructs.StakeBalance({
current: storedBalance.current,
next: storedBalance.next
});
} }
/// @dev Returns the activated & undelegated stake for a given owner. /// @dev Returns the amount stake that can be withdrawn for a given owner.
/// This stake is in the "Activated" state. /// @param owner of stake.
/// @param owner to query.
/// @return Activated stake for owner.
function getActivatedAndUndelegatedStake(address owner)
public
view
returns (uint256)
{
return activatedStakeByOwner[owner].safeSub(getStakeDelegatedByOwner(owner));
}
/// @dev Returns the stake that can be activated for a given owner.
/// This stake is in the "Deactivated & Withdrawable" state.
/// @param owner to query.
/// @return Activatable stake for owner.
function getActivatableStake(address owner)
public
view
returns (uint256)
{
return getDeactivatedStake(owner).safeSub(getTimeLockedStake(owner));
}
/// @dev Returns the stake that can be withdrawn for a given owner.
/// This stake is in the "Deactivated & Withdrawable" state.
/// @param owner to query.
/// @return Withdrawable stake for owner. /// @return Withdrawable stake for owner.
function getWithdrawableStake(address owner) function getWithdrawableStake(address owner)
public public
view view
returns (uint256) returns (uint256)
{ {
return getActivatableStake(owner); uint256 cachedWithdrawableStakeByOwner = withdrawableStakeByOwner[owner];
return _computeWithdrawableStake(owner, cachedWithdrawableStakeByOwner);
} }
/// @dev Returns the stake delegated by a given owner. /// @dev Returns the stake delegated by a given owner.
/// This stake is in the "Activated & Delegated" state. /// @param owner of stake.
/// @param owner to query.
/// @return Delegated stake for owner. /// @return Delegated stake for owner.
function getStakeDelegatedByOwner(address owner) function getStakeDelegatedByOwner(address owner)
public public
view view
returns (uint256) returns (IStructs.StakeBalance memory balance)
{ {
return delegatedStakeByOwner[owner]; IStructs.DelayedBalance memory storedBalance = _syncBalanceDestructive(delegatedStakeByOwner[owner]);
return IStructs.StakeBalance({
current: storedBalance.current,
next: storedBalance.next
});
} }
/// @dev Returns the stake delegated to a specific staking pool, by a given owner. /// @dev Returns the stake delegated to a specific staking pool, by a given owner.
/// This stake is in the "Activated & Delegated" state. /// @param owner of stake.
/// @param owner to query.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @return Stake delegaated to pool by owner. /// @return Stake delegaated to pool by owner.
function getStakeDelegatedToPoolByOwner(address owner, bytes32 poolId) function getStakeDelegatedToPoolByOwner(address owner, bytes32 poolId)
public public
view view
returns (uint256) returns (IStructs.StakeBalance memory balance)
{ {
return delegatedStakeToPoolByOwner[owner][poolId]; IStructs.DelayedBalance memory storedBalance = _syncBalanceDestructive(delegatedStakeToPoolByOwner[owner][poolId]);
return IStructs.StakeBalance({
current: storedBalance.current,
next: storedBalance.next
});
} }
/// @dev Returns the total stake delegated to a specific staking pool, across all members. /// @dev Returns the total stake delegated to a specific staking pool, across all members.
/// This stake is in the "Activated & Delegated" state.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @return Total stake delegaated to pool. /// @return Total stake delegated to pool.
function getTotalStakeDelegatedToPool(bytes32 poolId) function getTotalStakeDelegatedToPool(bytes32 poolId)
public public
view view
returns (uint256) returns (IStructs.StakeBalance memory balance)
{ {
return delegatedStakeByPoolId[poolId]; IStructs.DelayedBalance memory storedBalance = _syncBalanceDestructive(delegatedStakeByPoolId[poolId]);
return IStructs.StakeBalance({
current: storedBalance.current,
next: storedBalance.next
});
} }
/// @dev Returns the timeLocked stake for a given owner. /// @dev Returns the stake that can be withdrawn for a given owner.
/// This stake is in the "Deactivated & TimeLocked" state. /// This stake is in the "Deactive & Withdrawable" state.
/// @param owner to query. /// @param owner to query.
/// @return TimeLocked stake for owner. /// @return Withdrawable stake for owner.
function getTimeLockedStake(address owner) function _computeWithdrawableStake(address owner, uint256 cachedWithdrawableStakeByOwner)
public internal
view view
returns (uint256) returns (uint256)
{ {
(IStructs.TimeLock memory timeLock,) = _getSynchronizedTimeLock(owner); // stake cannot be withdrawn if it has been reallocated for the `next` epoch;
return timeLock.total; // so the upper bound of withdrawable stake is always limited by the value of `next`.
IStructs.DelayedBalance memory storedBalance = inactiveStakeByOwner[owner];
if (storedBalance.lastStored == currentEpoch) {
return storedBalance.next < cachedWithdrawableStakeByOwner ? storedBalance.next : cachedWithdrawableStakeByOwner;
} else if (uint256(storedBalance.lastStored).safeAdd(1) == currentEpoch) {
return storedBalance.next < storedBalance.current ? storedBalance.next : storedBalance.current;
} else {
return storedBalance.next;
} }
/// @dev Returns the starting TimeLock Period of timeLocked state for a given owner.
/// This stake is in the "Deactivated & TimeLocked" state.
/// See MixinScheduling and MixinTimeLock.
/// @param owner to query.
/// @return Start of timeLock for owner's timeLocked stake.
function getTimeLockStart(address owner)
public
view
returns (uint256)
{
(IStructs.TimeLock memory timeLock,) = _getSynchronizedTimeLock(owner);
return timeLock.lockedAt;
} }
} }

View File

@ -0,0 +1,196 @@
/*
Copyright 2018 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/LICENSE2.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 "../libs/LibSafeDowncast.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../interfaces/IStructs.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../sys/MixinScheduler.sol";
import "./MixinZrxVault.sol";
/// @dev This mixin contains logic for managing stake storage.
contract MixinStakeStorage is
IStakingEvents,
MixinDeploymentConstants,
Ownable,
MixinConstants,
MixinStorage,
MixinZrxVault,
MixinScheduler
{
using LibSafeMath for uint256;
/// @dev Moves stake between states: 'active', 'inactive' or 'delegated'.
/// This change comes into effect next epoch.
/// @param fromPtr pointer to storage location of `from` stake.
/// @param toPtr pointer to storage location of `to` stake.
/// @param amount of stake to move.
function _moveStake(
IStructs.DelayedBalance storage fromPtr,
IStructs.DelayedBalance storage toPtr,
uint256 amount
)
internal
{
// do nothing if pointers are equal
if (_arePointersEqual(fromPtr, toPtr)) {
return;
}
// load balance from storage and synchronize it
IStructs.DelayedBalance memory from = _syncBalanceDestructive(fromPtr);
IStructs.DelayedBalance memory to = _syncBalanceDestructive(toPtr);
// sanity check on balance
if (amount > from.next) {
revert("Insufficient Balance");
}
// move stake for next epoch
from.next = LibSafeDowncast.downcastToUint96(uint256(from.next).safeSub(amount));
to.next = LibSafeDowncast.downcastToUint96(uint256(to.next).safeAdd(amount));
// update state in storage
_storeBalance(fromPtr, from);
_storeBalance(toPtr, to);
}
/// @dev Synchronizes the fields of a stored balance.
/// The structs `current` field is set to `next` if the
/// current epoch is greater than the epoch in which the struct
/// was stored.
/// @param balance to update. This will be equal to the return value after calling.
/// @return synchronized balance.
function _syncBalanceDestructive(IStructs.DelayedBalance memory balance)
internal
view
returns (IStructs.DelayedBalance memory)
{
uint256 currentEpoch = getCurrentEpoch();
if (currentEpoch > balance.lastStored) {
balance.lastStored = LibSafeDowncast.downcastToUint64(currentEpoch);
balance.current = balance.next;
}
return balance;
}
/// @dev Mints new value in a balance.
/// This causes both the `current` and `next` fields to increase immediately.
/// @param balancePtr storage pointer to balance.
/// @param amount to mint.
function _mintBalance(IStructs.DelayedBalance storage balancePtr, uint256 amount)
internal
{
// Remove stake from balance
IStructs.DelayedBalance memory balance = _syncBalanceDestructive(balancePtr);
balance.next = LibSafeDowncast.downcastToUint96(uint256(balance.next).safeAdd(amount));
balance.current = LibSafeDowncast.downcastToUint96(uint256(balance.current).safeAdd(amount));
// update state
_storeBalance(balancePtr, balance);
}
/// @dev Burns existing value in a balance.
/// This causes both the `current` and `next` fields to decrease immediately.
/// @param balancePtr storage pointer to balance.
/// @param amount to mint.
function _burnBalance(IStructs.DelayedBalance storage balancePtr, uint256 amount)
internal
{
// Remove stake from balance
IStructs.DelayedBalance memory balance = _syncBalanceDestructive(balancePtr);
balance.next = LibSafeDowncast.downcastToUint96(uint256(balance.next).safeSub(amount));
balance.current = LibSafeDowncast.downcastToUint96(uint256(balance.current).safeSub(amount));
// update state
_storeBalance(balancePtr, balance);
}
/// @dev Increments a balance.
/// Ths updates the `next` field but not the `current` field.
/// @param balancePtr storage pointer to balance.
/// @param amount to increment by.
function _incrementBalance(IStructs.DelayedBalance storage balancePtr, uint256 amount)
internal
{
// Add stake to balance
IStructs.DelayedBalance memory balance = _syncBalanceDestructive(balancePtr);
balance.next = LibSafeDowncast.downcastToUint96(uint256(balance.next).safeAdd(amount));
// update state
_storeBalance(balancePtr, balance);
}
/// @dev Decrements a balance.
/// Ths updates the `next` field but not the `current` field.
/// @param balancePtr storage pointer to balance.
/// @param amount to decrement by.
function _decrementBalance(IStructs.DelayedBalance storage balancePtr, uint256 amount)
internal
{
// Remove stake from balance
IStructs.DelayedBalance memory balance = _syncBalanceDestructive(balancePtr);
balance.next = LibSafeDowncast.downcastToUint96(uint256(balance.next).safeSub(amount));
// update state
_storeBalance(balancePtr, balance);
}
/// @dev Stores a balance in storage.
/// @param balancePtr points to where `balance` will be stored.
/// @param balance to save to storage.
function _storeBalance(
IStructs.DelayedBalance storage balancePtr,
IStructs.DelayedBalance memory balance
)
private
{
// note - this compresses into a single `sstore` when optimizations are enabled,
// since the StakeBalance struct occupies a single word of storage.
balancePtr.lastStored = balance.lastStored;
balancePtr.next = balance.next;
balancePtr.current = balance.current;
}
/// @dev Returns true iff storage pointers resolve to same storage location.
/// @param balancePtrA first storage pointer.
/// @param balancePtrB second storage pointer.
/// @return true iff pointers are equal.
function _arePointersEqual(
// solhint-disable-next-line no-unused-vars
IStructs.DelayedBalance storage balancePtrA,
// solhint-disable-next-line no-unused-vars
IStructs.DelayedBalance storage balancePtrB
)
private
returns (bool areEqual)
{
assembly {
areEqual := and(
eq(balancePtrA_slot, balancePtrB_slot),
eq(balancePtrA_offset, balancePtrB_offset)
)
}
return areEqual;
}
}

View File

@ -1,150 +0,0 @@
/*
Copyright 2018 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibSafeDowncast.sol";
import "../libs/LibRewardMath.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../interfaces/IStakingEvents.sol";
import "../sys/MixinScheduler.sol";
/// @dev This mixin contains logic for timeLocking stake.
/// **** Read MixinStake before continuing ****
/// When stake moves from an Activated state it must first go to
/// the Deactivated & TimeLocked state. The stake will be timeLocked
/// for a period of time, called a TimeLock Period, which is measured in epochs.
/// (see MixinScheduler).
/// Stake remains timeLocked for at least one full TimeLock Period; so,
/// if your stake is locked sometime during TimeLock Period #1 then it will
/// be un-TimeLocked at TimeLock Period #3.
/// Note that no action is required by the user to un-TimeLock their stake, and
/// when stake is un-TimeLocked it is moved to the state Deactivated & Withdrawable.
/// (see MixinStake).
///
/// -- The TimeLocking Data Structure --
/// Three fields are used to represent a timeLock:
/// 1. Total timeLocked stake (called `total`)
/// 2. TimeLocked stake pending removal of timeLock, on next TimeLock Period (called `pending`)
/// 3. The most recent TimeLock Period in which stake was timeLocked. (called `lockedAt`)
///
/// Each user has exactly one instance of this timeLock struct, which manages all of
/// their timeLocked stake. This data structure is defined in `IStructs.TimeLock`.
/// This data structure was designed to fit into one word of storage, as a gas optimization.
/// Its fields are updated only when a user interacts with their stake.
/// ------------------------------------
///
/// -- TimeLocking Example --
/// In the example below, the user executes a series of actions on their stake (`Action`) during `TimeLock Period` N.
/// The fields of the user's timeLocked struct (`lockedAt`, `total`, `pending`) are illustrated exactly as
/// they would be represented in storage.
/// The field `un-TimeLocked` is the amount of un-TimeLocked stake, as represented *in storage*; however, because
/// state is only updated when the user interacts with their stake, this field may lag.
/// The field `un-TimeLocked (virtual)` is the true amount of un-TimeLocked stake, as represented in the system;
/// the value in this field represents stake that has moved from the state
/// "Deactivated & TimeLocke" to "Deactivated & Withdrawable" (see MixinStake).
///
/// | Action | TimeLock Period | lockedAt | total | pending | un-TimeLocked | un-TimeLocked (virtual) |
/// | | 0 | 0 | 0 | 0 | 0 | 0 |
/// | lock(5) | 1 | 1 | 5 | 0 | 0 | 0 |
/// | | 2 | 1 | 5 | 0 | 0 | 0 |
/// | lock(10) | 2 | 2 | 15 | 5 | 0 | 0 |
/// | | 3 | 2 | 15 | 5 | 0 | 5 |
/// | lock(15) | 3 | 3 | 30 | 15 | 5 | 5 |
/// | | 4 | 3 | 30 | 15 | 5 | 15 |
/// | | 5 | 3 | 30 | 15 | 5 | 30 |
/// | lock(0) | 5 | 5 | 30 | 30 | 30 | 30 |
/// | lock(20) | 6 | 6 | 50 | 30 | 30 | 30 |
/// | unlock(30) | 6 | 6 | 20 | 0 | 0 | 0 |
/// | | 7 | 6 | 20 | 0 | 0 | 0 |
/// | | 8 | 6 | 20 | 0 | 0 | 20 |
/// -------------------------------------------------------------------------------------------------------------
contract MixinTimeLockedStake is
IStakingEvents,
MixinDeploymentConstants,
MixinConstants,
MixinStorage,
MixinScheduler
{
using LibSafeMath for uint256;
using LibSafeDowncast for uint256;
/// @dev Forces the timeLock data structure to sync to state.
/// This is not necessary but may optimize some subsequent calls.
/// @param owner of Stake.
function forceTimeLockSync(address owner)
external
{
_syncTimeLockedStake(owner);
}
/// @dev TimeLocks Stake
/// This moves state into the Deactivated & TimeLocked state.
/// @param owner of Stake.
/// @param amount of Stake to timeLock.
function _timeLockStake(address owner, uint256 amount)
internal
{
(IStructs.TimeLock memory ownerTimeLock,) = _getSynchronizedTimeLock(owner);
uint256 total = uint256(ownerTimeLock.total);
ownerTimeLock.total = total.safeAdd(amount).downcastToUint96();
timeLockedStakeByOwner[owner] = ownerTimeLock;
}
/// @dev Updates storage to reflect the most up-to-date timeLock data structure for a given owner.
/// @param owner of Stake.
function _syncTimeLockedStake(address owner)
internal
{
(IStructs.TimeLock memory ownerTimeLock, bool isOutOfSync) = _getSynchronizedTimeLock(owner);
if (!isOutOfSync) {
return;
}
timeLockedStakeByOwner[owner] = ownerTimeLock;
}
/// @dev Returns the most up-to-date timeLock data structure for a given owner.
/// @param owner of Stake.
function _getSynchronizedTimeLock(address owner)
internal
view
returns (
IStructs.TimeLock memory ownerTimeLock,
bool isOutOfSync
)
{
uint256 currentTimeLockPeriod = getCurrentTimeLockPeriod();
ownerTimeLock = timeLockedStakeByOwner[owner];
isOutOfSync = false;
uint256 ownerLockedAt = uint256(ownerTimeLock.lockedAt);
if (currentTimeLockPeriod == ownerLockedAt.safeAdd(1)) {
// shift n periods
ownerTimeLock.pending = ownerTimeLock.total;
isOutOfSync = true;
} else if (currentTimeLockPeriod > ownerLockedAt) {
// TimeLock has expired - zero out
ownerTimeLock.lockedAt = 0;
ownerTimeLock.total = 0;
ownerTimeLock.pending = 0;
}
return (ownerTimeLock, isOutOfSync);
}
}

View File

@ -25,6 +25,9 @@ import "../immutable/MixinStorage.sol";
/// @dev This mixin contains logic for managing and interfacing with the Zrx Vault. /// @dev This mixin contains logic for managing and interfacing with the Zrx Vault.
/// (see vaults/ZrxVault.sol). /// (see vaults/ZrxVault.sol).
contract MixinZrxVault is contract MixinZrxVault is
MixinDeploymentConstants,
Ownable,
MixinConstants,
MixinStorage MixinStorage
{ {
/// @dev Set the Zrx Vault. /// @dev Set the Zrx Vault.
@ -45,4 +48,47 @@ contract MixinZrxVault is
{ {
return address(zrxVault); return address(zrxVault);
} }
/// @dev Deposits Zrx Tokens from the `owner` into the vault.
/// @param owner of Zrx Tokens
/// @param amount of tokens to deposit.
function _depositFromOwnerIntoZrxVault(address owner, uint256 amount)
internal
{
IZrxVault _zrxVault = zrxVault;
require(
address(_zrxVault) != address(0),
"INVALID_ZRX_VAULT"
);
_zrxVault.depositFrom(owner, amount);
}
/// @dev Withdraws Zrx Tokens from to `owner` from the vault.
/// @param owner of deposited Zrx Tokens
/// @param amount of tokens to withdraw.
function _withdrawToOwnerFromZrxVault(address owner, uint256 amount)
internal
{
IZrxVault _zrxVault = zrxVault;
require(
address(_zrxVault) != address(0),
"INVALID_ZRX_VAULT"
);
_zrxVault.withdrawFrom(owner, amount);
}
/// @dev Returns balance of `owner` in the ZRX ault.
/// @param owner of deposited Zrx Tokens.
function _balanceOfOwnerInZrxVault(address owner)
internal
view
returns (uint256)
{
IZrxVault _zrxVault = zrxVault;
require(
address(_zrxVault) != address(0),
"INVALID_ZRX_VAULT"
);
return _zrxVault.balanceOf(owner);
}
} }

View File

@ -0,0 +1,54 @@
/*
Copyright 2018 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,
MixinDeploymentConstants,
Ownable,
MixinConstants,
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);
}
}

View File

@ -28,7 +28,9 @@ import "../interfaces/IStructs.sol";
import "../interfaces/IStakingEvents.sol"; import "../interfaces/IStakingEvents.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol"; import "../immutable/MixinStorage.sol";
import "../sys/MixinScheduler.sol";
import "./MixinStakingPoolRewardVault.sol"; import "./MixinStakingPoolRewardVault.sol";
import "./MixinStakingPoolRewards.sol";
/// @dev This mixin contains logic for staking pools. /// @dev This mixin contains logic for staking pools.
@ -57,9 +59,15 @@ import "./MixinStakingPoolRewardVault.sol";
contract MixinStakingPool is contract MixinStakingPool is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinStakingPoolRewardVault MixinZrxVault,
MixinScheduler,
MixinStakingPoolRewardVault,
MixinStakeStorage,
MixinStakeBalances,
MixinStakingPoolRewards
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
@ -117,8 +125,12 @@ contract MixinStakingPool is
}); });
poolById[poolId] = pool; 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 // register pool in reward vault
_registerStakingPoolInRewardVault(poolId, operatorShare); rewardVault.registerStakingPool(poolId, operatorShare);
// notify // notify
emit StakingPoolCreated(poolId, operatorAddress, operatorShare); emit StakingPoolCreated(poolId, operatorAddress, operatorShare);

View File

@ -29,6 +29,7 @@ import "../immutable/MixinStorage.sol";
contract MixinStakingPoolRewardVault is contract MixinStakingPoolRewardVault is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage MixinStorage
{ {
@ -53,39 +54,6 @@ contract MixinStakingPoolRewardVault is
return address(rewardVault); return address(rewardVault);
} }
/// @dev Returns the total balance in ETH of a staking pool, as recorded in the vault.
/// @param poolId Unique id of pool.
/// @return Balance.
function getTotalBalanceInStakingPoolRewardVault(bytes32 poolId)
public
view
returns (uint256)
{
return rewardVault.balanceOf(poolId);
}
/// @dev Returns the balance in ETH of the staking pool operator, as recorded in the vault.
/// @param poolId Unique id of pool.
/// @return Balance.
function getBalanceOfOperatorInStakingPoolRewardVault(bytes32 poolId)
public
view
returns (uint256)
{
return rewardVault.balanceOfOperator(poolId);
}
/// @dev Returns the balance in ETH co-owned by the members of a pool, as recorded in the vault.
/// @param poolId Unique id of pool.
/// @return Balance.
function getBalanceOfMembersInStakingPoolRewardVault(bytes32 poolId)
public
view
returns (uint256)
{
return rewardVault.balanceOfMembers(poolId);
}
/// @dev Registers a staking pool in the reward vault. /// @dev Registers a staking pool in the reward vault.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param operatorShare Portion of rewards owned by the operator, in ppm. /// @param operatorShare Portion of rewards owned by the operator, in ppm.
@ -98,24 +66,6 @@ contract MixinStakingPoolRewardVault is
); );
} }
/// @dev Withdraws an amount in ETH of the reward for a pool operator.
/// @param poolId Unique id of pool.
/// @param amount The amount to withdraw.
function _withdrawFromOperatorInStakingPoolRewardVault(bytes32 poolId, uint256 amount)
internal
{
rewardVault.withdrawForOperator(poolId, amount);
}
/// @dev Withdraws an amount in ETH of the reward for a pool member.
/// @param poolId Unique id of pool.
/// @param amount The amount to withdraw.
function _withdrawFromMemberInStakingPoolRewardVault(bytes32 poolId, uint256 amount)
internal
{
rewardVault.withdrawForMember(poolId, amount);
}
/// @dev Deposits an amount in ETH into the reward vault. /// @dev Deposits an amount in ETH into the reward vault.
/// @param amount The amount in ETH to deposit. /// @param amount The amount in ETH to deposit.
function _depositIntoStakingPoolRewardVault(uint256 amount) function _depositIntoStakingPoolRewardVault(uint256 amount)
@ -125,12 +75,21 @@ contract MixinStakingPoolRewardVault is
rewardVaultAddress.transfer(amount); rewardVaultAddress.transfer(amount);
} }
/// @dev Records an amount deposited into the reward vault for a specific pool. /// @dev Transfer from transient Reward Pool vault to ETH Vault.
/// @param poolId Unique id of pool. /// @param poolId Unique Id of pool.
/// @param amount The amount in ETH to record. /// @param member of pool to transfer ETH to.
function _recordDepositInStakingPoolRewardVault(bytes32 poolId, uint256 amount) /// @param amount The amount in ETH to transfer.
function _transferMemberBalanceToEthVault(
bytes32 poolId,
address member,
uint256 amount
)
internal internal
{ {
rewardVault.recordDepositFor(poolId, amount); require(
address(rewardVault) != NIL_ADDRESS,
"REWARD_VAULT_NOT_SET"
);
rewardVault.transferMemberBalanceToEthVault(poolId, member, amount);
} }
} }

View File

@ -17,297 +17,225 @@
*/ */
pragma solidity ^0.5.9; 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
import "../libs/LibRewardMath.sol";
import "../immutable/MixinStorage.sol"; import "../immutable/MixinStorage.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
import "../stake/MixinStakeBalances.sol"; import "../stake/MixinStakeBalances.sol";
import "./MixinStakingPoolRewardVault.sol"; import "./MixinStakingPoolRewardVault.sol";
import "./MixinStakingPool.sol";
/// @dev This mixin contains logic for staking pool rewards.
/// Rewards for a pool are generated by their market makers trading on the 0x protocol (MixinStakingPool).
/// The operator of a pool receives a fixed percentage of all rewards; generally, the operator is the
/// sole market maker of a pool. The remaining rewards are divided among the members of a pool; each member
/// gets an amount proportional to how much stake they have delegated to the pool.
///
/// Note that members can freely join or leave a staking pool at any time, by delegating/undelegating their stake.
/// Moreover, there is no limit to how many members a pool can have. To limit the state-updates needed to track member balances,
/// we store only a single balance shared by all members. This state is updated every time a reward is paid to the pool - which
/// is currently at the end of each epoch. Additionally, each member has an associated "Shadow Balance" which is updated only
/// when a member delegates/undelegates stake to the pool, along with a "Total Shadow Balance" that represents the cumulative
/// Shadow Balances of all members in a pool.
///
/// -- Member Balances --
/// Terminology:
/// Real Balance - The reward balance in ETH of a member.
/// Total Real Balance - The sum total of reward balances in ETH across all members of a pool.
/// Shadow Balance - The realized reward balance of a member.
/// Total Shadow Balance - The sum total of realized reward balances across all members of a pool.
/// How it works:
/// 1. When a member delegates, their ownership of the pool increases; however, this new ownership applies
/// only to future rewards and must not change the rewards currently owned by other members. Thus, when a
/// member delegates stake, we *increase* their Shadow Balance and the Total Shadow Balance of the pool.
///
/// 2. When a member withdraws a portion of their reward, their realized balance increases but their ownership
/// within the pool remains unchanged. Thus, we simultaneously *decrease* their Real Balance and
/// *increase* their Shadow Balance by the amount withdrawn. The cumulative balance decrease and increase, respectively.
///
/// 3. When a member undelegates, the portion of their reward that corresponds to that stake is also withdrawn. Thus,
/// their realized balance *increases* while their ownership of the pool *decreases*. To reflect this, we
/// decrease their Shadow Balance, the Total Shadow Balance, their Real Balance, and the Total Real Balance.
contract MixinStakingPoolRewards is contract MixinStakingPoolRewards is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage, MixinStorage,
MixinZrxVault,
MixinScheduler, MixinScheduler,
MixinStakingPoolRewardVault, MixinStakingPoolRewardVault,
MixinStakingPool, MixinStakeStorage,
MixinTimeLockedStake,
MixinStakeBalances MixinStakeBalances
{ {
using LibSafeMath for uint256; using LibSafeMath for uint256;
/// @dev Withdraws an amount in ETH of the reward for the pool operator.
/// @param poolId Unique id of pool.
/// @param amount The amount to withdraw.
function withdrawRewardForStakingPoolOperator(bytes32 poolId, uint256 amount)
external
onlyStakingPoolOperator(poolId)
{
_withdrawFromOperatorInStakingPoolRewardVault(poolId, amount);
poolById[poolId].operatorAddress.transfer(amount);
}
/// @dev Withdraws the total balance in ETH of the reward for the pool operator.
/// @param poolId Unique id of pool.
/// @return The amount withdrawn.
function withdrawTotalRewardForStakingPoolOperator(bytes32 poolId)
external
onlyStakingPoolOperator(poolId)
returns (uint256)
{
uint256 amount = getBalanceOfOperatorInStakingPoolRewardVault(poolId);
_withdrawFromOperatorInStakingPoolRewardVault(poolId, amount);
poolById[poolId].operatorAddress.transfer(amount);
return amount;
}
/// @dev Withdraws an amount in ETH of the reward for a pool member.
/// @param poolId Unique id of pool.
/// @param amount The amount to withdraw.
function withdrawRewardForStakingPoolMember(bytes32 poolId, uint256 amount)
external
{
// sanity checks
address payable member = msg.sender;
uint256 memberBalance = computeRewardBalanceOfStakingPoolMember(poolId, member);
if (amount > memberBalance) {
LibRichErrors.rrevert(LibStakingRichErrors.WithdrawAmountExceedsMemberBalanceError(
amount,
memberBalance
));
}
// update shadow rewards
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId].safeAdd(amount);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId].safeAdd(amount);
// perform withdrawal
_withdrawFromMemberInStakingPoolRewardVault(poolId, amount);
member.transfer(amount);
}
/// @dev Withdraws the total balance in ETH of the reward for a pool member.
/// @param poolId Unique id of pool.
/// @return The amount withdrawn.
function withdrawTotalRewardForStakingPoolMember(bytes32 poolId)
external
returns (uint256)
{
// sanity checks
address payable member = msg.sender;
uint256 amount = computeRewardBalanceOfStakingPoolMember(poolId, member);
// update shadow rewards
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId].safeAdd(amount);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId].safeAdd(amount);
// perform withdrawal and return amount withdrawn
_withdrawFromMemberInStakingPoolRewardVault(poolId, amount);
member.transfer(amount);
return amount;
}
/// @dev Returns the sum total reward balance in ETH of a staking pool, across all members and the pool operator.
/// @param poolId Unique id of pool.
/// @return Balance.
function getTotalRewardBalanceOfStakingPool(bytes32 poolId)
external
view
returns (uint256)
{
return getTotalBalanceInStakingPoolRewardVault(poolId);
}
/// @dev Returns the reward balance in ETH of the pool operator.
/// @param poolId Unique id of pool.
/// @return Balance.
function getRewardBalanceOfStakingPoolOperator(bytes32 poolId)
external
view
returns (uint256)
{
return getBalanceOfOperatorInStakingPoolRewardVault(poolId);
}
/// @dev Returns the reward balance in ETH co-owned by the members of a pool.
/// @param poolId Unique id of pool.
/// @return Balance.
function getRewardBalanceOfStakingPoolMembers(bytes32 poolId)
external
view
returns (uint256)
{
return getBalanceOfMembersInStakingPoolRewardVault(poolId);
}
/// @dev Returns the shadow balance of a specific member of a staking pool.
/// @param poolId Unique id of pool.
/// @param member The member of the pool.
/// @return Balance.
function getShadowBalanceOfStakingPoolMember(bytes32 poolId, address member)
public
view
returns (uint256)
{
return shadowRewardsInPoolByOwner[member][poolId];
}
/// @dev Returns the total shadow balance of a staking pool.
/// @param poolId Unique id of pool.
/// @return Balance.
function getTotalShadowBalanceOfStakingPool(bytes32 poolId)
public
view
returns (uint256)
{
return shadowRewardsByPoolId[poolId];
}
/// @dev Computes the reward balance in ETH of a specific member of a pool. /// @dev Computes the reward balance in ETH of a specific member of a pool.
/// @param poolId Unique id of pool. /// @param poolId Unique id of pool.
/// @param member The member of the pool. /// @param member The member of the pool.
/// @return Balance. /// @return Balance in ETH.
function computeRewardBalanceOfStakingPoolMember(bytes32 poolId, address member) function computeRewardBalanceOfDelegator(bytes32 poolId, address member)
public public
view view
returns (uint256) returns (uint256)
{ {
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId); // cache some values to reduce sloads
return LibRewardMath._computePayoutDenominatedInRealAsset( IStructs.DelayedBalance memory delegatedStake = delegatedStakeToPoolByOwner[member][poolId];
delegatedStakeToPoolByOwner[member][poolId], uint256 currentEpoch = getCurrentEpoch();
delegatedStakeByPoolId[poolId],
shadowRewardsInPoolByOwner[member][poolId], // value is always zero in these two scenarios:
shadowRewardsByPoolId[poolId], // 1. The current epoch is zero: delegation begins at epoch 1
poolBalance // 2. The owner's delegated is current as of this epoch: their rewards have been moved to the ETH vault.
); if (currentEpoch == 0 || delegatedStake.lastStored == currentEpoch) return 0;
// compute reward accumulated during `lastStored` epoch;
// the `current` balance describes how much stake was collecting rewards when `lastStored` was set.
uint256 rewardsAccumulatedDuringLastStoredEpoch = (delegatedStake.current != 0)
? _computeMemberRewardOverInterval(
poolId,
delegatedStake.current,
delegatedStake.lastStored - 1,
delegatedStake.lastStored
)
: 0;
// compute the rewards accumulated by the `next` balance;
// this starts at `lastStored + 1` and goes up until the last epoch, during which
// rewards were accumulated. This is at most the most recently finalized epoch (current epoch - 1).
uint256 rewardsAccumulatedAfterLastStoredEpoch = (cumulativeRewardsByPoolLastStored[poolId] > delegatedStake.lastStored)
? _computeMemberRewardOverInterval(
poolId,
delegatedStake.next,
delegatedStake.lastStored,
cumulativeRewardsByPoolLastStored[poolId]
)
: 0;
// compute the total reward
uint256 totalReward = rewardsAccumulatedDuringLastStoredEpoch.safeAdd(rewardsAccumulatedAfterLastStoredEpoch);
return totalReward;
} }
/// @dev A member joins a staking pool. /// @dev Transfers a delegators accumulated rewards from the transient pool Reward Pool vault
/// This function increments the shadow balance of the member, along /// to the Eth Vault. This is required before the member's stake in the pool can be
/// with the total shadow balance of the pool. This ensures that /// modified.
/// any rewards belonging to existing members will not be diluted. /// @param poolId Unique id of pool.
/// @param poolId Unique Id of pool to join. /// @param member The member of the pool.
/// @param member The member to join. function _transferDelegatorsAccumulatedRewardsToEthVault(bytes32 poolId, address member)
/// @param amountOfStakeToDelegate The stake to be delegated by `member` upon joining. internal
/// @param totalStakeDelegatedToPool The amount of stake currently delegated to the pool. {
/// This does not include `amountOfStakeToDelegate`. // there are no delegators in the first epoch
function _joinStakingPool( uint256 currentEpoch = getCurrentEpoch();
if (currentEpoch == 0) {
return;
}
// compute balance owed to delegator
uint256 balance = computeRewardBalanceOfDelegator(poolId, member);
if (balance == 0) {
return;
}
// transfer from transient Reward Pool vault to ETH Vault
_transferMemberBalanceToEthVault(poolId, member, balance);
}
/// @dev Initializes Cumulative Rewards for a given pool.
function _initializeCumulativeRewards(bytes32 poolId)
internal
{
uint256 currentEpoch = getCurrentEpoch();
cumulativeRewardsByPool[poolId][currentEpoch] = IStructs.Fraction({numerator: 0, denominator: MIN_TOKEN_VALUE});
cumulativeRewardsByPoolLastStored[poolId] = currentEpoch;
}
/// @dev To compute a delegator's reward we must know the cumulative reward
/// at the epoch before they delegated. If they were already delegated then
/// we also need to know the value at the epoch in which they modified
/// their delegated stake for this pool. See `computeRewardBalanceOfDelegator`.
/// @param poolId Unique Id of pool.
/// @param epoch at which the stake was delegated by the delegator.
function _syncCumulativeRewardsNeededByDelegator(bytes32 poolId, uint256 epoch)
internal
{
// set default value if staking at epoch 0
if (epoch == 0) {
return;
}
// cache a storage pointer to the cumulative rewards for `poolId` indexed by epoch.
mapping (uint256 => IStructs.Fraction) storage cumulativeRewardsByPoolPtr = cumulativeRewardsByPool[poolId];
// 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.
uint256 cumulativeRewardsLastStored = cumulativeRewardsByPoolLastStored[poolId];
IStructs.Fraction memory mostRecentCumulativeRewards = cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored];
// copy our most up-to-date cumulative rewards for last epoch, if necessary.
uint256 lastEpoch = currentEpoch.safeSub(1);
if (cumulativeRewardsLastStored != lastEpoch) {
cumulativeRewardsByPoolPtr[lastEpoch] = mostRecentCumulativeRewards;
cumulativeRewardsByPoolLastStored[poolId] = lastEpoch;
}
// copy our most up-to-date cumulative rewards for last epoch, if necessary.
// this is necessary if the pool does not earn any rewards this epoch;
// if it does then this value may be overwritten when the epoch is finalized.
if (!_isCumulativeRewardSet(cumulativeRewardsByPoolPtr[epoch])) {
cumulativeRewardsByPoolPtr[epoch] = mostRecentCumulativeRewards;
}
}
/// @dev Records a reward for delegators. This adds to the `cumulativeRewardsByPool`.
/// @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 epoch at which this was earned.
function _recordRewardForDelegators(
bytes32 poolId, bytes32 poolId,
address payable member, uint256 reward,
uint256 amountOfStakeToDelegate, uint256 amountOfDelegatedStake,
uint256 totalStakeDelegatedToPool uint256 epoch
) )
internal internal
{ {
// update delegator's share of reward pool // cache a storage pointer to the cumulative rewards for `poolId` indexed by epoch.
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId); mapping (uint256 => IStructs.Fraction) storage cumulativeRewardsByPoolPtr = cumulativeRewardsByPool[poolId];
uint256 buyIn = LibRewardMath._computeBuyInDenominatedInShadowAsset(
amountOfStakeToDelegate, // fetch the last epoch at which we stored an entry for this pool;
totalStakeDelegatedToPool, // this is the most up-to-date cumulative rewards for this pool.
shadowRewardsByPoolId[poolId], uint256 cumulativeRewardsLastStored = cumulativeRewardsByPoolLastStored[poolId];
poolBalance IStructs.Fraction memory mostRecentCumulativeRewards = cumulativeRewardsByPoolPtr[cumulativeRewardsLastStored];
// compute new cumulative reward
(uint256 numerator, uint256 denominator) = LibSafeMath.addFractions(
mostRecentCumulativeRewards.numerator,
mostRecentCumulativeRewards.denominator,
reward,
amountOfDelegatedStake
); );
// the buy-in will be > 0 iff there exists a non-zero reward. // normalize fraction components by dividing by the min token value (10^18)
if (buyIn > 0) { (uint256 numeratorNormalized, uint256 denominatorNormalized) = (
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId].safeAdd(buyIn); numerator.safeDiv(MIN_TOKEN_VALUE),
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId].safeAdd(buyIn); denominator.safeDiv(MIN_TOKEN_VALUE)
} );
// store cumulative rewards
cumulativeRewardsByPoolPtr[epoch] = IStructs.Fraction({
numerator: numeratorNormalized,
denominator: denominatorNormalized
});
cumulativeRewardsByPoolLastStored[poolId] = epoch;
} }
/// @dev A member leaves a staking pool. /// @dev Computes a member's reward over a given epoch interval.
/// This function decrements the shadow balance of the member, along /// @param poolId Uniqud Id of pool.
/// with the total shadow balance of the pool. This ensures that /// @param memberStakeOverInterval Stake delegated to pool by meber over the interval.
/// any rewards belonging to co-members will not be inflated. /// @param beginEpoch beginning of interval.
/// @param poolId Unique Id of pool to leave. /// @param endEpoch end of interval.
/// @param member The member to leave. /// @return rewards accumulated over interval [beginEpoch, endEpoch]
/// @param amountOfStakeToUndelegate The stake to be undelegated by `member` upon leaving. function _computeMemberRewardOverInterval(
/// @param totalStakeDelegatedToPoolByMember The amount of stake currently delegated to the pool by the member.
/// This includes `amountOfStakeToUndelegate`.
/// @param totalStakeDelegatedToPool The total amount of stake currently delegated to the pool, across all members.
/// This includes `amountOfStakeToUndelegate`.
function _leaveStakingPool(
bytes32 poolId, bytes32 poolId,
address payable member, uint256 memberStakeOverInterval,
uint256 amountOfStakeToUndelegate, uint256 beginEpoch,
uint256 totalStakeDelegatedToPoolByMember, uint256 endEpoch
uint256 totalStakeDelegatedToPool
) )
internal private
view
returns (uint256)
{ {
// get payout IStructs.Fraction memory beginRatio = cumulativeRewardsByPool[poolId][beginEpoch];
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId); IStructs.Fraction memory endRatio = cumulativeRewardsByPool[poolId][endEpoch];
uint256 payoutInRealAsset = 0; uint256 reward = LibSafeMath.scaleFractionalDifference(
uint256 payoutInShadowAsset = 0; endRatio.numerator,
if (totalStakeDelegatedToPoolByMember == amountOfStakeToUndelegate) { endRatio.denominator,
// full payout; this is computed separately to avoid extra computation and rounding. beginRatio.numerator,
payoutInShadowAsset = shadowRewardsInPoolByOwner[member][poolId]; beginRatio.denominator,
payoutInRealAsset = LibRewardMath._computePayoutDenominatedInRealAsset( memberStakeOverInterval
amountOfStakeToUndelegate,
totalStakeDelegatedToPool,
payoutInShadowAsset,
shadowRewardsByPoolId[poolId],
poolBalance
);
} else {
// partial payout
(payoutInRealAsset, payoutInShadowAsset) = LibRewardMath._computePartialPayout(
amountOfStakeToUndelegate,
totalStakeDelegatedToPoolByMember,
totalStakeDelegatedToPool,
shadowRewardsInPoolByOwner[member][poolId],
shadowRewardsByPoolId[poolId],
poolBalance
); );
return reward;
} }
// update shadow rewards /// @dev returns true iff Cumulative Rewards are set
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId].safeSub(payoutInShadowAsset); function _isCumulativeRewardSet(IStructs.Fraction memory cumulativeReward)
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId].safeSub(payoutInShadowAsset); private
returns (bool)
// withdraw payout for member {
if (payoutInRealAsset > 0) { // we use the denominator as a proxy for whether the cumulative
_withdrawFromMemberInStakingPoolRewardVault(poolId, payoutInRealAsset); // reward is set, as setting the cumulative reward always sets this
member.transfer(payoutInRealAsset); // field to at least 1.
} return cumulativeReward.denominator != 0;
} }
} }

View File

@ -36,6 +36,7 @@ import "../interfaces/IStakingEvents.sol";
contract MixinScheduler is contract MixinScheduler is
IStakingEvents, IStakingEvents,
MixinDeploymentConstants, MixinDeploymentConstants,
Ownable,
MixinConstants, MixinConstants,
MixinStorage MixinStorage
{ {
@ -85,49 +86,6 @@ contract MixinScheduler is
return getCurrentEpochStartTimeInSeconds().safeAdd(getEpochDurationInSeconds()); return getCurrentEpochStartTimeInSeconds().safeAdd(getEpochDurationInSeconds());
} }
/// @dev Returns the current timeLock period.
/// @return TimeLock period.
function getCurrentTimeLockPeriod()
public
view
returns (uint256)
{
return currentTimeLockPeriod;
}
/// @dev Returns the length of a timeLock period, measured in epochs.
/// TimeLock period = [startEpoch..endEpoch)
/// @return TimeLock period end.
function getTimeLockDurationInEpochs()
public
pure
returns (uint256)
{
return TIMELOCK_DURATION_IN_EPOCHS;
}
/// @dev Returns the epoch that the current timeLock period started at.
/// TimeLock period = [startEpoch..endEpoch)
/// @return TimeLock period start.
function getCurrentTimeLockPeriodStartEpoch()
public
view
returns (uint256)
{
return currentTimeLockPeriodStartEpoch;
}
/// @dev Returns the epoch that the current timeLock period will end.
/// TimeLock period = [startEpoch..endEpoch)
/// @return TimeLock period.
function getCurrentTimeLockPeriodEndEpoch()
public
view
returns (uint256)
{
return getCurrentTimeLockPeriodStartEpoch().safeAdd(getTimeLockDurationInEpochs());
}
/// @dev Moves to the next epoch, given the current epoch period has ended. /// @dev Moves to the next epoch, given the current epoch period has ended.
/// Time intervals that are measured in epochs (like timeLocks) are also incremented, given /// Time intervals that are measured in epochs (like timeLocks) are also incremented, given
/// their periods have ended. /// their periods have ended.
@ -159,19 +117,5 @@ contract MixinScheduler is
currentEpochStartTimeInSeconds, currentEpochStartTimeInSeconds,
earliestEndTimeInSeconds earliestEndTimeInSeconds
); );
// increment timeLock period, if needed
if (getCurrentTimeLockPeriodEndEpoch() <= nextEpoch) {
currentTimeLockPeriod = currentTimeLockPeriod.safeAdd(1);
currentTimeLockPeriodStartEpoch = currentEpoch;
uint256 endEpoch = currentEpoch.safeAdd(getTimeLockDurationInEpochs());
// notify
emit TimeLockPeriodChanged(
currentTimeLockPeriod,
currentTimeLockPeriodStartEpoch,
endEpoch
);
}
} }
} }

View File

@ -0,0 +1,110 @@
/*
Copyright 2018 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 "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../interfaces/IEthVault.sol";
import "./MixinVaultCore.sol";
/// @dev This vault manages ETH.
contract EthVault is
Authorizable,
IEthVault,
IVaultCore,
MixinVaultCore
{
using LibSafeMath for uint256;
// mapping from Owner to ETH balance
mapping (address => uint256) internal balances;
/// @dev Constructor.
// solhint-disable-next-line no-empty-blocks
constructor() public {}
/// @dev Deposit an `amount` of ETH from `owner` into the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param owner of ETH Tokens.
function depositFor(address owner)
external
payable
{
// update balance
uint256 amount = msg.value;
balances[owner] = balances[owner].safeAdd(msg.value);
// notify
emit EthDepositedIntoVault(msg.sender, owner, amount);
}
/// @dev Withdraw an `amount` of ETH to `msg.sender` from the vault.
/// Note that only the Staking contract can call this.
/// Note that this can only be called when *not* in Catostrophic Failure mode.
/// @param amount of ETH to withdraw.
function withdraw(uint256 amount)
external
{
_withdrawFrom(msg.sender, amount);
}
/// @dev Withdraw ALL ETH to `msg.sender` from the vault.
function withdrawAll()
external
returns (uint256)
{
// get total balance
address payable owner = msg.sender;
uint256 totalBalance = balances[owner];
// withdraw ETH to owner
_withdrawFrom(owner, totalBalance);
return totalBalance;
}
/// @dev Returns the balance in ETH of the `owner`
/// @return Balance in ETH.
function balanceOf(address owner)
external
view
returns (uint256)
{
return balances[owner];
}
/// @dev Withdraw an `amount` of ETH to `owner` from the vault.
/// @param owner of ETH.
/// @param amount of ETH to withdraw.
function _withdrawFrom(address payable owner, uint256 amount)
internal
{
// update balance
// note that this call will revert if trying to withdraw more
// than the current balance
balances[owner] = balances[owner].safeSub(amount);
// notify
emit EthWithdrawnFromVault(msg.sender, owner, amount);
// withdraw ETH to owner
owner.transfer(amount);
}
}

View File

@ -25,6 +25,7 @@ import "../libs/LibStakingRichErrors.sol";
import "../libs/LibSafeDowncast.sol"; import "../libs/LibSafeDowncast.sol";
import "./MixinVaultCore.sol"; import "./MixinVaultCore.sol";
import "../interfaces/IStakingPoolRewardVault.sol"; import "../interfaces/IStakingPoolRewardVault.sol";
import "../interfaces/IEthVault.sol";
import "../immutable/MixinConstants.sol"; import "../immutable/MixinConstants.sol";
@ -40,6 +41,7 @@ import "../immutable/MixinConstants.sol";
contract StakingPoolRewardVault is contract StakingPoolRewardVault is
Authorizable, Authorizable,
IStakingPoolRewardVault, IStakingPoolRewardVault,
IVaultCore,
MixinDeploymentConstants, MixinDeploymentConstants,
MixinConstants, MixinConstants,
MixinVaultCore MixinVaultCore
@ -50,6 +52,9 @@ contract StakingPoolRewardVault is
// mapping from Pool to Reward Balance in ETH // mapping from Pool to Reward Balance in ETH
mapping (bytes32 => Balance) internal balanceByPoolId; mapping (bytes32 => Balance) internal balanceByPoolId;
// address of ether vault
IEthVault internal ethVault;
/// @dev Fallback function. This contract is payable, but only by the staking contract. /// @dev Fallback function. This contract is payable, but only by the staking contract.
function () function ()
external external
@ -60,24 +65,15 @@ contract StakingPoolRewardVault is
emit RewardDeposited(UNKNOWN_STAKING_POOL_ID, msg.value); emit RewardDeposited(UNKNOWN_STAKING_POOL_ID, msg.value);
} }
/// @dev Deposit a reward in ETH for a specific pool. /// @dev Sets the Eth Vault.
/// Note that this is only callable by the staking contract, and when /// Note that only the contract owner can call this.
/// not in catastrophic failure mode. /// @param ethVaultAddress Address of the Eth Vault.
/// @param poolId Unique Id of pool. function setEthVault(address ethVaultAddress)
function depositFor(bytes32 poolId)
external external
payable onlyOwner
onlyStakingContract
onlyNotInCatastrophicFailure
{ {
// update balance of pool ethVault = IEthVault(ethVaultAddress);
uint256 amount = msg.value; emit EthVaultChanged(ethVaultAddress);
Balance memory balance = balanceByPoolId[poolId];
_incrementBalanceStruct(balance, amount);
balanceByPoolId[poolId] = balance;
// notify
emit RewardDeposited(poolId, amount);
} }
/// @dev Record a deposit for a pool. This deposit should be in the same transaction, /// @dev Record a deposit for a pool. This deposit should be in the same transaction,
@ -86,26 +82,50 @@ contract StakingPoolRewardVault is
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. /// @param amount Amount in ETH to record.
function recordDepositFor(bytes32 poolId, uint256 amount) /// @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.
function recordDepositFor(
bytes32 poolId,
uint256 amount,
bool operatorOnly
)
external external
onlyStakingContract onlyStakingContract
onlyNotInCatastrophicFailure returns (
uint256 operatorPortion,
uint256 poolPortion
)
{ {
// update balance of pool // update balance of pool
Balance memory balance = balanceByPoolId[poolId]; Balance memory balance = balanceByPoolId[poolId];
_incrementBalanceStruct(balance, amount); (operatorPortion, poolPortion) = _incrementBalanceStruct(balance, amount, operatorOnly);
balanceByPoolId[poolId] = balance; balanceByPoolId[poolId] = balance;
return (operatorPortion, poolPortion);
} }
/// @dev Withdraw some amount in ETH of an operator's reward. /// @dev Withdraw some amount in ETH of an operator's reward.
/// Note that this is only callable by the staking contract, and when /// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. function transferOperatorBalanceToEthVault(
function withdrawForOperator(bytes32 poolId, uint256 amount) bytes32 poolId,
address operator,
uint256 amount
)
external external
onlyStakingContract onlyStakingContract
{ {
if (amount == 0) {
return;
}
// sanity check on eth vault
require(
address(ethVault) != address(0),
"ETH_VAULT_NOT_SET"
);
// sanity check - sufficient balance? // sanity check - sufficient balance?
uint256 operatorBalance = uint256(balanceByPoolId[poolId].operatorBalance); uint256 operatorBalance = uint256(balanceByPoolId[poolId].operatorBalance);
if (amount > operatorBalance) { if (amount > operatorBalance) {
@ -117,7 +137,7 @@ contract StakingPoolRewardVault is
// update balance and transfer `amount` in ETH to staking contract // update balance and transfer `amount` in ETH to staking contract
balanceByPoolId[poolId].operatorBalance = operatorBalance.safeSub(amount).downcastToUint96(); balanceByPoolId[poolId].operatorBalance = operatorBalance.safeSub(amount).downcastToUint96();
stakingContractAddress.transfer(amount); ethVault.depositFor.value(amount)(operator);
// notify // notify
emit RewardWithdrawnForOperator(poolId, amount); emit RewardWithdrawnForOperator(poolId, amount);
@ -127,11 +147,21 @@ contract StakingPoolRewardVault is
/// Note that this is only callable by the staking contract, and when /// Note that this is only callable by the staking contract, and when
/// not in catastrophic failure mode. /// not in catastrophic failure mode.
/// @param poolId Unique Id of pool. /// @param poolId Unique Id of pool.
/// @param amount Amount in ETH to record. /// @param amount Amount in ETH to transfer.
function withdrawForMember(bytes32 poolId, uint256 amount) function transferMemberBalanceToEthVault(
bytes32 poolId,
address member,
uint256 amount
)
external external
onlyStakingContract onlyStakingContract
{ {
// sanity check on eth vault
require(
address(ethVault) != address(0),
"ETH_VAULT_NOT_SET"
);
// sanity check - sufficient balance? // sanity check - sufficient balance?
uint256 membersBalance = uint256(balanceByPoolId[poolId].membersBalance); uint256 membersBalance = uint256(balanceByPoolId[poolId].membersBalance);
if (amount > membersBalance) { if (amount > membersBalance) {
@ -143,7 +173,7 @@ contract StakingPoolRewardVault is
// update balance and transfer `amount` in ETH to staking contract // update balance and transfer `amount` in ETH to staking contract
balanceByPoolId[poolId].membersBalance = membersBalance.safeSub(amount).downcastToUint96(); balanceByPoolId[poolId].membersBalance = membersBalance.safeSub(amount).downcastToUint96();
stakingContractAddress.transfer(amount); ethVault.depositFor.value(amount)(member);
// notify // notify
emit RewardWithdrawnForMember(poolId, amount); emit RewardWithdrawnForMember(poolId, amount);
@ -222,21 +252,35 @@ contract StakingPoolRewardVault is
/// pool operator and members of the pool based on the pool operator's share. /// pool operator and members of the pool based on the pool operator's share.
/// @param balance Balance struct to increment. /// @param balance Balance struct to increment.
/// @param amount Amount to add to balance. /// @param amount Amount to add to balance.
function _incrementBalanceStruct(Balance memory balance, uint256 amount) /// @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)
private private
pure pure
returns (uint256 operatorPortion, uint256 poolPortion)
{ {
// compute portions. One of the two must round down: the operator always receives the leftover from rounding. // compute portions. One of the two must round down: the operator always receives the leftover from rounding.
uint256 operatorPortion = LibMath.getPartialAmountCeil( operatorPortion = operatorOnly
uint256(balance.operatorShare), // Operator share out of 1e6 ? amount
: LibMath.getPartialAmountCeil(
uint256(balance.operatorShare),
PPM_DENOMINATOR, PPM_DENOMINATOR,
amount amount
); );
uint256 poolPortion = amount.safeSub(operatorPortion); poolPortion = amount.safeSub(operatorPortion);
// update balances // compute new balances
balance.operatorBalance = uint256(balance.operatorBalance).safeAdd(operatorPortion).downcastToUint96(); uint256 newOperatorBalance = uint256(balance.operatorBalance).safeAdd(operatorPortion);
balance.membersBalance = uint256(balance.membersBalance).safeAdd(poolPortion).downcastToUint96(); uint256 newMembersBalance = uint256(balance.membersBalance).safeAdd(poolPortion);
// save new balances
balance.operatorBalance = LibSafeDowncast.downcastToUint96(newOperatorBalance);
balance.membersBalance = LibSafeDowncast.downcastToUint96(newMembersBalance);
return (
operatorPortion,
poolPortion
);
} }
} }

View File

@ -35,6 +35,7 @@ import "./MixinVaultCore.sol";
/// corruption of related state in the staking contract. /// corruption of related state in the staking contract.
contract ZrxVault is contract ZrxVault is
Authorizable, Authorizable,
IVaultCore,
IZrxVault, IZrxVault,
MixinVaultCore MixinVaultCore
{ {

View File

@ -16,6 +16,7 @@
*/ */
pragma solidity ^0.5.9; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/Staking.sol"; import "../src/Staking.sol";

View File

@ -25,6 +25,9 @@ import "../src/interfaces/IStructs.sol";
contract TestStorageLayout is contract TestStorageLayout is
MixinDeploymentConstants,
Ownable,
MixinConstants,
MixinStorage MixinStorage
{ {
function assertExpectedStorageLayout() function assertExpectedStorageLayout()
@ -39,30 +42,70 @@ contract TestStorageLayout is
mstore(64, 0x00000016494e434f52524543545f53544f524147455f534c4f54000000000000) mstore(64, 0x00000016494e434f52524543545f53544f524147455f534c4f54000000000000)
mstore(96, 0) mstore(96, 0)
} }
if sub(owner_slot, 0) { revertIncorrectStorageSlot() } let slot := 0
if sub(stakingContract_slot, 1) { revertIncorrectStorageSlot() }
if sub(stakeByOwner_slot, 2) { revertIncorrectStorageSlot() } if sub(owner_slot, slot) { revertIncorrectStorageSlot() }
if sub(activatedStakeByOwner_slot, 3) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(timeLockedStakeByOwner_slot, 4) { revertIncorrectStorageSlot() }
if sub(delegatedStakeByOwner_slot, 5) { revertIncorrectStorageSlot() } if sub(stakingContract_slot, slot) { revertIncorrectStorageSlot() }
if sub(delegatedStakeToPoolByOwner_slot, 6) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(delegatedStakeByPoolId_slot, 7) { revertIncorrectStorageSlot() }
if sub(totalActivatedStake_slot, 8) { revertIncorrectStorageSlot() } if sub(activeStakeByOwner_slot, slot) { revertIncorrectStorageSlot() }
if sub(nextPoolId_slot, 9) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(poolById_slot, 10) { revertIncorrectStorageSlot() }
if sub(poolIdByMakerAddress_slot, 11) { revertIncorrectStorageSlot() } if sub(inactiveStakeByOwner_slot, slot) { revertIncorrectStorageSlot() }
if sub(makerAddressesByPoolId_slot, 12) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(currentEpoch_slot, 13) { revertIncorrectStorageSlot() }
if sub(currentEpochStartTimeInSeconds_slot, 14) { revertIncorrectStorageSlot() } if sub(delegatedStakeByOwner_slot, slot) { revertIncorrectStorageSlot() }
if sub(currentTimeLockPeriod_slot, 15) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(currentTimeLockPeriodStartEpoch_slot, 16) { revertIncorrectStorageSlot() }
if sub(protocolFeesThisEpochByPool_slot, 17) { revertIncorrectStorageSlot() } if sub(delegatedStakeToPoolByOwner_slot, slot) { revertIncorrectStorageSlot() }
if sub(activePoolsThisEpoch_slot, 18) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(shadowRewardsByPoolId_slot, 19) { revertIncorrectStorageSlot() }
if sub(shadowRewardsInPoolByOwner_slot, 20) { revertIncorrectStorageSlot() } if sub(delegatedStakeByPoolId_slot, slot) { revertIncorrectStorageSlot() }
if sub(validExchanges_slot, 21) { revertIncorrectStorageSlot() } slot := add(slot, 1)
if sub(zrxVault_slot, 22) { revertIncorrectStorageSlot() }
if sub(rewardVault_slot, 23) { revertIncorrectStorageSlot() } if sub(withdrawableStakeByOwner_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(nextPoolId_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(poolById_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(poolIdByMakerAddress_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(makerAddressesByPoolId_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(currentEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(currentEpochStartTimeInSeconds_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(protocolFeesThisEpochByPool_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(activePoolsThisEpoch_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(cumulativeRewardsByPool_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(cumulativeRewardsByPoolLastStored_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(validExchanges_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(zrxVault_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
if sub(rewardVault_slot, slot) { revertIncorrectStorageSlot() }
slot := add(slot, 1)
} }
} }
} }

View File

@ -36,8 +36,8 @@
"compile:truffle": "truffle compile" "compile:truffle": "truffle compile"
}, },
"config": { "config": {
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IWallet|IZrxVault|LibEIP712Hash|LibFixedMath|LibFixedMathRichErrors|LibRewardMath|LibSafeDowncast|LibSignatureValidator|LibStakingRichErrors|MixinConstants|MixinDelegatedStake|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinTimeLockedStake|MixinVaultCore|MixinZrxVault|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json", "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/@(EthVault|IEthVault|IStaking|IStakingEvents|IStakingPoolRewardVault|IStakingProxy|IStructs|IVaultCore|IWallet|IZrxVault|LibEIP712Hash|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibSignatureValidator|LibStakingRichErrors|MixinConstants|MixinDeploymentConstants|MixinEthVault|MixinExchangeFees|MixinExchangeManager|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewardVault|MixinStakingPoolRewards|MixinStorage|MixinVaultCore|MixinZrxVault|Staking|StakingPoolRewardVault|StakingProxy|TestCobbDouglas|TestLibFixedMath|TestStorageLayout|ZrxVault).json"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -5,6 +5,8 @@
*/ */
import { ContractArtifact } from 'ethereum-types'; import { ContractArtifact } from 'ethereum-types';
import * as EthVault from '../generated-artifacts/EthVault.json';
import * as IEthVault from '../generated-artifacts/IEthVault.json';
import * as IStaking from '../generated-artifacts/IStaking.json'; import * as IStaking from '../generated-artifacts/IStaking.json';
import * as IStakingEvents from '../generated-artifacts/IStakingEvents.json'; import * as IStakingEvents from '../generated-artifacts/IStakingEvents.json';
import * as IStakingPoolRewardVault from '../generated-artifacts/IStakingPoolRewardVault.json'; import * as IStakingPoolRewardVault from '../generated-artifacts/IStakingPoolRewardVault.json';
@ -16,23 +18,22 @@ import * as IZrxVault from '../generated-artifacts/IZrxVault.json';
import * as LibEIP712Hash from '../generated-artifacts/LibEIP712Hash.json'; import * as LibEIP712Hash from '../generated-artifacts/LibEIP712Hash.json';
import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json'; import * as LibFixedMath from '../generated-artifacts/LibFixedMath.json';
import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json'; import * as LibFixedMathRichErrors from '../generated-artifacts/LibFixedMathRichErrors.json';
import * as LibRewardMath from '../generated-artifacts/LibRewardMath.json';
import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json'; import * as LibSafeDowncast from '../generated-artifacts/LibSafeDowncast.json';
import * as LibSignatureValidator from '../generated-artifacts/LibSignatureValidator.json'; import * as LibSignatureValidator from '../generated-artifacts/LibSignatureValidator.json';
import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json'; import * as LibStakingRichErrors from '../generated-artifacts/LibStakingRichErrors.json';
import * as MixinConstants from '../generated-artifacts/MixinConstants.json'; import * as MixinConstants from '../generated-artifacts/MixinConstants.json';
import * as MixinDelegatedStake from '../generated-artifacts/MixinDelegatedStake.json';
import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json'; import * as MixinDeploymentConstants from '../generated-artifacts/MixinDeploymentConstants.json';
import * as MixinEthVault from '../generated-artifacts/MixinEthVault.json';
import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json'; import * as MixinExchangeFees from '../generated-artifacts/MixinExchangeFees.json';
import * as MixinExchangeManager from '../generated-artifacts/MixinExchangeManager.json'; import * as MixinExchangeManager from '../generated-artifacts/MixinExchangeManager.json';
import * as MixinScheduler from '../generated-artifacts/MixinScheduler.json'; import * as MixinScheduler from '../generated-artifacts/MixinScheduler.json';
import * as MixinStake from '../generated-artifacts/MixinStake.json'; 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 MixinStakingPool from '../generated-artifacts/MixinStakingPool.json'; import * as MixinStakingPool from '../generated-artifacts/MixinStakingPool.json';
import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json'; import * as MixinStakingPoolRewards from '../generated-artifacts/MixinStakingPoolRewards.json';
import * as MixinStakingPoolRewardVault from '../generated-artifacts/MixinStakingPoolRewardVault.json'; import * as MixinStakingPoolRewardVault from '../generated-artifacts/MixinStakingPoolRewardVault.json';
import * as MixinStorage from '../generated-artifacts/MixinStorage.json'; import * as MixinStorage from '../generated-artifacts/MixinStorage.json';
import * as MixinTimeLockedStake from '../generated-artifacts/MixinTimeLockedStake.json';
import * as MixinVaultCore from '../generated-artifacts/MixinVaultCore.json'; import * as MixinVaultCore from '../generated-artifacts/MixinVaultCore.json';
import * as MixinZrxVault from '../generated-artifacts/MixinZrxVault.json'; import * as MixinZrxVault from '../generated-artifacts/MixinZrxVault.json';
import * as Staking from '../generated-artifacts/Staking.json'; import * as Staking from '../generated-artifacts/Staking.json';
@ -50,6 +51,7 @@ export const artifacts = {
MixinConstants: MixinConstants as ContractArtifact, MixinConstants: MixinConstants as ContractArtifact,
MixinDeploymentConstants: MixinDeploymentConstants as ContractArtifact, MixinDeploymentConstants: MixinDeploymentConstants as ContractArtifact,
MixinStorage: MixinStorage as ContractArtifact, MixinStorage: MixinStorage as ContractArtifact,
IEthVault: IEthVault as ContractArtifact,
IStaking: IStaking as ContractArtifact, IStaking: IStaking as ContractArtifact,
IStakingEvents: IStakingEvents as ContractArtifact, IStakingEvents: IStakingEvents as ContractArtifact,
IStakingPoolRewardVault: IStakingPoolRewardVault as ContractArtifact, IStakingPoolRewardVault: IStakingPoolRewardVault as ContractArtifact,
@ -61,19 +63,19 @@ export const artifacts = {
LibEIP712Hash: LibEIP712Hash as ContractArtifact, LibEIP712Hash: LibEIP712Hash as ContractArtifact,
LibFixedMath: LibFixedMath as ContractArtifact, LibFixedMath: LibFixedMath as ContractArtifact,
LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact, LibFixedMathRichErrors: LibFixedMathRichErrors as ContractArtifact,
LibRewardMath: LibRewardMath as ContractArtifact,
LibSafeDowncast: LibSafeDowncast as ContractArtifact, LibSafeDowncast: LibSafeDowncast as ContractArtifact,
LibSignatureValidator: LibSignatureValidator as ContractArtifact, LibSignatureValidator: LibSignatureValidator as ContractArtifact,
LibStakingRichErrors: LibStakingRichErrors as ContractArtifact, LibStakingRichErrors: LibStakingRichErrors as ContractArtifact,
MixinDelegatedStake: MixinDelegatedStake as ContractArtifact,
MixinStake: MixinStake as ContractArtifact, MixinStake: MixinStake as ContractArtifact,
MixinStakeBalances: MixinStakeBalances as ContractArtifact, MixinStakeBalances: MixinStakeBalances as ContractArtifact,
MixinTimeLockedStake: MixinTimeLockedStake as ContractArtifact, MixinStakeStorage: MixinStakeStorage as ContractArtifact,
MixinZrxVault: MixinZrxVault as ContractArtifact, MixinZrxVault: MixinZrxVault as ContractArtifact,
MixinEthVault: MixinEthVault as ContractArtifact,
MixinStakingPool: MixinStakingPool as ContractArtifact, MixinStakingPool: MixinStakingPool as ContractArtifact,
MixinStakingPoolRewardVault: MixinStakingPoolRewardVault as ContractArtifact, MixinStakingPoolRewardVault: MixinStakingPoolRewardVault as ContractArtifact,
MixinStakingPoolRewards: MixinStakingPoolRewards as ContractArtifact, MixinStakingPoolRewards: MixinStakingPoolRewards as ContractArtifact,
MixinScheduler: MixinScheduler as ContractArtifact, MixinScheduler: MixinScheduler as ContractArtifact,
EthVault: EthVault as ContractArtifact,
MixinVaultCore: MixinVaultCore as ContractArtifact, MixinVaultCore: MixinVaultCore as ContractArtifact,
StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact, StakingPoolRewardVault: StakingPoolRewardVault as ContractArtifact,
ZrxVault: ZrxVault as ContractArtifact, ZrxVault: ZrxVault as ContractArtifact,

View File

@ -3,6 +3,8 @@
* Warning: This file is auto-generated by contracts-gen. Don't edit manually. * Warning: This file is auto-generated by contracts-gen. Don't edit manually.
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
export * from '../generated-wrappers/eth_vault';
export * from '../generated-wrappers/i_eth_vault';
export * from '../generated-wrappers/i_staking'; export * from '../generated-wrappers/i_staking';
export * from '../generated-wrappers/i_staking_events'; export * from '../generated-wrappers/i_staking_events';
export * from '../generated-wrappers/i_staking_pool_reward_vault'; export * from '../generated-wrappers/i_staking_pool_reward_vault';
@ -14,23 +16,22 @@ export * from '../generated-wrappers/i_zrx_vault';
export * from '../generated-wrappers/lib_e_i_p712_hash'; export * from '../generated-wrappers/lib_e_i_p712_hash';
export * from '../generated-wrappers/lib_fixed_math'; export * from '../generated-wrappers/lib_fixed_math';
export * from '../generated-wrappers/lib_fixed_math_rich_errors'; export * from '../generated-wrappers/lib_fixed_math_rich_errors';
export * from '../generated-wrappers/lib_reward_math';
export * from '../generated-wrappers/lib_safe_downcast'; export * from '../generated-wrappers/lib_safe_downcast';
export * from '../generated-wrappers/lib_signature_validator'; export * from '../generated-wrappers/lib_signature_validator';
export * from '../generated-wrappers/lib_staking_rich_errors'; export * from '../generated-wrappers/lib_staking_rich_errors';
export * from '../generated-wrappers/mixin_constants'; export * from '../generated-wrappers/mixin_constants';
export * from '../generated-wrappers/mixin_delegated_stake';
export * from '../generated-wrappers/mixin_deployment_constants'; export * from '../generated-wrappers/mixin_deployment_constants';
export * from '../generated-wrappers/mixin_eth_vault';
export * from '../generated-wrappers/mixin_exchange_fees'; export * from '../generated-wrappers/mixin_exchange_fees';
export * from '../generated-wrappers/mixin_exchange_manager'; export * from '../generated-wrappers/mixin_exchange_manager';
export * from '../generated-wrappers/mixin_scheduler'; export * from '../generated-wrappers/mixin_scheduler';
export * from '../generated-wrappers/mixin_stake'; 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_staking_pool'; export * from '../generated-wrappers/mixin_staking_pool';
export * from '../generated-wrappers/mixin_staking_pool_reward_vault'; export * from '../generated-wrappers/mixin_staking_pool_reward_vault';
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_time_locked_stake';
export * from '../generated-wrappers/mixin_vault_core'; export * from '../generated-wrappers/mixin_vault_core';
export * from '../generated-wrappers/mixin_zrx_vault'; export * from '../generated-wrappers/mixin_zrx_vault';
export * from '../generated-wrappers/staking'; export * from '../generated-wrappers/staking';

View File

@ -8,6 +8,7 @@ import { DelegatorBalances, StakerBalances } from '../utils/types';
import { StakerActor } from './staker_actor'; import { StakerActor } from './staker_actor';
export class DelegatorActor extends StakerActor { export class DelegatorActor extends StakerActor {
/**
constructor(owner: string, stakingWrapper: StakingWrapper) { constructor(owner: string, stakingWrapper: StakingWrapper) {
super(owner, stakingWrapper); super(owner, stakingWrapper);
} }
@ -155,4 +156,5 @@ export class DelegatorActor extends StakerActor {
).to.be.bignumber.equal(expectedBalances.stakeDelegatedToPool[i]); ).to.be.bignumber.equal(expectedBalances.stakeDelegatedToPool[i]);
} }
} }
*/
} }

View File

@ -8,6 +8,7 @@ import { StakerBalances } from '../utils/types';
import { BaseActor } from './base_actor'; import { BaseActor } from './base_actor';
export class StakerActor extends BaseActor { export class StakerActor extends BaseActor {
/*
constructor(owner: string, stakingWrapper: StakingWrapper) { constructor(owner: string, stakingWrapper: StakingWrapper) {
super(owner, stakingWrapper); super(owner, stakingWrapper);
} }
@ -136,27 +137,10 @@ export class StakerActor extends BaseActor {
expectedBalances.deactivatedStakeBalance, expectedBalances.deactivatedStakeBalance,
); );
} }
public async forceTimeLockSyncAsync(): Promise<void> { public async forceBalanceSyncAsync(): Promise<void> {
const initBalances = await this.getBalancesAsync(); const initBalances = await this.getBalancesAsync();
await this._stakingWrapper.forceTimeLockSyncAsync(this._owner); await this._stakingWrapper.stakeAsync(this._owner, new BigNumber(0));
await this.assertBalancesAsync(initBalances); await this.assertBalancesAsync(initBalances);
} }
public async skipToNextTimeLockPeriodAsync(): Promise<void> { */
// query some initial values
const initBalances = await this.getBalancesAsync();
const timeLockStart = await this._stakingWrapper.getTimeLockStartAsync(this._owner);
// skip to next period
await this._stakingWrapper.skipToNextTimeLockPeriodAsync();
// validate new balances
const expectedBalances = initBalances;
const currentTimeLockPeriod = await this._stakingWrapper.getCurrentTimeLockPeriodAsync();
if (currentTimeLockPeriod.minus(timeLockStart).isGreaterThan(1)) {
expectedBalances.activatableStakeBalance = initBalances.activatableStakeBalance.plus(
initBalances.timeLockedStakeBalance,
);
expectedBalances.withdrawableStakeBalance = expectedBalances.activatableStakeBalance;
expectedBalances.timeLockedStakeBalance = new BigNumber(0);
}
await this.assertBalancesAsync(expectedBalances);
}
} }

View File

@ -53,39 +53,22 @@ describe('Epochs', () => {
}); });
describe('Epochs & TimeLocks', () => { describe('Epochs & TimeLocks', () => {
it('basic epochs & timeLock periods', async () => { it('basic epochs & timeLock periods', async () => {
///// 0/3 Validate Assumptions ///// ///// 1/3 Validate Assumptions /////
expect(await stakingWrapper.getEpochDurationInSecondsAsync()).to.be.bignumber.equal( expect(await stakingWrapper.getEpochDurationInSecondsAsync()).to.be.bignumber.equal(
stakingConstants.EPOCH_DURATION_IN_SECONDS, stakingConstants.EPOCH_DURATION_IN_SECONDS,
); );
expect(await stakingWrapper.getTimeLockDurationInEpochsAsync()).to.be.bignumber.equal( ///// 2/3 Validate Initial Epoch & TimeLock Period /////
stakingConstants.TIMELOCK_DURATION_IN_EPOCHS,
);
///// 1/3 Validate Initial Epoch & TimeLock Period /////
{ {
// epoch // epoch
const currentEpoch = await stakingWrapper.getCurrentEpochAsync(); const currentEpoch = await stakingWrapper.getCurrentEpochAsync();
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH); expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH);
// timeLock period
const currentTimeLockPeriod = await stakingWrapper.getCurrentTimeLockPeriodAsync();
expect(currentTimeLockPeriod).to.be.bignumber.equal(stakingConstants.INITIAL_TIMELOCK_PERIOD);
} }
///// 2/3 Increment Epoch (TimeLock Should Not Increment) ///// ///// 3/3 Increment Epoch (TimeLock Should Not Increment) /////
await stakingWrapper.skipToNextEpochAsync(); await stakingWrapper.skipToNextEpochAsync();
{ {
// epoch // epoch
const currentEpoch = await stakingWrapper.getCurrentEpochAsync(); const currentEpoch = await stakingWrapper.getCurrentEpochAsync();
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH.plus(1)); expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH.plus(1));
// timeLock period
const currentTimeLockPeriod = await stakingWrapper.getCurrentTimeLockPeriodAsync();
expect(currentTimeLockPeriod).to.be.bignumber.equal(stakingConstants.INITIAL_TIMELOCK_PERIOD);
}
///// 3/3 Increment Epoch (TimeLock Should Increment) /////
await stakingWrapper.skipToNextTimeLockPeriodAsync();
{
// timeLock period
const currentTimeLockPeriod = await stakingWrapper.getCurrentTimeLockPeriodAsync();
expect(currentTimeLockPeriod).to.be.bignumber.equal(stakingConstants.INITIAL_TIMELOCK_PERIOD.plus(1));
} }
}); });
}); });

View File

@ -9,6 +9,7 @@ import { Simulation } from './utils/Simulation';
import { StakingWrapper } from './utils/staking_wrapper'; import { StakingWrapper } from './utils/staking_wrapper';
// tslint:disable:no-unnecessary-type-assertion // tslint:disable:no-unnecessary-type-assertion
blockchainTests('End-To-End Simulations', env => { blockchainTests('End-To-End Simulations', env => {
/*
// constants // constants
const ZRX_TOKEN_DECIMALS = new BigNumber(18); const ZRX_TOKEN_DECIMALS = new BigNumber(18);
const PPM_ONE = 1e6; const PPM_ONE = 1e6;
@ -96,7 +97,7 @@ blockchainTests('End-To-End Simulations', env => {
it('Should successfully simulate (delegators withdraw by undelegating / no shadow balances)', async () => { it('Should successfully simulate (delegators withdraw by undelegating / no shadow balances)', async () => {
// @TODO - get computations more accurate // @TODO - get computations more accurate
/*
\ // the expected payouts were computed by hand \ // the expected payouts were computed by hand
// @TODO - get computations more accurate // @TODO - get computations more accurate
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Weighted) | Payout Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Weighted) | Payout
@ -107,7 +108,7 @@ blockchainTests('End-To-End Simulations', env => {
Cumulative Fees = 43.75043836 Cumulative Fees = 43.75043836
Cumulative Weighted Stake = 386.8 Cumulative Weighted Stake = 386.8
Total Rewards = 43.75043836 Total Rewards = 43.75043836
*/
const simulationParams = { const simulationParams = {
users, users,
numberOfPools: 3, numberOfPools: 3,
@ -175,7 +176,7 @@ blockchainTests('End-To-End Simulations', env => {
it('Should successfully simulate (delegators withdraw by undelegating / includes shadow balances / delegators enter after reward payouts)', async () => { it('Should successfully simulate (delegators withdraw by undelegating / includes shadow balances / delegators enter after reward payouts)', async () => {
// @TODO - get computations more accurate // @TODO - get computations more accurate
/*
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled) Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled)
0 | 0.304958 | 42 | 0 | 42 0 | 0.304958 | 42 | 0 | 42
1 | 15.323258 | 84 | 0 | 84 1 | 15.323258 | 84 | 0 | 84
@ -189,7 +190,7 @@ blockchainTests('End-To-End Simulations', env => {
// The first delegator got to claim it all. This is due to the necessary conservation of payouts. // The first delegator got to claim it all. This is due to the necessary conservation of payouts.
// When a new delegator arrives, their new stake should not affect existing delegator payouts. // When a new delegator arrives, their new stake should not affect existing delegator payouts.
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator. // In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
*/
const simulationParams = { const simulationParams = {
users, users,
numberOfPools: 3, numberOfPools: 3,
@ -250,7 +251,7 @@ blockchainTests('End-To-End Simulations', env => {
it('Should successfully simulate (delegators withdraw without undelegating / includes shadow balances / delegators enter after reward payouts)', async () => { it('Should successfully simulate (delegators withdraw without undelegating / includes shadow balances / delegators enter after reward payouts)', async () => {
// @TODO - get computations more accurate // @TODO - get computations more accurate
/*
Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled) Pool | Total Fees | Total Stake | Total Delegated Stake | Total Stake (Scaled)
0 | 0.304958 | 42 | 0 | 42 0 | 0.304958 | 42 | 0 | 42
1 | 15.323258 | 84 | 0 | 84 1 | 15.323258 | 84 | 0 | 84
@ -264,7 +265,7 @@ blockchainTests('End-To-End Simulations', env => {
// The first delegator got to claim it all. This is due to the necessary conservation of payouts. // The first delegator got to claim it all. This is due to the necessary conservation of payouts.
// When a new delegator arrives, their new stake should not affect existing delegator payouts. // When a new delegator arrives, their new stake should not affect existing delegator payouts.
// In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator. // In this case, there was unclaimed $$ in the delegator pool - which is claimed by the first delegator.
*/
const simulationParams = { const simulationParams = {
users, users,
numberOfPools: 3, numberOfPools: 3,
@ -332,5 +333,6 @@ blockchainTests('End-To-End Simulations', env => {
await expect(tx).to.revertWith(revertError); await expect(tx).to.revertWith(revertError);
}); });
}); });
*/
}); });
// tslint:enable:no-unnecessary-type-assertion // tslint:enable:no-unnecessary-type-assertion

View File

@ -39,60 +39,9 @@ blockchainTests('Staking & Delegating', env => {
await stakingWrapper.deployAndConfigureContractsAsync(); await stakingWrapper.deployAndConfigureContractsAsync();
}); });
blockchainTests.resets('Staking', () => { blockchainTests.resets('Staking', () => {
it('basic staking/unstaking', async () => {
// setup test parameters
const amountToStake = StakingWrapper.toBaseUnitAmount(10);
const amountToDeactivate = StakingWrapper.toBaseUnitAmount(4);
const amountToReactivate = StakingWrapper.toBaseUnitAmount(1);
const amountToWithdraw = StakingWrapper.toBaseUnitAmount(1.5);
// run test - this actor will validate its own state
const staker = new StakerActor(stakers[0], stakingWrapper);
await staker.depositZrxAndMintActivatedStakeAsync(amountToStake);
await staker.deactivateAndTimeLockStakeAsync(amountToDeactivate);
// note - we cannot re-activate this timeLocked stake until at least one full timeLock period has passed.
// attempting to do so should revert.
const revertError = new StakingRevertErrors.InsufficientBalanceError(amountToReactivate, 0);
await staker.activateStakeAsync(amountToReactivate, revertError);
await staker.skipToNextTimeLockPeriodAsync();
await staker.activateStakeAsync(amountToReactivate, revertError);
await staker.skipToNextTimeLockPeriodAsync();
// this forces the internal state to update; it is not necessary to activate stake, but
// allows us to check that state is updated correctly after a timeLock period rolls over.
await staker.forceTimeLockSyncAsync();
// now we can activate stake
await staker.activateStakeAsync(amountToReactivate);
await staker.burnDeactivatedStakeAndWithdrawZrxAsync(amountToWithdraw);
});
}); });
blockchainTests.resets('Delegating', () => { blockchainTests.resets('Delegating', () => {
it('basic delegating/undelegating', async () => {
// setup test parameters
const amountToDelegate = StakingWrapper.toBaseUnitAmount(10);
const amountToDeactivate = StakingWrapper.toBaseUnitAmount(4);
const amountToReactivate = StakingWrapper.toBaseUnitAmount(1);
const amountToWithdraw = StakingWrapper.toBaseUnitAmount(1.5);
const poolOperator = stakers[1];
const operatorShare = 39;
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
// run test
const delegator = new DelegatorActor(stakers[0], stakingWrapper);
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amountToDelegate);
await delegator.deactivateAndTimeLockDelegatedStakeAsync(poolId, amountToDeactivate);
// note - we cannot re-activate this timeLocked stake until at least one full timeLock period has passed.
// attempting to do so should revert.
const revertError = new StakingRevertErrors.InsufficientBalanceError(amountToReactivate, 0);
await delegator.activateStakeAsync(amountToReactivate, revertError);
await delegator.skipToNextTimeLockPeriodAsync();
await delegator.activateStakeAsync(amountToReactivate, revertError);
await delegator.skipToNextTimeLockPeriodAsync();
// this forces the internal state to update; it is not necessary to activate stake, but
// allows us to check that state is updated correctly after a timeLock period rolls over.
await delegator.forceTimeLockSyncAsync();
// now we can activate stake
await delegator.activateAndDelegateStakeAsync(poolId, amountToReactivate);
await delegator.burnDeactivatedStakeAndWithdrawZrxAsync(amountToWithdraw);
});
}); });
}); });
// tslint:enable:no-unnecessary-type-assertion // tslint:enable:no-unnecessary-type-assertion

View File

@ -13,6 +13,9 @@ import { SimulationParams } from './types';
const REWARD_PRECISION = 12; const REWARD_PRECISION = 12;
export class Simulation { export class Simulation {
/*
private readonly _stakingWrapper: StakingWrapper; private readonly _stakingWrapper: StakingWrapper;
private readonly _p: SimulationParams; private readonly _p: SimulationParams;
private _userQueue: Queue<string>; private _userQueue: Queue<string>;
@ -272,4 +275,5 @@ export class Simulation {
Simulation._assertRewardsEqual(reward, expectedReward, `reward withdrawn from pool ${poolId} for operator`); Simulation._assertRewardsEqual(reward, expectedReward, `reward withdrawn from pool ${poolId} for operator`);
} }
} }
*/
} }

View File

@ -1,3 +1,4 @@
import { BaseContract } from '@0x/base-contract';
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy'; import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils'; import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils';
@ -13,11 +14,12 @@ import {
StakingPoolRewardVaultContract, StakingPoolRewardVaultContract,
StakingProxyContract, StakingProxyContract,
ZrxVaultContract, ZrxVaultContract,
EthVaultContract,
} from '../../src'; } from '../../src';
import { ApprovalFactory } from './approval_factory'; import { ApprovalFactory } from './approval_factory';
import { constants } from './constants'; import { constants } from './constants';
import { SignedStakingPoolApproval } from './types'; import { SignedStakingPoolApproval, StakeBalance } from './types';
export class StakingWrapper { export class StakingWrapper {
private readonly _web3Wrapper: Web3Wrapper; private readonly _web3Wrapper: Web3Wrapper;
@ -30,6 +32,7 @@ export class StakingWrapper {
private _stakingContractIfExists?: StakingContract; private _stakingContractIfExists?: StakingContract;
private _stakingProxyContractIfExists?: StakingProxyContract; private _stakingProxyContractIfExists?: StakingProxyContract;
private _zrxVaultContractIfExists?: ZrxVaultContract; private _zrxVaultContractIfExists?: ZrxVaultContract;
private _ethVaultContractIfExists?: EthVaultContract;
private _rewardVaultContractIfExists?: StakingPoolRewardVaultContract; private _rewardVaultContractIfExists?: StakingPoolRewardVaultContract;
public static toBaseUnitAmount(amount: BigNumber | number): BigNumber { public static toBaseUnitAmount(amount: BigNumber | number): BigNumber {
const decimals = 18; const decimals = 18;
@ -87,6 +90,10 @@ export class StakingWrapper {
this._validateDeployedOrThrow(); this._validateDeployedOrThrow();
return this._zrxVaultContractIfExists as ZrxVaultContract; return this._zrxVaultContractIfExists as ZrxVaultContract;
} }
public getEthVaultContract(): EthVaultContract {
this._validateDeployedOrThrow();
return this._ethVaultContractIfExists as EthVaultContract;
}
public getStakingPoolRewardVaultContract(): StakingPoolRewardVaultContract { public getStakingPoolRewardVaultContract(): StakingPoolRewardVaultContract {
this._validateDeployedOrThrow(); this._validateDeployedOrThrow();
return this._rewardVaultContractIfExists as StakingPoolRewardVaultContract; return this._rewardVaultContractIfExists as StakingPoolRewardVaultContract;
@ -101,6 +108,13 @@ export class StakingWrapper {
this._erc20ProxyContract.address, this._erc20ProxyContract.address,
this._zrxTokenContract.address, this._zrxTokenContract.address,
); );
// deploy eth vault
this._ethVaultContractIfExists = await EthVaultContract.deployFrom0xArtifactAsync(
artifacts.EthVault,
this._provider,
txDefaults,
artifacts,
);
// deploy reward vault // deploy reward vault
this._rewardVaultContractIfExists = await StakingPoolRewardVaultContract.deployFrom0xArtifactAsync( this._rewardVaultContractIfExists = await StakingPoolRewardVaultContract.deployFrom0xArtifactAsync(
artifacts.StakingPoolRewardVault, artifacts.StakingPoolRewardVault,
@ -108,6 +122,8 @@ export class StakingWrapper {
txDefaults, txDefaults,
artifacts, artifacts,
); );
// set eth vault in reward vault
await this._rewardVaultContractIfExists.setEthVault.sendTransactionAsync(this._ethVaultContractIfExists.address);
// configure erc20 proxy to accept calls from zrx vault // configure erc20 proxy to accept calls from zrx vault
await this._erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync( await this._erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
this._zrxVaultContractIfExists.address, this._zrxVaultContractIfExists.address,
@ -165,88 +181,48 @@ export class StakingWrapper {
return balance; return balance;
} }
///// STAKE ///// ///// STAKE /////
public async depositZrxAndMintDeactivatedStakeAsync( public async stakeAsync(
owner: string, owner: string,
amount: BigNumber, amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().depositZrxAndMintDeactivatedStake.getABIEncodedTransactionData( const calldata = this.getStakingContract().stake.getABIEncodedTransactionData(
amount, amount,
); );
const txReceipt = await this._executeTransactionAsync(calldata, owner); const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt; return txReceipt;
} }
public async depositZrxAndMintActivatedStakeAsync( public async unstakeAsync(
owner: string, owner: string,
amount: BigNumber, amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().depositZrxAndMintActivatedStake.getABIEncodedTransactionData(amount); const calldata = this.getStakingContract().unstake.getABIEncodedTransactionData(
const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt;
}
public async depositZrxAndDelegateToStakingPoolAsync(
owner: string,
poolId: string,
amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().depositZrxAndDelegateToStakingPool.getABIEncodedTransactionData(
poolId,
amount,
);
const txReceipt = await this._executeTransactionAsync(calldata, owner, new BigNumber(0), true);
return txReceipt;
}
public async activateStakeAsync(owner: string, amount: BigNumber): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().activateStake.getABIEncodedTransactionData(amount);
const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt;
}
public async activateAndDelegateStakeAsync(
owner: string,
poolId: string,
amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().activateAndDelegateStake.getABIEncodedTransactionData(
poolId,
amount, amount,
); );
const txReceipt = await this._executeTransactionAsync(calldata, owner); const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt; return txReceipt;
} }
public async deactivateAndTimeLockStakeAsync( public async moveStakeAsync(
owner: string, owner: string,
fromState: {
state: number,
poolId?: string
},
toState: {
state: number,
poolId?: string
},
amount: BigNumber, amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().deactivateAndTimeLockStake.getABIEncodedTransactionData(amount); fromState.poolId = fromState.poolId !== undefined ? fromState.poolId : constants.NIL_POOL_ID;
const txReceipt = await this._executeTransactionAsync(calldata, owner); toState.poolId = fromState.poolId !== undefined ? toState.poolId : constants.NIL_POOL_ID;
return txReceipt; const calldata = this.getStakingContract().moveStake.getABIEncodedTransactionData(
} fromState as any,
public async deactivateAndTimeLockDelegatedStakeAsync( toState as any,
owner: string,
poolId: string,
amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().deactivateAndTimeLockDelegatedStake.getABIEncodedTransactionData(
poolId,
amount,
);
const txReceipt = await this._executeTransactionAsync(calldata, owner, new BigNumber(0), true);
return txReceipt;
}
public async burnDeactivatedStakeAndWithdrawZrxAsync(
owner: string,
amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().burnDeactivatedStakeAndWithdrawZrx.getABIEncodedTransactionData(
amount, amount,
); );
const txReceipt = await this._executeTransactionAsync(calldata, owner); const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt; return txReceipt;
} }
public async forceTimeLockSyncAsync(owner: string): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().forceTimeLockSync.getABIEncodedTransactionData(owner);
const txReceipt = await this._executeTransactionAsync(calldata, this._ownerAddress);
return txReceipt;
}
///// STAKE BALANCES ///// ///// STAKE BALANCES /////
public async getTotalStakeAsync(owner: string): Promise<BigNumber> { public async getTotalStakeAsync(owner: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getTotalStake.getABIEncodedTransactionData(owner); const calldata = this.getStakingContract().getTotalStake.getABIEncodedTransactionData(owner);
@ -254,22 +230,16 @@ export class StakingWrapper {
const value = this.getStakingContract().getTotalStake.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getTotalStake.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getActivatedStakeAsync(owner: string): Promise<BigNumber> { public async getActiveStakeAsync(owner: string): Promise<StakeBalance> {
const calldata = this.getStakingContract().getActivatedStake.getABIEncodedTransactionData(owner); const calldata = this.getStakingContract().getActiveStake.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getActivatedStake.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getActiveStake.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getDeactivatedStakeAsync(owner: string): Promise<BigNumber> { public async getInactiveStakeAsync(owner: string): Promise<StakeBalance> {
const calldata = this.getStakingContract().getDeactivatedStake.getABIEncodedTransactionData(owner); const calldata = this.getStakingContract().getInactiveStake.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getDeactivatedStake.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getInactiveStake.getABIDecodedReturnData(returnData);
return value;
}
public async getActivatableStakeAsync(owner: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getActivatableStake.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getActivatableStake.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getWithdrawableStakeAsync(owner: string): Promise<BigNumber> { public async getWithdrawableStakeAsync(owner: string): Promise<BigNumber> {
@ -278,25 +248,13 @@ export class StakingWrapper {
const value = this.getStakingContract().getWithdrawableStake.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getWithdrawableStake.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getTimeLockedStakeAsync(owner: string): Promise<BigNumber> { public async getStakeDelegatedByOwnerAsync(owner: string): Promise<StakeBalance> {
const calldata = this.getStakingContract().getTimeLockedStake.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTimeLockedStake.getABIDecodedReturnData(returnData);
return value;
}
public async getTimeLockStartAsync(owner: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getTimeLockStart.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTimeLockStart.getABIDecodedReturnData(returnData);
return value;
}
public async getStakeDelegatedByOwnerAsync(owner: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getStakeDelegatedByOwner.getABIEncodedTransactionData(owner); const calldata = this.getStakingContract().getStakeDelegatedByOwner.getABIEncodedTransactionData(owner);
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getStakeDelegatedByOwner.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getStakeDelegatedByOwner.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getStakeDelegatedToPoolByOwnerAsync(poolId: string, owner: string): Promise<BigNumber> { public async getStakeDelegatedToPoolByOwnerAsync(poolId: string, owner: string): Promise<StakeBalance> {
const calldata = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIEncodedTransactionData( const calldata = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIEncodedTransactionData(
owner, owner,
poolId, poolId,
@ -305,7 +263,7 @@ export class StakingWrapper {
const value = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getTotalStakeDelegatedToPoolAsync(poolId: string): Promise<BigNumber> { public async getTotalStakeDelegatedToPoolAsync(poolId: string): Promise<StakeBalance> {
const calldata = this.getStakingContract().getTotalStakeDelegatedToPool.getABIEncodedTransactionData(poolId); const calldata = this.getStakingContract().getTotalStakeDelegatedToPool.getABIEncodedTransactionData(poolId);
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTotalStakeDelegatedToPool.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getTotalStakeDelegatedToPool.getABIDecodedReturnData(returnData);
@ -406,56 +364,51 @@ export class StakingWrapper {
return signedStakingPoolApproval; return signedStakingPoolApproval;
} }
///// EPOCHS ///// ///// EPOCHS /////
/*
public async testFinalizefees(rewards: {reward: BigNumber, poolId: string}[]): Promise<TransactionReceiptWithDecodedLogs> {
await this.fastForwardToNextEpochAsync();
const totalRewards = _.sumBy(rewards, (v: any) => {return v.reward.toNumber();});
const calldata = this.getStakingContract().testFinalizeFees.getABIEncodedTransactionData(rewards);
const txReceipt = await this._executeTransactionAsync(calldata, undefined, new BigNumber(totalRewards), true);
return txReceipt;
}
*/
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> { public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData(); const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData();
const txReceipt = await this._executeTransactionAsync(calldata, undefined, new BigNumber(0), true); const txReceipt = await this._executeTransactionAsync(calldata, undefined, new BigNumber(0), true);
logUtils.log(`Finalization costed ${txReceipt.gasUsed} gas`); logUtils.log(`Finalization costed ${txReceipt.gasUsed} gas`);
return txReceipt; return txReceipt;
} }
public async skipToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> { public async fastForwardToNextEpochAsync(): Promise<void> {
// increase timestamp of next block // increase timestamp of next block
const epochDurationInSeconds = await this.getEpochDurationInSecondsAsync(); const epochDurationInSeconds = await this.getEpochDurationInSecondsAsync();
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber()); await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
// mine next block // mine next block
await this._web3Wrapper.mineBlockAsync(); await this._web3Wrapper.mineBlockAsync();
}
public async skipToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
await this.fastForwardToNextEpochAsync();
// increment epoch in contracts // increment epoch in contracts
const txReceipt = await this.goToNextEpochAsync(); const txReceipt = await this.goToNextEpochAsync();
await this._web3Wrapper.mineBlockAsync(); await this._web3Wrapper.mineBlockAsync();
return txReceipt; return txReceipt;
} }
public async skipToNextTimeLockPeriodAsync(): Promise<void> {
const timeLockEndEpoch = await this.getCurrentTimeLockPeriodEndEpochAsync();
const currentEpoch = await this.getCurrentEpochAsync();
const nEpochsToJump = timeLockEndEpoch.minus(currentEpoch);
const nEpochsToJumpAsNumber = nEpochsToJump.toNumber();
for (let i = 0; i < nEpochsToJumpAsNumber; ++i) {
await this.skipToNextEpochAsync();
}
}
public async getEpochDurationInSecondsAsync(): Promise<BigNumber> { public async getEpochDurationInSecondsAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getEpochDurationInSeconds.getABIEncodedTransactionData(); const calldata = this.getStakingContract().getEpochDurationInSeconds.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getEpochDurationInSeconds.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getEpochDurationInSeconds.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getTimeLockDurationInEpochsAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getTimeLockDurationInEpochs.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTimeLockDurationInEpochs.getABIDecodedReturnData(returnData);
return value;
}
public async getCurrentEpochStartTimeInSecondsAsync(): Promise<BigNumber> { public async getCurrentEpochStartTimeInSecondsAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIEncodedTransactionData(); const calldata = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getCurrentTimeLockPeriodStartEpochAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentTimeLockPeriodStartEpoch.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getCurrentTimeLockPeriodStartEpoch.getABIDecodedReturnData(returnData);
return value;
}
public async getCurrentEpochEarliestEndTimeInSecondsAsync(): Promise<BigNumber> { public async getCurrentEpochEarliestEndTimeInSecondsAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentEpochEarliestEndTimeInSeconds.getABIEncodedTransactionData(); const calldata = this.getStakingContract().getCurrentEpochEarliestEndTimeInSeconds.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
@ -464,24 +417,12 @@ export class StakingWrapper {
); );
return value; return value;
} }
public async getCurrentTimeLockPeriodEndEpochAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentTimeLockPeriodEndEpoch.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getCurrentTimeLockPeriodEndEpoch.getABIDecodedReturnData(returnData);
return value;
}
public async getCurrentEpochAsync(): Promise<BigNumber> { public async getCurrentEpochAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentEpoch.getABIEncodedTransactionData(); const calldata = this.getStakingContract().getCurrentEpoch.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getCurrentEpoch.getABIDecodedReturnData(returnData); const value = this.getStakingContract().getCurrentEpoch.getABIDecodedReturnData(returnData);
return value; return value;
} }
public async getCurrentTimeLockPeriodAsync(): Promise<BigNumber> {
const calldata = this.getStakingContract().getCurrentTimeLockPeriod.getABIEncodedTransactionData();
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getCurrentTimeLockPeriod.getABIDecodedReturnData(returnData);
return value;
}
///// PROTOCOL FEES ///// ///// PROTOCOL FEES /////
public async payProtocolFeeAsync( public async payProtocolFeeAsync(
makerAddress: string, makerAddress: string,
@ -536,116 +477,18 @@ export class StakingWrapper {
return txReceipt; return txReceipt;
} }
///// REWARDS ///// ///// REWARDS /////
public async getTotalRewardBalanceOfStakingPoolAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getTotalRewardBalanceOfStakingPool.getABIEncodedTransactionData(
poolId,
);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTotalRewardBalanceOfStakingPool.getABIDecodedReturnData(returnData);
return value;
}
public async getRewardBalanceOfStakingPoolOperatorAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getRewardBalanceOfStakingPoolOperator.getABIEncodedTransactionData(
poolId,
);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getRewardBalanceOfStakingPoolOperator.getABIDecodedReturnData(
returnData,
);
return value;
}
public async getRewardBalanceOfStakingPoolMembersAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getRewardBalanceOfStakingPoolMembers.getABIEncodedTransactionData(
poolId,
);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getRewardBalanceOfStakingPoolMembers.getABIDecodedReturnData(
returnData,
);
return value;
}
public async computeRewardBalanceOfStakingPoolMemberAsync(poolId: string, owner: string): Promise<BigNumber> { public async computeRewardBalanceOfStakingPoolMemberAsync(poolId: string, owner: string): Promise<BigNumber> {
const calldata = this.getStakingContract().computeRewardBalanceOfStakingPoolMember.getABIEncodedTransactionData( const calldata = this.getStakingContract().computeRewardBalanceOfDelegator.getABIEncodedTransactionData(
poolId, poolId,
owner, owner,
); );
const returnData = await this._callAsync(calldata); const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().computeRewardBalanceOfStakingPoolMember.getABIDecodedReturnData( const value = this.getStakingContract().computeRewardBalanceOfDelegator.getABIDecodedReturnData(
returnData, returnData,
); );
return value; return value;
} }
public async getTotalShadowBalanceOfStakingPoolAsync(poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getTotalShadowBalanceOfStakingPool.getABIEncodedTransactionData(
poolId,
);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getTotalShadowBalanceOfStakingPool.getABIDecodedReturnData(returnData);
return value;
}
public async getShadowBalanceOfStakingPoolMemberAsync(owner: string, poolId: string): Promise<BigNumber> {
const calldata = this.getStakingContract().getShadowBalanceOfStakingPoolMember.getABIEncodedTransactionData(
owner,
poolId,
);
const returnData = await this._callAsync(calldata);
const value = this.getStakingContract().getShadowBalanceOfStakingPoolMember.getABIDecodedReturnData(returnData);
return value;
}
public async withdrawRewardForStakingPoolOperatorAsync(
poolId: string,
amount: BigNumber,
operatorAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().withdrawRewardForStakingPoolOperator.getABIEncodedTransactionData(
poolId,
amount,
);
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
return txReceipt;
}
public async withdrawRewardForStakingPoolMemberAsync(
poolId: string,
amount: BigNumber,
owner: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().withdrawRewardForStakingPoolMember.getABIEncodedTransactionData(
poolId,
amount,
);
const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt;
}
public async withdrawTotalRewardForStakingPoolOperatorAsync(
poolId: string,
operatorAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().withdrawTotalRewardForStakingPoolOperator.getABIEncodedTransactionData(
poolId,
);
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
return txReceipt;
}
public async withdrawTotalRewardForStakingPoolMemberAsync(
poolId: string,
owner: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().withdrawTotalRewardForStakingPoolMember.getABIEncodedTransactionData(
poolId,
);
const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt;
}
///// REWARD VAULT ///// ///// REWARD VAULT /////
public async rewardVaultDepositForAsync(
poolId: string,
amount: BigNumber,
stakingContractAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingPoolRewardVaultContract().depositFor.getABIEncodedTransactionData(poolId);
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress, amount);
return txReceipt;
}
public async rewardVaultEnterCatastrophicFailureModeAsync( public async rewardVaultEnterCatastrophicFailureModeAsync(
zeroExMultisigAddress: string, zeroExMultisigAddress: string,
): Promise<TransactionReceiptWithDecodedLogs> { ): Promise<TransactionReceiptWithDecodedLogs> {

View File

@ -48,3 +48,14 @@ export interface SimulationParams {
delegateInNextEpoch: boolean; delegateInNextEpoch: boolean;
withdrawByUndelegating: boolean; withdrawByUndelegating: boolean;
} }
export interface StakeBalance {
current: BigNumber,
next: BigNumber,
}
export enum StakeStateId {
ACTIVE,
INACTIVE,
DELEGATED
};

View File

@ -3,6 +3,8 @@
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [ "files": [
"generated-artifacts/EthVault.json",
"generated-artifacts/IEthVault.json",
"generated-artifacts/IStaking.json", "generated-artifacts/IStaking.json",
"generated-artifacts/IStakingEvents.json", "generated-artifacts/IStakingEvents.json",
"generated-artifacts/IStakingPoolRewardVault.json", "generated-artifacts/IStakingPoolRewardVault.json",
@ -14,23 +16,22 @@
"generated-artifacts/LibEIP712Hash.json", "generated-artifacts/LibEIP712Hash.json",
"generated-artifacts/LibFixedMath.json", "generated-artifacts/LibFixedMath.json",
"generated-artifacts/LibFixedMathRichErrors.json", "generated-artifacts/LibFixedMathRichErrors.json",
"generated-artifacts/LibRewardMath.json",
"generated-artifacts/LibSafeDowncast.json", "generated-artifacts/LibSafeDowncast.json",
"generated-artifacts/LibSignatureValidator.json", "generated-artifacts/LibSignatureValidator.json",
"generated-artifacts/LibStakingRichErrors.json", "generated-artifacts/LibStakingRichErrors.json",
"generated-artifacts/MixinConstants.json", "generated-artifacts/MixinConstants.json",
"generated-artifacts/MixinDelegatedStake.json",
"generated-artifacts/MixinDeploymentConstants.json", "generated-artifacts/MixinDeploymentConstants.json",
"generated-artifacts/MixinEthVault.json",
"generated-artifacts/MixinExchangeFees.json", "generated-artifacts/MixinExchangeFees.json",
"generated-artifacts/MixinExchangeManager.json", "generated-artifacts/MixinExchangeManager.json",
"generated-artifacts/MixinScheduler.json", "generated-artifacts/MixinScheduler.json",
"generated-artifacts/MixinStake.json", "generated-artifacts/MixinStake.json",
"generated-artifacts/MixinStakeBalances.json", "generated-artifacts/MixinStakeBalances.json",
"generated-artifacts/MixinStakeStorage.json",
"generated-artifacts/MixinStakingPool.json", "generated-artifacts/MixinStakingPool.json",
"generated-artifacts/MixinStakingPoolRewardVault.json", "generated-artifacts/MixinStakingPoolRewardVault.json",
"generated-artifacts/MixinStakingPoolRewards.json", "generated-artifacts/MixinStakingPoolRewards.json",
"generated-artifacts/MixinStorage.json", "generated-artifacts/MixinStorage.json",
"generated-artifacts/MixinTimeLockedStake.json",
"generated-artifacts/MixinVaultCore.json", "generated-artifacts/MixinVaultCore.json",
"generated-artifacts/MixinZrxVault.json", "generated-artifacts/MixinZrxVault.json",
"generated-artifacts/Staking.json", "generated-artifacts/Staking.json",

View File

@ -87,4 +87,62 @@ library LibSafeMath {
{ {
return a < b ? a : b; return a < b ? a : b;
} }
/// @dev Safely adds two fractions `n1/d1 + n2/d2`
/// @param n1 numerator of `1`
/// @param d1 denominator of `1`
/// @param n2 numerator of `2`
/// @param d2 denominator of `2`
/// @return numerator of sum
/// @return denominator of sum
function addFractions(
uint256 n1,
uint256 d1,
uint256 n2,
uint256 d2
)
internal
pure
returns (
uint256 numerator,
uint256 denominator
)
{
numerator = safeAdd(
safeMul(n1, d2),
safeMul(n2, d1)
);
denominator = safeMul(d1, d2);
return (numerator, denominator);
}
/// @dev Safely scales the difference two fractions.
/// @param n1 numerator of `1`
/// @param d1 denominator of `1`
/// @param n2 numerator of `2`
/// @param d2 denominator of `2`
/// @param s scalar to multiply by difference.
/// @return result = `s * (n1/d1 - n2/d2)`.
function scaleFractionalDifference(
uint256 n1,
uint256 d1,
uint256 n2,
uint256 d2,
uint256 s
)
internal
pure
returns (uint256)
{
uint256 numerator = safeSub(
safeMul(n1, d2),
safeMul(n2, d1)
);
uint256 tmp = safeDiv(numerator, d2);
uint256 result = safeDiv(
safeMul(s, tmp),
d1
);
return result;
}
} }