Simple Uni V2 Tree - On-chain TestNet Simulations

  • Assumptions:
    • Uses Simple Tree
    • Uses stablecoins (ie, USDC and USDT) to control for impermanent loss
    • Pre-minted supply of index tokens
  • LPs include:
    • USDC-USDT
    • USDC-iUSDC
import os
import json
import subprocess
from pprint import pprint
import time
import numpy as np
import pandas as pd
from uniswappy import *
from pachira import *
import seaborn as sns
import statsmodels.api as sm
import datetime
from time import sleep
import matplotlib.pyplot as plt
base_dir =  os.getcwd().replace("pachira_refactor/python/notebook","")
os.chdir(base_dir)

Contract paths

deploy_tree_path = 'script/simulate/SimTestNetDeployTree.s.sol'
check_amounts_path = 'script/simulate/SimTestNetCheckAmounts.s.sol'
invest_path = 'script/simulate/SimTestNetInvest.s.sol'
parent_arbitrage_path = 'script/simulate/SimTestNetParentArbitrage.s.sol'
child_arbitrage_path = 'script/simulate/SimTestNetChildArbitrage.s.sol'
swap_path = 'script/simulate/SimTestNetSwap.s.sol'
rebalance_path = 'script/simulate/SimTestNetRebalance.s.sol'

Script Functions

rpc_url = 'http://127.0.0.1:8545'
test_contract_dir = '/pachira_refactor/' 
output_dir = '/pachira_refactor/test/output/'

def exe_cmd(script_path, rpc_url, verbose = True, skip_sim = True, args = []):
    silent_cmd = '--silent' if not verbose else ''
    skip_sim_cmd = '--skip-simulation' if skip_sim else ''
    deploy_sig = [] if len(args) == 0 else ['--sig']
    deploy_arg_type = [] if len(args) == 0 else ['run('+','.join('uint256' for e in args)+')']
    deploy_args = [] if len(args) == 0 else [str(e) for e in args]

    shell_cmd1 = ['forge', 'script', script_path]
    shell_cmd2 = deploy_args + deploy_sig + deploy_arg_type
    shell_cmd3 = [ '--ignored-error-codes', '2072', 
                '--ignored-error-codes', '6321', 
                '--ignored-error-codes', '5667', 
                '--ignored-error-codes', '5574', 
                 '--ignored-error-codes', '2018']
    shell_cmd4 = ['--rpc-url', rpc_url, '--broadcast',  silent_cmd, skip_sim_cmd]
    shell_cmd = shell_cmd1 + shell_cmd2 + shell_cmd3 + shell_cmd4 
    return shell_cmd

def execute_script(script_path, rpc_url, args = [], verbose = True, skip_sim = True):
    shell_cmd = exe_cmd(script_path, rpc_url, args=args, verbose=verbose, skip_sim=skip_sim)
    test_directory = base_dir+test_contract_dir
    p = subprocess.Popen(shell_cmd, cwd=test_directory)
    p.wait()

def calc_arb(lp, sDel, tkn_x, tkn_y, user_nm, p):  
    swap_dx, swap_dy = sDel.calc(p, 1, 1)
    direction = 0;

    if(swap_dx >= 0):
        expected_amount_dep = SwapDeposit().apply(lp, tkn_x, user_nm, abs(swap_dx))
        expected_amount_out = WithdrawSwap().apply(lp, tkn_y, user_nm, abs(swap_dy))
        dep_tkn_x = abs(swap_dx); wd_tkn_x = 0
        wd_tkn_y = abs(swap_dy); dep_tkn_y = 0
        direction = 0
    elif(swap_dy >= 0):
        expected_amount_dep = SwapDeposit().apply(lp, tkn_y, user_nm, abs(swap_dy))
        expected_amount_out = WithdrawSwap().apply(lp, tkn_x, user_nm, abs(swap_dx)) 
        dep_tkn_y = abs(swap_dy); wd_tkn_y = 0
        wd_tkn_x = abs(swap_dx); dep_tkn_x = 0
        direction = 1
    
    dep_tkn_x = UniV3Helper().dec2gwei(dep_tkn_x)
    dep_tkn_y = UniV3Helper().dec2gwei(dep_tkn_y)
    wd_tkn_x = UniV3Helper().dec2gwei(wd_tkn_x)
    wd_tkn_y = UniV3Helper().dec2gwei(wd_tkn_y)

    return (dep_tkn_x, dep_tkn_y, wd_tkn_x, wd_tkn_y, direction)

