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