# [Client] Delegator As-Is Math Spec
###### tags: `theGraph` `ClientDoc`
## 1 - Overview
We review the delegator subsystems incentive model from [Official document on delegation reward](https://thegraph.com/docs/network#delegating) and [Solidity Code on staking](https://github.com/graphprotocol/contracts/tree/master/contracts/staking).
*Note: StakeV1 is deprecated older version.
We here produce lucid chart on current delegator subsystems, code citation, and
```
Workflow:
Document -> Lucid chart
Solidity code -> Math Spec (State, mechanism, math) -> cadCAD model
```
## 2 - Graph Doc on Delegation reward
Delegators earn reward through sharing the query fee and indexing reward of indexers. According to [Official document on delegation reward](https://thegraph.com/docs/network#delegating).

In the most simplified form, value dependencies are:
```flow
op1=>operation: Delegator i
op2=>operation: indexer j with delegation pool J
op4=>operation: collection of subgraph indexing/allocation + system inflation
op1->op2->op4
```
## 3 - Variables (States) for Delegator reward
### 3.1 code
from [staking data solidity](https://github.com/graphprotocol/contracts/blob/master/contracts/staking/IStakingData.sol)
```
struct DelegationPool {
uint32 cooldownBlocks; // Blocks to wait before updating parameters
uint32 indexingRewardCut; // in PPM
uint32 queryFeeCut; // in PPM
uint256 updatedAtBlock; // Block when the pool was last updated
uint256 tokens; // Total tokens as pool reserves
uint256 shares; // Total shares minted in the pool
mapping(address => Delegation) delegators; // Mapping of delegator => Delegation
}
/**
* @dev Individual delegation data of a delegator in a pool.
*/
struct Delegation {
uint256 shares; // Shares owned by a delegator in the pool
uint256 tokensLocked; // Tokens locked for undelegation
uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn
}
```
```
// -- Delegation --
// Set the delegation capacity multiplier defined by the delegation ratio
// If delegation ratio is 100, and an Indexer has staked 5 GRT,
// then they can use up to 500 GRT from the delegated stake
uint32 public delegationRatio;
// Time in blocks an indexer needs to wait to change delegation parameters
uint32 public delegationParametersCooldown;
// Time in epochs a delegator needs to wait to withdraw delegated stake
uint32 public delegationUnbondingPeriod; // in epochs
// Percentage of tokens to tax a delegation deposit
// Parts per million. (Allows for 4 decimal points, 999,999 = 99.9999%)
uint32 public delegationTaxPercentage;
// Delegation pools : indexer => DelegationPool
mapping(address => IStakingData.DelegationPool) public delegationPools;
```
### 3.2 Interpretation
### Token State
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Total GRT |graphtokens|`GRT`|$GRT$|
|Query Fee Revenue Stream |QueryRewards|`query_revenue`|$R_q$|
|Indexing Reward Stream |IndexingRewards|`indexing_revenue`|$R_i$|
### System Parameters
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Delegation tax (tax percentage to burn when delegated funds are deposited)|DelegationTaxPercentage|`delegation_tax`|$\beta_{del}$|
|unbonding period|_delegationUnbondingPeriod|`unbonding_timeblock`| $t_{unbonding}$|
|delegation ratio| _delegationRatio|`delegation_leverage`|$\lambda_{del}$
### Delegation Pool of Indexer State
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Address of Indexer|indexer id|`id_indexer`|$\mathcal{A}_{ind}$|
|Total delegated Stake|StakeDelegated|`pool_delegated_stake`|$D$
|Total Locked delegated Stake|StakeDelegatedLocked| `pool_locked_stake` | $L$|
|Total shares minted| shares | `shares`| $S$
|Mapping of all delegators address|delegators|`delegators`|$f: {A}_{del} \rightarrow \mathcal{A}_{ind}$|
|[Indexer stake](https://github.com/graphprotocol/contracts/blob/master/contracts/staking/Staking.sol)|??|`indexer_stake`|$S_i$|
|Indexer Revenue Cut |Indexer Cut|`indexer_revenue` | $I_r$|
<!-- |Total withdrawn delegated Stake|StakeDelegatedWithdrawn|`withdrawn_delegatedstake??` | $W$| -->
### Delegation Pool of Indexer Parameters
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Indexer's index reward cut| indexingRewardCut| `index_cut`|$\alpha$|
|Indexer's query Fee Cut| queryFeeCut|`query_fee_cut`| $\phi$|
|Time to cool down (measured in block number)|cooldownBlocks| `block_time`|$t_{cooldown}$|
|Last update time|updatedAtBlock|`last_update_T`|$t_{update}$|
### Delegator State
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Address of Delegator|delegator id|`id`|$\mathcal{A}_{del}$|
|Shares owned by delegator in a pool|shares|`shares`|$s$|
|Tokens locked in delegation|delegated_tokens|`delegated_tokens` |$d$|
|Tokens locked in undelegation|tokensLocked|`undelegated_tokens` |$l$|
|Freeze time (measure in block time)|tokensLockedUntil|`locked_until`|$t_{freeze}$|
<!-- |Tokens withdrawn from delegation||`withdrawn_tokens` |$w$| -->
### Agent State
|Name|Solidity name|cadCAD name|Symbol|
|-----|----|---|---|
|Tokens held by an agent that are delegator, prospective, or former delegator ||`holdings` |$h$|
### 3.3 Abstraction of the rest of the system
- allocation: delegator share indexer's revenue of query fee, we abstract away this process by assume allocation happens and generate query fee at random rate(?) (see below?)
- inflation/rebate: delegator share system inflation through indexing reward, which was introduced by epoch
- curation: ignored as is not directly related to delegator subsystems
- slashing: ignored(?) as is not directly related to delegator subsystems (? or maybe randomly create bad subgraph as stress test?)
#### Indexing Allocation Process
[allocation solidity code](https://github.com/graphprotocol/contracts/blob/master/contracts/staking/StakingStorage.sol), note that
- delegator's stake can be use with a numberx leverage, specified by `delegationRatio`
## 4 - Actions (Polices)
delegators can `delegate`, `undelegate`, `withdraw`(after cool down), `collectDelegationQueryRewards`, `collectDelegationIndexingRewards`
```
/**
* @dev Emitted when `delegator` delegated `tokens` to the `indexer`, the delegator
* gets `shares` for the delegation pool proportionally to the tokens staked.
*/
event StakeDelegated(
address indexed indexer,
address indexed delegator,
uint256 tokens,
uint256 shares
);
/**
* @dev Emitted when `delegator` undelegated `tokens` from `indexer`.
* Tokens get locked for withdrawal after a period of time.
*/
event StakeDelegatedLocked(
address indexed indexer,
address indexed delegator,
uint256 tokens,
uint256 shares,
uint256 until
);
/**
* @dev Emitted when `delegator` withdrew delegated `tokens` from `indexer`.
*/
event StakeDelegatedWithdrawn(
address indexed indexer,
address indexed delegator,
uint256 tokens
);
```
```
/**
* @dev Delegate tokens to an indexer.
* @param _indexer Address of the indexer to delegate tokens to
* @param _tokens Amount of tokens to delegate
* @return Amount of shares issued of the delegation pool
*/
function delegate(address _indexer, uint256 _tokens)
external
override
notPartialPaused
returns (uint256)
{
address delegator = msg.sender;
// Transfer tokens to delegate to this contract
require(graphToken().transferFrom(delegator, address(this), _tokens), "!transfer");
// Update state
return _delegate(delegator, _indexer, _tokens);
}
/**
* @dev Undelegate tokens from an indexer.
* @param _indexer Address of the indexer where tokens had been delegated
* @param _shares Amount of shares to return and undelegate tokens
* @return Amount of tokens returned for the shares of the delegation pool
*/
function undelegate(address _indexer, uint256 _shares)
external
override
notPartialPaused
returns (uint256)
{
return _undelegate(msg.sender, _indexer, _shares);
}
/**
* @dev Withdraw delegated tokens once the unbonding period has passed.
* @param _indexer Withdraw available tokens delegated to indexer
* @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address
*/
function withdrawDelegated(address _indexer, address _delegateToIndexer)
external
override
notPaused
returns (uint256)
{
return _withdrawDelegated(msg.sender, _indexer, _delegateToIndexer);
}
/**
* @dev Allocate available tokens to a subgraph deployment.
* @param _subgraphDeploymentID ID of the SubgraphDeployment where tokens will be allocated
* @param _tokens Amount of tokens to allocate
* @param _allocationID The allocation identifier
* @param _metadata IPFS hash for additional information about the allocation
* @param _proof A 65-bytes Ethereum signed message of `keccak256(indexerAddress,allocationID)`
*/
```
```
/**
* @dev Delegate tokens to an indexer.
* @param _delegator Address of the delegator
* @param _indexer Address of the indexer to delegate tokens to
* @param _tokens Amount of tokens to delegate
* @return Amount of shares issued of the delegation pool
*/
function _delegate(
address _delegator,
address _indexer,
uint256 _tokens
) private returns (uint256) {
// Only delegate a non-zero amount of tokens
require(_tokens > 0, "!tokens");
// Only delegate to non-empty address
require(_indexer != address(0), "!indexer");
// Only delegate to staked indexer
require(stakes[_indexer].hasTokens(), "!stake");
// Get the delegation pool of the indexer
DelegationPool storage pool = delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Collect delegation tax
uint256 delegationTax = _collectTax(graphToken(), _tokens, delegationTaxPercentage);
uint256 delegatedTokens = _tokens.sub(delegationTax);
// Calculate shares to issue
uint256 shares =
(pool.tokens == 0)
? delegatedTokens
: delegatedTokens.mul(pool.shares).div(pool.tokens);
// Update the delegation pool
pool.tokens = pool.tokens.add(delegatedTokens);
pool.shares = pool.shares.add(shares);
// Update the delegation
delegation.shares = delegation.shares.add(shares);
emit StakeDelegated(_indexer, _delegator, delegatedTokens, shares);
return shares;
}
/**
* @dev Undelegate tokens from an indexer.
* @param _delegator Address of the delegator
* @param _indexer Address of the indexer where tokens had been delegated
* @param _shares Amount of shares to return and undelegate tokens
* @return Amount of tokens returned for the shares of the delegation pool
*/
function _undelegate(
address _delegator,
address _indexer,
uint256 _shares
) private returns (uint256) {
// Can only undelegate a non-zero amount of shares
require(_shares > 0, "!shares");
// Get the delegation pool of the indexer
DelegationPool storage pool = delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Delegator need to have enough shares in the pool to undelegate
require(delegation.shares >= _shares, "!shares-avail");
// Withdraw tokens if available
if (getWithdraweableDelegatedTokens(delegation) > 0) {
_withdrawDelegated(_delegator, _indexer, address(0));
}
// Calculate tokens to get in exchange for the shares
uint256 tokens = _shares.mul(pool.tokens).div(pool.shares);
// Update the delegation pool
pool.tokens = pool.tokens.sub(tokens);
pool.shares = pool.shares.sub(_shares);
// Update the delegation
delegation.shares = delegation.shares.sub(_shares);
delegation.tokensLocked = delegation.tokensLocked.add(tokens);
delegation.tokensLockedUntil = epochManager().currentEpoch().add(delegationUnbondingPeriod);
emit StakeDelegatedLocked(
_indexer,
_delegator,
tokens,
_shares,
delegation.tokensLockedUntil
);
return tokens;
}
/**
* @dev Withdraw delegated tokens once the unbonding period has passed.
* @param _delegator Delegator that is withdrawing tokens
* @param _indexer Withdraw available tokens delegated to indexer
* @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address
*/
function _withdrawDelegated(
address _delegator,
address _indexer,
address _delegateToIndexer
) private returns (uint256) {
// Get the delegation pool of the indexer
DelegationPool storage pool = delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Validation
uint256 tokensToWithdraw = getWithdraweableDelegatedTokens(delegation);
require(tokensToWithdraw > 0, "!tokens");
// Reset lock
delegation.tokensLocked = 0;
delegation.tokensLockedUntil = 0;
emit StakeDelegatedWithdrawn(_indexer, _delegator, tokensToWithdraw);
// -- Interactions --
if (_delegateToIndexer != address(0)) {
// Re-delegate tokens to a new indexer
_delegate(_delegator, _delegateToIndexer, tokensToWithdraw);
} else {
// Return tokens to the delegator
require(graphToken().transfer(_delegator, tokensToWithdraw), "!transfer");
}
return tokensToWithdraw;
}
/**
* @dev Collect the delegation rewards for query fees.
* This function will assign the collected fees to the delegation pool.
* @param _indexer Indexer to which the tokens to distribute are related
* @param _tokens Total tokens received used to calculate the amount of fees to collect
* @return Amount of delegation rewards
*/
function _collectDelegationQueryRewards(address _indexer, uint256 _tokens)
private
returns (uint256)
{
uint256 delegationRewards = 0;
DelegationPool storage pool = delegationPools[_indexer];
if (pool.tokens > 0 && pool.queryFeeCut < MAX_PPM) {
uint256 indexerCut = uint256(pool.queryFeeCut).mul(_tokens).div(MAX_PPM);
delegationRewards = _tokens.sub(indexerCut);
pool.tokens = pool.tokens.add(delegationRewards);
}
return delegationRewards;
}
/**
* @dev Collect the delegation rewards for indexing.
* This function will assign the collected fees to the delegation pool.
* @param _indexer Indexer to which the tokens to distribute are related
* @param _tokens Total tokens received used to calculate the amount of fees to collect
* @return Amount of delegation rewards
*/
function _collectDelegationIndexingRewards(address _indexer, uint256 _tokens)
private
returns (uint256)
{
uint256 delegationRewards = 0;
DelegationPool storage pool = delegationPools[_indexer];
if (pool.tokens > 0 && pool.indexingRewardCut < MAX_PPM) {
uint256 indexerCut = uint256(pool.indexingRewardCut).mul(_tokens).div(MAX_PPM);
delegationRewards = _tokens.sub(indexerCut);
pool.tokens = pool.tokens.add(delegationRewards);
}
return delegationRewards;
}
```
## 5 - Rewards (Mechanism)
For the reference of `policy` see [Staking Solidity Code](https://github.com/graphprotocol/contracts/blob/master/contracts/staking/StakingV1.sol), for `variables` see above.
### 5.1 Indexing Rewards
```
function _collectDelegationIndexingRewards(address _indexer, uint256 _tokens)
private
returns (uint256)
{
uint256 delegationRewards = 0;
DelegationPool storage pool = delegationPools[_indexer];
if (pool.tokens > 0 && pool.indexingRewardCut < MAX_PPM) {
uint256 indexerCut = uint256(pool.indexingRewardCut).mul(_tokens).div(MAX_PPM);
delegationRewards = _tokens.sub(indexerCut);
pool.tokens = pool.tokens.add(delegationRewards);
}
return delegationRewards;
}
```
For an incoming stream of indexing rewards $R_i$, the indexer earns proportional to its cut:
$I_r = I_r + R_i \cdot \alpha$
and the remaining tokens are added back into the delegation pool:
$$ D^+ = D + R_i \cdot (1- \alpha)$$
### 5.2 Query Fee
```
function _collectDelegationQueryRewards(address _indexer, uint256 _tokens)
private
returns (uint256)
{
uint256 delegationRewards = 0;
DelegationPool storage pool = delegationPools[_indexer];
if (pool.tokens > 0 && pool.queryFeeCut < MAX_PPM) {
uint256 indexerCut = uint256(pool.queryFeeCut).mul(_tokens).div(MAX_PPM);
delegationRewards = _tokens.sub(indexerCut);
pool.tokens = pool.tokens.add(delegationRewards);
}
return delegationRewards;
}
```
For an incoming stream of query fees $R_q$, the indexer earns proportional to its cut:
$I_r^+ = I_r + R_q \cdot \phi$
and the remaining tokens are added back into the delegation pool:
$$ D^+ = D + R_q \cdot (1- \phi)$$
## 6 - Update (Partial State)

### Delegate Action State Update
Agent delegates some amount $\Delta d$ tokens from their wallet, containing their holdings $h$ tokens:
$h^+ = h - \Delta d$
**Note: This is not part of the data model
$d^+ = d + \Delta d \cdot (1-\beta_{del})$
**Note: Delegator does not have a quantity of delegated tokens, they only have quantity of shares ($s$)
The delegated tokens are taxed:
$tax = \Delta d \cdot \beta_{del}$
Tax is burned:
$GRT^+ = GRT - tax$
Compute $\Delta s$ to be awarded to the delegator and added to the indexer pool:
**Note: $\Delta s$ is calculated using $D$, not $D^+$
$$\Delta s = \frac{\Delta d \cdot (1-\beta_{del})}{D} \cdot S$$
The delegated tokens are added to the pool held by the indexer:
$D^+ = D + \Delta d \cdot (1-\beta_{del})$
$S^+ = S + \Delta s$
$s^+ = s + \Delta s$
Note for backing out tax:
$$tax =\Delta d - \frac{\Delta s \cdot D}{S}$$
### Undelegate Action State Update
Shares $\Delta s \ge s$ which may be requested in order to unbond delegated tokens.
$$\Delta l = \Delta s \frac{D}{S} $$
This results in the following flows:
$$s^+ = s - \Delta s\\
l_{t_{freeze}}^+ = \Delta l \\
l^+ = \Delta l + \sum{l_{t_{freeze}}} \\
d^+ = d - \Delta l\\
S^+ = S- \Delta s\\
D^+ = D- \Delta l\\
L^+ = L + \Delta l
$$
**Note: Delegated tokens $(d)$ at the delegator level does not actually exist, so that part is not true.
### Withdraw Action State Update
Withdraw tokens $\Delta w$ of withdrawable locked tokens $l_{t_{freeze}} \forall t \ge t_{freeze}$ .
While locked tokens $L$ are not pre-separated into a withdrawable state when the freeze time has passed, we can add the additional $W$ token state, though not necessary.
This results in the following flows from state:
$$
l^+ = l - \Delta w\\
h^+ = h + \Delta w\\
L^+ = L- \Delta w\\
$$
<!-- $$
w^+ = w + \Delta w\\
W^+ = W + \Delta w\\
$$ -->
# Reference
[Delegation Subsystem Problem Statement V0](https://hackmd.io/@Jiajia20/ByTvhFZG_)
[Delgation Subsystem As-Is V0](https://hackmd.io/@Jiajia20/rk_F8j6bu)
[Delgation Subsystem To-Be V0](https://hackmd.io/@Jiajia20/HJrq-CTmd)
[Delegation ReadMe](https://hackmd.io/@Jiajia20/B1IyVv6E_)