def calc_arb_contract(lp, sDel, tkn_x, tkn_y, user_nm, p):  
    swap_dx, swap_dy = sDel.calc(p, 1, 1)
    direction = 0;

    if(swap_dx >= 0):
        dep_tkn_x = abs(swap_dx); wd_tkn_x = 0
        wd_tkn_y = abs(swap_dy); dep_tkn_y = 0
        direction = 0
    elif(swap_dy >= 0):
        dep_tkn_y = abs(swap_dy); wd_tkn_y = 0
        wd_tkn_x = abs(swap_dx); dep_tkn_x = 0
        direction = 1
    
    dep_tkn_x = UniV3Helper().dec2gwei(dep_tkn_x)
    dep_tkn_y = UniV3Helper().dec2gwei(dep_tkn_y)
    wd_tkn_x = UniV3Helper().dec2gwei(wd_tkn_x)
    wd_tkn_y = UniV3Helper().dec2gwei(wd_tkn_y)

    return (dep_tkn_x, dep_tkn_y, wd_tkn_x, wd_tkn_y, direction)

def pool_state(pool_contract, verbose=False):
    reserves = pool_contract.functions.getReserves().call() 
    lp_amt = pool_contract.functions.totalSupply().call() 
    if(verbose): print(f'TKN0 {UniV3Helper().gwei2dec(reserves[0]):.2f} / TKN1 {UniV3Helper().gwei2dec(reserves[1]):.2f} / Liquidity {UniV3Helper().gwei2dec(lp_amt):.2f}') 
    return (reserves[0], reserves[1], lp_amt)

Simulate price data

tkn_nm = 'USDC'
itkn_nm = 'iUSDC'
usdt_nm = 'USDT'
iusdt_nm = 'iUSDT'
# *************************
# *** Simulation
# *************************
n_sim_runs = 1000
seconds_year = 31536000
shape = 2000
scale = 0.0005
saved_prices = True

if(saved_prices):
    price_df = pd.read_csv('pachira_refactor/python/resources/price_df.csv') 
    p_arr = price_df['price'].values
else:    
    p_arr = np.random.gamma(shape = shape, scale = scale, size = n_sim_runs)

if(not saved_prices):
    price_dic = {'price': p_arr}
    price_df = pd.DataFrame(price_dic)
    price_df.to_csv('pachira_refactor/python/resources/price_df.csv', encoding='utf-8', index=False)

n_runs = len(p_arr)-1    
dt = datetime.timedelta(seconds=seconds_year/n_sim_runs)
dates = [datetime.datetime.strptime("2024-09-01", '%Y-%m-%d') + k*dt for k in range(n_sim_runs)]   

x_val = np.arange(0,len(p_arr))
fig, (USD_ax) = plt.subplots(nrows=1, sharex=False, sharey=False, figsize=(18, 5))
USD_ax.plot(dates, p_arr, color = 'r',linestyle = 'dashdot', label='initial invest') 
USD_ax.set_title(f'Price Chart ({tkn_nm}/{usdt_nm})', fontsize=20)
USD_ax.set_ylabel('Price (USD)', size=20)
USD_ax.set_xlabel('Date', size=20)
Text(0.5, 0, 'Date')

png

Pool configuration - Contracts & Python

tkn0_amt_usd = 1000000
invest_amt_usd = 100
exe_contract = True
skip_sim = True
contract_time_lapse = 0.5

tkn0_amt = UniV3Helper().dec2gwei(tkn0_amt_usd)
tkn1_amt = int(p_arr[0]*tkn0_amt)
deploy_amt =  UniV3Helper().dec2gwei(tkn0_amt_usd/5)
mint_amt =  UniV3Helper().dec2gwei(tkn0_amt_usd/10)
invest_amt =  UniV3Helper().dec2gwei(invest_amt_usd)
mrk_cap = UniV3Helper().gwei2dec(tkn0_amt)
#TKN_amt = TokenDeltaModel(0.05*mrk_cap, shape=2, scale=0.1)
TKN_amt = TokenDeltaModel(0.1*mrk_cap) # validated model
swap_arr = np.array([TKN_amt.delta() for k in range(1000)])

fig, ax = plt.subplots(1, 1, figsize=(12,5))

sns.distplot(swap_arr, hist=True, kde=False, bins=int(50), color = 'darkblue',
             hist_kws={'edgecolor':'black'}, kde_kws={'linewidth': 2}, ax=ax)
ax.set_title('Histogram: Unit Swap Volume (USD)')
np.median(swap_arr)
14003.127419022314

png

Deploy simple tree - Contracts

deploy_args = [tkn0_amt, tkn1_amt, deploy_amt, mint_amt]
execute_script(deploy_tree_path, rpc_url, verbose=True, args=deploy_args, skip_sim=False)
No files changed, compilation skipped
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

