Skip to content

Commit c24b9d3

Browse files
authored
feat(protocol-contracts): python notebook for APR and APY computations (#1379)
* feat(protocol-contracts): python notebook for APR and APY computations * chore(protocol-contracts): optimize computation * ci(common): propagate actionlint version * ci(common): update actionlint version
1 parent baccce5 commit c24b9d3

File tree

2 files changed

+188
-1
lines changed

2 files changed

+188
-1
lines changed

.github/workflows/common-pull-request-lint.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55

66
env:
7-
ACTIONLINT_VERSION: 1.6.27
7+
ACTIONLINT_VERSION: 1.7.8
88

99
permissions: {}
1010

@@ -25,6 +25,7 @@ jobs:
2525
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
2626
with:
2727
flags: "-ignore SC2001"
28+
version: ${{ env.ACTIONLINT_VERSION }}
2829

2930
- name: Ensure SHA pinned actions
3031
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@64418826697dcd77c93a8e4a1f7601a1942e57b5 # v3.0.18
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "df0d74f3-487b-4f31-9750-fd9f7d572932",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"import math"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": 2,
16+
"id": "4cd2e6ad-55c0-431c-971c-9a9208904f5c",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"SECONDS_PER_YEAR = 365 * 24 * 60 * 60"
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": 3,
26+
"id": "a1322f0c-bf45-438b-aa36-eafce130147f",
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"def apr_to_apy(apr: float, compounds: int) -> float:\n",
31+
" if compounds <= 0:\n",
32+
" return apr\n",
33+
" periodic = apr / compounds\n",
34+
" return (1 + periodic) ** compounds - 1"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": 4,
40+
"id": "d86910eb-2a7d-423a-be04-3e6018cc373f",
41+
"metadata": {},
42+
"outputs": [],
43+
"source": [
44+
"# reward_rate : same value as given by `ProtocolStaking:rewardRate()`\n",
45+
"# num_tokens_per_pool: same values as given by `ProtocolStaking:balanceOf(address(OperatorStaking))` for every eligible OperatorStaking\n",
46+
"# owner_fees_per_pool: same values as given by `OperatorRewarder:ownerFeeBasisPoints()` for every corresponding OperatorRewarder\n",
47+
"def compute_native_APR(reward_rate: int, num_tokens_per_pool: list[int], owner_fees_per_pool: list[int]) -> list[float]:\n",
48+
" \"\"\"\n",
49+
" :param reward_rate: in amount of tokens per second according to the EVM, i.e reward_rate of 1e18 <=> 1 ZAMA token per second\n",
50+
" :param num_tokens_per_pool: list of amount of tokens deposited in each OperatorStaking pool\n",
51+
" :param owner_fees_per_pool: list of owner fees, one per OperatorStaking pool, in basis points, i.e owner_fee of 10000 <=> 100% fees \n",
52+
" \n",
53+
" :return: a float corresponding to the PERCENTAGE of _native_ APR i.e not considering direct token donations to OperatorStaking.\n",
54+
" It assumes reward_rate, num_tokens_per_pool and list_owner_fees are constants, to get an instantaneous APR value.\n",
55+
" \"\"\"\n",
56+
" assert len(num_tokens_per_pool)==len(owner_fees_per_pool), \"`num_tokens_per_pool` and `owner_fees_per_pool` length mismatch\"\n",
57+
" assert all(0 <= fee <= 10000 for fee in owner_fees_per_pool), \"owner fees must be in [0, 10000]\"\n",
58+
" assert all(0 <= num_token for num_token in num_tokens_per_pool), \"number of tokens must be non-negative\"\n",
59+
" assert reward_rate >=0, \"`reward_rate` must be non-negative\"\n",
60+
"\n",
61+
" weights = [int(math.sqrt(x)) for x in num_tokens_per_pool]\n",
62+
" total_weight = sum(weights)\n",
63+
" fee_factors = [1-fee/10000 for fee in owner_fees_per_pool]\n",
64+
" reward_rate_per_sec_per_pool = [reward_rate * (pool_weight / total_weight) for pool_weight in weights]\n",
65+
" pool_aprs = []\n",
66+
" for i in range(len(fee_factors)):\n",
67+
" net_reward_per_sec_per_pool = reward_rate_per_sec_per_pool[i] * fee_factors[i]\n",
68+
" pool_aprs.append((net_reward_per_sec_per_pool / num_tokens_per_pool[i]) * SECONDS_PER_YEAR * 100)\n",
69+
" \n",
70+
" return pool_aprs"
71+
]
72+
},
73+
{
74+
"cell_type": "code",
75+
"execution_count": 5,
76+
"id": "4802969a-2f8d-4a49-9fa3-8f293a719865",
77+
"metadata": {},
78+
"outputs": [],
79+
"source": [
80+
"def compute_native_APY(\n",
81+
" reward_rate: int,\n",
82+
" num_tokens_per_pool: list[int],\n",
83+
" owner_fees_per_pool: list[int],\n",
84+
" compounds: int,\n",
85+
") -> list[float]:\n",
86+
" \"\"\"\n",
87+
" Same inputs as compute_native_APR plus `compounds` per year.\n",
88+
" Returns APY as PERCENT (e.g. 5.1 for 5.1%).\n",
89+
" \"\"\"\n",
90+
" aprs_percent = compute_native_APR(reward_rate, num_tokens_per_pool, owner_fees_per_pool)\n",
91+
" apys_percent = [\n",
92+
" apr_to_apy(apr / 100.0, compounds) * 100.0 # convert % -> fraction -> APY -> %\n",
93+
" for apr in aprs_percent\n",
94+
" ]\n",
95+
" return apys_percent"
96+
]
97+
},
98+
{
99+
"cell_type": "code",
100+
"execution_count": 6,
101+
"id": "ea092cbf-f51e-4960-ab2a-a30effd7b7b9",
102+
"metadata": {},
103+
"outputs": [
104+
{
105+
"name": "stdout",
106+
"output_type": "stream",
107+
"text": [
108+
"APRs in % for each pool : [5.991840000000001, 5.991840000000001, 5.991840000000001, 5.991840000000001, 5.991840000000001]\n",
109+
"APYs in % for same pools : [6.174468300782898, 6.174468300782898, 6.174468300782898, 6.174468300782898, 6.174468300782898]\n"
110+
]
111+
}
112+
],
113+
"source": [
114+
"print(\"APRs in % for each pool :\" , compute_native_APR(1e18, 5*[100_000_000*1e18], 5*[500]))\n",
115+
"print(\"APYs in % for same pools :\", compute_native_APY(1e18, 5*[100_000_000*1e18], 5*[500], 365))"
116+
]
117+
},
118+
{
119+
"cell_type": "code",
120+
"execution_count": 7,
121+
"id": "5318b359-36d3-4eec-8b45-d509880699ff",
122+
"metadata": {},
123+
"outputs": [
124+
{
125+
"name": "stdout",
126+
"output_type": "stream",
127+
"text": [
128+
"APRs in % for each pool : [6.307199999999999, 5.991840000000001, 5.991840000000001, 5.991840000000001, 5.991840000000001]\n",
129+
"APYs in % for same pools : [6.509772041144912, 6.174468300782898, 6.174468300782898, 6.174468300782898, 6.174468300782898]\n"
130+
]
131+
}
132+
],
133+
"source": [
134+
"reward_rate = 1e18\n",
135+
"num_tokens_per_pool = 5*[100_000_000*1e18]\n",
136+
"owner_fees_per_pool = [0] + 4*[500] # first pool owner takes no fee, other 4 owners take 5%\n",
137+
"print(\"APRs in % for each pool :\" , compute_native_APR(reward_rate, num_tokens_per_pool, owner_fees_per_pool))\n",
138+
"print(\"APYs in % for same pools :\", compute_native_APY(reward_rate, num_tokens_per_pool, owner_fees_per_pool, 365))"
139+
]
140+
},
141+
{
142+
"cell_type": "code",
143+
"execution_count": 8,
144+
"id": "9226d508-6b2a-470b-a7ca-10d0c6d3b170",
145+
"metadata": {},
146+
"outputs": [
147+
{
148+
"name": "stdout",
149+
"output_type": "stream",
150+
"text": [
151+
"APRs in % for each pool : [73.07121951219511, 7.307121951219512, 7.307121951219512, 7.307121951219512, 7.307121951219512]\n",
152+
"APYs in % for same pools : [107.5042726922077, 7.579928500245625, 7.579928500245625, 7.579928500245625, 7.579928500245625]\n"
153+
]
154+
}
155+
],
156+
"source": [
157+
"reward_rate = 1e18\n",
158+
"num_tokens_per_pool = [1_000_000*1e18] + 4*[100_000_000*1e18] # first OperatorStaking pool has -99% less tokens staked than the other 4 pools\n",
159+
"owner_fees_per_pool = 5*[500] # first pool owner takes no fee, other 4 owners take 5%\n",
160+
"print(\"APRs in % for each pool :\" , compute_native_APR(reward_rate, num_tokens_per_pool, owner_fees_per_pool))\n",
161+
"print(\"APYs in % for same pools :\", compute_native_APY(reward_rate, num_tokens_per_pool, owner_fees_per_pool, 365))"
162+
]
163+
}
164+
],
165+
"metadata": {
166+
"kernelspec": {
167+
"display_name": "Python 3 (ipykernel)",
168+
"language": "python",
169+
"name": "python3"
170+
},
171+
"language_info": {
172+
"codemirror_mode": {
173+
"name": "ipython",
174+
"version": 3
175+
},
176+
"file_extension": ".py",
177+
"mimetype": "text/x-python",
178+
"name": "python",
179+
"nbconvert_exporter": "python",
180+
"pygments_lexer": "ipython3",
181+
"version": "3.11.10"
182+
}
183+
},
184+
"nbformat": 4,
185+
"nbformat_minor": 5
186+
}

0 commit comments

Comments
 (0)