Merge pull request #2248 from 0xProject/fix/staking/cleanup-accounting
Simplify staking state
This commit is contained in:
@@ -52,6 +52,24 @@ contract ZrxVault is
|
||||
// Asset data for the ERC20 Proxy
|
||||
bytes internal _zrxAssetData;
|
||||
|
||||
/// @dev Only stakingProxy can call this function.
|
||||
modifier onlyStakingProxy() {
|
||||
_assertSenderIsStakingProxy();
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Function can only be called in catastrophic failure mode.
|
||||
modifier onlyInCatastrophicFailure() {
|
||||
_assertInCatastrophicFailure();
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Function can only be called not in catastropic failure mode
|
||||
modifier onlyNotInCatastrophicFailure() {
|
||||
_assertNotInCatastrophicFailure();
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Constructor.
|
||||
/// @param _zrxProxyAddress Address of the 0x Zrx Proxy.
|
||||
/// @param _zrxTokenAddress Address of the Zrx Token.
|
||||
@@ -169,6 +187,15 @@ contract ZrxVault is
|
||||
return _balances[staker];
|
||||
}
|
||||
|
||||
/// @dev Returns the entire balance of Zrx tokens in the vault.
|
||||
function balanceOfZrxVault()
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return _zrxToken.balanceOf(address(this));
|
||||
}
|
||||
|
||||
/// @dev Withdraw an `amount` of Zrx Tokens to `staker` from the vault.
|
||||
/// @param staker of Zrx Tokens.
|
||||
/// @param amount of Zrx Tokens to withdraw.
|
||||
@@ -190,21 +217,7 @@ contract ZrxVault is
|
||||
);
|
||||
}
|
||||
|
||||
modifier onlyStakingProxy() {
|
||||
_assertSenderIsStakingProxy();
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyInCatastrophicFailure() {
|
||||
_assertInCatastrophicFailure();
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyNotInCatastrophicFailure() {
|
||||
_assertNotInCatastrophicFailure();
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Asserts that sender is stakingProxy contract.
|
||||
function _assertSenderIsStakingProxy()
|
||||
private
|
||||
view
|
||||
@@ -216,6 +229,7 @@ contract ZrxVault is
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Asserts that vault is in catastrophic failure mode.
|
||||
function _assertInCatastrophicFailure()
|
||||
private
|
||||
view
|
||||
@@ -225,6 +239,7 @@ contract ZrxVault is
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Asserts that vault is not in catastrophic failure mode.
|
||||
function _assertNotInCatastrophicFailure()
|
||||
private
|
||||
view
|
||||
|
@@ -41,21 +41,15 @@ contract MixinStorage is
|
||||
// address for read-only proxy to call
|
||||
address public readOnlyProxyCallee;
|
||||
|
||||
// mapping from StakeStatus to the total amount of stake in that status for the entire
|
||||
// staking system.
|
||||
mapping (uint8 => IStructs.StoredBalance) public globalStakeByStatus;
|
||||
|
||||
// mapping from Owner to Amount of Active Stake
|
||||
// mapping from StakeStatus to gloabl stored balance
|
||||
// (access using _loadSyncedBalance or _loadUnsyncedBalance)
|
||||
mapping (address => IStructs.StoredBalance) internal _activeStakeByOwner;
|
||||
// NOTE: only Status.DELEGATED is used to access this mapping, but this format
|
||||
// is used for extensibility
|
||||
mapping (uint8 => IStructs.StoredBalance) internal _globalStakeByStatus;
|
||||
|
||||
// Mapping from Owner to Amount of Inactive Stake
|
||||
// mapping from StakeStatus to address of staker to stored balance
|
||||
// (access using _loadSyncedBalance or _loadUnsyncedBalance)
|
||||
mapping (address => IStructs.StoredBalance) internal _inactiveStakeByOwner;
|
||||
|
||||
// Mapping from Owner to Amount Delegated
|
||||
// (access using _loadSyncedBalance or _loadUnsyncedBalance)
|
||||
mapping (address => IStructs.StoredBalance) internal _delegatedStakeByOwner;
|
||||
mapping (uint8 => mapping (address => IStructs.StoredBalance)) internal _ownerStakeByStatus;
|
||||
|
||||
// Mapping from Owner to Pool Id to Amount Delegated
|
||||
// (access using _loadSyncedBalance or _loadUnsyncedBalance)
|
||||
@@ -65,9 +59,6 @@ contract MixinStorage is
|
||||
// (access using _loadSyncedBalance or _loadUnsyncedBalance)
|
||||
mapping (bytes32 => IStructs.StoredBalance) internal _delegatedStakeByPoolId;
|
||||
|
||||
// mapping from Owner to Amount of Withdrawable Stake
|
||||
mapping (address => uint256) internal _withdrawableStakeByOwner;
|
||||
|
||||
// tracking Pool Id, a unique identifier for each staking pool.
|
||||
bytes32 public lastPoolId;
|
||||
|
||||
|
@@ -24,7 +24,8 @@ import "./IStructs.sol";
|
||||
|
||||
interface IStaking {
|
||||
|
||||
/// @dev Moves stake between statuses: 'active', 'inactive' or 'delegated'.
|
||||
/// @dev Moves stake between statuses: 'undelegated' or 'delegated'.
|
||||
/// Delegated stake can also be moved between pools.
|
||||
/// This change comes into effect next epoch.
|
||||
/// @param from status to move stake out of.
|
||||
/// @param to status to move stake into.
|
||||
|
@@ -53,29 +53,20 @@ interface IStructs {
|
||||
/// Note that these balances may be stale if the current epoch
|
||||
/// is greater than `currentEpoch`.
|
||||
/// Always load this struct using _loadSyncedBalance or _loadUnsyncedBalance.
|
||||
/// @param isInitialized
|
||||
/// @param currentEpoch the current epoch
|
||||
/// @param currentEpochBalance balance in the current epoch.
|
||||
/// @param nextEpochBalance balance in `currentEpoch+1`.
|
||||
struct StoredBalance {
|
||||
bool isInitialized;
|
||||
uint32 currentEpoch;
|
||||
uint64 currentEpoch;
|
||||
uint96 currentEpochBalance;
|
||||
uint96 nextEpochBalance;
|
||||
}
|
||||
|
||||
/// @dev Balance struct for stake.
|
||||
/// @param currentEpochBalance Balance in the current epoch.
|
||||
/// @param nextEpochBalance Balance in the next epoch.
|
||||
struct StakeBalance {
|
||||
uint256 currentEpochBalance;
|
||||
uint256 nextEpochBalance;
|
||||
}
|
||||
|
||||
/// @dev Statuses that stake can exist in.
|
||||
/// Any stake can be (re)delegated effective at the next epoch
|
||||
/// Undelegated stake can be withdrawn if it is available in both the current and next epoch
|
||||
enum StakeStatus {
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
UNDELEGATED,
|
||||
DELEGATED
|
||||
}
|
||||
|
||||
|
@@ -95,4 +95,10 @@ interface IZrxVault {
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
/// @dev Returns the entire balance of Zrx tokens in the vault.
|
||||
function balanceOfZrxVault()
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
}
|
||||
|
@@ -92,10 +92,6 @@ library LibStakingRichErrors {
|
||||
bytes4 internal constant POOL_EXISTENCE_ERROR_SELECTOR =
|
||||
0x9ae94f01;
|
||||
|
||||
// bytes4(keccak256("InvalidStakeStatusError(uint8)"))
|
||||
bytes4 internal constant INVALID_STAKE_STATUS_ERROR_SELECTOR =
|
||||
0x7cf20260;
|
||||
|
||||
// bytes4(keccak256("ProxyDestinationCannotBeNilError()"))
|
||||
bytes internal constant PROXY_DESTINATION_CANNOT_BE_NIL_ERROR =
|
||||
hex"6eff8285";
|
||||
@@ -268,17 +264,6 @@ library LibStakingRichErrors {
|
||||
);
|
||||
}
|
||||
|
||||
function InvalidStakeStatusError(IStructs.StakeStatus status)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
INVALID_STAKE_STATUS_ERROR_SELECTOR,
|
||||
status
|
||||
);
|
||||
}
|
||||
|
||||
function InitializationError(InitializationErrorCodes code)
|
||||
internal
|
||||
pure
|
||||
|
@@ -41,10 +41,10 @@ contract MixinStake is
|
||||
getZrxVault().depositFrom(staker, amount);
|
||||
|
||||
// mint stake
|
||||
_increaseCurrentAndNextBalance(_activeStakeByOwner[staker], amount);
|
||||
|
||||
// update global total of active stake
|
||||
_increaseCurrentAndNextBalance(globalStakeByStatus[uint8(IStructs.StakeStatus.ACTIVE)], amount);
|
||||
_increaseCurrentAndNextBalance(
|
||||
_ownerStakeByStatus[uint8(IStructs.StakeStatus.UNDELEGATED)][staker],
|
||||
amount
|
||||
);
|
||||
|
||||
// notify
|
||||
emit Stake(
|
||||
@@ -54,16 +54,23 @@ contract MixinStake is
|
||||
}
|
||||
|
||||
/// @dev Unstake. Tokens are withdrawn from the ZRX Vault and returned to
|
||||
/// the staker. Stake must be in the 'inactive' status for at least
|
||||
/// one full epoch to unstake.
|
||||
/// the staker. Stake must be in the 'undelegated' status in both the
|
||||
/// current and next epoch in order to be unstaked.
|
||||
/// @param amount of ZRX to unstake.
|
||||
function unstake(uint256 amount)
|
||||
external
|
||||
{
|
||||
address payable staker = msg.sender;
|
||||
address staker = msg.sender;
|
||||
|
||||
IStructs.StoredBalance memory undelegatedBalance =
|
||||
_loadSyncedBalance(_ownerStakeByStatus[uint8(IStructs.StakeStatus.UNDELEGATED)][staker]);
|
||||
|
||||
// stake must be undelegated in current and next epoch to be withdrawn
|
||||
uint256 currentWithdrawableStake = LibSafeMath.min256(
|
||||
undelegatedBalance.currentEpochBalance,
|
||||
undelegatedBalance.nextEpochBalance
|
||||
);
|
||||
|
||||
// sanity check
|
||||
uint256 currentWithdrawableStake = getWithdrawableStake(staker);
|
||||
if (amount > currentWithdrawableStake) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InsufficientBalanceError(
|
||||
@@ -73,15 +80,11 @@ contract MixinStake is
|
||||
);
|
||||
}
|
||||
|
||||
// burn inactive stake
|
||||
_decreaseCurrentAndNextBalance(_inactiveStakeByOwner[staker], amount);
|
||||
|
||||
// update global total of inactive stake
|
||||
_decreaseCurrentAndNextBalance(globalStakeByStatus[uint8(IStructs.StakeStatus.INACTIVE)], amount);
|
||||
|
||||
// update withdrawable field
|
||||
_withdrawableStakeByOwner[staker] =
|
||||
currentWithdrawableStake.safeSub(amount);
|
||||
// burn undelegated stake
|
||||
_decreaseCurrentAndNextBalance(
|
||||
_ownerStakeByStatus[uint8(IStructs.StakeStatus.UNDELEGATED)][staker],
|
||||
amount
|
||||
);
|
||||
|
||||
// withdraw equivalent amount of ZRX from vault
|
||||
getZrxVault().withdrawFrom(staker, amount);
|
||||
@@ -93,7 +96,8 @@ contract MixinStake is
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Moves stake between statuses: 'active', 'inactive' or 'delegated'.
|
||||
/// @dev Moves stake between statuses: 'undelegated' or 'delegated'.
|
||||
/// Delegated stake can also be moved between pools.
|
||||
/// This change comes into effect next epoch.
|
||||
/// @param from status to move stake out of.
|
||||
/// @param to status to move stake into.
|
||||
@@ -105,17 +109,6 @@ contract MixinStake is
|
||||
)
|
||||
external
|
||||
{
|
||||
// sanity check - do nothing if moving stake between the same status
|
||||
if (from.status != IStructs.StakeStatus.DELEGATED
|
||||
&& from.status == to.status)
|
||||
{
|
||||
return;
|
||||
} else if (from.status == IStructs.StakeStatus.DELEGATED
|
||||
&& from.poolId == to.poolId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
address payable staker = msg.sender;
|
||||
|
||||
// handle delegation; this must be done before moving stake as the
|
||||
@@ -136,31 +129,15 @@ contract MixinStake is
|
||||
);
|
||||
}
|
||||
|
||||
// cache the current withdrawal amount, which may change if we're
|
||||
// moving out of the inactive status.
|
||||
uint256 withdrawableStake =
|
||||
(from.status == IStructs.StakeStatus.INACTIVE)
|
||||
? getWithdrawableStake(staker)
|
||||
: 0;
|
||||
|
||||
// execute move
|
||||
IStructs.StoredBalance storage fromPtr = _getBalancePtrFromStatus(staker, from.status);
|
||||
IStructs.StoredBalance storage toPtr = _getBalancePtrFromStatus(staker, to.status);
|
||||
_moveStake(fromPtr, toPtr, amount);
|
||||
|
||||
// update global total of stake in the statuses being moved between
|
||||
IStructs.StoredBalance storage fromPtr = _ownerStakeByStatus[uint8(from.status)][staker];
|
||||
IStructs.StoredBalance storage toPtr = _ownerStakeByStatus[uint8(to.status)][staker];
|
||||
_moveStake(
|
||||
globalStakeByStatus[uint8(from.status)],
|
||||
globalStakeByStatus[uint8(to.status)],
|
||||
fromPtr,
|
||||
toPtr,
|
||||
amount
|
||||
);
|
||||
|
||||
// update withdrawable field, if necessary
|
||||
if (from.status == IStructs.StakeStatus.INACTIVE) {
|
||||
_withdrawableStakeByOwner[staker] =
|
||||
_computeWithdrawableStake(staker, withdrawableStake);
|
||||
}
|
||||
|
||||
// notify
|
||||
emit MoveStake(
|
||||
staker,
|
||||
@@ -198,7 +175,16 @@ contract MixinStake is
|
||||
);
|
||||
|
||||
// Increment how much stake has been delegated to pool.
|
||||
_increaseNextBalance(_delegatedStakeByPoolId[poolId], amount);
|
||||
_increaseNextBalance(
|
||||
_delegatedStakeByPoolId[poolId],
|
||||
amount
|
||||
);
|
||||
|
||||
// Increase next balance of global delegated stake
|
||||
_increaseNextBalance(
|
||||
_globalStakeByStatus[uint8(IStructs.StakeStatus.DELEGATED)],
|
||||
amount
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Un-Delegates a owners stake from a staking pool.
|
||||
@@ -227,36 +213,15 @@ contract MixinStake is
|
||||
);
|
||||
|
||||
// decrement how much stake has been delegated to pool
|
||||
_decreaseNextBalance(_delegatedStakeByPoolId[poolId], amount);
|
||||
}
|
||||
|
||||
/// @dev Returns a storage pointer to a user's stake in a given status.
|
||||
/// @param staker Owner of stake to query.
|
||||
/// @param status Status of user's stake to lookup.
|
||||
/// @return storage A storage pointer to the corresponding stake stake
|
||||
function _getBalancePtrFromStatus(
|
||||
address staker,
|
||||
IStructs.StakeStatus status
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (IStructs.StoredBalance storage)
|
||||
{
|
||||
// lookup status
|
||||
if (status == IStructs.StakeStatus.ACTIVE) {
|
||||
return _activeStakeByOwner[staker];
|
||||
} else if (status == IStructs.StakeStatus.INACTIVE) {
|
||||
return _inactiveStakeByOwner[staker];
|
||||
} else if (status == IStructs.StakeStatus.DELEGATED) {
|
||||
return _delegatedStakeByOwner[staker];
|
||||
}
|
||||
|
||||
// invalid status
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidStakeStatusError(status)
|
||||
_decreaseNextBalance(
|
||||
_delegatedStakeByPoolId[poolId],
|
||||
amount
|
||||
);
|
||||
|
||||
// required to compile ~ we should never hit this.
|
||||
revert("INVALID_STATE");
|
||||
// decrease next balance of global delegated stake
|
||||
_decreaseNextBalance(
|
||||
_globalStakeByStatus[uint8(IStructs.StakeStatus.DELEGATED)],
|
||||
amount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -29,135 +29,67 @@ contract MixinStakeBalances is
|
||||
{
|
||||
using LibSafeMath for uint256;
|
||||
|
||||
/// @dev Returns the total active stake across the entire staking system.
|
||||
/// @return Global active stake.
|
||||
function getGlobalActiveStake()
|
||||
/// @dev Gets global stake for a given status.
|
||||
/// @param stakeStatus UNDELEGATED or DELEGATED
|
||||
/// @return Global stake for given status.
|
||||
function getGlobalStakeByStatus(IStructs.StakeStatus stakeStatus)
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(
|
||||
globalStakeByStatus[uint8(IStructs.StakeStatus.ACTIVE)]
|
||||
balance = _loadSyncedBalance(
|
||||
_globalStakeByStatus[uint8(IStructs.StakeStatus.DELEGATED)]
|
||||
);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
if (stakeStatus == IStructs.StakeStatus.UNDELEGATED) {
|
||||
// Undelegated stake is the difference between total stake and delegated stake
|
||||
// Note that any ZRX erroneously sent to the vault will be counted as undelegated stake
|
||||
uint256 totalStake = getZrxVault().balanceOfZrxVault();
|
||||
balance.currentEpochBalance = totalStake.safeSub(balance.currentEpochBalance).downcastToUint96();
|
||||
balance.nextEpochBalance = totalStake.safeSub(balance.nextEpochBalance).downcastToUint96();
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Returns the total inactive stake across the entire staking system.
|
||||
/// @return Global inactive stake.
|
||||
function getGlobalInactiveStake()
|
||||
/// @dev Gets an owner's stake balances by status.
|
||||
/// @param staker Owner of stake.
|
||||
/// @param stakeStatus UNDELEGATED or DELEGATED
|
||||
/// @return Owner's stake balances for given status.
|
||||
function getOwnerStakeByStatus(
|
||||
address staker,
|
||||
IStructs.StakeStatus stakeStatus
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(
|
||||
globalStakeByStatus[uint8(IStructs.StakeStatus.INACTIVE)]
|
||||
balance = _loadSyncedBalance(
|
||||
_ownerStakeByStatus[uint8(stakeStatus)][staker]
|
||||
);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns the total stake delegated across the entire staking system.
|
||||
/// @return Global delegated stake.
|
||||
function getGlobalDelegatedStake()
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(
|
||||
globalStakeByStatus[uint8(IStructs.StakeStatus.DELEGATED)]
|
||||
);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Returns the total stake for a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @return Total active stake for staker.
|
||||
function getTotalStake(address staker)
|
||||
external
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return getZrxVault().balanceOf(staker);
|
||||
}
|
||||
|
||||
/// @dev Returns the active stake for a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @return Active stake for staker.
|
||||
function getActiveStake(address staker)
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(_activeStakeByOwner[staker]);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns the inactive stake for a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @return Inactive stake for staker.
|
||||
function getInactiveStake(address staker)
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(_inactiveStakeByOwner[staker]);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns the stake delegated by a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @return Delegated stake for staker.
|
||||
function getStakeDelegatedByOwner(address staker)
|
||||
external
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(_delegatedStakeByOwner[staker]);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns the amount stake that can be withdrawn for a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @return Withdrawable stake for staker.
|
||||
function getWithdrawableStake(address staker)
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return _computeWithdrawableStake(staker, _withdrawableStakeByOwner[staker]);
|
||||
}
|
||||
|
||||
/// @dev Returns the stake delegated to a specific staking pool, by a given staker.
|
||||
/// @param staker of stake.
|
||||
/// @param poolId Unique Id of pool.
|
||||
/// @return Stake delegaated to pool by staker.
|
||||
/// @return Stake delegated to pool by staker.
|
||||
function getStakeDelegatedToPoolByOwner(address staker, bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(_delegatedStakeToPoolByOwner[staker][poolId]);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
balance = _loadSyncedBalance(_delegatedStakeToPoolByOwner[staker][poolId]);
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Returns the total stake delegated to a specific staking pool,
|
||||
@@ -167,43 +99,9 @@ contract MixinStakeBalances is
|
||||
function getTotalStakeDelegatedToPool(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
IStructs.StoredBalance memory storedBalance = _loadSyncedBalance(_delegatedStakeByPoolId[poolId]);
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: storedBalance.currentEpochBalance,
|
||||
nextEpochBalance: storedBalance.nextEpochBalance
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Returns the stake that can be withdrawn for a given staker.
|
||||
/// @param staker to query.
|
||||
/// @param lastStoredWithdrawableStake The amount of withdrawable stake
|
||||
/// that was last stored.
|
||||
/// @return Withdrawable stake for staker.
|
||||
function _computeWithdrawableStake(
|
||||
address staker,
|
||||
uint256 lastStoredWithdrawableStake
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
// stake cannot be withdrawn if it has been reallocated for the `next` epoch;
|
||||
// so the upper bound of withdrawable stake is always limited by the value of `next`.
|
||||
IStructs.StoredBalance memory storedBalance = _loadUnsyncedBalance(_inactiveStakeByOwner[staker]);
|
||||
if (storedBalance.currentEpoch == currentEpoch) {
|
||||
return LibSafeMath.min256(
|
||||
storedBalance.nextEpochBalance,
|
||||
lastStoredWithdrawableStake
|
||||
);
|
||||
} else if (uint256(storedBalance.currentEpoch).safeAdd(1) == currentEpoch) {
|
||||
return LibSafeMath.min256(
|
||||
storedBalance.nextEpochBalance,
|
||||
storedBalance.currentEpochBalance
|
||||
);
|
||||
} else {
|
||||
return storedBalance.nextEpochBalance;
|
||||
}
|
||||
balance = _loadSyncedBalance(_delegatedStakeByPoolId[poolId]);
|
||||
return balance;
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ contract MixinStakeStorage is
|
||||
using LibSafeMath for uint256;
|
||||
using LibSafeDowncast for uint256;
|
||||
|
||||
/// @dev Moves stake between states: 'active', 'inactive' or 'delegated'.
|
||||
/// @dev Moves stake between states: 'undelegated' 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.
|
||||
@@ -87,7 +87,7 @@ contract MixinStakeStorage is
|
||||
// sync
|
||||
uint256 currentEpoch_ = currentEpoch;
|
||||
if (currentEpoch_ > balance.currentEpoch) {
|
||||
balance.currentEpoch = currentEpoch_.downcastToUint32();
|
||||
balance.currentEpoch = currentEpoch_.downcastToUint64();
|
||||
balance.currentEpochBalance = balance.nextEpochBalance;
|
||||
}
|
||||
return balance;
|
||||
@@ -176,8 +176,7 @@ contract MixinStakeStorage is
|
||||
private
|
||||
{
|
||||
// note - this compresses into a single `sstore` when optimizations are enabled,
|
||||
// since the StakeBalance struct occupies a single word of storage.
|
||||
balancePtr.isInitialized = true;
|
||||
// since the StoredBalance struct occupies a single word of storage.
|
||||
balancePtr.currentEpoch = balance.currentEpoch;
|
||||
balancePtr.nextEpochBalance = balance.nextEpochBalance;
|
||||
balancePtr.currentEpochBalance = balance.currentEpochBalance;
|
||||
|
@@ -137,7 +137,6 @@ contract MixinStakingPool is
|
||||
function _assertStakingPoolExists(bytes32 poolId)
|
||||
internal
|
||||
view
|
||||
returns (bool)
|
||||
{
|
||||
if (_poolById[poolId].operator == NIL_ADDRESS) {
|
||||
// we use the pool's operator as a proxy for its existence
|
||||
|
@@ -117,7 +117,6 @@ contract TestDelegatorRewards is
|
||||
delegator
|
||||
);
|
||||
IStructs.StoredBalance storage _stake = _delegatedStakeToPoolByOwner[delegator][poolId];
|
||||
_stake.isInitialized = true;
|
||||
_stake.currentEpochBalance += uint96(stake);
|
||||
_stake.nextEpochBalance += uint96(stake);
|
||||
_stake.currentEpoch = uint32(currentEpoch);
|
||||
@@ -146,7 +145,6 @@ contract TestDelegatorRewards is
|
||||
if (_stake.currentEpoch < currentEpoch) {
|
||||
_stake.currentEpochBalance = _stake.nextEpochBalance;
|
||||
}
|
||||
_stake.isInitialized = true;
|
||||
_stake.nextEpochBalance += uint96(stake);
|
||||
_stake.currentEpoch = uint32(currentEpoch);
|
||||
}
|
||||
@@ -170,7 +168,6 @@ contract TestDelegatorRewards is
|
||||
if (_stake.currentEpoch < currentEpoch) {
|
||||
_stake.currentEpochBalance = _stake.nextEpochBalance;
|
||||
}
|
||||
_stake.isInitialized = true;
|
||||
_stake.nextEpochBalance -= uint96(stake);
|
||||
_stake.currentEpoch = uint32(currentEpoch);
|
||||
}
|
||||
|
@@ -27,8 +27,8 @@ contract TestProtocolFees is
|
||||
TestStakingNoWETH
|
||||
{
|
||||
struct TestPool {
|
||||
uint256 operatorStake;
|
||||
uint256 membersStake;
|
||||
uint96 operatorStake;
|
||||
uint96 membersStake;
|
||||
mapping(address => bool) isMaker;
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ contract TestProtocolFees is
|
||||
/// @dev Create a test pool.
|
||||
function createTestPool(
|
||||
bytes32 poolId,
|
||||
uint256 operatorStake,
|
||||
uint256 membersStake,
|
||||
uint96 operatorStake,
|
||||
uint96 membersStake,
|
||||
address[] calldata makerAddresses
|
||||
)
|
||||
external
|
||||
@@ -99,11 +99,12 @@ contract TestProtocolFees is
|
||||
function getTotalStakeDelegatedToPool(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
TestPool memory pool = _testPools[poolId];
|
||||
uint256 stake = pool.operatorStake + pool.membersStake;
|
||||
return IStructs.StakeBalance({
|
||||
uint96 stake = pool.operatorStake + pool.membersStake;
|
||||
return IStructs.StoredBalance({
|
||||
currentEpoch: currentEpoch.downcastToUint64(),
|
||||
currentEpochBalance: stake,
|
||||
nextEpochBalance: stake
|
||||
});
|
||||
@@ -113,10 +114,11 @@ contract TestProtocolFees is
|
||||
function getStakeDelegatedToPoolByOwner(address, bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
returns (IStructs.StoredBalance memory balance)
|
||||
{
|
||||
TestPool memory pool = _testPools[poolId];
|
||||
return IStructs.StakeBalance({
|
||||
return IStructs.StoredBalance({
|
||||
currentEpoch: currentEpoch.downcastToUint64(),
|
||||
currentEpochBalance: pool.operatorStake,
|
||||
nextEpochBalance: pool.operatorStake
|
||||
});
|
||||
|
@@ -119,32 +119,16 @@ contract TestStorageLayoutAndConstants is
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
globalStakeByStatus_slot,
|
||||
globalStakeByStatus_offset,
|
||||
_globalStakeByStatus_slot,
|
||||
_globalStakeByStatus_offset,
|
||||
slot,
|
||||
offset
|
||||
)
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
_activeStakeByOwner_slot,
|
||||
_activeStakeByOwner_offset,
|
||||
slot,
|
||||
offset
|
||||
)
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
_inactiveStakeByOwner_slot,
|
||||
_inactiveStakeByOwner_offset,
|
||||
slot,
|
||||
offset
|
||||
)
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
_delegatedStakeByOwner_slot,
|
||||
_delegatedStakeByOwner_offset,
|
||||
_ownerStakeByStatus_slot,
|
||||
_ownerStakeByStatus_offset,
|
||||
slot,
|
||||
offset
|
||||
)
|
||||
@@ -166,14 +150,6 @@ contract TestStorageLayoutAndConstants is
|
||||
)
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
_withdrawableStakeByOwner_slot,
|
||||
_withdrawableStakeByOwner_offset,
|
||||
slot,
|
||||
offset
|
||||
)
|
||||
slot := add(slot, 0x1)
|
||||
|
||||
assertSlotAndOffset(
|
||||
lastPoolId_slot,
|
||||
lastPoolId_offset,
|
||||
|
@@ -55,7 +55,6 @@
|
||||
"@0x/dev-utils": "^2.4.0-beta.0",
|
||||
"@0x/sol-compiler": "^3.2.0-beta.0",
|
||||
"@0x/tslint-config": "^3.0.1",
|
||||
"@0x/utils": "^4.5.2",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/node": "*",
|
||||
"chai": "^4.0.1",
|
||||
|
@@ -3,24 +3,24 @@ import { BigNumber, RevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { StakingApiWrapper } from '../utils/api_wrapper';
|
||||
import { StakeBalance, StakeBalances, StakeInfo, StakeStatus } from '../utils/types';
|
||||
import { StakeBalances, StakeInfo, StakeStatus, StoredBalance } from '../utils/types';
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
export class StakerActor extends BaseActor {
|
||||
private readonly _poolIds: string[];
|
||||
|
||||
private static _incrementNextBalance(balance: StakeBalance, amount: BigNumber): void {
|
||||
private static _incrementNextBalance(balance: StoredBalance, amount: BigNumber): void {
|
||||
balance.nextEpochBalance = balance.nextEpochBalance.plus(amount);
|
||||
}
|
||||
private static _decrementNextBalance(balance: StakeBalance, amount: BigNumber): void {
|
||||
private static _decrementNextBalance(balance: StoredBalance, amount: BigNumber): void {
|
||||
balance.nextEpochBalance = balance.nextEpochBalance.minus(amount);
|
||||
}
|
||||
private static _incrementCurrentAndNextBalance(balance: StakeBalance, amount: BigNumber): void {
|
||||
private static _incrementCurrentAndNextBalance(balance: StoredBalance, amount: BigNumber): void {
|
||||
balance.currentEpochBalance = balance.currentEpochBalance.plus(amount);
|
||||
balance.nextEpochBalance = balance.nextEpochBalance.plus(amount);
|
||||
}
|
||||
private static _decrementCurrentAndNextBalance(balance: StakeBalance, amount: BigNumber): void {
|
||||
private static _decrementCurrentAndNextBalance(balance: StoredBalance, amount: BigNumber): void {
|
||||
balance.currentEpochBalance = balance.currentEpochBalance.minus(amount);
|
||||
balance.nextEpochBalance = balance.nextEpochBalance.minus(amount);
|
||||
}
|
||||
@@ -106,9 +106,8 @@ export class StakerActor extends BaseActor {
|
||||
const expectedBalances = initBalances;
|
||||
expectedBalances.zrxBalance = initBalances.zrxBalance.plus(amount);
|
||||
expectedBalances.stakeBalanceInVault = initBalances.stakeBalanceInVault.minus(amount);
|
||||
StakerActor._decrementCurrentAndNextBalance(expectedBalances.inactiveStakeBalance, amount);
|
||||
StakerActor._decrementCurrentAndNextBalance(expectedBalances.globalInactiveStakeBalance, amount);
|
||||
expectedBalances.withdrawableStakeBalance = initBalances.withdrawableStakeBalance.minus(amount);
|
||||
StakerActor._decrementCurrentAndNextBalance(expectedBalances.undelegatedStakeBalance, amount);
|
||||
StakerActor._decrementCurrentAndNextBalance(expectedBalances.globalUndelegatedStakeBalance, amount);
|
||||
await this._assertBalancesAsync(expectedBalances);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
@@ -149,7 +148,7 @@ export class StakerActor extends BaseActor {
|
||||
public async stakeWithPoolAsync(poolId: string, amount: BigNumber): Promise<void> {
|
||||
await this.stakeAsync(amount);
|
||||
await this.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
amount,
|
||||
);
|
||||
@@ -182,42 +181,39 @@ export class StakerActor extends BaseActor {
|
||||
}
|
||||
private _getNextEpochBalances(balances: StakeBalances): StakeBalances {
|
||||
const nextBalances = _.cloneDeep(balances);
|
||||
nextBalances.withdrawableStakeBalance = nextBalances.inactiveStakeBalance.nextEpochBalance.isLessThan(
|
||||
nextBalances.inactiveStakeBalance.currentEpochBalance,
|
||||
)
|
||||
? nextBalances.inactiveStakeBalance.nextEpochBalance
|
||||
: nextBalances.inactiveStakeBalance.currentEpochBalance;
|
||||
|
||||
for (const balance of [
|
||||
nextBalances.activeStakeBalance,
|
||||
nextBalances.inactiveStakeBalance,
|
||||
nextBalances.undelegatedStakeBalance,
|
||||
nextBalances.delegatedStakeBalance,
|
||||
nextBalances.globalActiveStakeBalance,
|
||||
nextBalances.globalInactiveStakeBalance,
|
||||
nextBalances.globalUndelegatedStakeBalance,
|
||||
nextBalances.globalDelegatedStakeBalance,
|
||||
...this._poolIds.map(poolId => nextBalances.delegatedStakeByPool[poolId]),
|
||||
...this._poolIds.map(poolId => nextBalances.totalDelegatedStakeByPool[poolId]),
|
||||
]) {
|
||||
balance.currentEpoch = balances.currentEpoch.plus(1);
|
||||
balance.currentEpochBalance = balance.nextEpochBalance;
|
||||
}
|
||||
return nextBalances;
|
||||
}
|
||||
private async _getBalancesAsync(): Promise<StakeBalances> {
|
||||
const balances: StakeBalances = {
|
||||
currentEpoch: await this._stakingApiWrapper.stakingContract.currentEpoch.callAsync(),
|
||||
zrxBalance: await this._stakingApiWrapper.zrxTokenContract.balanceOf.callAsync(this._owner),
|
||||
stakeBalance: await this._stakingApiWrapper.stakingContract.getTotalStake.callAsync(this._owner),
|
||||
stakeBalanceInVault: await this._stakingApiWrapper.zrxVaultContract.balanceOf.callAsync(this._owner),
|
||||
withdrawableStakeBalance: await this._stakingApiWrapper.stakingContract.getWithdrawableStake.callAsync(
|
||||
undelegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
this._owner,
|
||||
StakeStatus.Undelegated,
|
||||
),
|
||||
activeStakeBalance: await this._stakingApiWrapper.stakingContract.getActiveStake.callAsync(this._owner),
|
||||
inactiveStakeBalance: await this._stakingApiWrapper.stakingContract.getInactiveStake.callAsync(this._owner),
|
||||
delegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getStakeDelegatedByOwner.callAsync(
|
||||
delegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
this._owner,
|
||||
StakeStatus.Delegated,
|
||||
),
|
||||
globalUndelegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getGlobalStakeByStatus.callAsync(
|
||||
StakeStatus.Undelegated,
|
||||
),
|
||||
globalDelegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getGlobalStakeByStatus.callAsync(
|
||||
StakeStatus.Delegated,
|
||||
),
|
||||
globalActiveStakeBalance: await this._stakingApiWrapper.stakingContract.getGlobalActiveStake.callAsync(),
|
||||
globalInactiveStakeBalance: await this._stakingApiWrapper.stakingContract.getGlobalInactiveStake.callAsync(),
|
||||
globalDelegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getGlobalDelegatedStake.callAsync(),
|
||||
delegatedStakeByPool: {},
|
||||
totalDelegatedStakeByPool: {},
|
||||
};
|
||||
@@ -241,22 +237,14 @@ export class StakerActor extends BaseActor {
|
||||
expect(balances.stakeBalanceInVault, 'stake balance, recorded in vault').to.be.bignumber.equal(
|
||||
expectedBalances.stakeBalanceInVault,
|
||||
);
|
||||
expect(balances.withdrawableStakeBalance, 'withdrawable stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.withdrawableStakeBalance,
|
||||
);
|
||||
expect(balances.activeStakeBalance.currentEpochBalance, 'active stake balance (current)').to.be.bignumber.equal(
|
||||
expectedBalances.activeStakeBalance.currentEpochBalance,
|
||||
);
|
||||
expect(balances.activeStakeBalance.nextEpochBalance, 'active stake balance (next)').to.be.bignumber.equal(
|
||||
expectedBalances.activeStakeBalance.nextEpochBalance,
|
||||
);
|
||||
expect(
|
||||
balances.inactiveStakeBalance.currentEpochBalance,
|
||||
'inactive stake balance (current)',
|
||||
).to.be.bignumber.equal(expectedBalances.inactiveStakeBalance.currentEpochBalance);
|
||||
expect(balances.inactiveStakeBalance.nextEpochBalance, 'inactive stake balance (next)').to.be.bignumber.equal(
|
||||
expectedBalances.inactiveStakeBalance.nextEpochBalance,
|
||||
);
|
||||
balances.undelegatedStakeBalance.currentEpochBalance,
|
||||
'undelegated stake balance (current)',
|
||||
).to.be.bignumber.equal(expectedBalances.undelegatedStakeBalance.currentEpochBalance);
|
||||
expect(
|
||||
balances.undelegatedStakeBalance.nextEpochBalance,
|
||||
'undelegated stake balance (next)',
|
||||
).to.be.bignumber.equal(expectedBalances.undelegatedStakeBalance.nextEpochBalance);
|
||||
expect(
|
||||
balances.delegatedStakeBalance.currentEpochBalance,
|
||||
'delegated stake balance (current)',
|
||||
@@ -265,23 +253,17 @@ export class StakerActor extends BaseActor {
|
||||
expectedBalances.delegatedStakeBalance.nextEpochBalance,
|
||||
);
|
||||
expect(
|
||||
balances.globalActiveStakeBalance.currentEpochBalance,
|
||||
'global active stake (current)',
|
||||
).to.bignumber.equal(expectedBalances.globalActiveStakeBalance.currentEpochBalance);
|
||||
expect(
|
||||
balances.globalInactiveStakeBalance.currentEpochBalance,
|
||||
'global inactive stake (current)',
|
||||
).to.bignumber.equal(expectedBalances.globalInactiveStakeBalance.currentEpochBalance);
|
||||
balances.globalUndelegatedStakeBalance.currentEpochBalance,
|
||||
'global undelegated stake (current)',
|
||||
).to.bignumber.equal(expectedBalances.globalUndelegatedStakeBalance.currentEpochBalance);
|
||||
expect(
|
||||
balances.globalDelegatedStakeBalance.currentEpochBalance,
|
||||
'global delegated stake (current)',
|
||||
).to.bignumber.equal(expectedBalances.globalDelegatedStakeBalance.currentEpochBalance);
|
||||
expect(balances.globalActiveStakeBalance.nextEpochBalance, 'global active stake (next)').to.bignumber.equal(
|
||||
expectedBalances.globalActiveStakeBalance.nextEpochBalance,
|
||||
);
|
||||
expect(balances.globalInactiveStakeBalance.nextEpochBalance, 'global inactive stake (next)').to.bignumber.equal(
|
||||
expectedBalances.globalInactiveStakeBalance.nextEpochBalance,
|
||||
);
|
||||
expect(
|
||||
balances.globalUndelegatedStakeBalance.nextEpochBalance,
|
||||
'global undelegated stake (next)',
|
||||
).to.bignumber.equal(expectedBalances.globalUndelegatedStakeBalance.nextEpochBalance);
|
||||
expect(
|
||||
balances.globalDelegatedStakeBalance.nextEpochBalance,
|
||||
'global delegated stake (next)',
|
||||
@@ -309,19 +291,9 @@ export class StakerActor extends BaseActor {
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
// from
|
||||
if (from.status === StakeStatus.Active) {
|
||||
StakerActor._decrementNextBalance(expectedBalances.activeStakeBalance, amount);
|
||||
StakerActor._decrementNextBalance(expectedBalances.globalActiveStakeBalance, amount);
|
||||
} else if (from.status === StakeStatus.Inactive) {
|
||||
StakerActor._decrementNextBalance(expectedBalances.inactiveStakeBalance, amount);
|
||||
StakerActor._decrementNextBalance(expectedBalances.globalInactiveStakeBalance, amount);
|
||||
if (
|
||||
expectedBalances.inactiveStakeBalance.nextEpochBalance.isLessThan(
|
||||
expectedBalances.withdrawableStakeBalance,
|
||||
)
|
||||
) {
|
||||
expectedBalances.withdrawableStakeBalance = expectedBalances.inactiveStakeBalance.nextEpochBalance;
|
||||
}
|
||||
if (from.status === StakeStatus.Undelegated) {
|
||||
StakerActor._decrementNextBalance(expectedBalances.undelegatedStakeBalance, amount);
|
||||
StakerActor._decrementNextBalance(expectedBalances.globalUndelegatedStakeBalance, amount);
|
||||
} else if (from.status === StakeStatus.Delegated && from.poolId !== undefined) {
|
||||
StakerActor._decrementNextBalance(expectedBalances.delegatedStakeBalance, amount);
|
||||
StakerActor._decrementNextBalance(expectedBalances.globalDelegatedStakeBalance, amount);
|
||||
@@ -329,12 +301,9 @@ export class StakerActor extends BaseActor {
|
||||
StakerActor._decrementNextBalance(expectedBalances.totalDelegatedStakeByPool[from.poolId], amount);
|
||||
}
|
||||
// to
|
||||
if (to.status === StakeStatus.Active) {
|
||||
StakerActor._incrementNextBalance(expectedBalances.activeStakeBalance, amount);
|
||||
StakerActor._incrementNextBalance(expectedBalances.globalActiveStakeBalance, amount);
|
||||
} else if (to.status === StakeStatus.Inactive) {
|
||||
StakerActor._incrementNextBalance(expectedBalances.inactiveStakeBalance, amount);
|
||||
StakerActor._incrementNextBalance(expectedBalances.globalInactiveStakeBalance, amount);
|
||||
if (to.status === StakeStatus.Undelegated) {
|
||||
StakerActor._incrementNextBalance(expectedBalances.undelegatedStakeBalance, amount);
|
||||
StakerActor._incrementNextBalance(expectedBalances.globalUndelegatedStakeBalance, amount);
|
||||
} else if (to.status === StakeStatus.Delegated && to.poolId !== undefined) {
|
||||
StakerActor._incrementNextBalance(expectedBalances.delegatedStakeBalance, amount);
|
||||
StakerActor._incrementNextBalance(expectedBalances.globalDelegatedStakeBalance, amount);
|
||||
@@ -352,8 +321,8 @@ export class StakerActor extends BaseActor {
|
||||
// check balances
|
||||
expectedBalances.zrxBalance = expectedBalances.zrxBalance.minus(amount);
|
||||
expectedBalances.stakeBalanceInVault = expectedBalances.stakeBalanceInVault.plus(amount);
|
||||
StakerActor._incrementCurrentAndNextBalance(expectedBalances.activeStakeBalance, amount);
|
||||
StakerActor._incrementCurrentAndNextBalance(expectedBalances.globalActiveStakeBalance, amount);
|
||||
StakerActor._incrementCurrentAndNextBalance(expectedBalances.undelegatedStakeBalance, amount);
|
||||
StakerActor._incrementCurrentAndNextBalance(expectedBalances.globalUndelegatedStakeBalance, amount);
|
||||
return expectedBalances;
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import { StakingProxyReadOnlyModeSetEventArgs } from '../src';
|
||||
|
||||
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||
import { toBaseUnitAmount } from './utils/number_utils';
|
||||
import { StakeStatus } from './utils/types';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests.resets('Catastrophe Tests', env => {
|
||||
@@ -39,8 +40,11 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
||||
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||
from: actors[0],
|
||||
});
|
||||
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
const undelegatedStakeBalance = await stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
actors[0],
|
||||
StakeStatus.Undelegated,
|
||||
);
|
||||
expect(undelegatedStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
});
|
||||
it('should not change state when in read-only mode', async () => {
|
||||
// set to read-only mode
|
||||
@@ -50,8 +54,11 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
||||
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||
from: actors[0],
|
||||
});
|
||||
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(ZERO);
|
||||
const undelegatedStakeBalance = await stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
actors[0],
|
||||
StakeStatus.Undelegated,
|
||||
);
|
||||
expect(undelegatedStakeBalance.currentEpochBalance).to.be.bignumber.equal(ZERO);
|
||||
});
|
||||
it('should read values correctly when in read-only mode', async () => {
|
||||
// stake some zrx
|
||||
@@ -62,10 +69,11 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
||||
// set to read-only mode
|
||||
await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(true);
|
||||
// read stake balance in read-only mode
|
||||
const activeStakeBalanceReadOnly = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(
|
||||
const undelegatedStakeBalanceReadOnly = await stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
actors[0],
|
||||
StakeStatus.Undelegated,
|
||||
);
|
||||
expect(activeStakeBalanceReadOnly.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
expect(undelegatedStakeBalanceReadOnly.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
});
|
||||
it('should exit read-only mode', async () => {
|
||||
// set to read-only mode
|
||||
@@ -76,8 +84,11 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
||||
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||
from: actors[0],
|
||||
});
|
||||
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
const undelegatedStakeBalance = await stakingApiWrapper.stakingContract.getOwnerStakeByStatus.callAsync(
|
||||
actors[0],
|
||||
StakeStatus.Undelegated,
|
||||
);
|
||||
expect(undelegatedStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||
});
|
||||
it('should emit event when setting read-only mode', async () => {
|
||||
// set to read-only mode
|
||||
|
@@ -2,7 +2,7 @@ import { constants, expect, getCodesizeFromArtifact } from '@0x/contracts-test-u
|
||||
|
||||
import { artifacts } from '../src';
|
||||
|
||||
describe.skip('Contract Size Checks', () => {
|
||||
describe('Contract Size Checks', () => {
|
||||
describe('Staking', () => {
|
||||
it('should have a codesize less than the maximum', async () => {
|
||||
const actualSize = getCodesizeFromArtifact(artifacts.Staking);
|
||||
|
@@ -176,7 +176,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const amount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
amount,
|
||||
);
|
||||
@@ -248,7 +248,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const totalStakeAmount = toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[0],
|
||||
);
|
||||
@@ -259,7 +259,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[1],
|
||||
);
|
||||
@@ -355,7 +355,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// undelegate (withdraws delegator's rewards)
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
stakeAmount,
|
||||
);
|
||||
// sanity check final balances
|
||||
@@ -436,7 +436,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
stakeAmount,
|
||||
);
|
||||
|
||||
@@ -472,7 +472,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
@@ -499,7 +499,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
@@ -507,7 +507,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
await payProtocolFeeAndFinalize(rewardNotForDelegator);
|
||||
// delegate stake and go to next epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
@@ -530,7 +530,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
@@ -539,7 +539,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
stakeAmount,
|
||||
);
|
||||
// this should go to the delegator
|
||||
@@ -547,7 +547,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// delegate stake ~ this will result in a payout where rewards are computed on
|
||||
// the balance's `currentEpochBalance` field but not the `nextEpochBalance` field.
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
@@ -565,7 +565,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
@@ -595,7 +595,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const undelegateAmount = toBaseUnitAmount(2.5);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
undelegateAmount,
|
||||
);
|
||||
// finalize
|
||||
|
@@ -76,11 +76,31 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
amount,
|
||||
);
|
||||
});
|
||||
it("should be able to reassign next epoch's stake", async () => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
amount,
|
||||
);
|
||||
});
|
||||
@@ -88,30 +108,15 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
// stake is now inactive, should not be able to move it out of active status again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
amount,
|
||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||
);
|
||||
});
|
||||
it('should fail to reassign stake', async () => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
// stake is now delegated; should fail to re-assign it from inactive back to active
|
||||
// stake is now undelegated, should not be able to move it out of active status again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
amount,
|
||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||
);
|
||||
@@ -122,14 +127,14 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAndMoveAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
amount,
|
||||
);
|
||||
});
|
||||
@@ -137,79 +142,38 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAndMoveAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
amount,
|
||||
);
|
||||
// stake is now inactive, should not be able to move it out of active status again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
amount,
|
||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||
);
|
||||
});
|
||||
it('should fail to reassign stake', async () => {
|
||||
// epoch 1
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAndMoveAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
amount,
|
||||
);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
// stake is now delegated; should fail to re-assign it from inactive back to active
|
||||
// stake is now undelegated, should not be able to move it out of active status again
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
amount,
|
||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Move Zero Stake', () => {
|
||||
it('active -> active', async () => {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Active), ZERO);
|
||||
});
|
||||
it('active -> inactive', async () => {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), ZERO);
|
||||
});
|
||||
it('active -> delegated', async () => {
|
||||
it('undelegated -> undelegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('undelegated -> delegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('inactive -> active', async () => {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), ZERO);
|
||||
});
|
||||
it('inactive -> inactive', async () => {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Inactive), ZERO);
|
||||
});
|
||||
it('inactive -> delegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> active', async () => {
|
||||
it('delegated -> undelegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> inactive', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
@@ -233,51 +197,28 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
// setup
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
if (from.status !== StakeStatus.Active) {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), from, amount);
|
||||
if (from.status !== StakeStatus.Undelegated) {
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Undelegated), from, amount);
|
||||
}
|
||||
// run test, checking balances in epochs [n .. n + 2]
|
||||
// in epoch `n` - `next` is set
|
||||
// in epoch `n+1` - `current` is set
|
||||
// in epoch `n+2` - only withdrawable balance should change.
|
||||
await staker.moveStakeAsync(from, to, amount.div(2));
|
||||
await staker.goToNextEpochAsync();
|
||||
await staker.goToNextEpochAsync();
|
||||
};
|
||||
it('active -> active', async () => {
|
||||
await testMovePartialStake(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Active));
|
||||
it('undelegated -> undelegated', async () => {
|
||||
await testMovePartialStake(new StakeInfo(StakeStatus.Undelegated), new StakeInfo(StakeStatus.Undelegated));
|
||||
});
|
||||
it('active -> inactive', async () => {
|
||||
await testMovePartialStake(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive));
|
||||
});
|
||||
it('active -> delegated', async () => {
|
||||
it('undelegated -> delegated', async () => {
|
||||
await testMovePartialStake(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
);
|
||||
});
|
||||
it('inactive -> active', async () => {
|
||||
await testMovePartialStake(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active));
|
||||
});
|
||||
it('inactive -> inactive', async () => {
|
||||
await testMovePartialStake(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Inactive));
|
||||
});
|
||||
it('inactive -> delegated', async () => {
|
||||
await testMovePartialStake(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
);
|
||||
});
|
||||
it('delegated -> active', async () => {
|
||||
it('delegated -> undelegated', async () => {
|
||||
await testMovePartialStake(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
);
|
||||
});
|
||||
it('delegated -> inactive', async () => {
|
||||
await testMovePartialStake(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
);
|
||||
});
|
||||
it('delegated -> delegated (same pool)', async () => {
|
||||
@@ -292,22 +233,11 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
);
|
||||
});
|
||||
it('active -> delegated (non-existent pool)', async () => {
|
||||
it('undelegated -> delegated (non-existent pool)', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, unusedPoolId),
|
||||
amount,
|
||||
new StakingRevertErrors.PoolExistenceError(unusedPoolId, false),
|
||||
);
|
||||
});
|
||||
it('inactive -> delegated (non-existent pool)', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, unusedPoolId),
|
||||
amount,
|
||||
new StakingRevertErrors.PoolExistenceError(unusedPoolId, false),
|
||||
@@ -317,7 +247,7 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
@@ -328,131 +258,135 @@ blockchainTests.resets('Stake Statuses', env => {
|
||||
new StakingRevertErrors.PoolExistenceError(unusedPoolId, false),
|
||||
);
|
||||
});
|
||||
it('delegated (non-existent pool) -> active', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, unusedPoolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
amount,
|
||||
new StakingRevertErrors.PoolExistenceError(unusedPoolId, false),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Unstake', () => {
|
||||
it('should successfully unstake zero ZRX', async () => {
|
||||
const amount = toBaseUnitAmount(0);
|
||||
await staker.unstakeAsync(amount);
|
||||
});
|
||||
it('should successfully unstake after being inactive for 1 epoch', async () => {
|
||||
it('should successfully unstake after becoming undelegated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
amount,
|
||||
);
|
||||
await staker.goToNextEpochAsync(); // stake is now undelegated
|
||||
await staker.unstakeAsync(amount);
|
||||
});
|
||||
it('should fail to unstake with insufficient balance', async () => {
|
||||
it('should fail to unstake in the same epoch as stake was undelegated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
await staker.goToNextEpochAsync(); // stake is now delegated
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
amount,
|
||||
);
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
});
|
||||
it('should fail to unstake in the same epoch as stake was set to inactive', async () => {
|
||||
it('should fail to unstake in same epoch that undelegated stake has been delegated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
});
|
||||
it('should fail to unstake after being inactive for <1 epoch', async () => {
|
||||
it('should fail to unstake one epoch after undelegated stake has been delegated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.goToNextEpochAsync();
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
await staker.goToNextEpochAsync(); // stake is now undelegated
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
});
|
||||
it('should fail to unstake in same epoch that inactive/withdrawable stake has been reactivated', async () => {
|
||||
it('should fail to unstake >1 epoch after undelegated stake has been delegated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
amount,
|
||||
);
|
||||
await staker.goToNextEpochAsync(); // stake is now undelegated
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), amount);
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
});
|
||||
it('should fail to unstake one epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||
it('should successfuly unstake freshly deposited stake', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
});
|
||||
it('should fail to unstake >1 epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||
const amount = toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), amount);
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||
await staker.unstakeAsync(amount);
|
||||
});
|
||||
});
|
||||
describe('Simulations', () => {
|
||||
it('Simulation from Staking Spec', async () => {
|
||||
// Epoch 1: Stake some ZRX
|
||||
await staker.stakeAsync(toBaseUnitAmount(4));
|
||||
// Later in Epoch 1: User delegates and deactivates some stake
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
toBaseUnitAmount(1),
|
||||
);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
toBaseUnitAmount(2),
|
||||
);
|
||||
// Epoch 2: Status updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Epoch 3: Stake that has been inactive for an epoch can be withdrawn (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 3: User reactivates half of their inactive stake; this becomes Active next epoch
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
toBaseUnitAmount(0.5),
|
||||
);
|
||||
// Later in Epoch 3: User re-delegates half of their stake from Pool 1 to Pool 2
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
toBaseUnitAmount(1),
|
||||
);
|
||||
// Epoch 4: Status updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 4: User deactivates all active stake
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
toBaseUnitAmount(1.5),
|
||||
);
|
||||
// Later in Epoch 4: User withdraws all available inactive stake
|
||||
await staker.unstakeAsync(toBaseUnitAmount(0.5));
|
||||
// Epoch 5: Status updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 5: User reactivates a portion of their stake
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Inactive),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
toBaseUnitAmount(1),
|
||||
);
|
||||
// Epoch 6: Status updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
});
|
||||
// it('Simulation from Staking Spec', async () => {
|
||||
// // Epoch 1: Stake some ZRX
|
||||
// await staker.stakeAsync(toBaseUnitAmount(4));
|
||||
// // Later in Epoch 1: User delegates and deactivates some stake
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Active),
|
||||
// new StakeInfo(StakeStatus.Undelegated),
|
||||
// toBaseUnitAmount(1),
|
||||
// );
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Active),
|
||||
// new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
// toBaseUnitAmount(2),
|
||||
// );
|
||||
// // Epoch 2: Status updates (no user intervention required)
|
||||
// await staker.goToNextEpochAsync();
|
||||
// // Epoch 3: Stake that has been undelegated for an epoch can be withdrawn (no user intervention required)
|
||||
// await staker.goToNextEpochAsync();
|
||||
// // Later in Epoch 3: User reactivates half of their undelegated stake; this becomes Active next epoch
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Undelegated),
|
||||
// new StakeInfo(StakeStatus.Active),
|
||||
// toBaseUnitAmount(0.5),
|
||||
// );
|
||||
// // Later in Epoch 3: User re-delegates half of their stake from Pool 1 to Pool 2
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||
// new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||
// toBaseUnitAmount(1),
|
||||
// );
|
||||
// // Epoch 4: Status updates (no user intervention required)
|
||||
// await staker.goToNextEpochAsync();
|
||||
// // Later in Epoch 4: User deactivates all active stake
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Active),
|
||||
// new StakeInfo(StakeStatus.Undelegated),
|
||||
// toBaseUnitAmount(1.5),
|
||||
// );
|
||||
// // Later in Epoch 4: User withdraws all available undelegated stake
|
||||
// await staker.unstakeAsync(toBaseUnitAmount(0.5));
|
||||
// // Epoch 5: Status updates (no user intervention required)
|
||||
// await staker.goToNextEpochAsync();
|
||||
// // Later in Epoch 5: User reactivates a portion of their stake
|
||||
// await staker.moveStakeAsync(
|
||||
// new StakeInfo(StakeStatus.Undelegated),
|
||||
// new StakeInfo(StakeStatus.Active),
|
||||
// toBaseUnitAmount(1),
|
||||
// );
|
||||
// // Epoch 6: Status updates (no user intervention required)
|
||||
// await staker.goToNextEpochAsync();
|
||||
// });
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
@@ -26,19 +26,16 @@ blockchainTests.resets('MixinStakeStorage unit tests', env => {
|
||||
);
|
||||
await testContract.setCurrentEpoch.awaitTransactionSuccessAsync(CURRENT_EPOCH);
|
||||
defaultUninitializedBalance = {
|
||||
isInitialized: false,
|
||||
currentEpoch: constants.INITIAL_EPOCH,
|
||||
currentEpochBalance: new BigNumber(0),
|
||||
nextEpochBalance: new BigNumber(0),
|
||||
};
|
||||
defaultSyncedBalance = {
|
||||
isInitialized: true,
|
||||
currentEpoch: CURRENT_EPOCH,
|
||||
currentEpochBalance: new BigNumber(16),
|
||||
nextEpochBalance: new BigNumber(16),
|
||||
};
|
||||
defaultUnsyncedBalance = {
|
||||
isInitialized: true,
|
||||
currentEpoch: CURRENT_EPOCH.minus(1),
|
||||
currentEpochBalance: new BigNumber(10),
|
||||
nextEpochBalance: new BigNumber(16),
|
||||
@@ -48,7 +45,6 @@ blockchainTests.resets('MixinStakeStorage unit tests', env => {
|
||||
async function getTestBalancesAsync(index: Numberish): Promise<StoredBalance> {
|
||||
const storedBalance: Partial<StoredBalance> = {};
|
||||
[
|
||||
storedBalance.isInitialized,
|
||||
storedBalance.currentEpoch,
|
||||
storedBalance.currentEpochBalance,
|
||||
storedBalance.nextEpochBalance,
|
||||
@@ -71,13 +67,11 @@ blockchainTests.resets('MixinStakeStorage unit tests', env => {
|
||||
getTestBalancesAsync(INDEX_ONE),
|
||||
]);
|
||||
expect(actualBalances[0]).to.deep.equal({
|
||||
isInitialized: true,
|
||||
currentEpoch: CURRENT_EPOCH,
|
||||
currentEpochBalance: fromBalance.currentEpochBalance,
|
||||
nextEpochBalance: fromBalance.nextEpochBalance.minus(amount),
|
||||
});
|
||||
expect(actualBalances[1]).to.deep.equal({
|
||||
isInitialized: true,
|
||||
currentEpoch: CURRENT_EPOCH,
|
||||
currentEpochBalance: toBalance.currentEpochBalance,
|
||||
nextEpochBalance: toBalance.nextEpochBalance.plus(amount),
|
||||
|
@@ -120,7 +120,7 @@ export class CumulativeRewardTrackingSimulation {
|
||||
from: this._staker,
|
||||
});
|
||||
receipt = await this._stakingApiWrapper.stakingContract.moveStake.awaitTransactionSuccessAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
new StakeInfo(StakeStatus.Delegated, this._poolId),
|
||||
this._amountToStake,
|
||||
{ from: this._staker },
|
||||
@@ -130,7 +130,7 @@ export class CumulativeRewardTrackingSimulation {
|
||||
case TestAction.Undelegate:
|
||||
receipt = await this._stakingApiWrapper.stakingContract.moveStake.awaitTransactionSuccessAsync(
|
||||
new StakeInfo(StakeStatus.Delegated, this._poolId),
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Undelegated),
|
||||
this._amountToStake,
|
||||
{ from: this._staker },
|
||||
);
|
||||
|
@@ -16,7 +16,6 @@ export interface StakerBalances {
|
||||
zrxBalance: BigNumber;
|
||||
stakeBalance: BigNumber;
|
||||
stakeBalanceInVault: BigNumber;
|
||||
withdrawableStakeBalance: BigNumber;
|
||||
activatableStakeBalance: BigNumber;
|
||||
activatedStakeBalance: BigNumber;
|
||||
deactivatedStakeBalance: BigNumber;
|
||||
@@ -59,24 +58,17 @@ export interface EndOfEpochInfo {
|
||||
}
|
||||
|
||||
export interface StoredBalance {
|
||||
isInitialized: boolean;
|
||||
currentEpoch: number | BigNumber;
|
||||
currentEpochBalance: BigNumber;
|
||||
nextEpochBalance: BigNumber;
|
||||
}
|
||||
|
||||
export interface StakeBalance {
|
||||
currentEpoch: BigNumber;
|
||||
currentEpochBalance: BigNumber;
|
||||
nextEpochBalance: BigNumber;
|
||||
}
|
||||
|
||||
export interface StakeBalanceByPool {
|
||||
[key: string]: StakeBalance;
|
||||
[key: string]: StoredBalance;
|
||||
}
|
||||
|
||||
export enum StakeStatus {
|
||||
Active,
|
||||
Inactive,
|
||||
Undelegated,
|
||||
Delegated,
|
||||
}
|
||||
|
||||
@@ -91,16 +83,14 @@ export class StakeInfo {
|
||||
}
|
||||
|
||||
export interface StakeBalances {
|
||||
currentEpoch: BigNumber;
|
||||
zrxBalance: BigNumber;
|
||||
stakeBalance: BigNumber;
|
||||
stakeBalanceInVault: BigNumber;
|
||||
withdrawableStakeBalance: BigNumber;
|
||||
activeStakeBalance: StakeBalance;
|
||||
inactiveStakeBalance: StakeBalance;
|
||||
delegatedStakeBalance: StakeBalance;
|
||||
globalActiveStakeBalance: StakeBalance;
|
||||
globalInactiveStakeBalance: StakeBalance;
|
||||
globalDelegatedStakeBalance: StakeBalance;
|
||||
undelegatedStakeBalance: StoredBalance;
|
||||
delegatedStakeBalance: StoredBalance;
|
||||
globalUndelegatedStakeBalance: StoredBalance;
|
||||
globalDelegatedStakeBalance: StoredBalance;
|
||||
delegatedStakeByPool: StakeBalanceByPool;
|
||||
totalDelegatedStakeByPool: StakeBalanceByPool;
|
||||
}
|
||||
|
@@ -142,12 +142,6 @@ export class InvalidParamValueError extends RevertError {
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidStakeStatusError extends RevertError {
|
||||
constructor(status?: BigNumber | number | string) {
|
||||
super('InvalidStakeStatusError', 'InvalidStakeStatusError(uint8 status)', { status });
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidProtocolFeePaymentError extends RevertError {
|
||||
constructor(
|
||||
errorCode?: ProtocolFeePaymentErrorCodes,
|
||||
@@ -190,7 +184,6 @@ const types = [
|
||||
InitializationError,
|
||||
InsufficientBalanceError,
|
||||
InvalidProtocolFeePaymentError,
|
||||
InvalidStakeStatusError,
|
||||
InvalidParamValueError,
|
||||
MakerPoolAssignmentError,
|
||||
OnlyCallableByExchangeError,
|
||||
|
Reference in New Issue
Block a user