== Logs ==
  tkn0Amt = 1000000000000000000000000
  tkn1Amt = 1005580071821577923067904
  deployAmt = 200000000000000000000000
  token0Bal = 200000000000000000000000
  synthTkn0Addr = 0xcf11f6D694E1B2467a3AdA680B2703a69d8d98Eb
  synthTkn1Addr = 0xf2670B9B15baeA490900B2549Dc1CD41F41E3972
  baseTokenIn = 0x6F46D679aB36ebc8a36AC0D0F5B50C7eA399FC66
  indexToken = 0xf2670B9B15baeA490900B2549Dc1CD41F41E3972
  proceedsAmount = 46730061042074311691695
  childLPAddr = 0xd847C8dCCDD067C0e2484c091CdfE91177A22dBF
  address(this) = 0x63e1DF6430Ac34f72Cf8Ce19Ae6414B32020a4B2
  mintedLiquidity = 69908019701168269768338
  proceedsAmount = 42748875875872662696036
  proceedsAmount = 55476592623908026948897
  Balance synthTkn0 = 99931198511720447407864
  Parent totalSupply = 1151588665279221008599431
  Parent Actual totalSupply = 1151588665279221008599431
  Parent baseReserve0 = 1200000000000000000000000
  Parent baseReserve1 = 1105580071821577923067904
  Child totalSupply = 69908019701168269768338
  Child Actual totalSupply = 69908019701168269768338
  Child baseReserve0 = 100000000000000000000000
  Child baseReserve1 = 48871312185389309419612

## Setting up 1 EVM.

==========================

Chain 31337

Estimated gas price: 0.000000017 gwei

Estimated total gas used for script: 41331203

Estimated amount required: 0.000000000702630451 ETH

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /Users/ian_moore/repos/pachira_refactor/broadcast/SimTestNetDeployTree.s.sol/31337/run-latest.json

Sensitive values saved to: /Users/ian_moore/repos/pachira_refactor/cache_forge/SimTestNetDeployTree.s.sol/31337/run-latest.json

Retrieve pool configurations from contracts

test_net_addresses_path = '/Users/ian_moore/repos/pachira_refactor/script/deployments/SimTestNet-addresses.json'

f = open(test_net_addresses_path)
test_net_addresses = json.load(f)
base_pool_addr = test_net_addresses['parentPool']
child_pool_addr = test_net_addresses['childPool1']
tkn0_addr = test_net_addresses['tkn0']
tkn1_addr = test_net_addresses['tkn1']
uniV2Factory_addr = test_net_addresses['uniV2Factory']

abi = ABILoad(Platform.PACHIRA, JSONContract.UniswapV2Pair)
contract_interface = abi.get_abi_by_filename(abi.get_abi_path())
connect = ConnectW3(Net.LOCALHOST)
connect.apply()
w3 = connect.get_w3()

base_pool_contract = w3.eth.contract(address=w3.to_checksum_address(base_pool_addr), abi=contract_interface['abi'])
child_pool_contract = w3.eth.contract(address=w3.to_checksum_address(child_pool_addr), abi=contract_interface['abi'])
base_pool_contract = w3.eth.contract(address=w3.to_checksum_address(base_pool_addr), abi=contract_interface['abi'])
child_pool_contract = w3.eth.contract(address=w3.to_checksum_address(child_pool_addr), abi=contract_interface['abi'])
base_reserves = base_pool_contract.functions.getReserves().call() 
base_lp_amt = base_pool_contract.functions.totalSupply().call() 
child_reserves = child_pool_contract.functions.getReserves().call() 
child_lp_amt = child_pool_contract.functions.totalSupply().call() 

Deploy simple tree (using contract amounts) - Python

user_nm = 'user_machine_check'
token0Addr = '0x01'
token1Addr = '0x02'
basePoolAddr = '0x05'

tkn_nm = 'USDC'
itkn_nm = 'iUSDC'
usdt_nm = 'USDT'
iusdt_nm = 'iUSDT'
(base_res0, base_res1, base_lp_amt) = pool_state(base_pool_contract)
(child_res0, child_res1, child_lp_amt) = pool_state(child_pool_contract)

tkn1 = ERC20(tkn_nm, "0x09")
usdt1 = ERC20(usdt_nm, "0x111")
exchg_data = UniswapExchangeData(tkn0 = tkn1, tkn1 = usdt1, symbol="LP", address="0x011")

iVault1 = IndexVault('iVault1', "0x7")
factory = UniswapFactory(f"{tkn_nm} pool factory", "0x2")
base_lp = factory.deploy(exchg_data)
Join().apply(base_lp, user_nm, UniV3Helper().gwei2dec(base_res0), UniV3Helper().gwei2dec(base_res1))

tkn2 = ERC20(tkn_nm, "0x09")
itkn1 = IndexERC20(itkn_nm, "0x09", tkn1, base_lp)
exchg_data1 = UniswapExchangeData(tkn0 = tkn2, tkn1 = itkn1, symbol="LP1", address="0x012")
child1_lp = factory.deploy(exchg_data1)
Join().apply(child1_lp, user_nm, UniV3Helper().gwei2dec(child_res0), UniV3Helper().gwei2dec(child_res1))
(100000000000000000000000, 48871312185389310000000)

