Repository: curveresearch/curvesim (language: python)

Issue:
Pull and process cryptoswap params
- [x] Update curve volume subgraph to pull the following fields:
   -  gamma
   - mid_fee
   - out_fee
   - allowed_extra_profit
   - fee_gamma
   - adjustment_step
   - ma_half_time
   - price_scale
   - price_oracle
   - last_prices
   - last_prices_timestamp
- [x] Update `pool_snapshot` in `network.subgraph` to pull and process cryptoswap params
- [x] Update `PoolData` and `PoolMetaData` to properly initialize a crypto pool


Discussion / hints:

Fix patch:
diff --git a/changelog.d/20230515_175738_chanhosuh_get_cryptopool.rst b/changelog.d/20230515_175738_chanhosuh_get_cryptopool.rst
new file mode 100644
index 000000000..56bc3ec0f
--- /dev/null
+++ b/changelog.d/20230515_175738_chanhosuh_get_cryptopool.rst
@@ -0,0 +1,21 @@
+Added
+-----
+
+- `curvesim.pool.get` can now be used to fetch and instantiate cryptopools.
+- `curvesim` now uses python logging with log levels.  This allows for debug logging and
+  saving logs to files.
+
+Changed
+-------
+
+- By default, pool instantiation will now create balances in native token units.
+  Previously it had normalized to 18 decimals.  This option still exists but must
+  now be chosen explicitly.
+- Some internal objects used by `curvesim` were refactored for better maintainability,
+  namely `PoolData` is now split into `PoolDataCache` and `PoolMetaData`.
+
+
+Fixed
+-----
+
+- Timezone issue in subgraph queries.
diff --git a/curvesim/_bonding_curve/__init__.py b/curvesim/_bonding_curve/__init__.py
index 019b2f5da..0a39cbb42 100644
--- a/curvesim/_bonding_curve/__init__.py
+++ b/curvesim/_bonding_curve/__init__.py
@@ -59,8 +59,9 @@ def bonding_curve(pool, *, truncate=0.0005, resolution=1000, show=True):
     for n, combo in enumerate(combos):
         i, j = combo
 
+        truncated_D = int(D * truncate)
         xs_n = linspace(
-            int(D * truncate), pool.get_y(j, i, D * truncate, xp), resolution
+            truncated_D, pool.get_y(j, i, truncated_D, xp), resolution
         ).round()
 
         ys_n = []
diff --git a/curvesim/logging.py b/curvesim/logging.py
index d210380cc..0ec607b00 100644
--- a/curvesim/logging.py
+++ b/curvesim/logging.py
@@ -71,11 +71,7 @@
 }
 
 # 3rd party loggers that we want to largely ignore
-silenced_loggers = [
-    "matplotlib",
-    "asyncio",
-    "rlp",
-]
+silenced_loggers = ["matplotlib", "asyncio", "rlp", "parso"]
 configured_loggers = CUSTOM_LOGGING_CONFIG["loggers"]
 for name in silenced_loggers:
     configured_loggers[name] = {
diff --git a/curvesim/network/subgraph.py b/curvesim/network/subgraph.py
index 21ddacba7..51c9e0af4 100644
--- a/curvesim/network/subgraph.py
+++ b/curvesim/network/subgraph.py
@@ -253,11 +253,26 @@ async def _pool_snapshot(address, chain):
 
             A
             fee
+            adminFee
             offPegFeeMultiplier
             reserves
             normalizedReserves
             virtualPrice
             timestamp
+
+            gamma
+            midFee
+            outFee
+            feeGamma
+            allowedExtraProfit
+            adjustmentStep
+            maHalfTime
+            priceScale
+            priceOracle
+            lastPrices
+            lastPricesTimestamp
+            xcpProfit
+            xcpProfitA
           }
         }
     """
@@ -332,29 +347,67 @@ async def pool_snapshot(address, chain):
         basepool = None
 
     # Output
-    data = {
-        "name": r["name"],
-        "address": to_checksum_address(r["address"]),
-        "chain": chain,
-        "symbol": r["symbol"].strip(),
-        "version": version,
-        "pool_type": r["poolType"],
-        "params": {
-            "A": int(r["A"]),
-            "fee": int(float(r["fee"]) * 10**10),
-            "fee_mul": fee_mul,
-        },
-        "coins": coins,
-        "reserves": {
-            "D": D,
-            "by_coin": normalized_reserves,
-            "unnormalized_by_coin": unnormalized_reserves,
-            "virtual_price": int(r["virtualPrice"]),
-            "tokens": D * 10**18 // int(r["virtualPrice"]),
-        },
-        "basepool": basepool,
-        "timestamp": int(r["timestamp"]),
-    }
+    if version == 1:
+        data = {
+            "name": r["name"],
+            "address": to_checksum_address(r["address"]),
+            "chain": chain,
+            "symbol": r["symbol"].strip(),
+            "version": version,
+            "pool_type": r["poolType"],
+            "params": {
+                "A": int(r["A"]),
+                "fee": int(float(r["fee"]) * 10**10),
+                "fee_mul": fee_mul,
+                "admin_fee": int(r["adminFee"]),
+            },
+            "coins": coins,
+            "reserves": {
+                "D": D,
+                "by_coin": normalized_reserves,
+                "unnormalized_by_coin": unnormalized_reserves,
+                "virtual_price": int(r["virtualPrice"]),
+                "tokens": D * 10**18 // int(r["virtualPrice"]),
+            },
+            "basepool": basepool,
+            "timestamp": int(r["timestamp"]),
+        }
+    else:
+        data = {
+            "name": r["name"],
+            "address": to_checksum_address(r["address"]),
+            "chain": chain,
+            "symbol": r["symbol"].strip(),
+            "version": version,
+            "pool_type": r["poolType"],
+            "params": {
+                "A": int(r["A"]),
+                "gamma": int(r["gamma"]),
+                "fee_gamma": int(r["feeGamma"]),
+                "mid_fee": int(r["midFee"]),
+                "out_fee": int(r["outFee"]),
+                "allowed_extra_profit": int(r["allowedExtraProfit"]),
+                "adjustment_step": int(r["adjustmentStep"]),
+                "ma_half_time": int(r["adjustmentStep"]),
+                "price_scale": int(r["priceScale"]),
+                "price_oracle": int(r["priceOracle"]),
+                "last_prices": int(r["lastPrices"]),
+                "last_prices_timestamp": int(r["lastPricesTimestamp"]),
+                "admin_fee": int(r["adminFee"]),
+                "xcp_profit": int(r["xcpProfit"]),
+                "xcp_profit_a": int(r["xcpProfitA"]),
+            },
+            "coins": coins,
+            "reserves": {
+                "D": D,
+                "by_coin": normalized_reserves,
+                "unnormalized_by_coin": unnormalized_reserves,
+                "virtual_price": int(r["virtualPrice"]),
+                "tokens": D * 10**18 // int(r["virtualPrice"]),
+            },
+            "basepool": basepool,
+            "timestamp": int(r["timestamp"]),
+        }
 
     return override_subgraph_data(data, "pool_snapshot", (address, chain))
 
diff --git a/curvesim/pipelines/arbitrage.py b/curvesim/pipelines/arbitrage.py
index b95b1ed20..a74807189 100644
--- a/curvesim/pipelines/arbitrage.py
+++ b/curvesim/pipelines/arbitrage.py
@@ -8,21 +8,24 @@
 from numpy import array, isnan
 from scipy.optimize import least_squares, root_scalar
 
-from curvesim.metrics import StateLog, init_metrics, make_results, metrics as Metrics
 from curvesim.iterators.param_samplers import Grid
 from curvesim.iterators.price_samplers import PriceVolume
 from curvesim.logging import get_logger
+from curvesim.metrics import StateLog, init_metrics, make_results
+from curvesim.metrics import metrics as Metrics
+from curvesim.pool import get_sim_pool
+from curvesim.pool_data.cache import PoolDataCache
 
-from .templates import run_pipeline, TradeData
+from .templates import TradeData, run_pipeline
 from .utils import compute_volume_multipliers
 
-
 logger = get_logger(__name__)
 
 
 # pylint: disable-next=too-many-arguments
 def volume_limited_arbitrage(
-    pool_data,
+    pool_metadata,
+    pool_data_cache=None,
     variable_params=None,
     fixed_params=None,
     metrics=None,
@@ -45,8 +48,8 @@ def volume_limited_arbitrage(
 
     Parameters
     ----------
-    pool_data : :class:`.PoolData`
-        Pool data object for the pool of interest.
+    pool_metadata : :class:`.PoolMetaDataInterface`
+        Pool metadata object for the pool of interest.
 
     variable_params : dict, defaults to broad range of A/fee values
         Pool parameters to vary across simulations.
@@ -113,20 +116,23 @@ def volume_limited_arbitrage(
     variable_params = variable_params or DEFAULT_PARAMS
     metrics = metrics or DEFAULT_METRICS
 
-    pool = pool_data.sim_pool()
-    coins = pool_data.coins
+    if pool_data_cache is None:
+        pool_data_cache = PoolDataCache(pool_metadata, days=days, end=end)
+
+    pool = get_sim_pool(pool_metadata, pool_data_cache=pool_data_cache)
+    coins = pool_metadata.coins
 
     param_sampler = Grid(pool, variable_params, fixed_params=fixed_params)
     price_sampler = PriceVolume(coins, days=days, data_dir=data_dir, src=src, end=end)
 
     if vol_mult is None:
-        volumes = pool_data.volume(days=days, end=end)
-        total_volumes = price_sampler.total_volumes()
+        total_pool_volume = pool_data_cache.volume
+        total_market_volume = price_sampler.total_volumes()
         vol_mult = compute_volume_multipliers(
-            volumes,
-            total_volumes,
-            pool_data.n,
-            pool_data.type,
+            total_pool_volume,
+            total_market_volume,
+            pool_metadata.n,
+            pool_metadata.pool_type,
             mode=vol_mode,
         )
 
diff --git a/curvesim/pipelines/utils.py b/curvesim/pipelines/utils.py
index d418103d9..576069194 100644
--- a/curvesim/pipelines/utils.py
+++ b/curvesim/pipelines/utils.py
@@ -37,10 +37,10 @@ def compute_volume_multipliers(pool_vol, market_vol, n, pool_type, mode=1):
         3: mode 2 for trades with meta-pool asset, mode 1 for basepool-only trades
 
     """
