Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: separate logic for price oracle #103

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions curvesim/pool/cryptoswap/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Mainly a module to house the `CryptoPool`, a cryptoswap implementation in Python.
"""
import time
from typing import List
from typing import Any, List, Tuple

from curvesim.exceptions import CalculationError, CryptoPoolError, CurvesimValueError
from curvesim.pool.base import Pool
Expand Down Expand Up @@ -35,7 +35,7 @@ class CurveCryptoPool(Pool):
"admin_fee",
"ma_half_time",
"price_scale",
"_price_oracle",
"cached_price_oracle",
"last_prices",
"last_prices_timestamp",
"_block_timestamp",
Expand Down Expand Up @@ -126,7 +126,7 @@ def __init__(
self.admin_fee = admin_fee

self.price_scale = initial_price
self._price_oracle = initial_price
self.cached_price_oracle = initial_price
self.last_prices = initial_price
self.ma_half_time = ma_half_time

Expand Down Expand Up @@ -401,24 +401,21 @@ def _tweak_price( # noqa: complexity: 12
Also claims admin fees if appropriate (enough profit and price scale
and oracle is close enough).
"""
price_oracle: int = self._price_oracle

old_price_oracle: int = self.cached_price_oracle
last_prices: int = self.last_prices
price_scale: int = self.price_scale
last_prices_timestamp: int = self.last_prices_timestamp
block_timestamp: int = self._block_timestamp
p_new: int = 0

block_timestamp: int = self._block_timestamp
n_coins: int = self.n

if last_prices_timestamp < block_timestamp:
# MA update required
ma_half_time: int = self.ma_half_time
alpha: int = _halfpow(
(block_timestamp - last_prices_timestamp) * 10**18 // ma_half_time
)
price_oracle = (
last_prices * (10**18 - alpha) + price_oracle * alpha
) // 10**18
self._price_oracle = price_oracle
# Calculate price oracle:
price_oracle = self.price_oracle_logic(self)

if old_price_oracle != price_oracle:
# Price oracle has been updated:
self.cached_price_oracle = price_oracle
self.last_prices_timestamp = block_timestamp

D_unadjusted: int = new_D # Withdrawal methods know new D already
Expand Down Expand Up @@ -941,7 +938,7 @@ def _calc_withdraw_one_coin(
i: int,
update_D: bool,
calc_price: bool,
) -> (int, int, int, List[int]):
) -> Tuple[int, int, int, List[int]]:
token_supply: int = self.tokens
assert token_amount <= token_supply # dev: token amount more than supply
assert i < self.n # dev: coin out of range
Expand Down Expand Up @@ -1017,20 +1014,29 @@ def lp_price(self) -> int:
Returns an LP token price approximating behavior as a constant-product AMM.
"""
return (
2 * self.virtual_price * _sqrt_int(self.internal_price_oracle()) // 10**18
2
* self.virtual_price
* _sqrt_int(self.price_oracle_logic(self))
// 10**18
)

def internal_price_oracle(self) -> int:
@staticmethod
def price_oracle_logic(data_obj: Any[Pool]) -> int:
"""
Return the value of the EMA price oracle.

Calculates an exponential moving average using the AMM's state.
"""
price_oracle: int = self._price_oracle
last_prices_timestamp: int = self.last_prices_timestamp

block_timestamp: int = self._block_timestamp
# inputs from data object to calculate EMA:
price_oracle: int = data_obj.price_oracle
last_prices_timestamp: int = data_obj.last_prices_timestamp
ma_half_time: int = data_obj.ma_half_time
block_timestamp: int = data_obj.block_timestamp
last_prices: int = data_obj.last_prices

# internal price oracle logic:
if last_prices_timestamp < block_timestamp:
ma_half_time: int = self.ma_half_time
last_prices: int = self.last_prices
alpha: int = _halfpow(
(block_timestamp - last_prices_timestamp) * 10**18 // ma_half_time
)
Expand All @@ -1045,7 +1051,7 @@ def price_oracle(self) -> int:
Same as `internal_price_oracle`. Kept for compatability with the
vyper interface.
"""
return self.internal_price_oracle()
return self.price_oracle_logic(self)