Rebalance base pool - Python

# Re-balance LP price after JoinTree
if(base_lp.get_reserve(usdt1) > base_lp.get_reserve(tkn1)):
    SwapDeposit().apply(base_lp, tkn1, user_nm, base_lp.get_reserve(usdt1) - base_lp.get_reserve(tkn1))
else:
    SwapDeposit().apply(base_lp, usdt1, user_nm, base_lp.get_reserve(tkn1) - base_lp.get_reserve(usdt1))

sDel_base = SolveDeltas(base_lp)
sDel_child = SolveDeltas(child1_lp)

base_lp.summary()
child1_lp.summary()
Exchange USDC-USDT (LP)
Reserves: USDC = 1200000.0, USDT = 1200000.0
Liquidity: 1199927.6280287034 

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 100000.0, iUSDC = 48871.31218538931
Liquidity: 69908.01970116828 

Rebalance base pool - Contracts

execute_script(rebalance_path, rpc_url, verbose=True, skip_sim=False)
No files changed, compilation skipped
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

== Logs ==
  parentPoolAddr = 0xACf9FAce714149942D1837165ed4Fe6AE24C9bbC
  iParentPoolAddr = 0x7553e3118AE2921dc8aCE684bA74ee2a883D3FeB
  childPool1Addr = 0xd847C8dCCDD067C0e2484c091CdfE91177A22dBF
  iChildPool1Addr = 0x6a2b245136520FE4d7B93B63735808ADee8D4088
  tkn0Addr = 0x6F46D679aB36ebc8a36AC0D0F5B50C7eA399FC66
  tkn1Addr = 0x0D93968496Ef4D115a4c20586ED23aE75F0FF355
  proceedsAmount = 48107588929307396742759
  Parent totalSupply = 1199683568581351176009583
  Parent baseReserve0 = 1200000000000000000000000
  Parent baseReserve1 = 1200000000000000000000000

## Setting up 1 EVM.

==========================

Chain 31337

Estimated gas price: 0.000000017 gwei

Estimated total gas used for script: 375209

Estimated amount required: 0.000000000006378553 ETH

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /Users/ian_moore/repos/pachira_refactor/broadcast/SimTestNetRebalance.s.sol/31337/run-latest.json

Sensitive values saved to: /Users/ian_moore/repos/pachira_refactor/cache_forge/SimTestNetRebalance.s.sol/31337/run-latest.json

Take an investment position - Contracts

invest_args = [invest_amt]
execute_script(invest_path, rpc_url, args=invest_args, verbose=True, skip_sim=False)

output_dir = '/pachira_refactor/script/deployments/'
output_file = 'SimTestNet-investment.json'
output_file_path = os.getcwd()+output_dir+output_file
with open(output_file_path) as f:
    contract_output = json.loads(f.read())

lp1_invest_track_contract = UniV3Helper().gwei2dec(contract_output['treeYieldOut'])
No files changed, compilation skipped
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

== Logs ==
  parentPoolAddr = 0xACf9FAce714149942D1837165ed4Fe6AE24C9bbC
  iParentPoolAddr = 0x7553e3118AE2921dc8aCE684bA74ee2a883D3FeB
  childPool1Addr = 0xd847C8dCCDD067C0e2484c091CdfE91177A22dBF
  iChildPool1Addr = 0x6a2b245136520FE4d7B93B63735808ADee8D4088
  tkn0Addr = 0x6F46D679aB36ebc8a36AC0D0F5B50C7eA399FC66
  tkn1Addr = 0x0D93968496Ef4D115a4c20586ED23aE75F0FF355
  devs = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  proceedsAmount = 49921770369159820657
  proceedsAmount = 50947683786753511943
  Parent totalSupply = 1199733479264064520838179
  Parent totalSupply = 1199733479264064520838179
  Parent baseReserve0 = 1200000000000000000000000
  Parent baseReserve1 = 1200100000000000000000000
  Child totalSupply = 69943654373027139429999
  Child totalSupply = 69943654373027139429999
  Child baseReserve0 = 100000000000000000000000
  Child baseReserve1 = 48921222868102654248208
  treeYieldOut = 35634671858869661661

## Setting up 1 EVM.

==========================

Chain 31337

Estimated gas price: 0.000000017 gwei

Estimated total gas used for script: 9698240

Estimated amount required: 0.00000000016487008 ETH

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /Users/ian_moore/repos/pachira_refactor/broadcast/SimTestNetInvest.s.sol/31337/run-latest.json

Sensitive values saved to: /Users/ian_moore/repos/pachira_refactor/cache_forge/SimTestNetInvest.s.sol/31337/run-latest.json

Take an investment position - Python

invested_user_nm = 'invested_user'
tkn_invest_amt = UniV3Helper().gwei2dec(invest_amt)

