Documentation for MixinDelegatedStake

This commit is contained in:
Greg Hysen
2019-06-27 22:29:22 -07:00
parent 93844343de
commit 3e6cae0ca0
7 changed files with 127 additions and 62 deletions

View File

@@ -47,10 +47,10 @@ contract Staking is
MixinStakeBalances,
MixinStakingPool,
MixinTimelockedStake,
MixinStake,
MixinDelegatedStake,
MixinStakingPoolRewards,
MixinExchangeFees
MixinExchangeFees,
MixinStake,
MixinDelegatedStake
{
// this contract can receive ETH

View File

@@ -19,7 +19,6 @@
pragma solidity ^0.5.5;
import "../libs/LibSafeMath.sol";
import "../libs/LibRewardMath.sol";
import "../immutable/MixinConstants.sol";
import "../immutable/MixinStorage.sol";
import "../interfaces/IStakingEvents.sol";
@@ -29,6 +28,7 @@ import "./MixinScheduler.sol";
import "./MixinStakeBalances.sol";
import "./MixinTimelockedStake.sol";
import "./MixinStake.sol";
import "./MixinStakingPoolRewards.sol";
contract MixinDelegatedStake is
@@ -42,21 +42,40 @@ contract MixinDelegatedStake is
MixinScheduler,
MixinStakingPoolRewardVault,
MixinStakeBalances,
MixinStakingPool,
MixinTimelockedStake,
MixinStakingPoolRewards,
MixinStake
{
/// @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).
using LibSafeMath for uint256;
function depositAndDelegate(bytes32 poolId, uint256 amount)
/// @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 owner = msg.sender;
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
@@ -64,10 +83,14 @@ contract MixinDelegatedStake is
public
{
activateStake(amount);
address owner = msg.sender;
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
{
@@ -76,7 +99,11 @@ contract MixinDelegatedStake is
_undelegateStake(owner, poolId, amount);
}
function _delegateStake(address owner, bytes32 poolId, uint256 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
@@ -84,6 +111,14 @@ contract MixinDelegatedStake is
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._add(amount);
@@ -92,20 +127,6 @@ contract MixinDelegatedStake is
// increment how much stake has been delegated to pool
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId._add(amount);
// update delegator's share of reward pool
// note that this uses the snapshot parameters
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId);
uint256 buyIn = LibRewardMath._computeBuyInDenominatedInShadowAsset(
amount,
_delegatedStakeByPoolId,
shadowRewardsByPoolId[poolId],
poolBalance
);
if (buyIn > 0) {
shadowRewardsInPoolByOwner[owner][poolId] = shadowRewardsInPoolByOwner[owner][poolId]._add(buyIn);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId]._add(buyIn);
}
}
// question - should we then return the amount withdrawn?
@@ -117,6 +138,15 @@ contract MixinDelegatedStake is
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._sub(amount);
@@ -125,39 +155,5 @@ contract MixinDelegatedStake is
// decrement how much stake has been delegated to pool
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId._sub(amount);
// get payout
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId);
uint256 payoutInRealAsset;
uint256 payoutInShadowAsset;
if (_delegatedStakeToPoolByOwner == amount) {
// full payout
payoutInShadowAsset = shadowRewardsInPoolByOwner[owner][poolId];
payoutInRealAsset = LibRewardMath._computePayoutDenominatedInRealAsset(
amount,
_delegatedStakeByPoolId,
payoutInShadowAsset,
shadowRewardsByPoolId[poolId],
poolBalance
);
} else {
// partial payout
(payoutInRealAsset, payoutInShadowAsset) = LibRewardMath._computePartialPayout(
amount,
_delegatedStakeToPoolByOwner,
_delegatedStakeByPoolId,
shadowRewardsInPoolByOwner[owner][poolId],
shadowRewardsByPoolId[poolId],
poolBalance
);
}
shadowRewardsInPoolByOwner[owner][poolId] = shadowRewardsInPoolByOwner[owner][poolId]._sub(payoutInShadowAsset);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId]._sub(payoutInShadowAsset);
// withdraw payout for delegator
if (payoutInRealAsset > 0) {
_withdrawFromMemberInStakingPoolRewardVault(poolId, payoutInRealAsset);
owner.transfer(payoutInRealAsset);
}
}
}

View File

