From 739ab9ed61eae61705fbea1bbca2491b5fda0478 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Sun, 16 Jul 2023 12:19:11 -0400 Subject: [PATCH 01/11] Refactor bonding curve - return better data structure - plot logic cleanly separated from main logic - other readability improvements --- curvesim/_bonding_curve/__init__.py | 61 +++++++++++++---------------- curvesim/_bonding_curve/__main__.py | 7 ++++ 2 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 curvesim/_bonding_curve/__main__.py diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py index 0a39cbb42..57fc86400 100644 --- a/curvesim/_bonding_curve/__init__.py +++ b/curvesim/_bonding_curve/__init__.py @@ -10,7 +10,7 @@ from curvesim.pool import CurveMetaPool -def bonding_curve(pool, *, truncate=0.0005, resolution=1000, show=True): +def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): """ Computes and optionally plots a pool's bonding curve and current reserves. @@ -25,8 +25,8 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, show=True): resolution : int, default=1000 Number of points along the bonding curve to compute. - show : bool, default=True - If true, plots the bonding curve. + plot : bool, default=True + If true, plots the bonding curve using Matplotlib. Returns ------- @@ -48,43 +48,38 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, show=True): except (AttributeError, KeyError): labels = [f"Coin {str(label)}" for label in range(pool.n)] - if show: - _, axs = plt.subplots(1, len(combos), constrained_layout=True) - D = pool.D() xp = pool._xp() # pylint: disable=protected-access - xs_out = [] - ys_out = [] - for n, combo in enumerate(combos): - i, j = combo - + pair_to_curve = {} + for (i, j) in combos: truncated_D = int(D * truncate) - xs_n = linspace( - truncated_D, pool.get_y(j, i, truncated_D, xp), resolution - ).round() - - ys_n = [] - for x in xs_n: - ys_n.append(pool.get_y(i, j, int(x), xp)) - - xs_n = [x / 10**18 for x in xs_n] - ys_n = [y / 10**18 for y in ys_n] - xs_out.append(xs_n) - ys_out.append(ys_n) - - if show: - if len(combos) == 1: - ax = axs - else: - ax = axs[n] - - ax.plot(xs_n, ys_n, color="black") + x_max = pool.get_y(j, i, truncated_D, xp) + xs = linspace(truncated_D, x_max, resolution).round() + + curve = [] + for x in xs: + y = pool.get_y(i, j, int(x), xp) + curve.append((x, y)) + curve = [(x / 10**18, y / 10**18) for x, y in curve] + pair_to_curve[(i, j)] = curve + + if plot: + n = len(combos) + _, axs = plt.subplots(1, n, constrained_layout=True) + if n == 1: + axs = [axs] + + for pair, ax in zip(combos, axs): + curve = pair_to_curve[pair] + xs, ys = zip(*curve) + ax.plot(xs, ys, color="black") + + i, j = pair ax.scatter(xp[i] / 10**18, xp[j] / 10**18, s=40, color="black") ax.set_xlabel(labels[i]) ax.set_ylabel(labels[j]) - if show: plt.show() - return xs_out, ys_out + return pair_to_curve diff --git a/curvesim/_bonding_curve/__main__.py b/curvesim/_bonding_curve/__main__.py new file mode 100644 index 000000000..76bd4adc2 --- /dev/null +++ b/curvesim/_bonding_curve/__main__.py @@ -0,0 +1,7 @@ +import curvesim + +from . import bonding_curve + +pool_address = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" +pool = curvesim.pool.get(pool_address) +pair_to_curve = bonding_curve(pool) From a32b64e811b3fc3a446becfb38473074c5b02727 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Sun, 16 Jul 2023 12:28:39 -0400 Subject: [PATCH 02/11] Update docstrings --- curvesim/_bonding_curve/__init__.py | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py index 57fc86400..722fa0388 100644 --- a/curvesim/_bonding_curve/__init__.py +++ b/curvesim/_bonding_curve/__init__.py @@ -1,6 +1,7 @@ """ -Contains bonding_curve function for computing and optionally plotting a pool's -bonding curve and current reserves. +Contains the bonding_curve function, which computes a pool's bonding +curve and current reserves for each pair of coins and optionally +plots the curves using Matplotlib. """ from itertools import combinations @@ -17,25 +18,24 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): Parameters ---------- pool : CurvePool or CurveMetaPool - A pool object to compute the bonding curve for. + The pool object for which the bonding curve is computed. - truncate : float, default=0.0001 - Determines where to truncate the bonding curve (i.e., D*truncate). + truncate : float, optional (default=0.0005) + Determines where to truncate the bonding curve. The truncation point is given + by D*truncate, where D is the total supply of tokens in the pool. - resolution : int, default=1000 - Number of points along the bonding curve to compute. + resolution : int, optional (default=1000) + The number of points to compute along the bonding curve. - plot : bool, default=True - If true, plots the bonding curve using Matplotlib. + plot : bool, optional (default=False) + Plots the bonding curves using Matplotlib. Returns ------- - xs : list of lists - Lists of reserves for the first coin in each combination of token pairs - - ys : list of lists - Lists of reserves for the second coin in each combination of token pairs - + pair_to_curve : dict + Dictionary with coin index pairs as keys and lists of corresponding reserves + as values. Each list of reserves is a list of pairs, where each pair consists + of the reserves for the first and second coin of the corresponding pair. """ if isinstance(pool, CurveMetaPool): From a88e95b0ef53a017a651a3724401484408d595bc Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Sun, 16 Jul 2023 12:33:38 -0400 Subject: [PATCH 03/11] Refactor --- curvesim/_bonding_curve/__init__.py | 46 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py index 722fa0388..bb67d00ab 100644 --- a/curvesim/_bonding_curve/__init__.py +++ b/curvesim/_bonding_curve/__init__.py @@ -10,6 +10,8 @@ from curvesim.pool import CurveMetaPool +D_UNIT = 10**18 + def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): """ @@ -43,11 +45,6 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): else: combos = list(combinations(range(pool.n), 2)) - try: - labels = pool.metadata["coins"]["names"] - except (AttributeError, KeyError): - labels = [f"Coin {str(label)}" for label in range(pool.n)] - D = pool.D() xp = pool._xp() # pylint: disable=protected-access @@ -61,25 +58,34 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): for x in xs: y = pool.get_y(i, j, int(x), xp) curve.append((x, y)) - curve = [(x / 10**18, y / 10**18) for x, y in curve] + curve = [(x / D_UNIT, y / D_UNIT) for x, y in curve] pair_to_curve[(i, j)] = curve if plot: - n = len(combos) - _, axs = plt.subplots(1, n, constrained_layout=True) - if n == 1: - axs = [axs] + try: + labels = pool.metadata["coins"]["names"] + except (AttributeError, KeyError): + labels = [f"Coin {str(label)}" for label in range(pool.n)] - for pair, ax in zip(combos, axs): - curve = pair_to_curve[pair] - xs, ys = zip(*curve) - ax.plot(xs, ys, color="black") + _plot_bonding_curve(pair_to_curve, labels, xp) - i, j = pair - ax.scatter(xp[i] / 10**18, xp[j] / 10**18, s=40, color="black") - ax.set_xlabel(labels[i]) - ax.set_ylabel(labels[j]) + return pair_to_curve - plt.show() - return pair_to_curve +def _plot_bonding_curve(pair_to_curve, labels, xp): + n = len(pair_to_curve) + _, axs = plt.subplots(1, n, constrained_layout=True) + if n == 1: + axs = [axs] + + for pair, ax in zip(pair_to_curve, axs): + curve = pair_to_curve[pair] + xs, ys = zip(*curve) + ax.plot(xs, ys, color="black") + + i, j = pair + ax.scatter(xp[i] / D_UNIT, xp[j] / D_UNIT, s=40, color="black") + ax.set_xlabel(labels[i]) + ax.set_ylabel(labels[j]) + + plt.show() From a30ab49e685fc2abb3e4d4f4b7703bd289e0dc57 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Sun, 16 Jul 2023 12:38:54 -0400 Subject: [PATCH 04/11] Update docs --- docs/api.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 3cd1a9493..b460c6eab 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -39,12 +39,12 @@ Curve Pools :members: -.. Pool Plots -.. ---------- -.. -.. .. _poolviewersapi: -.. -.. .. autofunction:: curvesim.bonding_curve +Pool Plots +---------- + +.. _poolviewersapi: + +.. autofunction:: curvesim.bonding_curve .. .. autofunction:: curvesim.order_book From 8c79c4dd01fa0738c7841f73e4196b6f8fd484ea Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 11:17:12 -0400 Subject: [PATCH 05/11] Use pool interface for names --- curvesim/_bonding_curve/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py index bb67d00ab..ad5993e78 100644 --- a/curvesim/_bonding_curve/__init__.py +++ b/curvesim/_bonding_curve/__init__.py @@ -62,9 +62,8 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): pair_to_curve[(i, j)] = curve if plot: - try: - labels = pool.metadata["coins"]["names"] - except (AttributeError, KeyError): + labels = pool.coin_names + if not labels: labels = [f"Coin {str(label)}" for label in range(pool.n)] _plot_bonding_curve(pair_to_curve, labels, xp) From 3e2b191dca6bec3df60c356f8677d9fef5b46b5a Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 11:24:58 -0400 Subject: [PATCH 06/11] Minor cleanup --- curvesim/_bonding_curve/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py index ad5993e78..66878003e 100644 --- a/curvesim/_bonding_curve/__init__.py +++ b/curvesim/_bonding_curve/__init__.py @@ -43,7 +43,7 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): if isinstance(pool, CurveMetaPool): combos = [(0, 1)] else: - combos = list(combinations(range(pool.n), 2)) + combos = combinations(range(pool.n), 2) D = pool.D() xp = pool._xp() # pylint: disable=protected-access From 53a7d9f75ed0ef1315d7d9f4fdf5d8d3880637de Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 11:50:53 -0400 Subject: [PATCH 07/11] Move bonding curve module --- curvesim/__init__.py | 2 +- curvesim/_bonding_curve/__main__.py | 7 ------- curvesim/tools/__init__.py | 5 +++++ .../{_bonding_curve/__init__.py => tools/bonding_curve.py} | 0 4 files changed, 6 insertions(+), 8 deletions(-) delete mode 100644 curvesim/_bonding_curve/__main__.py create mode 100644 curvesim/tools/__init__.py rename curvesim/{_bonding_curve/__init__.py => tools/bonding_curve.py} (100%) diff --git a/curvesim/__init__.py b/curvesim/__init__.py index a138c4f48..10170ee3b 100644 --- a/curvesim/__init__.py +++ b/curvesim/__init__.py @@ -1,7 +1,7 @@ """Package to simulate Curve pool.""" __all__ = ["autosim", "bonding_curve", "order_book", "__version__", "__version_info__"] -from ._bonding_curve import bonding_curve from ._order_book import order_book from .sim import autosim +from .tools import bonding_curve from .version import __version__, __version_info__ diff --git a/curvesim/_bonding_curve/__main__.py b/curvesim/_bonding_curve/__main__.py deleted file mode 100644 index 76bd4adc2..000000000 --- a/curvesim/_bonding_curve/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -import curvesim - -from . import bonding_curve - -pool_address = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" -pool = curvesim.pool.get(pool_address) -pair_to_curve = bonding_curve(pool) diff --git a/curvesim/tools/__init__.py b/curvesim/tools/__init__.py new file mode 100644 index 000000000..a2c0d2ea2 --- /dev/null +++ b/curvesim/tools/__init__.py @@ -0,0 +1,5 @@ +"Handy functions for the user interacting with Curvesim pools." + +__all__ = ["bonding_curve"] + +from .bonding_curve import bonding_curve diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/tools/bonding_curve.py similarity index 100% rename from curvesim/_bonding_curve/__init__.py rename to curvesim/tools/bonding_curve.py From ffb15a24a361656ae4fccfd3837d5f8e9e1833c2 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 11:51:09 -0400 Subject: [PATCH 08/11] Add a simple (cruddy?) test --- test/integration/test_bonding_curve.py | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/integration/test_bonding_curve.py diff --git a/test/integration/test_bonding_curve.py b/test/integration/test_bonding_curve.py new file mode 100644 index 000000000..20934057e --- /dev/null +++ b/test/integration/test_bonding_curve.py @@ -0,0 +1,35 @@ +import curvesim +from curvesim import bonding_curve + + +def test_bonding_curve(): + """Simple test of the bonding curve.""" + A = 2000 + balances = [96930673769101734848937206, 96029665968769, 94203880672841] + rates = [10**18, 10**30, 10**30] + pool = curvesim.pool.make(A, balances, 3, rates=rates) + pair_to_curve = bonding_curve(pool, resolution=5) + expected_result = { + (0, 1): [ + (143582.10515040305, 207709896.10151905), + (52035160.60424256, 140938067.54743478), + (103926739.10333471, 89033886.31117772), + (155818317.6024269, 37171084.50615266), + (207709896.10151905, 143582.1051504031), + ], + (0, 2): [ + (143582.10515040305, 205740351.86334246), + (51542774.54469842, 139604657.24397892), + (102941966.98424643, 88192864.27513982), + (154341159.42379445, 36822438.40404793), + (205740351.86334246, 143582.10515040308), + ], + (1, 2): [ + (143582.10515040305, 204771181.49157524), + (51300481.95175662, 138945948.269023), + (102457381.79836284, 87776447.07614492), + (153614281.64496905, 36648317.74309717), + (204771181.49157527, 143582.10515040296), + ], + } + assert pair_to_curve == expected_result From 0b6ad14b74648d5645c3280e89f2772bc6f2ef65 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 11:55:33 -0400 Subject: [PATCH 09/11] Add example to docstring --- curvesim/tools/bonding_curve.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/curvesim/tools/bonding_curve.py b/curvesim/tools/bonding_curve.py index 66878003e..312e59450 100644 --- a/curvesim/tools/bonding_curve.py +++ b/curvesim/tools/bonding_curve.py @@ -38,6 +38,13 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, plot=False): Dictionary with coin index pairs as keys and lists of corresponding reserves as values. Each list of reserves is a list of pairs, where each pair consists of the reserves for the first and second coin of the corresponding pair. + + Example + -------- + >>> import curvesim + >>> pool_address = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7" + >>> pool = curvesim.pool.get(pool_address) + >>> pair_to_curve = curvesim.bonding_curve(pool, plot=True) """ if isinstance(pool, CurveMetaPool): From f93c89a436d137620fdbefc8db668ca3355707bc Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 12:08:25 -0400 Subject: [PATCH 10/11] Add changelog entry --- .../20230724_120508_chanhosuh_bonding_curve.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 changelog.d/20230724_120508_chanhosuh_bonding_curve.rst diff --git a/changelog.d/20230724_120508_chanhosuh_bonding_curve.rst b/changelog.d/20230724_120508_chanhosuh_bonding_curve.rst new file mode 100644 index 000000000..f967ff913 --- /dev/null +++ b/changelog.d/20230724_120508_chanhosuh_bonding_curve.rst @@ -0,0 +1,13 @@ +Added +----- +- End-to-end test for the bonding curve that creates a pool + using the `make` pool factory. This covers some important + gaps in our codebase. + + +Changed +------- +- Refactored the bonding curve function, separating the core logic + from the plotting functionality. +- Created `tools` module to house the bonding curve and in anticipation + of further tools, e.g. orderbook. From aa3c1e8521c5261a6a66d5101f2352c6e37413c2 Mon Sep 17 00:00:00 2001 From: Chan-Ho Suh Date: Mon, 24 Jul 2023 15:45:28 -0400 Subject: [PATCH 11/11] Add test for metapool --- test/integration/test_bonding_curve.py | 49 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/integration/test_bonding_curve.py b/test/integration/test_bonding_curve.py index 20934057e..0513b707a 100644 --- a/test/integration/test_bonding_curve.py +++ b/test/integration/test_bonding_curve.py @@ -2,8 +2,8 @@ from curvesim import bonding_curve -def test_bonding_curve(): - """Simple test of the bonding curve.""" +def test_bonding_curve_stableswap(): + """Simple test of the bonding curve for a regular stableswap.""" A = 2000 balances = [96930673769101734848937206, 96029665968769, 94203880672841] rates = [10**18, 10**30, 10**30] @@ -33,3 +33,48 @@ def test_bonding_curve(): ], } assert pair_to_curve == expected_result + + +def test_bonding_curve_metapool(): + """Simple test of the bonding curve for a regular stableswap. + + Note: test data was generated via + + pool_address = "0x4e43151b78b5fbb16298C1161fcbF7531d5F8D93" + pool = curvesim.pool.get(pool_address) + basepool = pool.basepool + pair_to_curve = bonding_curve(pool, resolution=5) + """ + pool_address = "0x4e43151b78b5fbb16298C1161fcbF7531d5F8D93" + pool = curvesim.pool.get(pool_address) + basepool = pool.basepool + pair_to_curve = bonding_curve(pool, resolution=5) + + A = 1500 + rates = [10**18, 10**30] + balances = [350744085115649212803306457, 141003714500628] + bp_tokens = 491124709934878945923137105 + basepool = curvesim.pool.make(A, balances, 2, rates=rates, tokens=bp_tokens) + + A = 1500 + balances = [7059917, 88935085280709722288137] + rate_multiplier = 10**34 + pool = curvesim.pool.make( + A, + balances, + 2, + rate_multiplier=rate_multiplier, + basepool=basepool, + ) + pair_to_curve = bonding_curve(pool, resolution=5) + expected_result = { + (0, 1): [ + (79.81988656375063, 182748.88552045962), + (45747.08629503773, 113904.53710112568), + (91414.35270351169, 68226.56650379577), + (137081.61911198567, 22614.30606286462), + (182748.88552045965, 79.81988656375057), + ] + } + + assert pair_to_curve == expected_result