SwapIndexMint(iVault1, opposing = False).apply(base_lp, tkn1, invested_user_nm, tkn_invest_amt)
mint_itkn1_deposit = child1_lp.convert_to_human(iVault1.index_tokens[itkn_nm]['last_lp_deposit'])
SwapDeposit().apply(child1_lp, itkn1, invested_user_nm, mint_itkn1_deposit)

base_lp.summary()
child1_lp.summary()

lp_invest_track  = base_lp.get_liquidity_from_provider(invested_user_nm)
lp1_invest_track  = child1_lp.get_liquidity_from_provider(invested_user_nm)

# Redeem from parent
tkn_redeem_parent = LPQuote(False).get_amount_from_lp(base_lp, tkn1, lp_invest_track)

# Redeem from tree (child + parent)
itkn_redeem_child = LPQuote(False).get_amount_from_lp(child1_lp, itkn1, lp1_invest_track)
tkn_redeem_tree = LPQuote(False).get_amount_from_lp(base_lp, tkn1, itkn_redeem_child) 

print(f'{tkn_redeem_parent:.3f} USDC redeemed from {lp_invest_track:.3f} LP tokens if {UniV3Helper().gwei2dec(invest_amt):.1f} invested USDC immediately pulled from parent')
print(f'{tkn_redeem_tree:.3f} USDC redeemed from {lp1_invest_track:.3f} LP1 tokens if {UniV3Helper().gwei2dec(invest_amt):.1f} invested USDC immediately pulled from tree')
Exchange USDC-USDT (LP)
Reserves: USDC = 1200100.0, USDT = 1200000.0
Liquidity: 1199977.5488650722 

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 100000.0, iUSDC = 48921.23302175813
Liquidity: 69943.66162057084 

99.700 USDC redeemed from 49.921 LP tokens if 100.0 invested USDC immediately pulled from parent
99.401 USDC redeemed from 35.642 LP1 tokens if 100.0 invested USDC immediately pulled from tree

Check tree - Contracts

execute_script(check_amounts_path, rpc_url, verbose=True, skip_sim=False)
No files changed, compilation skipped
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

== Logs ==
  parentPoolAddr = 0xACf9FAce714149942D1837165ed4Fe6AE24C9bbC
  iParentPoolAddr = 0x7553e3118AE2921dc8aCE684bA74ee2a883D3FeB
  childPool1Addr = 0xd847C8dCCDD067C0e2484c091CdfE91177A22dBF
  iChildPool1Addr = 0x6a2b245136520FE4d7B93B63735808ADee8D4088
  tkn0Addr = 0x6F46D679aB36ebc8a36AC0D0F5B50C7eA399FC66
  tkn1Addr = 0x0D93968496Ef4D115a4c20586ED23aE75F0FF355
  Parent totalSupply = 1199733479264064520838179
  Parent totalSupply = 1199733479264064520838179
  Parent baseReserve0 = 1200000000000000000000000
  Parent baseReserve1 = 1200100000000000000000000
  Child totalSupply = 69943654373027139429999
  Child totalSupply = 69943654373027139429999
  Child baseReserve0 = 100000000000000000000000
  Child baseReserve1 = 48921222868102654248208

## Setting up 1 EVM.

==========================

Chain 31337

Estimated gas price: 0.000000017 gwei

Estimated total gas used for script: 81176

Estimated amount required: 0.000000000001379992 ETH

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /Users/ian_moore/repos/pachira_refactor/broadcast/SimTestNetCheckAmounts.s.sol/31337/run-latest.json

Sensitive values saved to: /Users/ian_moore/repos/pachira_refactor/cache_forge/SimTestNetCheckAmounts.s.sol/31337/run-latest.json

Begin simulation - Python & Contracts

p_sim_arr1 = []; p_python_arr1 = []; p_contract_arr1 = []
p_sim_arr2 = []; p_python_arr2 = []; p_contract_arr2 = []
lp_direct_invest_arr = []; lp1_direct_invest_arr = []; lp1_tree_invest_arr = []; 

print('Begin liquidity tree simulation\n')

