Documentation for MixinDelegatedStake
This commit is contained in:
@@ -47,10 +47,10 @@ contract Staking is
|
|||||||
MixinStakeBalances,
|
MixinStakeBalances,
|
||||||
MixinStakingPool,
|
MixinStakingPool,
|
||||||
MixinTimelockedStake,
|
MixinTimelockedStake,
|
||||||
MixinStake,
|
|
||||||
MixinDelegatedStake,
|
|
||||||
MixinStakingPoolRewards,
|
MixinStakingPoolRewards,
|
||||||
MixinExchangeFees
|
MixinExchangeFees,
|
||||||
|
MixinStake,
|
||||||
|
MixinDelegatedStake
|
||||||
{
|
{
|
||||||
|
|
||||||
// this contract can receive ETH
|
// this contract can receive ETH
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
pragma solidity ^0.5.5;
|
pragma solidity ^0.5.5;
|
||||||
|
|
||||||
import "../libs/LibSafeMath.sol";
|
import "../libs/LibSafeMath.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";
|
||||||
@@ -29,6 +28,7 @@ import "./MixinScheduler.sol";
|
|||||||
import "./MixinStakeBalances.sol";
|
import "./MixinStakeBalances.sol";
|
||||||
import "./MixinTimelockedStake.sol";
|
import "./MixinTimelockedStake.sol";
|
||||||
import "./MixinStake.sol";
|
import "./MixinStake.sol";
|
||||||
|
import "./MixinStakingPoolRewards.sol";
|
||||||
|
|
||||||
|
|
||||||
contract MixinDelegatedStake is
|
contract MixinDelegatedStake is
|
||||||
@@ -42,21 +42,40 @@ contract MixinDelegatedStake is
|
|||||||
MixinScheduler,
|
MixinScheduler,
|
||||||
MixinStakingPoolRewardVault,
|
MixinStakingPoolRewardVault,
|
||||||
MixinStakeBalances,
|
MixinStakeBalances,
|
||||||
|
MixinStakingPool,
|
||||||
MixinTimelockedStake,
|
MixinTimelockedStake,
|
||||||
|
MixinStakingPoolRewards,
|
||||||
MixinStake
|
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;
|
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
|
external
|
||||||
{
|
{
|
||||||
address owner = msg.sender;
|
address payable owner = msg.sender;
|
||||||
_mintStake(owner, amount);
|
_mintStake(owner, amount);
|
||||||
activateStake(amount);
|
activateStake(amount);
|
||||||
_delegateStake(owner, poolId, 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(
|
function activateAndDelegateStake(
|
||||||
bytes32 poolId,
|
bytes32 poolId,
|
||||||
uint256 amount
|
uint256 amount
|
||||||
@@ -64,10 +83,14 @@ contract MixinDelegatedStake is
|
|||||||
public
|
public
|
||||||
{
|
{
|
||||||
activateStake(amount);
|
activateStake(amount);
|
||||||
address owner = msg.sender;
|
address payable owner = msg.sender;
|
||||||
_delegateStake(owner, poolId, amount);
|
_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)
|
function deactivateAndTimelockDelegatedStake(bytes32 poolId, uint256 amount)
|
||||||
public
|
public
|
||||||
{
|
{
|
||||||
@@ -76,7 +99,11 @@ contract MixinDelegatedStake is
|
|||||||
_undelegateStake(owner, poolId, amount);
|
_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
|
private
|
||||||
{
|
{
|
||||||
// take snapshot of parameters before any computation
|
// take snapshot of parameters before any computation
|
||||||
@@ -84,6 +111,14 @@ contract MixinDelegatedStake is
|
|||||||
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
|
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
|
||||||
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
|
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
|
||||||
|
|
||||||
|
// join staking pool
|
||||||
|
_joinStakingPool(
|
||||||
|
poolId,
|
||||||
|
owner,
|
||||||
|
amount,
|
||||||
|
_delegatedStakeByPoolId
|
||||||
|
);
|
||||||
|
|
||||||
// increment how much stake the owner has delegated
|
// increment how much stake the owner has delegated
|
||||||
delegatedStakeByOwner[owner] = _delegatedStakeByOwner._add(amount);
|
delegatedStakeByOwner[owner] = _delegatedStakeByOwner._add(amount);
|
||||||
|
|
||||||
@@ -92,20 +127,6 @@ contract MixinDelegatedStake is
|
|||||||
|
|
||||||
// increment how much stake has been delegated to pool
|
// increment how much stake has been delegated to pool
|
||||||
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId._add(amount);
|
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?
|
// question - should we then return the amount withdrawn?
|
||||||
@@ -117,6 +138,15 @@ contract MixinDelegatedStake is
|
|||||||
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
|
uint256 _delegatedStakeToPoolByOwner = delegatedStakeToPoolByOwner[owner][poolId];
|
||||||
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
|
uint256 _delegatedStakeByPoolId = delegatedStakeByPoolId[poolId];
|
||||||
|
|
||||||
|
// leave the staking pool
|
||||||
|
_leaveStakingPool(
|
||||||
|
poolId,
|
||||||
|
owner,
|
||||||
|
amount,
|
||||||
|
_delegatedStakeToPoolByOwner,
|
||||||
|
_delegatedStakeByPoolId
|
||||||
|
);
|
||||||
|
|
||||||
// decrement how much stake the owner has delegated
|
// decrement how much stake the owner has delegated
|
||||||
delegatedStakeByOwner[owner] = _delegatedStakeByOwner._sub(amount);
|
delegatedStakeByOwner[owner] = _delegatedStakeByOwner._sub(amount);
|
||||||
|
|
||||||
@@ -125,39 +155,5 @@ contract MixinDelegatedStake is
|
|||||||
|
|
||||||
// decrement how much stake has been delegated to pool
|
// decrement how much stake has been delegated to pool
|
||||||
delegatedStakeByPoolId[poolId] = _delegatedStakeByPoolId._sub(amount);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,4 +218,73 @@ contract MixinStakingPoolRewards is
|
|||||||
poolBalance
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class DelegatorActor extends StakerActor {
|
|||||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||||
super(owner, stakingWrapper);
|
super(owner, stakingWrapper);
|
||||||
}
|
}
|
||||||
public async depositAndDelegateAsync(
|
public async depositZrxAndDelegateToStakingPoolAsync(
|
||||||
poolId: string,
|
poolId: string,
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
revertReason?: RevertReason,
|
revertReason?: RevertReason,
|
||||||
@@ -26,7 +26,7 @@ export class DelegatorActor extends StakerActor {
|
|||||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
|
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
|
||||||
// deposit stake
|
// deposit stake
|
||||||
const txReceiptPromise = this._stakingWrapper.depositAndDelegateAsync(this._owner, poolId, amount);
|
const txReceiptPromise = this._stakingWrapper.depositZrxAndDelegateToStakingPoolAsync(this._owner, poolId, amount);
|
||||||
if (revertReason !== undefined) {
|
if (revertReason !== undefined) {
|
||||||
await expectTransactionFailedAsync(txReceiptPromise, revertReason);
|
await expectTransactionFailedAsync(txReceiptPromise, revertReason);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ describe('Staking & Delegating', () => {
|
|||||||
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
|
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare);
|
||||||
// run test
|
// run test
|
||||||
const delegator = new DelegatorActor(stakers[0], stakingWrapper);
|
const delegator = new DelegatorActor(stakers[0], stakingWrapper);
|
||||||
await delegator.depositAndDelegateAsync(poolId, amountToDelegate);
|
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amountToDelegate);
|
||||||
await delegator.deactivateAndTimelockDelegatedStakeAsync(poolId, amountToDeactivate);
|
await delegator.deactivateAndTimelockDelegatedStakeAsync(poolId, amountToDeactivate);
|
||||||
// note - we cannot re-activate this timelocked stake until at least one full timelock period has passed.
|
// note - we cannot re-activate this timelocked stake until at least one full timelock period has passed.
|
||||||
// attempting to do so should revert.
|
// attempting to do so should revert.
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export class Simulation {
|
|||||||
for (const j of _.range(numberOfDelegatorsInPool)) {
|
for (const j of _.range(numberOfDelegatorsInPool)) {
|
||||||
const delegator = this._delegators[delegatorIdx];
|
const delegator = this._delegators[delegatorIdx];
|
||||||
const amount = p.stakeByDelegator[delegatorIdx];
|
const amount = p.stakeByDelegator[delegatorIdx];
|
||||||
await delegator.depositAndDelegateAsync(poolId, amount);
|
await delegator.depositZrxAndDelegateToStakingPoolAsync(poolId, amount);
|
||||||
delegatorIdx += 1;
|
delegatorIdx += 1;
|
||||||
}
|
}
|
||||||
poolIdx += 1;
|
poolIdx += 1;
|
||||||
|
|||||||
@@ -190,12 +190,12 @@ export class StakingWrapper {
|
|||||||
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
||||||
return txReceipt;
|
return txReceipt;
|
||||||
}
|
}
|
||||||
public async depositAndDelegateAsync(
|
public async depositZrxAndDelegateToStakingPoolAsync(
|
||||||
owner: string,
|
owner: string,
|
||||||
poolId: string,
|
poolId: string,
|
||||||
amount: BigNumber,
|
amount: BigNumber,
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
): 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);
|
const txReceipt = await this._executeTransactionAsync(calldata, owner, new BigNumber(0), true);
|
||||||
return txReceipt;
|
return txReceipt;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user