def get_virtual_price(self) -> int:
"""
Expand Down
26 changes: 13 additions & 13 deletions test/unit/test_cryptopool.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def initialize_pool(vyper_cryptopool):

price_oracle = vyper_cryptopool.eval("self._price_oracle")
# pylint: disable-next=protected-access
pool._price_oracle = price_oracle
pool.cached_price_oracle = price_oracle

last_prices = vyper_cryptopool.last_prices()
last_prices_timestamp = vyper_cryptopool.last_prices_timestamp()
Expand All @@ -99,7 +99,7 @@ def sync_ema_logic(
"""
# pylint: disable=protected-access
price_oracle = vyper_cryptopool.eval("self._price_oracle")
pool._price_oracle = price_oracle
pool.cached_price_oracle = price_oracle

# synchronize the times between the two pools and reset
# last_prices and last_prices_timestamp
Expand Down Expand Up @@ -369,12 +369,12 @@ def test_tweak_price(

# ------- test no oracle update and no scale adjustment ------------- #
assert pool.price_scale == vyper_cryptopool.price_scale()
assert pool._price_oracle == vyper_cryptopool.eval("self._price_oracle")
assert pool.cached_price_oracle == vyper_cryptopool.eval("self._price_oracle")

old_scale = pool.price_scale
assert old_scale == pool._price_oracle
assert old_scale == pool.cached_price_oracle

old_oracle = pool._price_oracle
old_oracle = pool.cached_price_oracle

pool._tweak_price(A, gamma, xp, last_price, 0) # pylint: disable=protected-access
vyper_cryptopool.eval(f"self.tweak_price({A_gamma}, {xp}, {last_price}, 0)")
Expand All @@ -383,7 +383,7 @@ def test_tweak_price(
# no price adjustment since price oracle is same as price scale (`norm` is 0)
assert pool.price_scale == old_scale
# EMA price oracle won't update if price oracle and last price is the same
assert old_oracle == pool._price_oracle
assert old_oracle == pool.cached_price_oracle

# ------- test oracle updates with no scale adjustment ------------- #

Expand All @@ -392,7 +392,7 @@ def test_tweak_price(
assert pool.virtual_price == vyper_cryptopool.virtual_price()
assert pool.xcp_profit == vyper_cryptopool.xcp_profit()

old_oracle = pool._price_oracle
old_oracle = pool.cached_price_oracle
old_scale = pool.price_scale
old_virtual_price = pool.virtual_price

Expand All @@ -401,23 +401,23 @@ def test_tweak_price(

# check the pools are the same
assert pool.price_scale == vyper_cryptopool.price_scale()
assert pool._price_oracle == vyper_cryptopool.eval("self._price_oracle")
assert pool.cached_price_oracle == vyper_cryptopool.eval("self._price_oracle")
assert pool.virtual_price == vyper_cryptopool.virtual_price()
assert pool.D == vyper_cryptopool.D()

# check oracle updated
# scale shouldn't change because no adjustment is possible
# with no profit
assert pool._price_oracle != old_oracle
assert pool.cached_price_oracle != old_oracle
assert pool.price_scale == old_scale
assert pool.virtual_price == old_virtual_price

# ------- test scale adjustment ----------------------- #
sync_ema_logic(vyper_cryptopool, pool, last_price)

assert pool.price_scale != pool._price_oracle
assert pool.price_scale != pool.cached_price_oracle

old_oracle = pool._price_oracle
old_oracle = pool.cached_price_oracle
old_scale = pool.price_scale
old_virtual_price = pool.virtual_price

Expand All @@ -428,7 +428,7 @@ def test_tweak_price(
vyper_cryptopool.eval(f"self.tweak_price({A_gamma}, {xp}, 0, 0)")

assert pool.price_scale == vyper_cryptopool.price_scale()
assert pool._price_oracle == vyper_cryptopool.eval("self._price_oracle")
assert pool.cached_price_oracle == vyper_cryptopool.eval("self._price_oracle")

assert pool.D == vyper_cryptopool.D()
assert pool.virtual_price == vyper_cryptopool.virtual_price()
Expand All @@ -447,7 +447,7 @@ def test_tweak_price(
vyper_cryptopool.eval(f"self.tweak_price({A_gamma}, {xp}, 0, 0)")

assert pool.price_scale == vyper_cryptopool.price_scale()
assert pool._price_oracle == vyper_cryptopool.eval("self._price_oracle")
assert pool.cached_price_oracle == vyper_cryptopool.eval("self._price_oracle")

assert pool.D == vyper_cryptopool.D()
assert pool.virtual_price == vyper_cryptopool.virtual_price()
Expand Down