for k in range(n_sim_runs):
    
    amt_swap = TKN_amt.delta()

    # ****************************************
    # ***** Calculate Arbitrage Amounts ******
    # ****************************************   
    p_sim1 = p_arr[k]
    if(exe_contract):
        (dep_tkn_x1, dep_tkn_y1, wd_tkn_x1, wd_tkn_y1, direction1) = calc_arb_contract(base_lp, sDel_base, tkn1, usdt1, user_nm, p_sim1)
    else:   
        (dep_tkn_x1, dep_tkn_y1, wd_tkn_x1, wd_tkn_y1, direction1) = calc_arb(base_lp, sDel_base, tkn1, usdt1, user_nm, p_sim1)
    
    p_sim2 = LPQuote().get_lp_from_amount(base_lp, usdt1, child1_lp.get_reserve(tkn2))/child1_lp.get_reserve(tkn2)
    if(exe_contract):
        (dep_tkn_x2, dep_tkn_y2, wd_tkn_x2, wd_tkn_y2, direction2) = calc_arb_contract(child1_lp, sDel_child, tkn2, itkn1, user_nm, p_sim2)
        
    else:
        (dep_tkn_x2, dep_tkn_y2, wd_tkn_x2, wd_tkn_y2, direction2) = calc_arb(child1_lp, sDel_child, tkn2, itkn1, user_nm, p_sim2)
    
    # *****************************
    # **** Contract Arbitrage *****
    # *****************************  
    
    if(exe_contract):
        arb_args1 = [dep_tkn_x1, dep_tkn_y1, wd_tkn_x1, wd_tkn_y1, direction1]
        execute_script(parent_arbitrage_path, rpc_url, args=arb_args1, verbose=False, skip_sim=skip_sim)
        (base_res0, base_res1, base_lp_amt) = pool_state(base_pool_contract, verbose=False)
        price_tkn0_contract1 = base_res1/base_res0
        sleep(contract_time_lapse)
    
        arb_args2 = [dep_tkn_x2, dep_tkn_y2, wd_tkn_x2, wd_tkn_y2, direction2]
        execute_script(child_arbitrage_path, rpc_url, args=arb_args2, verbose=False, skip_sim=skip_sim)
        (child_res0, child_res1, base_lp_amt) = pool_state(child_pool_contract, verbose=False)
        price_tkn0_contract2 = child_res1/child_res0
        sleep(contract_time_lapse)

        # **************************************
        # ** Recalibrate Pool State - Python ***
        # **************************************   
        (base_res0, base_res1, base_lp_amt) = pool_state(base_pool_contract)
        (child_res0, child_res1, child_lp_amt) = pool_state(child_pool_contract)
        base_lp.reserve0 = base_res0; base_lp.reserve1 = base_res1; base_lp.total_supply = base_lp_amt
        child1_lp.reserve0 = child_res0; child1_lp.reserve1 = child_res1; child1_lp.total_supply = child_lp_amt

    price_tkn0_python1 = base_lp.get_price(tkn1)     
    price_tkn0_python2 = child1_lp.get_price(tkn2) 

    # ************************************
    # ** Induce Trade Volume - Contract **
    # ************************************ 
    
    if(exe_contract):
        arb_args = [UniV3Helper().dec2gwei(amt_swap), 0]
        execute_script(swap_path, rpc_url, args=arb_args, verbose=False, skip_sim=skip_sim)
        sleep(contract_time_lapse)
    
        arb_args = [UniV3Helper().dec2gwei(amt_swap), 1]
        execute_script(swap_path, rpc_url, args=arb_args, verbose=False, skip_sim=skip_sim)
        sleep(contract_time_lapse)

        # **************************************
        # ** Recalibrate Pool State - Python ***
        # **************************************  

        (base_res0, base_res1, base_lp_amt) = pool_state(base_pool_contract)
        (child_res0, child_res1, child_lp_amt) = pool_state(child_pool_contract)
        base_lp.reserve0 = base_res0; base_lp.reserve1 = base_res1; base_lp.total_supply = base_lp_amt
        child1_lp.reserve0 = child_res0; child1_lp.reserve1 = child_res1; child1_lp.total_supply = child_lp_amt

    # ***********************************
    # *** Induce Trade Volume - Python **
    # *********************************** 

    if(not exe_contract):
        Swap().apply(base_lp, tkn1, user_nm, amt_swap) 
        Swap().apply(base_lp, usdt1, user_nm, amt_swap) 
    
        Swap().apply(child1_lp, tkn2, user_nm, 0.2*amt_swap) 
        Swap().apply(child1_lp, itkn1, user_nm, LPQuote().get_lp_from_amount(base_lp, usdt1, 0.2*amt_swap))

    
    # *****************************
    # ***** Data Collection *******
    # *****************************   
    p_sim_arr1.append(p_sim1)
    p_python_arr1.append(price_tkn0_python1)
    p_sim_arr2.append(p_sim2)
    p_python_arr2.append(price_tkn0_python2)
    
    if(exe_contract):
        p_contract_arr1.append(price_tkn0_contract1)
        p_contract_arr2.append(price_tkn0_contract2)

    # investment performance
    tkn_redeem_parent = LPQuote(False).get_amount_from_lp(base_lp, tkn1, lp_invest_track)
    itkn_redeem_child = LPQuote(False).get_amount_from_lp(child1_lp, itkn1, lp1_invest_track_contract) 
    tkn_redeem_tree = LPQuote(False).get_amount_from_lp(base_lp, tkn1, itkn_redeem_child)     

    lp_direct_invest_arr.append(tkn_redeem_parent)
    lp1_direct_invest_arr.append(LPQuote(False).get_amount_from_lp(child1_lp, tkn2, lp1_invest_track))    
    lp1_tree_invest_arr.append(tkn_redeem_tree)
    
    if((exe_contract) and (k % int(n_sim_runs/10)) == 0):
        print(f'[Parent: step {k}] Actual price {p_sim1:.5f} / Python price {price_tkn0_python1:.5f} / Contract price {price_tkn0_contract1:.5f}')
        print(f'[Child:  step {k}] Actual price {p_sim2:.5f} / Python price {price_tkn0_python2:.5f} / Contract price {price_tkn0_contract2:.5f}')
        print()
    
    elif ((not exe_contract) and (k % int(n_sim_runs/10)) == 0):
        print(f'[Parent: step {k}] Actual price {p_sim1:.5f} / Python price {price_tkn0_python1:.5f}')
        print(f'[Child:  step {k}] Actual price {p_sim2:.5f} / Python price {price_tkn0_python2:.5f}')
        print()
    

