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, MixinStakeBalances,
MixinStakingPool, MixinStakingPool,
MixinTimelockedStake, MixinTimelockedStake,
MixinStake,
MixinDelegatedStake,
MixinStakingPoolRewards, MixinStakingPoolRewards,
MixinExchangeFees MixinExchangeFees,
MixinStake,
MixinDelegatedStake
{ {
// this contract can receive ETH // this contract can receive ETH

View File

@@ -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);
}
} }
} }

View File

@@ -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);
}
}
} }

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;
} }