@@ -218,4 +218,73 @@ contract MixinStakingPoolRewards is
poolBalance
);
}
function _joinStakingPool(
bytes32 poolId,
address payable member,
uint256 amountOfStakeToDelegate,
uint256 totalStakeDelegatedToPool
)
internal
{
// update delegator's share of reward pool
// note that this uses the snapshot parameters
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId);
uint256 buyIn = LibRewardMath._computeBuyInDenominatedInShadowAsset(
amountOfStakeToDelegate,
totalStakeDelegatedToPool,
shadowRewardsByPoolId[poolId],
poolBalance
);
if (buyIn > 0) {
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId]._add(buyIn);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId]._add(buyIn);
}
}
function _leaveStakingPool(
bytes32 poolId,
address payable member,
uint256 amountOfStakeToUndelegate,
uint256 totalStakeDelegatedToPoolByMember,
uint256 totalStakeDelegatedToPool
)
internal
{
// get payout
uint256 poolBalance = getBalanceOfMembersInStakingPoolRewardVault(poolId);
uint256 payoutInRealAsset = 0;
uint256 payoutInShadowAsset = 0;
if (totalStakeDelegatedToPoolByMember == amountOfStakeToUndelegate) {
// full payout; this is computed separately to avoid extra computation and rounding.
payoutInShadowAsset = shadowRewardsInPoolByOwner[member][poolId];
payoutInRealAsset = LibRewardMath._computePayoutDenominatedInRealAsset(
amountOfStakeToUndelegate,
totalStakeDelegatedToPool,
payoutInShadowAsset,
shadowRewardsByPoolId[poolId],
poolBalance
);
} else {
// partial payout
(payoutInRealAsset, payoutInShadowAsset) = LibRewardMath._computePartialPayout(
amountOfStakeToUndelegate,
totalStakeDelegatedToPoolByMember,
totalStakeDelegatedToPool,
shadowRewardsInPoolByOwner[member][poolId],
shadowRewardsByPoolId[poolId],
poolBalance
);
}
// update shadow rewards
shadowRewardsInPoolByOwner[member][poolId] = shadowRewardsInPoolByOwner[member][poolId]._sub(payoutInShadowAsset);
shadowRewardsByPoolId[poolId] = shadowRewardsByPoolId[poolId]._sub(payoutInShadowAsset);
// withdraw payout for member
if (payoutInRealAsset > 0) {
_withdrawFromMemberInStakingPoolRewardVault(poolId, payoutInRealAsset);
member.transfer(payoutInRealAsset);
}
}
}

View File

@@ -17,7 +17,7 @@ export class DelegatorActor extends StakerActor {
constructor(owner: string, stakingWrapper: StakingWrapper) {
super(owner, stakingWrapper);
}
public async depositAndDelegateAsync(
public async depositZrxAndDelegateToStakingPoolAsync(
poolId: string,
amount: BigNumber,
revertReason?: RevertReason,
@@ -26,7 +26,7 @@ export class DelegatorActor extends StakerActor {
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
// deposit stake
const txReceiptPromise = this._stakingWrapper.depositAndDelegateAsync(this._owner, poolId, amount);
const txReceiptPromise = this._stakingWrapper.depositZrxAndDelegateToStakingPoolAsync(this._owner, poolId, amount);
if (revertReason !== undefined) {
await expectTransactionFailedAsync(txReceiptPromise, revertReason);
return;

View File

@@ -95,7 +95,7 @@ describe('Staking & Delegating', () => {
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
// run test
const delegator = new DelegatorActor(stakers[0], stakingWrapper);
await delegator.depositAndDelegateAsync(poolId, amountToDelegate);
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.

View File

@@ -179,7 +179,7 @@ export class Simulation {
for (const j of _.range(numberOfDelegatorsInPool)) {
const delegator = this._delegators[delegatorIdx];
const amount = p.stakeByDelegator[delegatorIdx];
await delegator.depositAndDelegateAsync(poolId, amount);
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amount);
delegatorIdx += 1;
}
poolIdx += 1;

View File

@@ -190,12 +190,12 @@ export class StakingWrapper {
const txReceipt = await this._executeTransactionAsync(calldata, owner);
return txReceipt;
}
public async depositAndDelegateAsync(
public async depositZrxAndDelegateToStakingPoolAsync(
owner: string,
poolId: string,
amount: BigNumber,
): Promise<TransactionReceiptWithDecodedLogs> {
const calldata = this.getStakingContract().depositAndDelegate.getABIEncodedTransactionData(poolId, amount);
const calldata = this.getStakingContract().depositZrxAndDelegateToStakingPool.getABIEncodedTransactionData(poolId, amount);
const txReceipt = await this._executeTransactionAsync(calldata, owner, new BigNumber(0), true);
return txReceipt;
}