base_lp.summary()
child1_lp.summary()

# Redeem from parent
tkn_redeem_parent = LPQuote(False).get_amount_from_lp(base_lp, tkn1, lp_invest_track)

# Redeem from tree (child + parent)
itkn_redeem_child = LPQuote(False).get_amount_from_lp(child1_lp, itkn1, lp1_invest_track_contract) 
tkn_redeem_tree = LPQuote(False).get_amount_from_lp(base_lp, tkn1, itkn_redeem_child) 

print(f'{tkn_redeem_parent:.3f} USDC redeemed from {lp_invest_track:.3f} LP tokens if {UniV3Helper().gwei2dec(invest_amt):.1f} invested USDC pulled from parent (lp)')
print(f'{tkn_redeem_tree:.3f} USDC redeemed from {lp1_invest_track_contract:.3f} LP1 tokens if {UniV3Helper().gwei2dec(invest_amt):.1f} invested USDC pulled from tree (lp + lp1)')
Begin liquidity tree simulation

[Parent: step 0] Actual price 1.00558 / Python price 1.00573 / Contract price 1.00573
[Child:  step 0] Actual price 0.51160 / Python price 0.51132 / Contract price 0.51132

[Parent: step 100] Actual price 0.99577 / Python price 0.99575 / Contract price 0.99575
[Child:  step 100] Actual price 0.50987 / Python price 0.50987 / Contract price 0.50987

[Parent: step 200] Actual price 1.04163 / Python price 1.04164 / Contract price 1.04164
[Child:  step 200] Actual price 0.49199 / Python price 0.49219 / Contract price 0.49219

[Parent: step 300] Actual price 0.97814 / Python price 0.97861 / Contract price 0.97861
[Child:  step 300] Actual price 0.49221 / Python price 0.49231 / Contract price 0.49231

[Parent: step 400] Actual price 0.96791 / Python price 0.96805 / Contract price 0.96805
[Child:  step 400] Actual price 0.49928 / Python price 0.49927 / Contract price 0.49927

[Parent: step 500] Actual price 1.00374 / Python price 1.00370 / Contract price 1.00370
[Child:  step 500] Actual price 0.49661 / Python price 0.49661 / Contract price 0.49661

[Parent: step 600] Actual price 1.04946 / Python price 1.04893 / Contract price 1.04893
[Child:  step 600] Actual price 0.49043 / Python price 0.49040 / Contract price 0.49040

[Parent: step 700] Actual price 0.97452 / Python price 0.97461 / Contract price 0.97461
[Child:  step 700] Actual price 0.49008 / Python price 0.49008 / Contract price 0.49008

[Parent: step 800] Actual price 1.00885 / Python price 1.00920 / Contract price 1.00920
[Child:  step 800] Actual price 0.47213 / Python price 0.47223 / Contract price 0.47223

[Parent: step 900] Actual price 1.01132 / Python price 1.01096 / Contract price 1.01096
[Child:  step 900] Actual price 0.48866 / Python price 0.48868 / Contract price 0.48868

Exchange USDC-USDT (LP)
Reserves: USDC = 1542808.4758102486, USDT = 1589101.571632045
Liquidity: 1470257.625638831 

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 119743.29568975396, iUSDC = 58604.79369967114
Liquidity: 74475.62736035028 

104.610 USDC redeemed from 49.921 LP tokens if 100.0 invested USDC pulled from parent (lp)
117.339 USDC redeemed from 35.635 LP1 tokens if 100.0 invested USDC pulled from tree (lp + lp1)

Plot results

lp_direct_invest_arr = np.array(lp_direct_invest_arr)
lp1_direct_invest_arr = np.array(lp1_direct_invest_arr)
lp1_tree_invest_arr = np.array(lp1_tree_invest_arr)

