---
# System prepended metadata

title: 'Technical Interview: Centrifuge <> William'

---

# Technical Interview: Centrifuge <> William 

[toc]

## High level description

* Based on extension to paper about scalable block rewards under assumption of constant stake
* Extension solved for changing stakes for a single currency
* Implementation scales by having multiple currencies
* Base idea: Pull based approach which enables accounting reward for one currency in O(1) instead of iterating over all stakers
* By keeping track of stake changes, can derive rewards for epoch from current stake and sum of rates

## Liquidity Rewards requirements

* Complexity: O(1) computation for rewarding a group (sacrifice space to have better time complexity)
    * Still `O(|Groups * MaxCurrenciesPerGroup|)` by iterating over Groups on epoch change
* `StakeAccount --> Currency --> Group --> Rate`
  * Different reward rates for different groups
  * Stakes can be moved from one group to another
* Min duration to claim reward = 1 epoch
    * deferred + liquidity rewards = 2 epochs
* Min duration to unlock =  0
    * block rewards: 
        > "The money can not be unstaked"


## Code hierarchy and structure

Copy pasted from spec:
```plantuml
@startuml

skinparam component {
  BackgroundColor<<utility>> PaleGoldenRod
  BackgroundColor<<with extrinsics>> SkyBlue 
}

skinparam storage {
  BackgroundColor GoldenRod 
}

skinparam rectangle {
  BackgroundColor Orange
}

storage "Common types/functions" as common
rectangle "Rewards trait (interface)" as rewards_i
[pallet-rewards] <<utility>> as rewards
[pallet-deferred-rewards] <<utility>> as deferred_rewards 
[pallet-liquidity-rewards] <<with extrinsics>> as liquidity
[pallet-block-rewards] <<with extrinsics>> as block

rewards ..> rewards_i : implements
deferred_rewards ..> rewards_i : implements
rewards_i <-- liquidity : use
rewards_i <-- block : use 
rewards -up-> common : use 
deferred_rewards -up-> common : use 

@enduml
```

## Audit/review

### Runtime API

