myco-bonding-curve/notebooks/05_stress_scenarios.ipynb

325 lines
11 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Stress Scenarios\n",
"\n",
"Bank runs, imbalance attacks, parameter sweeps — testing the system's resilience."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"%matplotlib inline\n",
"plt.rcParams['figure.figsize'] = (14, 5)\n",
"plt.rcParams['figure.dpi'] = 100"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Bank Run: Varying Redemption Pressure\n",
"\n",
"How much reserve is preserved under different redemption intensities?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.composed.simulator import scenario_bank_run\n",
"\n",
"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
"\n",
"fractions = [0.02, 0.05, 0.10, 0.20]\n",
"colors = ['blue', 'green', 'orange', 'red']\n",
"\n",
"for frac, color in zip(fractions, colors):\n",
" result = scenario_bank_run(\n",
" initial_reserve=100_000, n_assets=3,\n",
" redemption_fraction=frac, duration=100,\n",
" )\n",
" label = f'{frac*100:.0f}%/step'\n",
" axes[0].plot(result.times, result.reserve_value / 100_000 * 100, color=color, label=label)\n",
" axes[1].plot(result.times, result.supply, color=color, label=label)\n",
" axes[2].plot(result.times, result.backing_ratio, color=color, label=label)\n",
"\n",
"axes[0].set_ylabel('Reserve (% of initial)')\n",
"axes[0].set_title('Reserve Preservation')\n",
"axes[0].legend()\n",
"\n",
"axes[1].set_ylabel('MYCO Supply')\n",
"axes[1].set_title('Supply Decay')\n",
"\n",
"axes[2].set_ylabel('Backing Ratio')\n",
"axes[2].set_title('Backing Ratio')\n",
"axes[2].axhline(y=1.0, color='k', linestyle='--', alpha=0.3)\n",
"\n",
"for ax in axes:\n",
" ax.set_xlabel('Day')\n",
" ax.axvline(x=10, color='gray', linestyle=':', alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Flow Dampening Parameter Sweep\n",
"\n",
"How does the flow memory parameter affect protection?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.primitives.flow_dampening import simulate_bank_run as sim_flow\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"for memory, color in [(0.9, 'red'), (0.95, 'orange'), (0.99, 'green'), (0.999, 'blue')]:\n",
" result = sim_flow(initial_reserve=10000, outflow_per_step=500, memory=memory, steps=100)\n",
" axes[0].plot(result['reserve'], color=color, label=f'memory={memory}')\n",
" axes[1].plot(result['penalties'], color=color, label=f'memory={memory}')\n",
"\n",
"axes[0].set_xlabel('Step')\n",
"axes[0].set_ylabel('Reserve')\n",
"axes[0].set_title('Reserve Under Different Memory Parameters')\n",
"axes[0].legend()\n",
"\n",
"axes[1].set_xlabel('Step')\n",
"axes[1].set_ylabel('Penalty multiplier')\n",
"axes[1].set_title('Flow Penalty Over Time')\n",
"axes[1].legend()\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Imbalance Attack\n",
"\n",
"What happens if an attacker repeatedly deposits only one asset?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.composed.myco_surface import MycoSystem, MycoSystemConfig\n",
"from src.primitives.reserve_tranching import current_weights\n",
"\n",
"system = MycoSystem(MycoSystemConfig(n_reserve_assets=3))\n",
"\n",
"# Bootstrap with balanced deposit\n",
"system.deposit(np.array([10000.0, 10000.0, 10000.0]), 0.0)\n",
"\n",
"# Attacker deposits only asset 0\n",
"imbalances = []\n",
"weights_history = []\n",
"fees = []\n",
"minted_per_dollar = []\n",
"\n",
"for i in range(20):\n",
" amount = np.array([1000.0, 0.0, 0.0])\n",
" minted, meta = system.deposit(amount, float(i + 1))\n",
" metrics = system.get_metrics()\n",
" \n",
" imbalances.append(metrics['imbalance'])\n",
" weights_history.append(metrics['reserve_weights'].copy())\n",
" fees.append(meta.get('fee_rate', 0))\n",
" minted_per_dollar.append(minted / 1000.0 if minted > 0 else 0)\n",
"\n",
"fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
"\n",
"steps = list(range(1, 21))\n",
"\n",
"ax = axes[0, 0]\n",
"w_arr = np.array(weights_history)\n",
"for j in range(3):\n",
" ax.plot(steps, w_arr[:, j], '-o', markersize=4, label=f'Asset {j}')\n",
"ax.axhline(y=1/3, color='k', linestyle='--', alpha=0.3, label='Target (1/3)')\n",
"ax.set_xlabel('Attacker deposit #')\n",
"ax.set_ylabel('Weight')\n",
"ax.set_title('Reserve Weight Drift')\n",
"ax.legend()\n",
"\n",
"ax = axes[0, 1]\n",
"ax.plot(steps, imbalances, 'r-o', markersize=4)\n",
"ax.set_xlabel('Attacker deposit #')\n",
"ax.set_ylabel('Imbalance')\n",
"ax.set_title('Reserve Imbalance')\n",
"\n",
"ax = axes[1, 0]\n",
"ax.plot(steps, fees, 'purple', marker='o', markersize=4)\n",
"ax.set_xlabel('Attacker deposit #')\n",
"ax.set_ylabel('Fee rate')\n",
"ax.set_title('Surge Fee (penalizes imbalanced deposits)')\n",
"\n",
"ax = axes[1, 1]\n",
"ax.plot(steps, minted_per_dollar, 'g-o', markersize=4)\n",
"ax.set_xlabel('Attacker deposit #')\n",
"ax.set_ylabel('MYCO per $1 deposited')\n",
"ax.set_title('Minting Efficiency (decreasing returns)')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. P-AMM Parameter Sweep\n",
"\n",
"How do alpha and theta_bar affect redemption behavior?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.primitives.redemption_curve import PAMMParams, PAMMState, compute_redemption_rate\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"ba_range = np.linspace(0.01, 1.5, 200)\n",
"\n",
"# Sweep alpha\n",
"ax = axes[0]\n",
"for alpha, color in [(0.5, 'red'), (1.0, 'orange'), (2.0, 'green'), (5.0, 'blue')]:\n",
" params = PAMMParams(alpha_bar=alpha)\n",
" rates = [compute_redemption_rate(PAMMState(reserve_value=ba*1000, myco_supply=1000), params, 10.0)\n",
" for ba in ba_range]\n",
" ax.plot(ba_range, rates, color=color, linewidth=2, label=f'α={alpha}')\n",
"ax.axhline(y=1.0, color='k', linestyle='--', alpha=0.3)\n",
"ax.set_xlabel('Backing Ratio')\n",
"ax.set_ylabel('Redemption Rate')\n",
"ax.set_title('P-AMM: Effect of α (steepness)')\n",
"ax.legend()\n",
"\n",
"# Sweep theta_bar (floor)\n",
"ax = axes[1]\n",
"for theta, color in [(0.0, 'red'), (0.1, 'orange'), (0.3, 'green'), (0.5, 'blue')]:\n",
" params = PAMMParams(theta_bar=theta)\n",
" rates = [compute_redemption_rate(PAMMState(reserve_value=ba*1000, myco_supply=1000), params, 10.0)\n",
" for ba in ba_range]\n",
" ax.plot(ba_range, rates, color=color, linewidth=2, label=f'θ̄={theta}')\n",
"ax.axhline(y=1.0, color='k', linestyle='--', alpha=0.3)\n",
"ax.set_xlabel('Backing Ratio')\n",
"ax.set_ylabel('Redemption Rate')\n",
"ax.set_title('P-AMM: Effect of θ̄ (floor rate)')\n",
"ax.legend()\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Commitment Dilution Stress Test\n",
"\n",
"What happens if commitment channels mint aggressively?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from src.composed.myco_surface import MycoSystem, MycoSystemConfig\n",
"from src.commitments.labor import ContributorState, attest_contribution\n",
"\n",
"# Compare: commitment cap at 10%, 25%, 50%\n",
"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
"\n",
"for cap, color, label in [(0.1, 'blue', '10% cap'), (0.25, 'orange', '25% cap'), (0.5, 'red', '50% cap')]:\n",
" config = MycoSystemConfig(\n",
" n_reserve_assets=2,\n",
" max_labor_mint_fraction=cap,\n",
" )\n",
" system = MycoSystem(config)\n",
" system.deposit(np.array([10000.0, 10000.0]), 0.0)\n",
"\n",
" supplies = []\n",
" backing_ratios = []\n",
" commitment_pcts = []\n",
"\n",
" contrib = ContributorState(address='heavy_contributor')\n",
" for day in range(1, 91):\n",
" t = float(day)\n",
" contrib = attest_contribution(system.state.labor_system, contrib, 'code', 50.0, t)\n",
" contrib, tokens = system.mint_from_labor(contrib, 'code', t)\n",
" metrics = system.get_metrics()\n",
" supplies.append(metrics['supply'])\n",
" backing_ratios.append(metrics['backing_ratio'])\n",
" total = metrics['financial_minted'] + metrics['commitment_minted']\n",
" commitment_pcts.append(metrics['commitment_minted'] / total * 100 if total > 0 else 0)\n",
"\n",
" days = list(range(1, 91))\n",
" axes[0].plot(days, supplies, color=color, label=label)\n",
" axes[1].plot(days, backing_ratios, color=color, label=label)\n",
" axes[2].plot(days, commitment_pcts, color=color, label=label)\n",
"\n",
"axes[0].set_ylabel('Supply')\n",
"axes[0].set_title('Supply Growth Under Heavy Labor Minting')\n",
"axes[0].legend()\n",
"\n",
"axes[1].set_ylabel('Backing Ratio')\n",
"axes[1].set_title('Backing Ratio Dilution')\n",
"axes[1].axhline(y=1.0, color='k', linestyle='--', alpha=0.3)\n",
"axes[1].legend()\n",
"\n",
"axes[2].set_ylabel('Commitment %')\n",
"axes[2].set_title('Commitment Share of Total Supply')\n",
"axes[2].legend()\n",
"\n",
"for ax in axes:\n",
" ax.set_xlabel('Day')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}