New staking mechanics: delay on entry opposed to exit
This commit is contained in:
parent
88e56356c4
commit
eb6ad7d29d
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -32,6 +32,7 @@ import "../immutable/MixinStorage.sol";
|
|||||||
contract MixinExchangeManager is
|
contract MixinExchangeManager is
|
||||||
IStakingEvents,
|
IStakingEvents,
|
||||||
MixinDeploymentConstants,
|
MixinDeploymentConstants,
|
||||||
|
Ownable,
|
||||||
MixinConstants,
|
MixinConstants,
|
||||||
MixinStorage
|
MixinStorage
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
contracts/staking/contracts/src/interfaces/IEthVault.sol
Normal file
71
contracts/staking/contracts/src/interfaces/IEthVault.sol
Normal 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);
|
||||||
|
}
|
@ -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(
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
196
contracts/staking/contracts/src/stake/MixinStakeStorage.sol
Normal file
196
contracts/staking/contracts/src/stake/MixinStakeStorage.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
110
contracts/staking/contracts/src/vaults/EthVault.sol
Normal file
110
contracts/staking/contracts/src/vaults/EthVault.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
|
};
|
||||||
|
@ -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",
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user