lp_direct_invest_arr = np.array(lp_direct_invest_arr)-(lp_direct_invest_arr[0]-lp1_tree_invest_arr[0])
lp1_direct_invest_arr = np.array(lp1_direct_invest_arr)-(lp1_direct_invest_arr[0]-lp1_tree_invest_arr[0])
lp1_tree_invest_arr = np.array(lp1_tree_invest_arr)

lowess = sm.nonparametric.lowess
x = range(0,n_sim_runs)
res = lowess(lp_direct_invest_arr, x, frac=1/15); sm_lp_direct = res[:,1]
res = lowess(lp1_direct_invest_arr, x, frac=1/15); sm_lp1_direct = res[:,1]
res = lowess(lp1_tree_invest_arr, x, frac=1/15); sm_lp1_tree= res[:,1]

parent_return = 100*sm_lp_direct[-1]/invest_amt_usd-100
child_return = 100*sm_lp1_direct[-1]/invest_amt_usd-100
tree_return = 100*sm_lp1_tree[-1]/invest_amt_usd-100

strt_ind = 0
fig, (p_ax) = plt.subplots(nrows=1, sharex=True, sharey=False, figsize=(15, 8))
fig.suptitle('On-chain performance of Stablecoin Tree (USDC / USDT) via simulation', fontsize=20)
p_ax.plot(dates[strt_ind:n_sim_runs], lp_direct_invest_arr[strt_ind:], linestyle='dashed', linewidth=0.5, color = 'g', label = 'Parent token') 
p_ax.plot(dates[strt_ind:n_sim_runs], sm_lp_direct[strt_ind:], color = 'g', label = 'Expected return from parent (LP)') 
p_ax.plot(dates[strt_ind:n_sim_runs], lp1_tree_invest_arr[strt_ind:], linestyle='dashed', linewidth=0.5, color = 'r', label = 'Tree token')
p_ax.plot(dates[strt_ind:n_sim_runs], sm_lp1_tree[strt_ind:], color = 'r', label = 'Expected return from tree (LP+LP1)') 
p_ax.text(0.7, 0.1, f'Parent LP token return: {parent_return:.4}%', fontsize=18, color='g', horizontalalignment='left',verticalalignment='center',transform=ax.transAxes)
p_ax.text(0.7, 0.2, f'Tree LP token return: {tree_return:.4}%', fontsize=18, color='r', horizontalalignment='left',verticalalignment='center',transform=ax.transAxes)
p_ax.set_ylim(UniV3Helper().gwei2dec(invest_amt)*95/100, UniV3Helper().gwei2dec(invest_amt)*125/100) 
p_ax.set_ylabel("$100 USD Investment", fontsize=14)
p_ax.legend(fontsize=12, loc='upper left')
<matplotlib.legend.Legend at 0x17f615fc0>

png

Check parent and child pool - Contracts

execute_script(check_amounts_path, rpc_url, verbose=True, skip_sim=False)
No files changed, compilation skipped
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

== Logs ==
  parentPoolAddr = 0x5baBe4182Fff652BFf47631d848A6b3ccC765F80
  iParentPoolAddr = 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
  childPool1Addr = 0x147f014B5a4636459335A4d72fa28603FC6da960
  iChildPool1Addr = 0x0165878A594ca255338adfa4d48449f69242Eb8F
  tkn0Addr = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  tkn1Addr = 0xB424592A971c77dfa282D55419f9e45e9bC60da6
  Parent totalSupply = 1470257625638830897462917
  Parent totalSupply = 1470257625638830897462917
  Parent baseReserve0 = 1542808475810248665182342
  Parent baseReserve1 = 1589101571632045074422045
  Child totalSupply = 74475627360350270540225
  Child totalSupply = 74475627360350270540225
  Child baseReserve0 = 119743295689753956639654
  Child baseReserve1 = 58604793699671142761629

## Setting up 1 EVM.

==========================

Chain 31337

Estimated gas price: 0.000000017 gwei

Estimated total gas used for script: 81176

Estimated amount required: 0.000000000001379992 ETH

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /Users/ian_moore/repos/pachira_refactor/broadcast/SimTestNetCheckAmounts.s.sol/31337/run-latest.json

Sensitive values saved to: /Users/ian_moore/repos/pachira_refactor/cache_forge/SimTestNetCheckAmounts.s.sol/31337/run-latest.json

Check parent and child pool - Python

base_lp.summary()
child1_lp.summary()
Exchange USDC-USDT (LP)
Reserves: USDC = 1542808.4758102486, USDT = 1589101.571632045
Liquidity: 1470257.625638831 

Exchange USDC-iUSDC (LP1)
Reserves: USDC = 119743.29568975396, iUSDC = 58604.79369967114
Liquidity: 74475.62736035028