-    if pool_type == CurvePool:
+    if pool_type is CurvePool:
         vol_mult = pool_vol_mult(pool_vol, market_vol, n, mode)
 
-    elif pool_type == CurveMetaPool:
+    elif pool_type is CurveMetaPool:
         vol_mult = metapool_vol_mult(pool_vol, market_vol, n, mode)
 
     else:
diff --git a/curvesim/pool/__init__.py b/curvesim/pool/__init__.py
index fb20811d4..2777a3b4f 100644
--- a/curvesim/pool/__init__.py
+++ b/curvesim/pool/__init__.py
@@ -26,7 +26,8 @@
 ]
 
 from curvesim.exceptions import CurvesimValueError
-from curvesim.pool_data import get as _get_pool_data
+from curvesim.pool_data import get_metadata
+from curvesim.pool_data.metadata import PoolMetaData, PoolMetaDataInterface
 
 from .base import Pool
 from .cryptoswap import CurveCryptoPool
@@ -133,14 +134,16 @@ def make(
     return pool
 
 
-def get(
-    address_or_symbol,
+def get_pool(
+    pool_metadata,
     chain="mainnet",
     balanced=False,
     balanced_base=False,
     normalize=False,
 ):
     """
+    Constructs a pool object based on the stored data.
+
     Parameters
     ----------
     address_or_symbol: str
@@ -164,6 +167,9 @@ def get(
     normalize : bool, default=False
         If True, normalizes balances to 18 decimals (useful for sim calculations).
 
+    sim: bool, default=False
+        If True, returns a `SimPool` version of the pool.
+
     Returns
     -------
     :class:`Pool`
@@ -172,8 +178,73 @@ def get(
     --------
     >>> import curvesim
     >>> pool = curvesim.pool.get("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", "mainnet")
+
     """
-    pool_data = _get_pool_data(address_or_symbol, chain=chain)
-    return pool_data.pool(
-        balanced=balanced, balanced_base=balanced_base, normalize=normalize
-    )
+    if isinstance(pool_metadata, str):
+        pool_metadata = get_metadata(pool_metadata, chain=chain)
+    elif isinstance(pool_metadata, dict):
+        pool_metadata = PoolMetaData(pool_metadata)
+    elif isinstance(pool_metadata, PoolMetaDataInterface):
+        pass
+    else:
+        raise CurvesimValueError(
+            "`pool_metadata` must be of type `str`, `dict`, or `PoolMetaDataInterface`."
+        )
+
+    init_kwargs = pool_metadata.init_kwargs(balanced, balanced_base, normalize)
+
+    pool_type = pool_metadata.pool_type
+    pool = pool_type(**init_kwargs)
+
+    pool.metadata = pool_metadata._dict  # pylint: disable=protected-access
+
+    return pool
+
+
+POOL_TYPE_TO_CUSTOM_KWARGS = {SimCurveRaiPool: ["redemption_prices"]}
+
+
+def get_sim_pool(
+    pool_metadata,
+    chain="mainnet",
+    balanced=True,
+    balanced_base=True,
+    custom_kwargs=None,
+    pool_data_cache=None,
+):
+    """
+    Effectively the same as the `get_pool` function but returns
+    an object in the `SimPool` hierarchy.
+    """
+    custom_kwargs = custom_kwargs or {}
+
+    if isinstance(pool_metadata, str):
+        pool_metadata = get_metadata(pool_metadata, chain=chain)
+    elif isinstance(pool_metadata, dict):
+        pool_metadata = PoolMetaData(pool_metadata)
+    elif isinstance(pool_metadata, PoolMetaDataInterface):
+        pass
+    else:
+        raise CurvesimValueError(
+            "`pool_metadata` must be of type `str`, `dict`, or `PoolMetaDataInterface`."
+        )
+
+    init_kwargs = pool_metadata.init_kwargs(balanced, balanced_base, normalize=True)
+
+    pool_type = pool_metadata.sim_pool_type
+
+    custom_keys = POOL_TYPE_TO_CUSTOM_KWARGS.get(pool_type, [])
+    for key in custom_keys:
+        try:
+            init_kwargs[key] = custom_kwargs.get(key) or getattr(pool_data_cache, key)
+        except KeyError as e:
+            raise CurvesimValueError(f"'{pool_type.__name__}' needs '{key}'.") from e
+
+    pool = pool_type(**init_kwargs)
+
+    pool.metadata = pool_metadata._dict  # pylint: disable=protected-access
+
+    return pool
+
+
+get = get_pool
diff --git a/curvesim/pool/base.py b/curvesim/pool/base.py
index 232f0b970..f72bf6e77 100644
--- a/curvesim/pool/base.py
+++ b/curvesim/pool/base.py
@@ -4,15 +4,25 @@
 """
 
 
-class Pool:
+from curvesim.pool.snapshot import SnapshotMixin
+
+
+class Pool(SnapshotMixin):
     """
-    The `Pool` base class has the explicitly required properties for any pool-like
-    object used in Curvesim.
+    The `Pool` base class has the explicitly required properties for any
+    pool-like object used in Curvesim.
+
+    The `SnapshotMixin` gives the ability to snapshot balances and revert
+    balance changes.
 
     Currently the base attributes are not informative for pools
     constructed manually rather than from chain data.
     """
 
+    # need to configure in derived class otherwise the
+    # snapshotting will not work
+    snapshot_class = None
+
     @property
     def name(self):
         """Descriptive name for this pool"""
@@ -41,6 +51,13 @@ def coin_addresses(self):
             return self.metadata["coins"]["addresses"]
         return []
 
+    @property
+    def coin_decimals(self):
+        """Addresses for the pool coins."""
+        if hasattr(self, "metadata"):
+            return self.metadata["coins"]["decimals"]
+        return []
+
     @property
     def chain(self):
         """Chain for this pool"""
diff --git a/curvesim/pool/cryptoswap/pool.py b/curvesim/pool/cryptoswap/pool.py
index 7f3e57f14..168aa4e6a 100644
--- a/curvesim/pool/cryptoswap/pool.py
+++ b/curvesim/pool/cryptoswap/pool.py
@@ -62,12 +62,14 @@ def __init__(
         allowed_extra_profit: int,
         fee_gamma: int,
         adjustment_step: int,
-        admin_fee: int,
         ma_half_time: int,
-        initial_price: int,
+        price_scale: int,
+        price_oracle=None,
+        last_prices=None,
         balances=None,
         D=None,
         tokens=None,
+        admin_fee: int = 5 * 10**9,
         xcp_profit=10**18,
         xcp_profit_a=10**18,
     ):
@@ -97,12 +99,17 @@ def __init__(
             Factor used to control the transition from `mid_fee` to `out_fee`.
         adjustment_step:
             Minimum step size to adjust the price scale.
-        admin_fee: int
-            Percentage of `fee` with 10**10 precision.  Fee paid to the DAO.
         ma_half_time: int
             "Half-life" for exponential moving average of trade prices.
-        initial_price: int
-            Initial price scale for the pool.
+        price_scale: int
+            Price scale value for the pool.  This is where liquidity is concentrated.
+        price_oracle: int, optional
+            Price oracle value for the pool.  This is the EMA price used to
+            adjust the price scale toward.
+            Defaults to `price_scale`.
+        last_prices: int, optional
+            Last trade price for the pool.
+            Defaults to `price_scale`.
         balances: list of int, optional
             Coin balances in native token units;
             either `balances` or `D` is required
@@ -112,6 +119,9 @@ def __init__(
         tokens: int, optional
             LP token supply (default is calculated from `D`, which is also
             calculated if needed)
+        admin_fee: int, optional
+            Percentage of `fee` with 10**10 precision.  Fee paid to the DAO
+            (default = 5*10**9)
         xcp_profit: int, optional
             Counter for accumulated profits, no losses (default = 10**18)
         xcp_profit_a: int, optional
@@ -127,9 +137,9 @@ def __init__(
         self.adjustment_step = adjustment_step
         self.admin_fee = admin_fee
 
-        self.price_scale = initial_price
-        self._price_oracle = initial_price
-        self.last_prices = initial_price
+        self.price_scale = price_scale
+        self._price_oracle = price_oracle or price_scale
+        self.last_prices = last_prices or price_scale
         self.ma_half_time = ma_half_time
 
         self._block_timestamp = _get_unix_timestamp()
@@ -459,18 +469,16 @@ def _tweak_price(  # noqa: complexity: 12
         if old_virtual_price > 0:
             xcp: int = _geometric_mean(xp, True)
             virtual_price = 10**18 * xcp // total_supply
-            xcp_profit = old_xcp_profit * virtual_price // old_virtual_price
 
             if virtual_price < old_virtual_price:
                 raise CryptoPoolError("Loss")
 
+            xcp_profit = old_xcp_profit * virtual_price // old_virtual_price
+
         self.xcp_profit = xcp_profit
 
         norm: int = price_oracle * 10**18 // price_scale
-        if norm > 10**18:
-            norm -= 10**18
-        else:
-            norm = 10**18 - norm
+        norm = abs(norm - 10**18)
         adjustment_step: int = max(self.adjustment_step, norm // 5)
 
         needs_adjustment: bool = self.not_adjusted
@@ -500,19 +508,17 @@ def _tweak_price(  # noqa: complexity: 12
             # Calculate "extended constant product" invariant xCP and virtual price
             D: int = self._newton_D(A, gamma, xp)
             xp = [D // n_coins, D * PRECISION // (n_coins * p_new)]
-            # We reuse old_virtual_price here but it's not old anymore
-            old_virtual_price = 10**18 * _geometric_mean(xp, True) // total_supply
+            new_virtual_price = 10**18 * _geometric_mean(xp, True) // total_supply
 
             # Proceed if we've got enough profit:
-            # if (old_virtual_price > 10**18) and
-            # (2 * (old_virtual_price - 10**18) > xcp_profit - 10**18):
-            if (old_virtual_price > 10**18) and (
-                2 * old_virtual_price - 10**18 > xcp_profit
+            #   new_virtual_price > 10**18
+            #   new_virtual_price - 10**18 > (xcp_profit - 10**18) / 2
+            if (new_virtual_price > 10**18) and (
+                2 * new_virtual_price - 10**18 > xcp_profit
             ):
                 self.price_scale = p_new
                 self.D = D
-                self.virtual_price = old_virtual_price
-
+                self.virtual_price = new_virtual_price
                 return
 
         # If we are here, the price_scale adjustment did not happen
@@ -662,7 +668,8 @@ def _exchange(
             dy = dy * PRECISION // price_scale
         dy = dy // prec_j
 
-        dy -= self._fee(xp) * dy // 10**10
+        fee = self._fee(xp) * dy // 10**10
+        dy -= fee
         assert dy >= min_dy, "Slippage"
         y -= dy
 
@@ -684,7 +691,7 @@ def _exchange(
 
         self._tweak_price(A, gamma, xp, p, 0)
 
-        return dy
+        return dy, fee
 
     def exchange(
         self,
@@ -709,8 +716,8 @@ def exchange(
 
         Returns
         -------
-        int
-            Out-coin amount from the swap.
+        (int, int)
+            (amount of coin `j` received, trading fee)
 
         Note
         -----
diff --git a/curvesim/pool/sim_interface/metapool.py b/curvesim/pool/sim_interface/metapool.py
index d3a576a8e..8b8becf6b 100644
--- a/curvesim/pool/sim_interface/metapool.py
+++ b/curvesim/pool/sim_interface/metapool.py
@@ -4,15 +4,11 @@
 
 from curvesim.exceptions import CurvesimValueError
 from curvesim.pool.sim_interface.simpool import SimStableswapBase
-from curvesim.pool.snapshot import CurveMetaPoolBalanceSnapshot
 from curvesim.pool.stableswap.metapool import CurveMetaPool
 from curvesim.utils import override
 
 
 class SimCurveMetaPool(SimStableswapBase, CurveMetaPool):
-
-    snapshot_class = CurveMetaPoolBalanceSnapshot
-
     @property
     def _precisions(self):
         p_base = self.basepool.rates[:]
diff --git a/curvesim/pool/sim_interface/pool.py b/curvesim/pool/sim_interface/pool.py
index 3f82e7d5c..45846eb03 100644
--- a/curvesim/pool/sim_interface/pool.py
+++ b/curvesim/pool/sim_interface/pool.py
@@ -2,7 +2,6 @@
 
 from numpy import isnan
 
-from curvesim.pool.snapshot import CurvePoolBalanceSnapshot
 from curvesim.utils import override
 
 from ..stableswap import CurvePool
@@ -10,9 +9,6 @@
 
 
 class SimCurvePool(SimStableswapBase, CurvePool):
-
-    snapshot_class = CurvePoolBalanceSnapshot
-
     @property
     def _precisions(self):
         return self.rates[:]
diff --git a/curvesim/pool/sim_interface/simpool.py b/curvesim/pool/sim_interface/simpool.py
index c2aec772f..ee5f6e538 100644
--- a/curvesim/pool/sim_interface/simpool.py
+++ b/curvesim/pool/sim_interface/simpool.py
@@ -3,24 +3,18 @@
 
 from curvesim.exceptions import CurvesimValueError
 from curvesim.pipelines.templates import SimPool
-from curvesim.pool.snapshot import SnapshotMixin
 from curvesim.utils import cache
 
 
-class SimStableswapBase(SimPool, SnapshotMixin):
+class SimStableswapBase(SimPool):
     """
     This base class contains common logic useful for all Curve
     stableswap implementations used in arbitrage pipelines:
 
     - translate from coin names to Curve pool indices
     - compute liquidity density of a coin pair and price-depth
-    - ability to snapshot balances and revert balance changes
     """
 
-    # need to configure in derived class otherwise the
-    # snapshotting will not work
-    snapshot_class = None
-
     @abstractmethod
     def _init_coin_indices(self):
         """Produces the coin ID to index mapping from the pool's metadata."""
diff --git a/curvesim/pool/stableswap/metapool.py b/curvesim/pool/stableswap/metapool.py
index 55aee054d..4e06aceae 100644
--- a/curvesim/pool/stableswap/metapool.py
+++ b/curvesim/pool/stableswap/metapool.py
@@ -5,6 +5,8 @@
 
 from gmpy2 import mpz
 
+from curvesim.pool.snapshot import CurveMetaPoolBalanceSnapshot
+
 from ..base import Pool
 
 
@@ -13,6 +15,8 @@ class CurveMetaPool(Pool):  # pylint: disable=too-many-instance-attributes
     Basic stableswap metapool implementation in Python.
     """
 
+    snapshot_class = CurveMetaPoolBalanceSnapshot
+
     __slots__ = (
         "A",
         "n",
diff --git a/curvesim/pool/stableswap/pool.py b/curvesim/pool/stableswap/pool.py
index cb2e2fc7d..247480c23 100644
--- a/curvesim/pool/stableswap/pool.py
+++ b/curvesim/pool/stableswap/pool.py
@@ -5,6 +5,8 @@
 
 from gmpy2 import mpz
 
+from curvesim.pool.snapshot import CurvePoolBalanceSnapshot
+
 from ..base import Pool
 
 
@@ -13,6 +15,8 @@ class CurvePool(Pool):  # pylint: disable=too-many-instance-attributes
     Basic stableswap implementation in Python.
     """
 
+    snapshot_class = CurvePoolBalanceSnapshot
+
     __slots__ = (
         "A",
         "n",
diff --git a/curvesim/pool_data/__init__.py b/curvesim/pool_data/__init__.py
index b2304e843..53f634543 100644
--- a/curvesim/pool_data/__init__.py
+++ b/curvesim/pool_data/__init__.py
@@ -3,13 +3,15 @@
 Currently supports stableswap pools, meta-pools, and rebasing (RAI) metapools.
 """
 
-__all__ = ["PoolData", "from_address", "from_symbol", "get"]
+__all__ = ["PoolDataCache", "from_address", "from_symbol", "get_data_cache"]
 
-from .data import PoolData
+from curvesim.pool_data.metadata import PoolMetaData
+
+from .cache import PoolDataCache
 from .queries import from_address, from_symbol
 
 
-def get(address_or_symbol, chain="mainnet"):
+def get_data_cache(address_or_symbol, chain="mainnet", days=60, end=None):
     """
     Pulls pool state and metadata from daily snapshot.
 
@@ -32,7 +34,36 @@ def get(address_or_symbol, chain="mainnet"):
     else:
         from_x = from_symbol
 
-    params = from_x(address_or_symbol, chain)
-    pool_data = PoolData(params)
+    metadata_dict = from_x(address_or_symbol, chain)
+    pool_data = PoolDataCache(metadata_dict, days=days, end=end)
 
     return pool_data
+
+
+def get_metadata(address_or_symbol, chain="mainnet"):
+    """
+    Pulls pool state and metadata from daily snapshot.
+
+    Parameters
+    ----------
+    address_or_symbol : str
+        Pool address prefixed with “0x” or LP token symbol.
+
+    chain : str
+        Chain/layer2 identifier, e.g. “mainnet”, “arbitrum”, “optimism".
+
+    Returns
+    -------
+    PoolData
+
+    """
+    # TODO: validate function arguments
+    if address_or_symbol.startswith("0x"):
+        from_x = from_address
+    else:
+        from_x = from_symbol
+
+    metadata_dict = from_x(address_or_symbol, chain)
+    metadata = PoolMetaData(metadata_dict)
+
+    return metadata
diff --git a/curvesim/pool_data/cache.py b/curvesim/pool_data/cache.py
new file mode 100644
index 000000000..2c2f4a441
--- /dev/null
+++ b/curvesim/pool_data/cache.py
@@ -0,0 +1,157 @@
+from numpy import array
+
+from curvesim.exceptions import CurvesimValueError
+from curvesim.logging import get_logger
+from curvesim.network.subgraph import redemption_prices_sync as _redemption_prices
+from curvesim.network.subgraph import volume_sync as _volume
+from curvesim.pool.stableswap.metapool import CurveMetaPool
+from curvesim.pool_data.metadata.base import PoolMetaDataInterface
+
+from .metadata import PoolMetaData
+
+logger = get_logger(__name__)
+
+
+class PoolDataCache:
+    """
+    Container with methods to return pool state, metadata, and pools employing them.
+    """
+
+    def __init__(self, metadata_dict, cache_data=False, days=60, end=None):
+        """
+        Parameters
+        ----------
+        metadata_dict : dict, PoolMetaDataInterface
+            Pool metadata in the format returned by network.subgraph.pool_snapshot.
+
+        cache_data : bool, optional
+            If True, fetches and caches historical volume and redemption price.
+
+        days : int, default=60
+            Number of days to pull data for if caching.
+
+        """
+        if isinstance(metadata_dict, dict):
+            self.metadata = PoolMetaData(metadata_dict)
+        elif isinstance(metadata_dict, PoolMetaDataInterface):
+            self.metadata = metadata_dict
+        else:
+            raise CurvesimValueError(
+                "Metadata must be of type dict or PoolMetaDataInterface."
+            )
+
+        self.days = days
+        self.end = end
+
+        self._cached_volume = None
+        self._cached_redemption_prices = None
+
+        if cache_data:
+            self.set_cache()
+
+    def set_cache(self):
+        """
+        Fetches and caches historical volume and redemption price data.
+
+        Parameters
+        ----------
+        days : int, default=60
+            number of days to pull data for
+        """
+        self._cached_volume = self._get_volume()
+        self._cached_redemption_prices = self._get_redemption_prices()
+
+    def clear_cache(self):
+        """
+        Clears any cached data.
+        """
+        self._cached_volume = None
+        self._cached_redemption_prices = None
+
+    @property
+    def volume(self):
+        """
+        Fetches the pool's historical volume over the specified number of days.
+
+        Parameters
+        ----------
+        days : int, default=60
+            Number of days to pull data for.
+
+        store : bool, default=False
+            If true, caches the fetched data.
+
+        get_cache : bool, default=True
+            If true, returns cached data when available.
+
+        Returns
+        -------
+        numpy.ndarray
+            Total volume summed across the specified number of days.
+
+        """
+        if self._cached_volume is not None:
+            logger.info("Getting cached historical volume...")
+            return self._cached_volume
+
+        return self._get_volume()
+
+    def _get_volume(self):
+        logger.info("Fetching historical volume...")
+        addresses = self.metadata.address
+        chain = self.metadata.chain
+        days = self.days
+        end = self.end
+
+        if issubclass(self.metadata.pool_type, CurveMetaPool):
+            # pylint: disable-next=protected-access
+            basepool_address = self.metadata._dict["basepool"]["address"]
+            addresses = [addresses, basepool_address]
+            vol = _volume(addresses, chain, days=days, end=end)
+            summed_vol = array([sum(v) for v in vol])
+        else:
+            vol = _volume(addresses, chain, days=days, end=end)
+            summed_vol = array(sum(vol))
+
+        return summed_vol
+
+    @property
+    def redemption_prices(self):
+        """
+        Fetches the pool's redemption price over the specified number of days.
+
+        Note: only returns data for RAI3CRV pool. Otherwise, returns None.
+
+        Parameters
+        ----------
+        days : int, default=60
+            Number of days to pull data for.
+
+        store : bool, default=False
+            If True, caches the fetched data.
+
+        get_cache : bool, default=True
+            If True, returns cached data when available.
+
+        Returns
+        -------
+        pandas.DataFrame
+            Timestamped redemption prices across the specified number of days.
+
+        """
+        if self._cached_redemption_prices is not None:
+            logger.info("Getting cached redemption prices...")
+            return self._cached_redemption_prices
+
+        return self._get_redemption_prices()
+
+    def _get_redemption_prices(self):
+        address = self.metadata.address
+        chain = self.metadata.chain
+
+        days = self.days
+        end = self.end
+
+        r = _redemption_prices(address, chain, days=days, end=end)
+
+        return r
diff --git a/curvesim/pool_data/data.py b/curvesim/pool_data/data.py
index 78b92a0ed..530a129dd 100644
--- a/curvesim/pool_data/data.py
+++ b/curvesim/pool_data/data.py
@@ -62,13 +62,13 @@ def pool(self, balanced=False, balanced_base=False, normalize=False, sim=False):
 
         Parameters
         ----------
-        balanced : bool, default=True
+        balanced : bool, default=False
             If True, balances the pool value across assets.
 
-        balanced_base : bool, default=True
+        balanced_base : bool, default=False
             If True and pool is metapool, balances the basepool value across assets.
 
-        normalize : bool, default=True
+        normalize : bool, default=False
             If True, normalizes balances to 18 decimals (useful for sim calculations).
 
         sim: bool, default=False
diff --git a/curvesim/pool_data/metadata/__init__.py b/curvesim/pool_data/metadata/__init__.py
new file mode 100644
index 000000000..e974ba6d8
--- /dev/null
+++ b/curvesim/pool_data/metadata/__init__.py
@@ -0,0 +1,66 @@
+__all__ = ["PoolMetaData", "PoolMetaDataInterface"]
+
+
+from curvesim.exceptions import CurvesimException
+from curvesim.network.subgraph import has_redemption_prices
+from curvesim.pool.cryptoswap import CurveCryptoPool
+from curvesim.pool.sim_interface import SimCurveMetaPool, SimCurvePool, SimCurveRaiPool
+from curvesim.pool.stableswap import CurveMetaPool, CurvePool, CurveRaiPool
+from curvesim.pool_data.metadata.cryptoswap import CryptoswapMetaData
+
+from .base import PoolMetaDataInterface
+from .stableswap import StableswapMetaData
+
+_SIM_POOL_TYPE = {
+    CurvePool: SimCurvePool,
+    CurveMetaPool: SimCurveMetaPool,
+    CurveRaiPool: SimCurveRaiPool,
+    # TODO: create sim pool for crypto pools
+    CurveCryptoPool: None,
+}
+
+_METADATA_TYPE = {
+    CurvePool: StableswapMetaData,
+    CurveMetaPool: StableswapMetaData,
+    CurveRaiPool: StableswapMetaData,
+    CurveCryptoPool: CryptoswapMetaData,
+}
+
+
+def PoolMetaData(metadata_dict):
+    metadata_type, pool_type, sim_pool_type = _parse_metadata_for_types(metadata_dict)
+    return metadata_type(metadata_dict, pool_type, sim_pool_type)
+
+
+def _parse_metadata_for_types(metadata_dict):
+    if metadata_dict["basepool"]:
+        if _has_redemption_prices(metadata_dict):
+            pool_type = CurveRaiPool
+        else:
+            pool_type = CurveMetaPool
+    else:
+        if metadata_dict["params"].get("gamma"):
+            pool_type = CurveCryptoPool
+        else:
+            pool_type = CurvePool
+
+    try:
+        sim_pool_type = _SIM_POOL_TYPE[pool_type]
+    except KeyError as e:
+        raise CurvesimException(
+            f"No sim pool type for this pool type: {pool_type}"
+        ) from e
+    try:
+        metadata_type = _METADATA_TYPE[pool_type]
+    except KeyError as e:
+        raise CurvesimException(
+            f"No metadata type for this pool type: {pool_type}"
+        ) from e
+
+    return metadata_type, pool_type, sim_pool_type
+
+
+def _has_redemption_prices(metadata_dict):
+    address = metadata_dict["address"]
+    chain = metadata_dict["chain"]
+    return has_redemption_prices(address, chain)
diff --git a/curvesim/pool_data/metadata/base.py b/curvesim/pool_data/metadata/base.py
new file mode 100644
index 000000000..e3de13715
--- /dev/null
+++ b/curvesim/pool_data/metadata/base.py
@@ -0,0 +1,117 @@
+from abc import abstractmethod, abstractproperty
+
+
+class PoolMetaDataInterface:
+    @abstractmethod
+    def init_kwargs(self, balanced=True, balanced_base=True, normalize=True):
+        raise NotImplementedError
+
+    @abstractproperty
+    def address(self):
+        raise NotImplementedError
+
+    @abstractproperty
+    def chain(self):
+        raise NotImplementedError
+
+    @abstractproperty
+    def pool_type(self):
+        """
+        Returns the pool type.
+
+        Returns
+        -------
+        Pool
+
+        """
+        raise NotImplementedError
+
+    @abstractproperty
+    def sim_pool_type(self):
+        """
+        Returns the pool type.
+
+        Returns
+        -------
+        SimPool
+
+        """
+        raise NotImplementedError
+
+    @abstractproperty
+    def coins(self):
+        """
+        Returns coin addresses for the pool's holdings.
+
+        For pools that are not on Ethereum mainnet, the address
+        for the corresponding mainnet token is returned.
+
+        For lending tokens (e.g., aTokens or cTokens), the
+        address for the underlying token is returned.
+
+        Returns
+        -------
+        list of strings
+            coin addresses
+
+        """
+        raise NotImplementedError
+
+    @abstractproperty
+    def coin_names(self):
+        """
+        Returns coin names for the pool's holdings.
+
+        For pools that are not on Ethereum mainnet, the name
+        of the corresponding mainnet token is returned.
+
+        For lending tokens (e.g., aTokens or cTokens), the
+        name of the underlying token is returned.
+
+        Returns
+        -------
+        list of strings
+            coin names
+
+        """
+        raise NotImplementedError
+
+    @abstractproperty
+    def n(self):
+        """
+        Returns the number of token-types (e.g., DAI, USDC, USDT) in a pool.
+
+        Returns
+        -------
+        int or list of ints
+            Number of token-types.
+
+            For metapools, a list [n_metapool, n_basepool] is returned.
+
+            N_metapool includes the basepool LP token.
+
+        """
+        raise NotImplementedError
+
+
+class PoolMetaDataBase(PoolMetaDataInterface):
+    def __init__(self, metadata_dict, pool_type, sim_pool_type):
+        self._dict = metadata_dict
+        self._pool_type = pool_type
+        self._sim_pool_type = sim_pool_type
+
+    @property
+    def address(self):
+        return self._dict["address"]
+
+    @property
+    def chain(self):
+        return self._dict["chain"]
+
+    @property
+    def pool_type(self):
+        return self._pool_type
+
+    @property
+    def sim_pool_type(self):
+        return self._sim_pool_type
diff --git a/curvesim/pool_data/metadata/cryptoswap.py b/curvesim/pool_data/metadata/cryptoswap.py
new file mode 100644
index 000000000..b1bbb7e65
--- /dev/null
+++ b/curvesim/pool_data/metadata/cryptoswap.py
@@ -0,0 +1,53 @@
+from .base import PoolMetaDataBase
+
+
+class CryptoswapMetaData(PoolMetaDataBase):
+    def init_kwargs(self, balanced=True, balanced_base=True, normalize=True):
+        data = self._dict
+
+        def process_to_kwargs(data, balanced, normalize):
+            kwargs = {
+                "A": data["params"]["A"],
+                "gamma": data["params"]["gamma"],
+                "n": len(data["coins"]["names"]),
+                "D": data["reserves"]["D"],
+                "mid_fee": data["params"]["mid_fee"],
+                "out_fee": data["params"]["out_fee"],
+                "allowed_extra_profit": data["params"]["allowed_extra_profit"],
+                "fee_gamma": data["params"]["fee_gamma"],
+                "adjustment_step": data["params"]["adjustment_step"],
+                "price_scale": data["params"]["price_scale"],
+                # FIXME: when subgraph is fixed, we can pull this
+                # "admin_fee": data["params"]["admin_fee"],
+                "ma_half_time": data["params"]["ma_half_time"],
+                "tokens": data["reserves"]["tokens"],
+                "xcp_profit": data["params"]["xcp_profit"],
+                "xcp_profit_a": data["params"]["xcp_profit_a"],
+            }
+            if not normalize:
+                kwargs["precisions"] = [
+                    10 ** (18 - d) for d in data["coins"]["decimals"]
+                ]
+            if not balanced:
+                if normalize:
+                    coin_balances = data["reserves"]["by_coin"]
+                else:
+                    coin_balances = data["reserves"]["unnormalized_by_coin"]
+                kwargs["balances"] = coin_balances
+            return kwargs
+
+        kwargs = process_to_kwargs(data, balanced, normalize)
+
+        return kwargs
+
+    @property
+    def coins(self):
+        return self._dict["coins"]["addresses"]
+
+    @property
+    def coin_names(self):
+        return self._dict["coins"]["names"]
+
+    @property
+    def n(self):
+        return len(self._dict["coins"]["names"])
diff --git a/curvesim/pool_data/metadata.py b/curvesim/pool_data/metadata/stableswap.py
similarity index 63%
rename from curvesim/pool_data/metadata.py
rename to curvesim/pool_data/metadata/stableswap.py
index 838687f08..789cd6cda 100644
--- a/curvesim/pool_data/metadata.py
+++ b/curvesim/pool_data/metadata/stableswap.py
@@ -1,20 +1,9 @@
-from curvesim.exceptions import CurvesimException
-from curvesim.network.subgraph import has_redemption_prices
-from curvesim.pool.sim_interface import SimCurveMetaPool, SimCurvePool, SimCurveRaiPool
-from curvesim.pool.stableswap import CurveMetaPool, CurvePool, CurveRaiPool
+from curvesim.pool.stableswap.pool import CurvePool
 
+from .base import PoolMetaDataBase
 
-class PoolMetaData:
-
-    _SIM_POOL_TYPE = {
-        CurvePool: SimCurvePool,
-        CurveMetaPool: SimCurveMetaPool,
-        CurveRaiPool: SimCurveRaiPool,
-    }
-
-    def __init__(self, metadata_dict):
-        self._dict = metadata_dict
 
+class StableswapMetaData(PoolMetaDataBase):
     def init_kwargs(self, balanced=True, balanced_base=True, normalize=True):
         data = self._dict
 
@@ -54,39 +43,6 @@ def process_to_kwargs(data, balanced, normalize):
 
         return kwargs
 
-    @property
-    def address(self):
-        return self._dict["address"]
-
-    @property
-    def chain(self):
-        return self._dict["chain"]
-
-    @property
-    def has_redemption_prices(self):
-        address = self.address
-        chain = self.chain
-        return has_redemption_prices(address, chain)
-
-    @property
-    def pool_type(self):
-        if self._dict["basepool"]:
-            if self.has_redemption_prices:
-                _pool_type = CurveRaiPool
-            else:
-                _pool_type = CurveMetaPool
-        else:
-            _pool_type = CurvePool
-        return _pool_type
-
-    @property
-    def sim_pool_type(self):
-        pool_type = self.pool_type
-        try:
-            return self._SIM_POOL_TYPE[pool_type]
-        except KeyError:
-            raise CurvesimException(f"No sim pool type for this pool type: {pool_type}")
-
     @property
     def coins(self):
         if not self._dict["basepool"]:
diff --git a/curvesim/sim/__init__.py b/curvesim/sim/__init__.py
index d8851b4fa..1a106c7a1 100644
--- a/curvesim/sim/__init__.py
+++ b/curvesim/sim/__init__.py
@@ -8,12 +8,14 @@
 """
 from curvesim.logging import get_logger
 from curvesim.pipelines.arbitrage import DEFAULT_PARAMS, volume_limited_arbitrage
-from curvesim.pool_data import get as get_pool_data
+from curvesim.pool_data import get_metadata
 
 logger = get_logger(__name__)
 
 
-def autosim(pool=None, chain="mainnet", pool_data=None, **kwargs):
+def autosim(
+    pool=None, chain="mainnet", pool_metadata=None, pool_data_cache=None, **kwargs
+):
     """
     The autosim() function simulates existing Curve pools with a range of
     parameters (e.g., the amplitude parameter, A, and/or the exchange fee).
@@ -32,7 +34,7 @@ def autosim(pool=None, chain="mainnet", pool_data=None, **kwargs):
         This string identifies the pool by address or LP token symbol.
 
         .. note::
-            Either `pool` or `pool_data` must be provided.
+            Either `pool` or `pool_metadata` must be provided.
 
         .. warning::
             An LP token symbol need not be unique.  In particular, factory
@@ -46,11 +48,15 @@ def autosim(pool=None, chain="mainnet", pool_data=None, **kwargs):
             "mainnet", "arbitrum", "optimism", "fantom", "avalanche"
             "matic", "xdai"
 
-    pool_data: PoolData, optional
+    pool_metadata: PoolMetaDataInterface, optional
         Pool data necessary to instantiate a pool object.
 
         .. note::
-            Either `pool` or `pool_data` must be provided.
+            Either `pool` or `pool_metadata` must be provided.
+
+    pool_data_cache: PoolDataCache, optional
+        Cached data used in sims.  Useful for replication of results and
+        avoiding re-fetches of data.
 
     A: int or iterable of int, optional
         Amplification coefficient.  This controls the curvature of the
@@ -132,13 +138,17 @@ def autosim(pool=None, chain="mainnet", pool_data=None, **kwargs):
     dict
         Dictionary of results, each value being a pandas.Series.
     """
-    assert any([pool, pool_data]), "Must input 'pool' or 'pool_data'"
+    assert any([pool, pool_metadata]), "Must input 'pool' or 'pool_metadata'"
 
-    pool_data = pool_data or get_pool_data(pool, chain)
+    pool_metadata = pool_metadata or get_metadata(pool, chain)
     p_var, p_fixed, kwargs = _parse_arguments(**kwargs)
 
     results = volume_limited_arbitrage(
-        pool_data, variable_params=p_var, fixed_params=p_fixed, **kwargs
+        pool_metadata,
+        pool_data_cache,
+        variable_params=p_var,
+        fixed_params=p_fixed,
+        **kwargs,
     )
 
     return results
diff --git a/notebooks/stableswap_demo.ipynb b/notebooks/stableswap_demo.ipynb
new file mode 100644
index 000000000..d4deed09f
--- /dev/null
+++ b/notebooks/stableswap_demo.ipynb
@@ -0,0 +1,367 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "0d257434",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "sys.path.insert(0, \"..\")\n",
+    "import curvesim"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "e27baea0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pool_address = \"0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7\"  # 3Pool\n",
+    "pool = curvesim.pool.get(pool_address)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "262ac0d7",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['DAI', 'USDC', 'USDT']"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.coin_names"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "d8b5ae69",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['0x6B175474E89094C44Da98b954EedeAC495271d0F',\n",
+       " '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n",
+       " '0xdAC17F958D2ee523a2206206994597C13D831ec7']"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.coin_addresses"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "4c46d853",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[18, 6, 6]"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.coin_decimals"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "cd5007b4",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[150726805240173777552867109, 158272436496568, 79307996630068]"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.balances"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "f6f70f37",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def format_balances(balances, decimals):\n",
+    "    return [f\"{b//10**d:,}\" for b,d in zip(balances, decimals)]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "3e694e97",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['150,726,805', '158,272,436', '79,307,996']"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "format_balances(pool.balances, pool.coin_decimals)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "8b52fa52",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(9994337981, 999533)"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "i = 1  # USDC\n",
+    "j = 2  # Tether\n",
+    "dx = 10**4 * 10**6\n",
+    "\n",
+    "# with pool.use_snapshot_context():  # reverts balances after each exchange\n",
+    "dy, fee = pool.exchange(i, j, dx)\n",
+    "dy, fee"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "62e310b4",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "2000"
+      ]
+     },
+     "execution_count": 10,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.A"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "id": "154adf28",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "388297785"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "pool.D() // 10**18  # pool value, assuming all coins mean-revert to $1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "0783a021",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "i = 0  # DAI\n",
+    "j = 1  # USDC\n",
+    "dx = 10**5 * 10**18\n",
+    "\n",
+    "As = []\n",
+    "dys = []\n",
+    "for A in range(100, 10000, 100):\n",
+    "    pool.A = A\n",
+    "    with pool.use_snapshot_context():  # reverts balances after each exchange\n",
+    "        dy, fee = pool.exchange(i, j, dx)\n",
+    "    As.append(A)\n",
+    "    dys.append(dy // 10**6)\n",
+    "\n",
+    "# reset to orig A\n",
+    "pool.A = pool.metadata[\"params\"][\"A\"]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "03b70e38",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib inline\n",
+    "import matplotlib.pyplot as plt\n",
+    "import pandas as pd"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "55f3110c",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(         dy\n",
+       " A          \n",
+       " 100  100035\n",
+       " 200  100013\n",
+       " 300  100005\n",
+       " 400  100001\n",
+       " 500   99999,\n",
+       "          dy\n",
+       " A          \n",
+       " 9500  99990\n",
+       " 9600  99990\n",
+       " 9700  99990\n",
+       " 9800  99990\n",
+       " 9900  99990)"
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "df = pd.DataFrame({\"A\": As, \"dy\": dys})\n",
+    "df = df.set_index('A')\n",
+    "df.head(), df.tail()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "61be952b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<AxesSubplot: xlabel='A'>"
+      ]
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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3Wrfvr2ioqLUr18/bd682fO6MUbTp09Xx44dFRUVpczMTH3++ede73Hs2DGNHTtWsbGxio+P13333aeysjKvaz777DNdd911ioyMVFpamp577rmgfD47qa6u1hNPPKEuXbooKipKl112mZ555hmvreAZ7+b58MMPNXr0aKWmpsrhcGjp0qVerwdzfBcvXqxevXopMjJS/fr10wcffNC4D2NauUWLFhmXy2VeeeUVs3PnTnP//feb+Ph4U1hYaHVptpaVlWXmzZtnduzYYXJycsx3v/tdk56ebsrKyjzXPPDAAyYtLc2sXLnSbN682Vx99dVm6NChnterqqpM3759TWZmptm6dav54IMPTGJiopk2bZrnmv3795vo6GgzZcoUs2vXLvOXv/zFhIeHm2XLlgX189rJxo0bTefOnU3//v3Ngw8+6Hme8fafY8eOmUsvvdTcc889ZsOGDWb//v1m+fLlZu/evZ5rZs2aZeLi4szSpUvNtm3bzPe+9z3TpUsXc/r0ac81N910k7niiivM+vXrzUcffWS6detm7rrrLs/rJSUlJjk52YwdO9bs2LHDvPHGGyYqKsr89a9/DerntdqMGTNM+/btzXvvvWcOHDhgFi9ebGJiYsyf/vQnzzWMd/N88MEH5rHHHjNvv/22kWSWLFni9XqwxveTTz4x4eHh5rnnnjO7du0yjz/+uImIiDDbt2/3+bO0+uDyrW99y2RnZ3u+rq6uNqmpqWbmzJkWVhV6ioqKjCSzdu1aY4wxxcXFJiIiwixevNhzze7du40ks27dOmPM2f+RwsLCTEFBgeeaOXPmmNjYWFNeXm6MMeaRRx4xffr08fpZd9xxh8nKygr0R7KlEydOmO7du5sVK1aYG264wRNcGG//evTRR821115b5+s1NTUmJSXF/Pa3v/U8V1xcbNxut3njjTeMMcbs2rXLSDKbNm3yXPPPf/7TOBwO89VXXxljjHnxxRdNu3btPONf+7N79uzp749ka6NGjTL33nuv13O33XabGTt2rDGG8fa3/w4uwRzfH/zgB2bUqFFe9QwZMsT85Cc/8bn+Vn2rqKKiQlu2bFFmZqbnubCwMGVmZmrdunUWVhZ6SkpKJEkJCQmSpC1btqiystJrbHv16qX09HTP2K5bt079+vVTcnKy55qsrCyVlpZq586dnmsufI/aa1rrn092drZGjRr1jTFhvP3rH//4hwYPHqzbb79dSUlJGjhwoP72t795Xj9w4IAKCgq8xiouLk5DhgzxGu/4+HgNHjzYc01mZqbCwsK0YcMGzzXXX3+9XC6X55qsrCzl5ubq+PHjgf6YtjF06FCtXLlSe/bskSRt27ZNH3/8sUaOHCmJ8Q60YI6vP/6NadXB5ejRo6qurvb6h1ySkpOTVVBQYFFVoaempkaTJ0/WNddco759+0qSCgoK5HK5FB8f73XthWNbUFBw0bGvfa2+a0pLS3X69OlAfBzbWrRokT799FPNnDnzG68x3v61f/9+zZkzR927d9fy5cs1ceJE/fznP9eCBQsknR+v+v7tKCgoUFJSktfrTqdTCQkJjfozaQ1+8Ytf6M4771SvXr0UERGhgQMHavLkyRo7dqwkxjvQgjm+dV3TmPEP+UMWYb3s7Gzt2LFDH3/8sdWltFj5+fl68MEHtWLFCkVGRlpdTotXU1OjwYMH69lnn5UkDRw4UDt27NBLL72k8ePHW1xdy/PWW2/p73//uxYuXKg+ffooJydHkydPVmpqKuONb2jVMy6JiYkKDw//xsqLwsJCpaSkWFRVaJk0aZLee+89rV692uuU7pSUFFVUVKi4uNjr+gvHNiUl5aJjX/tafdfExsYqKirK3x/HtrZs2aKioiJdeeWVcjqdcjqdWrt2rf785z/L6XQqOTmZ8fajjh07qnfv3l7PXX755crLy5N0frzq+7cjJSVFRUVFXq9XVVXp2LFjjfozaQ2mTp3qmXXp16+fxo0bp4ceesgzu8h4B1Ywx7euaxoz/q06uLhcLg0aNEgrV670PFdTU6OVK1cqIyPDwsrszxijSZMmacmSJVq1apW6dOni9fqgQYMUERHhNba5ubnKy8vzjG1GRoa2b9/u9T/DihUrFBsb6/mlkZGR4fUetde0tj+fG2+8Udu3b1dOTo7nMXjwYI0dO9bz34y3/1xzzTXfWN6/Z88eXXrppZKkLl26KCUlxWusSktLtWHDBq/xLi4u1pYtWzzXrFq1SjU1NRoyZIjnmg8//FCVlZWea1asWKGePXuqXbt2Aft8dnPq1CmFhXn/OgoPD1dNTY0kxjvQgjm+fvk3xuc23hZq0aJFxu12m/nz55tdu3aZH//4xyY+Pt5r5QW+aeLEiSYuLs6sWbPGHD582PM4deqU55oHHnjApKenm1WrVpnNmzebjIwMk5GR4Xm9dnnuiBEjTE5Ojlm2bJnp0KHDRZfnTp061ezevdvMnj27VS7PvZgLVxUZw3j708aNG43T6TQzZswwn3/+ufn73/9uoqOjzeuvv+65ZtasWSY+Pt6888475rPPPjM333zzRZePDhw40GzYsMF8/PHHpnv37l7LR4uLi01ycrIZN26c2bFjh1m0aJGJjo5uFctzLzR+/HhzySWXeJZDv/322yYxMdE88sgjnmsY7+Y5ceKE2bp1q9m6dauRZH7/+9+brVu3moMHDxpjgje+n3zyiXE6neZ3v/ud2b17t3nyySdZDt0Uf/nLX0x6erpxuVzmW9/6llm/fr3VJdmepIs+5s2b57nm9OnT5qc//alp166diY6ONrfeeqs5fPiw1/t88cUXZuTIkSYqKsokJiaahx9+2FRWVnpds3r1ajNgwADjcrlM165dvX5Ga/bfwYXx9q93333X9O3b17jdbtOrVy/z8ssve71eU1NjnnjiCZOcnGzcbre58cYbTW5urtc1X3/9tbnrrrtMTEyMiY2NNRMmTDAnTpzwumbbtm3m2muvNW6321xyySVm1qxZAf9sdlNaWmoefPBBk56ebiIjI03Xrl3NY4895rWslvFuntWrV1/03+zx48cbY4I7vm+99Zbp0aOHcblcpk+fPub9999v1GfhdGgAABAyWnWPCwAACC0EFwAAEDIILgAAIGQQXAAAQMgguAAAgJBBcAEAACGD4AIAAEIGwQUAAIQMggsAAAgZBBcAtrRu3TqFh4dr1KhRVpcCwEbY8h+ALf3oRz9STEyM5s6dq9zcXKWmplpdEgAbYMYFgO2UlZXpzTff1MSJEzVq1CjNnz/f6pIA2ATBBYDtvPXWW+rVq5d69uypu+++W6+88oqYHAYgEVwA2NDcuXN19913S5JuuukmlZSUaO3atRZXBcAO6HEBYCu5ubnq27evvvrqKyUlJUmSJk2apJKSEr322msWVwfAak6rCwCAC82dO1dVVVVezbjGGLndbr3wwguKi4uzsDoAVuNWEQDbqKqq0quvvqrnn39eOTk5nse2bduUmpqqN954w+oSAViMW0UAbGPp0qW64447VFRU9I2ZlUcffVSrVq3Spk2bLKoOgB0QXADYxujRo1VTU6P333//G69t3LhRQ4YM0bZt29S/f38LqgNgBwQXAAAQMuhxAQAAIYPgAgAAQgbBBQAAhAyCCwAACBkEFwAAEDIILgAAIGQQXAAAQMgguAAAgJBBcAEAACGD4AIAAEIGwQUAAIQMggsAAAgZ/x/g50HlkpstzQAAAABJRU5ErkJggg==",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "df.plot()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "id": "a2fcd2cc",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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VpIkQaPRAACWLVuGiIgI2Ns/7l/MnDkTBw4cMPVe1CQkJAQGgwG3b99Ghw4dqj28vb0BAA8fPoRWW735GBtrZWWlxTOTMthqmwLYrqhxBg8ejCtXrmDPnj2YNm0azpw5g0GDBuH999+v0+urtr2q/1vJLNmmWCxSrc6dO4eAgADcv38fycnJ+Mc//gF7e3vY29ujdevWqKioUOyk/KKiImRlZSErKwvA4+UFsrKykJ2djU6dOiEqKgoxMTFISkrC1atXcfjwYcTFxWHbtm0AgPHjx+PIkSN47733cPHiRRw7dgwvv/wy2rZti5CQEIGfjKyZmtsUwHZFDePq6gq9Xv/E8w8ePICbm5vpZwcHBwwaNAi/+93vsGvXLrz33nt4//33UVZW9tT3NxgMuHjxIgICAgAAnTp1gl6vR05OjrwfxAyspk01YI4lqcizJuMvW7ZM+tvf/iY999xz0qlTp6o9lixZIvn6+koVFRVPfS9rlJaWZlpioepjzpw5kiRJUllZmbR48WKpXbt2koODg+Tj4yNNnjxZOnnypOk9Vq1aJYWEhEjNmjWTPDw8pIkTJ0rnzp0T9InIWthqm5IktitqmN/85jdS9+7dn3g+OjpaCgsLq/V1GzZskDQajaTX6yVJqv0Gl2+++UYCIO3du1eSJEnKzs5WzA0u1tKmWCzauDlz5khjxoyRcnJypJs3b0qZmZnSH//4R8nZ2Vl68cUXpYqKCik4OFj63e9+98RrHzx4IDk6Okpbt241vZeSTmxE5sA2RVQ/ly9flpycnKRf/epX0okTJ6Tz589LS5Yskezt7aUdO3ZIkvS4EPziiy+ko0ePSlevXpW2bdsmBQYGSsOHDze9z5AhQ6R58+ZJOTk50o0bN6SMjAzprbfekhwcHKTXXnut2t9cunSppNFopF/84hfSvn37pGvXrknff/+99Morr0gLFy606OdXAhaLNu7nCwh7eHhIYWFh0rJlyySDwSAdPXpUAlDjgsGSJEljx46VJk+ebHovntjI1rFNEdXf4cOHpZEjR0oeHh6Sm5ub1Ldv32rLxHz44YdSaGio5O7uLjk5OUnt27eXXn/9denu3bumY36+KLePj4/04osvSklJSTX+zdTUVGn06NFSixYtJCcnJ6lz587Sb37zG+nWrVvm/riKo5EkrkdARERERDXjDS5EREREVCsWi0RERERUKxaLRERERFQrFotEREREVCsWi0RERERUKxaLRERERFQrFotEREREVCsWi0RERERUKxaLRERERFQrFotEREREVCsWi0RERERUq/8Po2/SAhPYS9UAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 3 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "_ = curvesim.bonding_curve(pool)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4c48f6d1",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}


Test patch:
diff --git a/test/ci.py b/test/ci.py
index 63a8d0f64..c044f8e9a 100644
--- a/test/ci.py
+++ b/test/ci.py
@@ -54,15 +54,17 @@ def main():  # noqa: C901
         end_ts = pool["end_timestamp"]
         vol_mult = pool.get("vol_mult", None)
 
-        f_name = os.path.join(data_dir, f"{address}-pool_data.pickle")
-        pool_data = read_pickle(f_name)
+        f_name = os.path.join(data_dir, f"{address}-pool_data_cache.pickle")
+        pool_data_cache = read_pickle(f_name)
+        f_name = os.path.join(data_dir, f"{address}-pool_metadata.pickle")
+        pool_metadata = read_pickle(f_name)
 
         results = curvesim.autosim(
-            address,
+            pool_metadata=pool_metadata,
             test=True,
             src="local",
             data_dir=data_dir,
-            pool_data=pool_data,
+            pool_data_cache=pool_data_cache,
             end=end_ts,
             vol_mult=vol_mult,
         )
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data.pickle
deleted file mode 100644
index 18bda4ee8..000000000
Binary files a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data.pickle and /dev/null differ
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data_cache.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data_cache.pickle
new file mode 100644
index 000000000..361a4ee7d
Binary files /dev/null and b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_data_cache.pickle differ
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_metadata.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_metadata.pickle
new file mode 100644
index 000000000..c45600e0e
Binary files /dev/null and b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-pool_metadata.pickle differ
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_run.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_run.pickle
index 3e6224ec6..88de1d0a1 100644
Binary files a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_run.pickle and b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_run.pickle differ
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_trade.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_trade.pickle
index 9c674b60c..6e78ed5fd 100644
Binary files a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_trade.pickle and b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_per_trade.pickle differ
diff --git a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_summary.pickle b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_summary.pickle
index 7bb26d9e1..81a66ac29 100644
Binary files a/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_summary.pickle and b/test/data/0xbebc44782c7db0a1a60cb6fe97d0b483032ff1c7-results_summary.pickle differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data.pickle
deleted file mode 100644
index cae65d1ad..000000000
Binary files a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data.pickle and /dev/null differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data_cache.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data_cache.pickle
new file mode 100644
index 000000000..08b5d7ffb
Binary files /dev/null and b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_data_cache.pickle differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_metadata.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_metadata.pickle
new file mode 100644
index 000000000..ffe2776f3
Binary files /dev/null and b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-pool_metadata.pickle differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_run.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_run.pickle
index 0b0403f3d..576cce99a 100644
Binary files a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_run.pickle and b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_run.pickle differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_trade.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_trade.pickle
index 53980a18a..bf9081253 100644
Binary files a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_trade.pickle and b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_per_trade.pickle differ
diff --git a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_summary.pickle b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_summary.pickle
index 86ef5db2b..8be84fb21 100644
Binary files a/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_summary.pickle and b/test/data/0xdebf20617708857ebe4f679508e7b7863a8a8eee-results_summary.pickle differ
diff --git a/test/data/FRAX3CRV-f-pool_data.pickle b/test/data/FRAX3CRV-f-pool_data.pickle
deleted file mode 100644
index 71ee24a6b..000000000
Binary files a/test/data/FRAX3CRV-f-pool_data.pickle and /dev/null differ
diff --git a/test/data/FRAX3CRV-f-pool_data_cache.pickle b/test/data/FRAX3CRV-f-pool_data_cache.pickle
new file mode 100644
index 000000000..7859f5298
Binary files /dev/null and b/test/data/FRAX3CRV-f-pool_data_cache.pickle differ
diff --git a/test/data/FRAX3CRV-f-pool_metadata.pickle b/test/data/FRAX3CRV-f-pool_metadata.pickle
new file mode 100644
index 000000000..c6dfd6a1c
Binary files /dev/null and b/test/data/FRAX3CRV-f-pool_metadata.pickle differ
diff --git a/test/data/FRAX3CRV-f-results_per_run.pickle b/test/data/FRAX3CRV-f-results_per_run.pickle
index fff49ffc4..3f8d15dca 100644
Binary files a/test/data/FRAX3CRV-f-results_per_run.pickle and b/test/data/FRAX3CRV-f-results_per_run.pickle differ
diff --git a/test/data/FRAX3CRV-f-results_per_trade.pickle b/test/data/FRAX3CRV-f-results_per_trade.pickle
index 3f25e86bb..911081eed 100644
Binary files a/test/data/FRAX3CRV-f-results_per_trade.pickle and b/test/data/FRAX3CRV-f-results_per_trade.pickle differ
diff --git a/test/data/FRAX3CRV-f-results_summary.pickle b/test/data/FRAX3CRV-f-results_summary.pickle
index b3755dc1d..b75d60045 100644
Binary files a/test/data/FRAX3CRV-f-results_summary.pickle and b/test/data/FRAX3CRV-f-results_summary.pickle differ
diff --git a/test/data/OUSD3CRV-f-pool_data.pickle b/test/data/OUSD3CRV-f-pool_data.pickle
deleted file mode 100644
index 436fe76f9..000000000
Binary files a/test/data/OUSD3CRV-f-pool_data.pickle and /dev/null differ
diff --git a/test/data/OUSD3CRV-f-pool_data_cache.pickle b/test/data/OUSD3CRV-f-pool_data_cache.pickle
new file mode 100644
index 000000000..d869addd7
Binary files /dev/null and b/test/data/OUSD3CRV-f-pool_data_cache.pickle differ
diff --git a/test/data/OUSD3CRV-f-pool_metadata.pickle b/test/data/OUSD3CRV-f-pool_metadata.pickle
new file mode 100644
index 000000000..4d727820d
Binary files /dev/null and b/test/data/OUSD3CRV-f-pool_metadata.pickle differ
diff --git a/test/data/OUSD3CRV-f-results_per_run.pickle b/test/data/OUSD3CRV-f-results_per_run.pickle
index 2e919be2c..e432f22b3 100644
Binary files a/test/data/OUSD3CRV-f-results_per_run.pickle and b/test/data/OUSD3CRV-f-results_per_run.pickle differ
diff --git a/test/data/OUSD3CRV-f-results_per_trade.pickle b/test/data/OUSD3CRV-f-results_per_trade.pickle
index 208b1477e..e69af5fe0 100644
Binary files a/test/data/OUSD3CRV-f-results_per_trade.pickle and b/test/data/OUSD3CRV-f-results_per_trade.pickle differ
diff --git a/test/data/OUSD3CRV-f-results_summary.pickle b/test/data/OUSD3CRV-f-results_summary.pickle
index bd1527007..379d02b42 100644
Binary files a/test/data/OUSD3CRV-f-results_summary.pickle and b/test/data/OUSD3CRV-f-results_summary.pickle differ
diff --git a/test/integration/test_subgraph.py b/test/integration/test_subgraph.py
index 5a443e0d0..ab96f5cf5 100644
--- a/test/integration/test_subgraph.py
+++ b/test/integration/test_subgraph.py
@@ -20,8 +20,8 @@ def test_convex_subgraph_volume_query():
     assert len(volumes) == 0
 
 
-def test_convex_subgraph_pool_snapshot_query():
-    """Test the pool snapshot query."""
+def test_convex_subgraph_stableswap_snapshot_query():
+    """Test the pool snapshot query for stableswap."""
 
     chain = "mainnet"
     address = "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"
@@ -30,5 +30,27 @@ def test_convex_subgraph_pool_snapshot_query():
     assert snapshot["address"] == address
     assert snapshot["chain"] == chain
 
+    assert snapshot["version"] == 1
+
+    params = snapshot["params"]
+    assert params["A"] > 0
+
     with pytest.raises(SubgraphError):
         _snapshot_sync(ZERO_ADDRESS, chain)
+
+
+def test_convex_subgraph_cryptoswap_snapshot_query():
+    """Test the pool snapshot query for cryptoswap."""
+
+    chain = "mainnet"
+    address = "0x3211C6cBeF1429da3D0d58494938299C92Ad5860"
+    _snapshot_sync = sync(pool_snapshot)
+    snapshot = _snapshot_sync(address, chain)
+    assert snapshot["address"] == address
+    assert snapshot["chain"] == chain
+
+    assert snapshot["version"] == 2
+
+    params = snapshot["params"]
+    assert params["A"] > 0
+    assert params["gamma"] > 0
diff --git a/test/scripts/make_test_data.py b/test/scripts/make_test_data.py
index 935d6aabd..cea8c04bf 100644
--- a/test/scripts/make_test_data.py
+++ b/test/scripts/make_test_data.py
@@ -1,16 +1,16 @@
 """Script to generate data for CI test"""
 import os
 import pickle
-import shutil
 import sys
-from itertools import combinations
-
-from pandas import read_pickle
 
 import curvesim
-from curvesim.network.nomics import coin_ids_from_addresses_sync
+
+# from curvesim.network.nomics import coin_ids_from_addresses_sync
 from curvesim.pipelines.arbitrage import volume_limited_arbitrage as pipeline
 
+# import shutil
+# from itertools import combinations
+
 
 def main(fetch_data=False):
     """
@@ -55,31 +55,33 @@ def main(fetch_data=False):
             address = pool["address"]
             end_ts = pool["end_timestamp"]
 
-            pool_data = curvesim.pool_data.get(address)
-
-            # Store pool_data
-            pool_data.set_cache(end=end_ts)
-            f_name = os.path.join(test_data_dir, address + "-pool_data.pickle")
-            with open(f_name, "wb") as f:
-                pickle.dump(pool_data, f)
-
-            # Store price data
-            coins = pool_data.coins
-            curvesim.price_data.get(
-                coins, src="nomics", data_dir=test_data_dir, end=end_ts
-            )
-
-            # Rename files from coin IDs to addresses.
-            # Need to do this because the sim pipeline actually uses the addresses.
-            # FIXME: update the nomics file download to use addresses.
-            coin_combos = combinations(coins, 2)
-            for pair in coin_combos:
-                id_pair = coin_ids_from_addresses_sync(pair)
-                f_from = os.path.join(
-                    test_data_dir, f"{id_pair[0]}-{id_pair[1]}-{end_ts}.csv"
-                )
-                f_to = os.path.join(test_data_dir, f"{pair[0]}-{pair[1]}-{end_ts}.csv")
-                shutil.copyfile(f_from, f_to)
+        pool_data_cache = curvesim.pool_data.get_data_cache(address, end=end_ts)
+        pool_metadata = curvesim.pool_data.get_metadata(address)
+
+        # Store pool_data
+        pool_data_cache.set_cache()
+        f_name = os.path.join(test_data_dir, address + "-pool_data_cache.pickle")
+        with open(f_name, "wb") as f:
+            pickle.dump(pool_data_cache, f)
+        f_name = os.path.join(test_data_dir, address + "-pool_metadata.pickle")
+        with open(f_name, "wb") as f:
+            pickle.dump(pool_metadata, f)
+
+        # Store price data
+        # coins = pool_data.coins
+        # curvesim.price_data.get(coins, src="nomics", data_dir=test_data_dir, end=end_ts)
+
+        # # Rename files from coin IDs to addresses.
+        # # Need to do this because the sim pipeline actually uses the addresses.
+        # # FIXME: update the nomics file download to use addresses.
+        # coin_combos = combinations(coins, 2)
+        # for pair in coin_combos:
+        #     id_pair = coin_ids_from_addresses_sync(pair)
+        #     f_from = os.path.join(
+        #         test_data_dir, f"{id_pair[0]}-{id_pair[1]}-{end_ts}.csv"
+        #     )
+        #     f_to = os.path.join(test_data_dir, f"{pair[0]}-{pair[1]}-{end_ts}.csv")
+        #     shutil.copyfile(f_from, f_to)
 
     # Run sim from stored data and save results
     print("Getting sim results...")
@@ -88,11 +90,16 @@ def main(fetch_data=False):
         end_ts = pool["end_timestamp"]
         vol_mult = pool.get("vol_mult", None)
 
-        f_name = os.path.join(test_data_dir, address + "-pool_data.pickle")
-        pool_data = read_pickle(f_name)
+        f_name = os.path.join(test_data_dir, address + "-pool_data_cache.pickle")
+        with open(f_name, "rb") as f:
+            pool_data_cache = pickle.load(f)
+        f_name = os.path.join(test_data_dir, address + "-pool_metadata.pickle")
+        with open(f_name, "rb") as f:
+            pool_metadata = pickle.load(f)
 
         results = pipeline(
-            pool_data,
+            pool_metadata,
+            pool_data_cache,
             test=True,
             src="local",
             data_dir=test_data_dir,
diff --git a/test/unit/test_cryptopool.py b/test/unit/test_cryptopool.py
index 528667cb2..ff0865d7f 100644
--- a/test/unit/test_cryptopool.py
+++ b/test/unit/test_cryptopool.py
@@ -54,7 +54,7 @@ def initialize_pool(vyper_cryptopool):
         adjustment_step=adjustment_step,
         admin_fee=admin_fee,
         ma_half_time=ma_half_time,
-        initial_price=price_scale,
+        price_scale=price_scale,
         balances=balances,
         D=D,
         tokens=lp_total_supply,
@@ -512,7 +512,7 @@ def test_exchange(vyper_cryptopool, dx_perc, i):
     dx = pool.balances[i] * dx_perc // 100
 
     expected_dy = vyper_cryptopool.exchange(i, j, dx, 0)
-    dy = pool.exchange(i, j, dx)
+    dy, _ = pool.exchange(i, j, dx)
 
     assert dy == expected_dy
 
diff --git a/test/unit/test_pool_metadata.py b/test/unit/test_pool_metadata.py
index 5ae6a37fc..c86f04448 100644
--- a/test/unit/test_pool_metadata.py
+++ b/test/unit/test_pool_metadata.py
@@ -1,5 +1,6 @@
 import json
 
+from curvesim.pool.cryptoswap.pool import CurveCryptoPool
 from curvesim.pool.sim_interface.metapool import SimCurveMetaPool
 from curvesim.pool.sim_interface.pool import SimCurvePool
 from curvesim.pool.stableswap.metapool import CurveMetaPool
@@ -80,7 +81,8 @@
             "params": {
                 "A": 1500,
                 "fee": 1000000,
-                "fee_mul": null
+                "fee_mul": null,
+                "admin_fee": 5000000000
             },
             "coins": {
                 "names": ["FRAX", "USDC"],
@@ -104,7 +106,6 @@
 }
 """
 
-# TODO; this needs to be updated once we have v2 snapshots
 CRYPTOPOOL_TEST_METADATA_JSON = """
 {
     "name": "Curve.fi Factory Crypto Pool: STG/USDC",
@@ -113,24 +114,40 @@
     "symbol": "STGUSDC-f",
     "version": 2,
     "pool_type": "CRYPTO_FACTORY",
-    "params": {"A": 400000, "fee": 40971769, "fee_mul": null},
+    "params": {
+        "A": 400000,
+        "gamma": 72500000000000,
+        "fee_gamma": 230000000000000,
+        "mid_fee": 26000000,
+        "out_fee": 45000000,
+        "allowed_extra_profit": 2000000000000,
+        "adjustment_step": 146000000000000,
+        "ma_half_time": 146000000000000,
+        "price_scale": 1532848669525694314,
+        "price_oracle": 1629891359676425537,
+        "last_prices": 1625755383082188296,
+        "last_prices_timestamp": 1684107935,
+        "admin_fee": 5000000000,
+        "xcp_profit": 1073065310463073367,
+        "xcp_profit_a": 1073065310463073367
+    },
     "coins": {
         "names": ["STG", "USDC"],
         "addresses": [
             "0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6",
-            "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"]
+            "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
+        ],
+        "decimals": [18, 6]
     },
     "reserves": {
-        "D": 27009344624167129044473141,
-        "by_coin": [
-            13497126535708129065554254,
-            13512218088459000000000000
-        ],
-        "virtual_price": 1031212780979726100,
-        "tokens": 26191824929192900959501494
+        "D": 18116170684879887969148488,
+        "by_coin": [11278350350009782994292193, 6837820334873000000000000],
+        "unnormalized_by_coin": [11278350350009782994292193, 6837820334873],
+        "virtual_price": 1036543672382221695,
+        "tokens": 17477479403491661243983086
     },
     "basepool": null,
-    "timestamp": 1677715200
+    "timestamp": 1684108800
 }
 """
 
@@ -157,7 +174,7 @@ def test_pool():
 
     assert metadata.n == 3
 
-    assert metadata.has_redemption_prices is False
+    assert metadata.pool_type is CurvePool
 
     assert metadata.init_kwargs() == {
         "A": 2000,
@@ -216,7 +233,7 @@ def test_metapool():
 
     assert metadata.n == [2, 2]
 
-    assert metadata.has_redemption_prices is False
+    assert metadata.pool_type is CurveMetaPool
 
     init_kwargs = metadata.init_kwargs()
     _ = init_kwargs.pop("basepool")
@@ -254,3 +271,83 @@ def test_metapool():
         "tokens": 9145685457506457679415433,
         "rate_multiplier": 10000000000000000000000000000000000,
     }
+
+
+def test_cryptopool():
+    metadata = PoolMetaData(cryptopool_test_metadata)
+
+    assert metadata.address == "0x3211C6cBeF1429da3D0d58494938299C92Ad5860"
+    assert metadata.chain == "mainnet"
+
+    assert metadata.pool_type == CurveCryptoPool
+    # TODO: create sim pool for crypto pools
+    # assert metadata.sim_pool_type == SimCurveCryptoPool
+
+    assert metadata.coin_names == ["STG", "USDC"]
+    assert metadata.coins == [
+        "0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6",
+        "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+    ]
+
+    assert metadata.n == 2
+
+    assert metadata.init_kwargs() == {
+        "A": 400000,
+        "gamma": 72500000000000,
+        "D": 18116170684879887969148488,
+        "n": 2,
+        "mid_fee": 26000000,
+        "out_fee": 45000000,
+        "adjustment_step": 146000000000000,
+        "allowed_extra_profit": 2000000000000,
+        "fee_gamma": 230000000000000,
+        "tokens": 17477479403491661243983086,
+        "ma_half_time": 146000000000000,
+        "price_scale": 1532848669525694314,
+        "xcp_profit": 1073065310463073367,
+        "xcp_profit_a": 1073065310463073367,
+    }
+    assert metadata.init_kwargs(balanced=False) == {
+        "A": 400000,
+        "gamma": 72500000000000,
+        "D": 18116170684879887969148488,
+        "balances": [
+            11278350350009782994292193,
+            6837820334873000000000000,
+        ],
+        "n": 2,
+        "mid_fee": 26000000,
+        "out_fee": 45000000,
+        "adjustment_step": 146000000000000,
+        "allowed_extra_profit": 2000000000000,
+        "fee_gamma": 230000000000000,
+        "tokens": 17477479403491661243983086,
+        "ma_half_time": 146000000000000,
+        "price_scale": 1532848669525694314,
+        "xcp_profit": 1073065310463073367,
+        "xcp_profit_a": 1073065310463073367,
+    }
+    assert metadata.init_kwargs(balanced=False, normalize=False) == {
+        "A": 400000,
+        "gamma": 72500000000000,
+        "D": 18116170684879887969148488,
+        "balances": [
+            11278350350009782994292193,
+            6837820334873,
+        ],
+        "n": 2,
+        "mid_fee": 26000000,
+        "out_fee": 45000000,
+        "adjustment_step": 146000000000000,
+        "allowed_extra_profit": 2000000000000,
+        "fee_gamma": 230000000000000,
+        "tokens": 17477479403491661243983086,
+        "ma_half_time": 146000000000000,
+        "price_scale": 1532848669525694314,
+        "xcp_profit": 1073065310463073367,
+        "xcp_profit_a": 1073065310463073367,
+        "precisions": [
+            1,
+            1000000000000,
+        ],
+    }