* Reward calculaion should be exposed via runtime API (not as extrinsic!)
* Best practice for claiming multiple currencies would be to handle via runtime API and batch call (see [my comment on PR #944](https://github.com/centrifuge/centrifuge-chain/pull/944#discussion_r999014384)):
    1. Do runtime API call to get vec of staked currencies
    2. Do batch call for claiming currencies

### Loopholes?
* `pallet-deferred-rewards` substitution to get rid of storing last epoch's rpt seems incorrect, see the bottom of [S15 Appendix A](https://docs.google.com/presentation/d/1EwEcsXVc5HLyULQehz1L-0XMOf7KGuvQWAYVgKcS2Qk/edit#slide=id.g1613d09df7c_0_816):

```diff
- rewarded_amount * rpt == rewarded_amount * rpt-1 - rewarded_amount * rate
+ rewarded_amount * rpt == rewarded_amount * rpt-1 + rewarded_amount * rate
```

$$
\begin{align}
rpt_n & = \sum_{k=1}^{n}\frac{reward_k}{T_k} = rpt_{n-1} + rate \\
& \stackrel{\text{slide 15}}{\ne} rpt_{n-1} - rate
\\ \Leftrightarrow rpt_{n-1} & = rpt_n - rate
\\
\\ \Rightarrow rewarded\_amount * rpt_{n-1}& = rewarded\_amount * ( rpt_n + rate)
\end{align}
$$

* Also sign of `restitution[i]` in `claim` call by implication (see [Appendix A S16](https://docs.google.com/presentation/d/1EwEcsXVc5HLyULQehz1L-0XMOf7KGuvQWAYVgKcS2Qk/edit#slide=id.g1613d09df7c_0_861)):

```diff
- Base::claim_reward(i) - rewarded_stake[i] * rate + restitution[i]
+ Base::claim_reward(i) - rewarded_stake[i] * rate - restitution[i]
```

### Future 
* Maximum number of groups for `pallet-liquidity-rewards` and `pallet-block-rewards` critical until WeightsV2, theoretically could exceed PoV size by iterating over BoundedVec in inherent during epoch change (but practically not)
    * However, Collator size seems to be planned to stay 16
    * Liquidity pools should also stay reasonably small

## How to incentivize longer staking periods?

_Disclaimer: Impracticable, only makes sense in [V2](#V2). However, the majority of the concept of V1 still applies. Did not have time to combine V1 and V2._

_The rewards shall not be paid from the same pot as the epoch rewards. I recommend using the Treasury. However, minting additional rewards into another pot is also an option, but this increases inflation._

### V1

* Add new field `last_claimed` to `StakeAccount` to account for the last epoch in which a reward was claimed

```rust
struct StakeAccount {
    stake: Balance,        
    reward_tally: SignedBalance,
    currency_version: u32,
    // new, init with 0
    last_claimed: u32
}
```
* When claiming a reward, the difference of `current_epoch` and `last_claimed` determines an additional reward rate for longterm holding
    * On success (e.g. not zero), `reward * longterm_rate` will be **awarded from a different pot (e.g. Treasury)** to the account which receives rewards in `claim_reward` call
    * For every claim, bump `last_claimed` to current epoch
* Add storage item `LongtermRewards`:

```rust
struct LongtermReward {
    rate: Rate,
    threshold: u32,
}

type LongtermRewards = Map<CurrencyId, LongtermReward>
```

```rust
// Add to `Reward` trait and StakeAccount
// Internally called by `claim_reward`
//
// Iff non-zero rate, `claim_reward` will mint additional
// `reward * longterm_rate` tokens for AccountId from Treasury
fn compute_longterm_reward_rate(currency_id: currency_id, num_epochs: u32) -> Rate {
	let longterm_reward =
		LongtermRewards::<T>::get(currency_id).ok_or(Error::<T>::CurrenyWithoutLongtermReward)?;

	if num_epochs > longterm_reward.threshold {
		longterm_reward.rate
	} else {
		Rate::zero()
	}
}
```
```rust
fn claim_reward(account_id: AccountId, currency_id: CurrencyId) -> DispatchResult {
	// ...

	// Inside StakeAccount::<T>::mutate ...
		let longterm_reward: Self::Balance =
			compute_longterm_reward_rate(currency_id, staker.last_claimed) * reward;
		let treasury = T::LongtermPot::get().into_account_truncating();
		if longterm_reward.is_zero() && T::Currency::free_balance(treasury) > longterm_reward {
			T::Currency::withdraw(
				treasury,
				longterm_reward,
				WithdrawReasons::TRANSFER,
				ExistenceRequirement::KeepAlive,
			)?;
		}
		staker.last_claimed = current_epoch;

	// ...
	Ok(())
}
```

* Expose set functions in trait `Rewards` which require admin origin in `pallet-liquidity-rewards` and `pallet-block-rewards` 

```rust
fn set_longterm_threshold(currency_id: CurrencyId, threshold: u32);

fn set_longterm_rate(currency_id: CurrencyId, rate: Rate);
```

**Q: Period length and reward rate = global setting in `pallet-rewards` or associated to specific Currency/Group?**
* If group: Potential loss/gain when moving Currency to new group
* If currency: Inconsistent with reward rates set for groups
* If global: Inflexible


### Downsides of Proposal: 

* Users are disincentivized to call `claim_rewards` before reaching longterm threshold to not miss out on longterm rewards
* However: Longterm rewards could be regarded as opportunity cost of missing out of liquidating/efficiently using earlier received rewards

### V2

* Add a second threshold of type `Balance` to `LongtermReward`
* Only reset `last_claimed` (now moved to a new DoubleStorageMap, see below) if any of the following happens:
  * User claimed a longterm reward or 
  * User staked below the threshold in `withdraw_stake` or
  * User claimed with current stake below threshold
* Store sum of claimed rewards which will be multiplied with the longterm rate upon claiming longterm rewards
  * This is reset, when the last epoch counter for the user is reset 

```rust
struct StakeAccount {
    stake: Balance,        
    reward_tally: SignedBalance,
    currency_version: u32,
    // new, init with 0
    longterm_reward_sum: Balance,
}
```

```rust
struct LongtermReward {
    // Reward rate
    rate: Rate,
    // Minimum required number of epochs in which user must hold min_stake
    min_duration: u32,
    // Minimum required stake which needs to be hold over min_duration
    min_stake: Balance,
}
```

Instead of polluting `StakeAccount` with `last_claimed` epoch, add a new `DoubleStorageMap` to store `last_claimed` of V1, e.g. the last epoch in which the longterm reward was claimed or the stake fell below the `min_stake` threshold. 


```rust
type StakeAccountsEpoch = DoubleMap<AccountId, CurrencyId, u32>;
```

#### Downsides

* Have to educate and warn users about risks of staking below threshold
* When `LongtermReward.min_stake`...
  * Either have to accept some accounts might be below threshold or
  * Have to migrate `StakeAccounts` (no-go!)

____

## Appendix

### WIP: Prove deferred potential incorrect math for Substitution in Appendix (restitution)
* Assume we are in epoch `n` and have a user who first claims claims rewards, then stakes amount X, then unstakes amount Y (!= X) in the same epoch such that `rewarded` in `unstake` is not zero

* In the the next epoch `n+1`, stake is untouched and claim is called again
* Same for the following epoch `n+2`
* Let's compare the reward and tally calculations of Slide 15 with Slide 16

### 
$$
\begin{align}
rpt_n & = \sum_{k=1}^{n}\frac{reward_k}{T_k} = rpt_{n-1} + rate \\
& \stackrel{\text{slide 15}}{\ne} rpt_{n-1} - rate
\\ \Leftrightarrow rpt_{n-1} & = rpt_n - rate \tag{1}
\\ \Rightarrow rewarded\_amount * rpt_{n-1}& = rewarded\_amount * ( rpt_n + rate)
\end{align}
$$

### V1: Using "old rate" from [S15 Appendix A](https://docs.google.com/presentation/d/1EwEcsXVc5HLyULQehz1L-0XMOf7KGuvQWAYVgKcS2Qk/edit#slide=id.g1613d09df7c_0_816)

TODO: Why does this resolve to `-rewarded * rate`?

$$
\begin{align}
  tally[i] & = stake[i] * rpt - rewarded\_stake[i] * rate + rewarded\_amount * rate
  \\ reward[i] & = stake[i] * rpt - tally[i] - rewarded\_stake[i] * rate
  \\ & = stake[i] * rpt - stake[i] * rpt + rewarded\_stake[i] * rate - rewarded\_amount * rate 
  \\ & - rewarded\_stake[i] * rate
  \\ & = - rewarded * rate \tag{V1}
\end{align}
$$

### V2: Using restitution from [S16 Appendix A](https://docs.google.com/presentation/d/1EwEcsXVc5HLyULQehz1L-0XMOf7KGuvQWAYVgKcS2Qk/edit#slide=id.g1613d09df7c_0_861)


$$
\begin{align}
  tally[i] & = stake[i] * rpt
 \\ restitution[i] & = -rewarded\_stake[i] * rate + rewarded\_amount * rate 
  \\ reward[i] & = stake[i] * rpt - tally[i] - rewarded\_stake[i] * rate + restitution[i]
  \\ & = stake[i] * rpt - stake[i] * rpt - rewarded\_stake[i] * rate 
  \\ & - rewarded\_stake[i] * rate + rewarded\_amount * rate
  \\ & = -2 * rewared\_stake[i] + rewarded\_amount
  \\ & \stackrel{V1}{\neq}  - rewarded * rate
  \\
  \\ \Rightarrow reward[i] & \stackrel{!}{=} stake[i] * rpt - tally[i] - rewarded\_stake[i] * rate \color{red}{-} restitution[i]
\end{align}
$$