325 lines
11 KiB
Plaintext
325 lines
11 KiB
Plaintext
{
|
||
"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
|
||
}
|