[
"autonomous agent",
{
"getters": "{
$get_leverages = () => [2, 5, 10, 20, 50, 100];
$singularity_threshold = 0.01;
$trade_merge_period = 1; // seconds
$update_recent_data = ($recent, $p, $final_p, $trigger_initial_address, $tax_token, $traded_amount, $paid_tax, $period_length) => {
$period_start_ts = floor(timestamp / $period_length) * $period_length;
$pmin = min($p, $final_p);
$pmax = max($p, $final_p);
if (+$recent.current.start_ts < $period_start_ts){
$recent.prev = $recent.current;
$recent.current = {start_ts: $period_start_ts, pmin: $pmin, pmax: $pmax};
}
else{
$recent.current.pmin = min($recent.current.pmin, $pmin);
$recent.current.pmax = max($recent.current.pmax, $pmax);
}
if ($recent.last_trade AND $recent.last_trade.address == $trigger_initial_address AND $recent.last_ts >= timestamp - $trade_merge_period){ // closely following trades are merged into one trade
$recent.last_trade.pmin = min($recent.last_trade.pmin, $pmin);
$recent.last_trade.pmax = max($recent.last_trade.pmax, $pmax);
$recent.last_trade.amounts[$tax_token] = $recent.last_trade.amounts[$tax_token] + $traded_amount;
$recent.last_trade.paid_taxes[$tax_token] = $recent.last_trade.paid_taxes[$tax_token] + $paid_tax;
}
else{
$amounts = {x:0, y:0};
$paid_taxes = {x:0, y:0};
$amounts[$tax_token] = $traded_amount;
$paid_taxes[$tax_token] = $paid_tax;
$recent.last_trade = {
address: $trigger_initial_address,
pmin: $pmin,
pmax: $pmax,
amounts: $amounts,
paid_taxes: $paid_taxes,
};
}
$recent.last_ts = timestamp;
};
$get_utilization_ratio = ($balances, $l_balances, $x0, $y0, $alpha) => {
$beta = 1 - $alpha;
$ratio = reduce($get_leverages(), 6, ($acc, $L) => $acc + $l_balances[$L||'x'].balance/($balances.x+$x0)*($L-1)/$beta + $l_balances[-$L||'x'].balance/($balances.y+$y0)*($L-1)/$alpha, 0);
$ratio
};
// X through Y:
// without LP leverage (Lambda)
$get_final_x = ($X, $Y, $final_Y, $X0, $Y0, $pool_props, $inverted) => {
require($final_Y >= $Y, "not selling Y");
$a = $inverted ? $pool_props.beta : $pool_props.alpha; // alpha
$b = 1 - $a; // beta
$final_X = ($X + $X0) * (($Y + $Y0)/($final_Y + $Y0))^($b/$a) - $X0;
require($final_X >= 0, "bad final_X " || $final_X);
$deltaX = $X - $final_X;
require($deltaX >= 0, "bad deltaX " || $deltaX);
$final_X
};
// along x means keeping x fully leveraged (y underleveraged)
$get_final_x_along_x = ($X, $Y, $final_Y, $pool_props, $inverted) => {
// log('get_final_x_along_x', $X, $Y, $final_Y);
$b = $inverted ? $pool_props.alpha : $pool_props.beta; // beta
$X * ($final_Y/$Y)^($b * $pool_props.Lambda/($b * $pool_props.Lambda - 1))
};
// along y means keeping y fully leveraged (x underleveraged)
$get_final_x_along_y = ($X, $Y, $final_Y, $pool_props, $inverted) => {
// log('get_final_x_along_y', $X, $Y, $final_Y);
$a = $inverted ? $pool_props.beta : $pool_props.alpha; // alpha
$X * ($final_Y/$Y)^(1-1/$a/$pool_props.Lambda)
};
// Y through X:
// without LP leverage (Lambda)
$get_final_y = ($X, $Y, $final_X, $X0, $Y0, $pool_props, $inverted) => {
// require($final_X <= $X, "not buying X"); // selling when redeeming L-tokens
$a = $inverted ? $pool_props.beta : $pool_props.alpha; // alpha
$b = 1 - $a; // beta
$final_Y = ($Y + $Y0) * (($X + $X0)/($final_X + $X0))^($a/$b) - $Y0;
require($final_Y >= 0, "bad final_Y " || $final_Y);
// $deltaY = $final_Y - $Y;
// require($deltaY >= 0, "bad deltaY " || $deltaY);
$final_Y
};
// along x means keeping x fully leveraged (y underleveraged)
$get_final_y_along_x = ($X, $Y, $final_X, $pool_props, $inverted) => {
$b = $inverted ? $pool_props.alpha : $pool_props.beta; // beta
$Y * ($final_X/$X)^(1 - 1/$b/$pool_props.Lambda)
};
// along y means keeping y fully leveraged (x underleveraged)
$get_final_y_along_y = ($X, $Y, $final_X, $pool_props, $inverted) => {
$a = $inverted ? $pool_props.beta : $pool_props.alpha; // alpha
$Y * ($final_X/$X)^($a*$pool_props.Lambda/($a*$pool_props.Lambda - 1))
};
$add_net_balance_without_changing_price = ($balances, $profits, $side, $amount, $Lambda) => {
if (!$amount)
return;
if ($Lambda == 1){
$profits[$side] = $profits[$side] + $amount;
return;
}
$opposite = $side == 'x' ? 'y' : 'x';
$side_n = $side || 'n';
$opposite_n = $opposite || 'n';
$Xn = $balances[$side_n];
$Yn = $balances[$opposite_n];
$X = $balances[$side];
$Y = $balances[$opposite];
$underleveraged = $Xn > ceil($X/$Lambda);
$delta_Xn = $amount;
// $delta_Yn = 0;
// the price doesn't change as X and Y grow proportionally
if (!$underleveraged){ // along X
// Y is underleveraged, increase Y proportionally while keeping Yn intact
$full_delta_Y = $Y * $delta_Xn/$Xn;
if ($Y + $full_delta_Y > $Yn * $Lambda){ // would overshoot and make Y overleveraged
$ratio = $Yn * $Lambda / $Y - 1;
$delta_X = $ratio * $X;
$delta_Y = $ratio * $Y;
}
else{
$delta_X = $delta_Xn * $Lambda;
$delta_Y = $full_delta_Y;
}
}
else{
$delta_X = 0; // only net X gets increased
$delta_Y = 0;
}
$balances[$side_n] = $balances[$side_n] + $delta_Xn;
// $balances[$opposite_n] = $balances[$opposite_n] + $delta_Yn;
$balances[$side] = $balances[$side] + $delta_X;
$balances[$opposite] = $balances[$opposite] + $delta_Y;
};
$pow = ($precomputed, $power) => {
require(typeof($precomputed[$power]) == 'number', "no precomputed power " || $power);
$precomputed[$power]
};
$precompute = $v => {
$pre = {};
$pre['2'] = $v * $v;
$pre['5'] = $pre['2'] * $pre['2'] * $v;
$pre['10'] = $pre['5'] * $pre['5'];
$pre['20'] = $pre['10'] * $pre['10'];
$pre['50'] = $pre['20'] * $pre['20'] * $pre['10'];
$pre['100'] = $pre['50'] * $pre['50'];
$pre
};
// returns $v^($L-1)
$powL1 = ($v, $L) => {
if ($L == 2)
return $v;
$v2 = $v * $v;
$v5 = $v2 * $v2 * $v;
if ($L == 5)
return $v5/$v;
$v10 = $v5 * $v5;
if ($L == 10)
return $v10/$v;
$v20 = $v10 * $v10;
if ($L == 20)
return $v20/$v;
$v50 = $v20 * $v20 * $v10;
if ($L == 50)
return $v50/$v;
$v100 = $v50 * $v50;
if ($L == 100)
return $v100/$v;
bounce("unknown L=" || $L);
};
$charge_interest = ($balances, $l_balances, $profits, $x0, $y0, $last_ts, $i, $alpha, $Lambda) => {
require($last_ts, "no last ts");
if (length($l_balances) == 0 OR $i == 0 OR timestamp == $last_ts)
return;
$beta = 1 - $alpha;
$x = $balances.x;
$y = $balances.y;
$accrued_rate = (timestamp - $last_ts)/3600/24/360 * $i;
$factor = max(1 - $accrued_rate, 0); // how much is left
$factor_powers = $precompute($factor);
$y2x = ($y + $y0) / ($x + $x0);
$p = $alpha/$beta * $y2x;
$n_deltas = {dxn:0, dyn:0};
$charged_interest = {x:0, y:0};
// we change x and y in such a way that the price does not change
foreach($get_leverages(), 6, ($L) => {
$xL = $l_balances[$L||'x'].balance;
$yL = $l_balances[-$L||'x'].balance;
if (!$xL AND !$yL)
return;
$factorL1 = $factor ? $pow($factor_powers, $L) / $factor : 0; // $factor^($L-1)
$factorL1L1 = $powL1($factorL1, $L); // charge ($L-1) times more since higher leverages have a ($L-1) factor in the utilization ratio
if ($xL){
$dxL = -$xL * (1 - $factorL1L1);
$l_balances[$L||'x'].balance = $xL + $dxL;
// change in the amount lent out by the swap pool (swap pool's assets)
$delta_yn = $dxL * $p * ($L-1)/$L; // < 0
$n_deltas.dyn = $n_deltas.dyn + $delta_yn;
if ($Lambda == 1){
$n_deltas.dxn = $n_deltas.dxn + $delta_yn / $y2x; // proportional - no price change
$profits.x = $profits.x - $dxL - $delta_yn / $y2x; // income from interest + transferred from the pool to keep the price
}
else
$n_deltas.dxn = $n_deltas.dxn - $dxL; // > 0
$charged_interest.x = $charged_interest.x - $dxL + $delta_yn / $p; // -$dxL / $L
}
if ($yL){
$dyL = -$yL * (1 - $factorL1L1);
$l_balances[-$L||'x'].balance = $yL + $dyL;
// change in the amount lent out by the swap pool (swap pool's assets)
$delta_xn = $dyL / $p * ($L-1)/$L; // < 0
$n_deltas.dxn = $n_deltas.dxn + $delta_xn;
if ($Lambda == 1){
$n_deltas.dyn = $n_deltas.dyn + $delta_xn * $y2x; // proportional - no price change
$profits.y = $profits.y - $dyL - $delta_xn * $y2x; // income from interest + transferred from the pool to keep the price
}
else
$n_deltas.dyn = $n_deltas.dyn - $dyL; // > 0
$charged_interest.y = $charged_interest.y - $dyL + $delta_xn * $p; // -$dyL / $L
}
});
// log('interest', $n_deltas);
$dxn = $n_deltas.dxn;
$dyn = $n_deltas.dyn;
if ($Lambda == 1){
$balances.xn = $balances.xn + $dxn;
$balances.yn = $balances.yn + $dyn;
$balances.x = $balances.x + $dxn;
$balances.y = $balances.y + $dyn;
}
else{
$add_net_balance_without_changing_price($balances, $profits, 'x', $dxn, $Lambda);
$add_net_balance_without_changing_price($balances, $profits, 'y', $dyn, $Lambda);
}
$charged_interest
};
$update_leveraged_balances = ($l_balances, $P, $final_P, $inverted) => {
$ratio = $final_P/$P;
$ratio_powers = $precompute($ratio);
$totals = {
delta_XL: 0, // (L>0) X added to the L-pools (bought from the swap pool) minus (L<0) new X borrowed by the L-pools (sent to the swap pool for buying Y)
delta_YL: 0, // (L>0) Y added to the L-pools (bought from the swap pool) minus (L<0) new Y borrowed by the L-pools (sent to the swap pool for buying X)
XL_denom: 0,
YL_denom: 0,
}; // if inverted, XL corresponds to y, YL to x
foreach($get_leverages(), 6, ($L) => {
$allyL = $inverted ? -$L : $L;
$balance = $l_balances[$allyL||'x'].balance;
$obalance = $l_balances[-$allyL||'x'].balance;
if (!$balance AND !$obalance)
return;
$ratio_L1 = $pow($ratio_powers, $L) / $ratio; // $ratio^($L-1)
$debt_ratio = ($L-1)/$L;
if ($balance) {
$delta_XL_balance = $balance * ($ratio_L1 - 1);
$new_XL_balance = $balance + $delta_XL_balance;
$l_balances[$allyL||'x'].balance = $new_XL_balance;
$delta_YL_balance = -(($new_XL_balance * $final_P - $balance * $P) * $debt_ratio); // borrowed
$totals.delta_XL = $totals.delta_XL + $delta_XL_balance;
$totals.delta_YL = $totals.delta_YL + $delta_YL_balance;
$totals.XL_denom = $totals.XL_denom + $new_XL_balance * ($L-1);
}
if ($obalance) { // e.g. L=-2
$delta_YL_obalance = $obalance * (1/$ratio_L1 - 1);
$new_YL_obalance = $obalance + $delta_YL_obalance;
$l_balances[-$allyL||'x'].balance = $new_YL_obalance;
$delta_XL_obalance = -(($new_YL_obalance / $final_P - $obalance / $P) * $debt_ratio); // borrowed
$totals.delta_YL = $totals.delta_YL + $delta_YL_obalance;
$totals.delta_XL = $totals.delta_XL + $delta_XL_obalance;
$totals.YL_denom = $totals.YL_denom + $new_YL_obalance * ($L-1);
}
});
$totals
};
$swap = ($balances, $l_balances, $profits, $recent, $x0, $y0, $y_in, $delta_Yn, $in_final_P, $received_amount_Y, $min_amount_out, $trigger_initial_address, $pool_props) => {
require(!$in_final_P, "no final price please, this is swap by Y");
$alpha = $pool_props.alpha;
$beta = $pool_props.beta;
$Lambda = $pool_props.Lambda;
$xn = $balances.xn;
$yn = $balances.yn;
$x = $balances.x;
$y = $balances.y;
if ($y_in){
$inverted = false;
$X = $x;
$Y = $y;
$Xn = $xn;
$Yn = $yn;
$X0 = $x0;
$Y0 = $y0;
$a = $alpha;
$b = $beta;
$in_token = 'y';
$out_token = 'x';
}
else{ // x <-> y swap their roles. Uppercase X, Y, and P refer to invertable values
$inverted = true;
$X = $y;
$Y = $x;
$Xn = $yn;
$Yn = $xn;
$X0 = $y0;
$Y0 = $x0;
$a = $beta;
$b = $alpha;
$in_token = 'x';
$out_token = 'y';
}
require($delta_Yn > 0, "bad delta " || $delta_Yn);
if ($Lambda > 1){
$underleveraged = $Xn > ceil($X/$Lambda);
}
$final_Yn = $Yn + $delta_Yn;
if ($Lambda == 1){
$final_Xn = $get_final_x($X, $Y, $final_Yn, $X0, $Y0, $pool_props, $inverted);
$final_X = $final_Xn;
$final_Y = $final_Yn;
}
else if (!$underleveraged){ // along X
$delta_Y = -($b*$Lambda-1)/$a*$delta_Yn;
$final_Y = $Y + $delta_Y;
require($final_Y > 0, "fully leveraged: negative final_Y="||$final_Y);
$final_X = $get_final_x_along_x($X, $Y, $final_Y, $pool_props, $inverted);
$final_Xn = $final_X/$Lambda;
}
else if ($underleveraged){
$delta_Yn_inflection = $Y * (( $Lambda/($Lambda-1) * ($b + ($a * $Lambda - 1) * $Xn/$X) )^($a * $Lambda/($a*$Lambda-1)) - 1) / $Lambda;
require($delta_Yn_inflection > 0, "negative delta_Yn_inflection="||$delta_Yn_inflection);
$inflected = $delta_Yn > $delta_Yn_inflection;
// along Y until the inflection point
$inflection_Yn = $Yn + $delta_Yn_inflection;
$final_Yn1 = $inflected ? $inflection_Yn : $final_Yn;
$final_Y1 = $final_Yn1 * $Lambda;
$final_X1 = $get_final_x_along_y($X, $Y, $final_Y1, $pool_props, $inverted);
$delta_X1 = $final_X1 - $X;
$delta_Xn1 = -$b/($a*$Lambda-1) * $delta_X1;
$final_Xn1 = $Xn + $delta_Xn1;
require($final_Xn1 > 0, "negative final_Xn1="||$final_Xn1);
if ($inflected){
// then, along X
log('inflected at ', $delta_Yn_inflection);
$delta_Yn2 = $final_Yn - $final_Yn1;
$delta_Y2 = -($b*$Lambda-1)/$a*$delta_Yn2;
$final_Y = $final_Y1 + $delta_Y2;
require($final_Y > 0, "inflected: negative final_Y="||$final_Y);
$final_X = $get_final_x_along_x($final_X1, $final_Y1, $final_Y, $pool_props, $inverted);
$final_Xn = $final_X/$Lambda;
require($final_Xn <= $final_Xn1, "Xn didn't decrease");
}
else{
$final_X = $final_X1;
$final_Xn = $final_Xn1;
$final_Y = $final_Y1;
}
}
else
bounce("???");
$balances.x = $y_in ? $final_X : $final_Y;
$balances.y = $y_in ? $final_Y : $final_X;
$balances.xn = $y_in ? $final_Xn : $final_Yn;
$balances.yn = $y_in ? $final_Yn : $final_Xn;
$final_y = $balances.y;
$final_x = $balances.x;
$p = $alpha/$beta * ($y + $y0) / ($x + $x0); // price of x in terms of y
$P = $inverted ? 1/$p : $p; // price of X in terms of Y
$final_p = $alpha/$beta * ($final_y + $y0) / ($final_x + $x0);
$final_P = $inverted ? 1/$final_p : $final_p;
require($final_P > $P, "price should have risen but hasn't, old " || $P || ", new " || $final_P);
// if inverted, XL corresponds to y, YL to x
$totals = $update_leveraged_balances($l_balances, $P, $final_P, $inverted);
$amount_X_exact = -($final_Xn - $Xn + $totals.delta_XL);
$amount_Y_exact = $final_Yn - $Yn + $totals.delta_YL;
$amount_Y = ceil($amount_Y_exact);
if ($received_amount_Y >= 0)
require($received_amount_Y >= $amount_Y, "expected " || $amount_Y || ", received " || $received_amount_Y);
require($amount_X_exact >= 0, "to pay " || $amount_X_exact);
$change = $received_amount_Y - $amount_Y;
$denom = 1 - $totals.XL_denom/$b/($final_X+$X0) - $totals.YL_denom/$a/($final_Y+$Y0);
// log('denom after swap by delta', $denom);
require($denom >= $singularity_threshold, "too close to the singularity point, denom="||$denom||", need more liquidity in order to swap this amount");
// arb tax based on price difference
if ($recent.last_trade AND $recent.last_trade.address == $trigger_initial_address AND $recent.last_ts >= timestamp - $trade_merge_period){
$min_P = min($P, $y_in ? $recent.last_trade.pmin : 1/$recent.last_trade.pmax);
$max_P = max($final_P, $y_in ? $recent.last_trade.pmax : 1/$recent.last_trade.pmin);
$recent_traded_amount = $recent.last_trade.amounts[$out_token];
$recent_paid_tax = $recent.last_trade.paid_taxes[$out_token];
}
else{
$min_P = $P;
$max_P = $final_P;
}
$arb_profit_in_Y = ($max_P - $min_P) * ($recent_traded_amount + $amount_X_exact) / 2; // in Y
$arb_profit_in_X = $arb_profit_in_Y / $min_P;
$arb_profit_tax = $arb_profit_in_X * $pool_props.arb_profit_tax - $recent_paid_tax;
require($arb_profit_tax >= 0, "negative arb profit tax");
$swap_fee = $amount_X_exact * $pool_props.swap_fee;
$fee = $arb_profit_tax + $swap_fee;
$net_amount_X_exact = $amount_X_exact - $fee;
$net_amount_X = floor($net_amount_X_exact);
$rounding_fee_X = $net_amount_X_exact - $net_amount_X;
$rounding_fee_Y = $amount_Y - $amount_Y_exact;
$total_fee = $fee + $rounding_fee_X;
$avg_price = $amount_Y / $net_amount_X;
require($avg_price > $P, "avg price "||$avg_price||" below initial price "||$P);
if ($min_amount_out)
require($net_amount_X >= $min_amount_out, "output amount " || $net_amount_X || " would be less than the expected minimum " || $min_amount_out);
// include rounding fees
$fees = {
X: $total_fee,
Y: $rounding_fee_Y,
};
// add the fee to the pool without trading and affecting the price (Lambda>1) or to a separate profit accumulator (Lambda=1)
$add_net_balance_without_changing_price($balances, $profits, $out_token, $fees.X, $Lambda);
$add_net_balance_without_changing_price($balances, $profits, $in_token, $fees.Y, $Lambda);
// log({fees: $fees, profits: $profits});
$update_recent_data($recent, $p, $final_p, $trigger_initial_address, $out_token, $amount_X_exact, $arb_profit_tax, $pool_props.period_length);
$event = json_stringify({
type: 'swap',
direction: $y_in ? 'y2x' : 'x2y',
in: $amount_Y,
out: $net_amount_X,
swap_fee: $swap_fee,
arb_profit_tax: $arb_profit_tax,
total_fee: $total_fee,
});
{
net_amount_X: $net_amount_X,
amount_Y: $amount_Y,
swap_fee: $swap_fee,
arb_profit_tax: $arb_profit_tax,
total_fee: $total_fee,
fees: $fees,
change: $change,
initial_price: $P,
final_price: $final_P,
event: $event,
}
};
$buy_shares = ($s, $balances, $profits, $recent, $x0, $y0, $received_amount_x, $received_amount_y, $pool_props) => {
$Lambda = $pool_props.Lambda;
$alpha = $pool_props.alpha;
$beta = $pool_props.beta;
// $get_shares = ($x_balance, $y_balance) => round($x_balance^$alpha * $y_balance^$beta);
$get_shares = ($x_balance, $y_balance) => round(($x_balance/$y_balance)^$alpha * $y_balance);
$xn = $balances.xn;
$yn = $balances.yn;
$x = $balances.x;
$y = $balances.y;
$recent.last_ts = timestamp;
if (!$s){
require($received_amount_x > 0 AND $received_amount_y > 0, "send both assets for the first issue");
$mid_price = $pool_props.mid_price;
if ($mid_price){
// first issue must be at mid price
require($received_amount_y == round($mid_price * $received_amount_x), "first issue must be at mid price "||$mid_price);
$gamma = $pool_props.gamma;
$shares_amount = round($received_amount_x * $pool_props.mid_price_beta * $gamma / ($gamma - 1));
}
else{
// first issue determines the price
$shares_amount = $get_shares($received_amount_x, $received_amount_y);
}
$balances.xn = $balances.xn + $received_amount_x;
$balances.yn = $balances.yn + $received_amount_y;
$balances.x = $balances.x + $received_amount_x * $Lambda;
$balances.y = $balances.y + $received_amount_y * $Lambda;
$event = json_stringify({
type: 'add',
x: $received_amount_x,
y: $received_amount_y,
shares: $shares_amount,
});
return {
shares_amount: $shares_amount,
coef: 1,
change_x: 0,
change_y: 0,
event: $event,
};
}
$p = $alpha/$beta * ($y + $y0) / ($x + $x0);
if ($Lambda > 1 AND $recent.prev){
$target_xn = $x/$Lambda;
if ($xn > ceil($target_xn)){ // x is underleveraged
// use the worst (for the user) price that was seen recently
$share_price_in_y = ($yn + max($recent.current.pmax, $recent.prev.pmax) * $xn) / $s;
$max_delta_yn = ($xn/$target_xn-1)*$yn;
$delta_yn1 = min($max_delta_yn, $received_amount_y);
$delta_y1 = $delta_yn1 * $Lambda;
$delta_x1 = $x * $delta_yn1/$yn; // proportional
$shares1 = $delta_yn1/$share_price_in_y;
}
else{
$target_yn = $y/$Lambda;
if ($yn > ceil($target_yn)){ // y is underleveraged
// use the worst (for the user) price that was seen recently
$share_price_in_x = ($xn + 1/min($recent.current.pmin, $recent.prev.pmin) * $yn) / $s;
$max_delta_xn = ($yn/$target_yn-1)*$xn;
$delta_xn1 = min($max_delta_xn, $received_amount_x);
$delta_x1 = $delta_xn1 * $Lambda;
$delta_y1 = $y * $delta_xn1/$xn; // proportional
$shares1 = $delta_xn1/$share_price_in_x;
// log({delta_xn1:$delta_xn1, shares1:$shares1, share_price_in_x:$share_price_in_x, p:$p, recent:$recent});
}
}
$balances.xn = $balances.xn + $delta_xn1;
$balances.yn = $balances.yn + $delta_yn1;
$balances.x = $balances.x + $delta_x1;
$balances.y = $balances.y + $delta_y1;
}
else{
$delta_xn1 = 0;
$delta_yn1 = 0;
$shares1 = 0;
}
$remaining = {
x: $received_amount_x - $delta_xn1,
y: $received_amount_y - $delta_yn1,
};
$y_to_x = ($yn+$delta_yn1)/($xn+$delta_xn1);
// $y_to_x = $balances.yn/$balances.xn;
if ($profits.x OR $profits.y){
require($Lambda == 1, "have profits while Lambda is " || $Lambda);
require($profits.x >= 0 AND $profits.y >= 0, "negative profits?"); // should never happen
// latest prices, not recent min/max
$share_price_in_y = ($yn + $p * $xn) / $s;
$share_price_in_x = ($xn + 1/$p * $yn) / $s;
// move proportional amounts of profit from both x and y. The shares to be issued for the moved profit belong to the pool and will not be actually issued
$profits_proportional_y = $y_to_x * $profits.x;
if ($profits.y > $profits_proportional_y){
$delta_profit_x = $profits.x;
$delta_profit_y = $profits_proportional_y;
$symmetric_moved_profit_shares = $delta_profit_x/$xn * $s;
}
else{
$profits_proportional_x = $profits.y / $y_to_x;
require($profits_proportional_x <= $profits.x, "profits x " || $profits.x || ", proportional " || $profits_proportional_x);
$delta_profit_x = $profits_proportional_x;
$delta_profit_y = $profits.y;
$symmetric_moved_profit_shares = $delta_profit_y/$yn * $s;
}
$profits.x = $profits.x - $delta_profit_x;
$profits.y = $profits.y - $delta_profit_y;
$balances.xn = $balances.xn + $delta_profit_x;
$balances.yn = $balances.yn + $delta_profit_y;
$balances.x = $balances.x + $delta_profit_x;
$balances.y = $balances.y + $delta_profit_y;
// log('after proportional profits: delta_profit_x', $delta_profit_x, 'delta_profit_y', $delta_profit_y, 'remaining profits', $profits, 'symmetric_moved_profit_shares', $symmetric_moved_profit_shares);
// calc the shares to be issued for moving the one-sided profits to the pool, these shares belong to the pool and will not be actually issued. The user contributes the other side and gets the shares cheaper than their fair price because profits are not included in the price calculation.
$moved_profit_x = min($profits.x, $remaining.y / $y_to_x);
$moved_profit_y = min($profits.y, $remaining.x * $y_to_x);
$moved_profit_shares = $moved_profit_x/$share_price_in_x + $moved_profit_y/$share_price_in_y + $symmetric_moved_profit_shares;
// log('share_price_in_x', $share_price_in_x, 'share_price_in_y', $share_price_in_y, 'moved_profit_shares', $moved_profit_shares, 'moved_profit_x', $moved_profit_x, 'moved_profit_y', $moved_profit_y);
$profits.x = $profits.x - $moved_profit_x;
$profits.y = $profits.y - $moved_profit_y;
$remaining.x = $remaining.x + $moved_profit_x;
$remaining.y = $remaining.y + $moved_profit_y;
}
// part 2: proportional buying
// log('before proportional buying: remaining', $remaining, 'y_to_x', $y_to_x);
$remaining_proportional_y = $y_to_x * $remaining.x;
if ($remaining.y > $remaining_proportional_y){ // excessive y
$proportional_delta_xn = $remaining.x;
$proportional_delta_yn = $remaining_proportional_y;
$exact_change_x = 0;
$exact_change_y = $remaining.y - $remaining_proportional_y;
// log({proportional_delta_yn:$proportional_delta_yn, exact_change_y:$exact_change_y, change_y_exact: $remaining.y - $remaining_proportional_y, remaining_y:$remaining.y, remaining_proportional_y:$remaining_proportional_y});
$shares_proportional = $remaining.x / ($xn + $delta_xn1) * ($s + $shares1);
}
else{ // excessive x
$remaining_proportional_x = $remaining.y / $y_to_x;
$proportional_delta_xn = $remaining_proportional_x;
$proportional_delta_yn = $remaining.y;
$exact_change_x = $remaining.x - $remaining_proportional_x;
// log({proportional_delta_xn:$proportional_delta_xn, exact_change_x:$exact_change_x, remaining_x:$remaining.x, remaining_proportional_x:$remaining_proportional_x});
require($exact_change_x >= 0, "received x " || $remaining.x || ", proportional " || $remaining_proportional_x);
$exact_change_y = 0;
$shares_proportional = $remaining.y / ($yn + $delta_yn1) * ($s + $shares1);
}
$gross_shares_amount = $shares1 + $symmetric_moved_profit_shares + $shares_proportional;
$shares_amount = floor($gross_shares_amount - $moved_profit_shares);
$coef = $Lambda == 1 ? ($s + $gross_shares_amount) / ($s + $shares_amount) : 1;
// log({shares_proportional:$shares_proportional, moved_profit_shares:$moved_profit_shares, shares_amount:$shares_amount});
$balances.xn = $balances.xn + $proportional_delta_xn;
$balances.yn = $balances.yn + $proportional_delta_yn;
$balances.x = $balances.x + $proportional_delta_xn * $Lambda;
$balances.y = $balances.y + $proportional_delta_yn * $Lambda;
$change_x = floor($exact_change_x);
$change_y = floor($exact_change_y);
$rounding_fee_x = $exact_change_x - $change_x;
$rounding_fee_y = $exact_change_y - $change_y;
$add_net_balance_without_changing_price($balances, $profits, 'x', $rounding_fee_x, $Lambda);
$add_net_balance_without_changing_price($balances, $profits, 'y', $rounding_fee_y, $Lambda);
$event = json_stringify({
type: 'add',
x: $received_amount_x - $change_x,
y: $received_amount_y - $change_y,
shares: $shares_amount,
});
{
shares_amount: $shares_amount,
coef: $coef,
change_x: $change_x,
change_y: $change_y,
event: $event,
}
};
$redeem_shares = ($s, $balances, $l_balances, $profits, $recent, $x0, $y0, $received_shares_amount, $asset, $pool_props) => {
$xn = $balances.xn;
$yn = $balances.yn;
$x = $balances.x;
$y = $balances.y;
$exit_fee = ($received_shares_amount < $s) ? $pool_props.exit_fee : 0; // 0 fee for the last LP
$net_of_exit_fee = 1 - $exit_fee;
$x_asset = $pool_props.x_asset;
$y_asset = $pool_props.y_asset;
$Lambda = $pool_props.Lambda;
$alpha = $pool_props.alpha;
if ($asset){ // one-sided redemption first, then proportional
require($asset == $x_asset OR $asset == $y_asset, "wrong preferred asset");
require($Lambda > 1, "only proportional withdrawals allowed");
$asset_label = $asset == $x_asset ? 'x' : 'y';
$net_balance = $asset == $x_asset ? $xn : $yn;
$effective_balance = $asset == $x_asset ? $x : $y;
$target_net_balance = $effective_balance / $Lambda;
// don't fail, just skip it then
// require($target_net_balance < $net_balance, "the preferred asset is already fully leveraged");
$excess_net_balance = max($net_balance - $target_net_balance, 0);
require($recent.prev, "too early, prev price not known yet");
// use the worst (for the user) price that was seen recently
$share_price_in_asset = (($asset_label == 'y') ? ($yn + min($recent.current.pmin, $recent.prev.pmin) * $xn) / $s : ($xn + 1/max($recent.current.pmax, $recent.prev.pmax) * $yn) / $s) * $net_of_exit_fee;
$max_asset = $received_shares_amount * $share_price_in_asset;
$one_sided_amount = min($max_asset, $excess_net_balance);
$one_sided_fee = $one_sided_amount / $net_of_exit_fee * $exit_fee;
if ($asset_label == 'y'){
$yn_amount1 = $one_sided_amount;
$xn_amount1 = 0;
$one_sided_y_fee = $one_sided_fee;
}
else{
$xn_amount1 = $one_sided_amount;
$yn_amount1 = 0;
$one_sided_x_fee = $one_sided_fee;
}
$remaining_received_shares = max($received_shares_amount - ($one_sided_amount / $share_price_in_asset), 0);
}
else{
$remaining_received_shares = $received_shares_amount;
$xn_amount1 = 0;
$yn_amount1 = 0;
}
$share_of_shares = $remaining_received_shares / $s;
$remaining_share_of_shares = 1 - $share_of_shares;
$remaining_share_of_assets = $remaining_share_of_shares;
$share_of_assets = 1 - $remaining_share_of_assets;
$x_amount = $share_of_assets * $x * $net_of_exit_fee;
$y_amount = $share_of_assets * $y * $net_of_exit_fee;
$xn_amount = $share_of_assets * ($xn - $xn_amount1) * $net_of_exit_fee + $xn_amount1;
$yn_amount = $share_of_assets * ($yn - $yn_amount1) * $net_of_exit_fee + $yn_amount1;
// if ($asset)
// log({xn_amount1:$xn_amount1, yn_amount1:$yn_amount1, shares_for_excess: $one_sided_amount / $share_price_in_asset, remaining_received_shares:$remaining_received_shares, xn_amount:$xn_amount, yn_amount:$yn_amount, asset_label:$asset_label, share_price_in_asset:$share_price_in_asset, max_asset: $max_asset, excess_net_balance:$excess_net_balance, recent:$recent, pmin:min($recent.current.pmin, $recent.prev.pmin), pmax:max($recent.current.pmax, $recent.prev.pmax)});
// log({ remaining_received_shares:$remaining_received_shares, xn_amount:$xn_amount, yn_amount:$yn_amount, x_amount:$x_amount, y_amount:$y_amount});
// log('balances before', $balances);
$balances.x = $balances.x - $x_amount;
$balances.y = $balances.y - $y_amount;
$balances.xn = $balances.xn - $xn_amount;
$balances.yn = $balances.yn - $yn_amount;
// log('balances after', $balances);
$coef = $Lambda == 1 AND $received_shares_amount < $s ? ($s - $received_shares_amount * $net_of_exit_fee) / ($s - $received_shares_amount) : 1;
$new_x0 = $x0 * ($s-$received_shares_amount)/$s;
$new_y0 = $y0 * ($s-$received_shares_amount)/$s;
$denom = 1 - $get_utilization_ratio($balances, $l_balances, $new_x0, $new_y0, $alpha);
require($denom >= $singularity_threshold, "redemption amount too large, it would bring us too close to the singularity point, denom="||$denom);
$int_xn_amount = floor($xn_amount);
$int_yn_amount = floor($yn_amount);
$rounding_fee_x = $xn_amount - $int_xn_amount;
$rounding_fee_y = $yn_amount - $int_yn_amount;
$add_net_balance_without_changing_price($balances, $profits, 'x', $rounding_fee_x, $Lambda);
$add_net_balance_without_changing_price($balances, $profits, 'y', $rounding_fee_y, $Lambda);
// log('balances after rounding fees', $balances);
$recent.last_ts = timestamp;
$event = json_stringify({
type: 'remove',
shares: $received_shares_amount,
x: $int_xn_amount,
y: $int_yn_amount,
x_fee: $share_of_assets * ($xn - $xn_amount1) * $exit_fee + $one_sided_x_fee,
y_fee: $share_of_assets * ($yn - $yn_amount1) * $exit_fee + $one_sided_y_fee,
});
{
xn_amount: $int_xn_amount,
yn_amount: $int_yn_amount,
coef: $coef,
event: $event,
}
};
$update_other_l_balances_and_get_sums = ($l_balances, $P, $final_P, $Leverage, $inverted) => {
$ratio = $final_P/$P;
$ratio_powers = $precompute($ratio);
$sums = {
initial: 0,
final: 0,
delta_XL: 0,
XL_denom: 0,
YL_denom: 0,
PL1_ratio: $pow($ratio_powers, $Leverage) / $ratio, // $ratio^($Leverage-1)
};
foreach($get_leverages(), 6, $L => {
$allyL = $inverted ? -$L : $L;
$balance = $l_balances[$allyL||'x'].balance;
$obalance = $l_balances[-$allyL||'x'].balance;
if (!$balance AND !$obalance)
return;
$ratio_L1 = $pow($ratio_powers, $L) / $ratio; // $ratio^($L-1)
if ($balance){
$sums.initial = $sums.initial + $balance * ($L-1)/$L;
if ($L != $Leverage){
$new_balance = $balance * $ratio_L1;
$l_balances[$allyL||'x'].balance = $new_balance;
$sums.final = $sums.final + $new_balance * ($L-1)/$L;
$sums.XL_denom = $sums.XL_denom + $new_balance * ($L-1);
$sums.delta_XL = $sums.delta_XL + $new_balance - $balance;
}
}
if ($obalance){
$sums.initial = $sums.initial - $obalance/$P;
$new_obalance = $obalance / $ratio_L1;
$l_balances[-$allyL||'x'].balance = $new_obalance;
$sums.final = $sums.final - $new_obalance/$final_P;
$sums.YL_denom = $sums.YL_denom + $new_obalance * ($L-1);
$sums.delta_XL = $sums.delta_XL - ($new_obalance / $final_P - $obalance / $P) * ($L-1)/$L;
}
});
$sums
};
$move_unleveraged = ($pool, $l_balances, $X0, $Y0, $dXn, $Leverage, $pool_props, $inverted) => {
$a = $inverted ? $pool_props.beta : $pool_props.alpha;
$b = 1 - $a;
$L_key = ($inverted ? -$Leverage : $Leverage) || 'x';
$l_bal_direction = $dXn < 0 ? "grow" : "fall";
$Xt = $pool.X + $X0;
$P = $a/$b * ($pool.Y + $Y0) / $Xt;
$final_Xn = $pool.Xn + $dXn;
require($final_Xn > 0, "unleveraged: final_Xn=" || $final_Xn);
$final_X = $final_Xn;
$final_Xt = $final_X + $X0;
$final_Y = $get_final_y($pool.X, $pool.Y, $final_X, $X0, $Y0, $pool_props, $inverted);
$delta_Y = $final_Y - $pool.Y;
$delta_Yn = $delta_Y;
$final_Yn = $pool.Yn + $delta_Yn;
require($final_Yn > 0, "unleveraged: final_Yn=" || $final_Yn);
$final_P = $a/$b * ($final_Y + $Y0) / $final_Xt;
// log('l balances before', $l_balances);
$sums = $update_other_l_balances_and_get_sums($l_balances, $P, $final_P, $Leverage, $inverted);
// log('sums', $sums);
// log('l balances after', $l_balances);
// update the final balance of our L-pool
$b1 = $sums.initial + $b/($b-1)*$Xt;
$new_l_balance = $Leverage/($Leverage-1) * ( -$sums.final - $b/($b-1)*$final_Xt + $b1 * ($final_Xt/$Xt)^(1/$b) );
// log('new_l_balance', $new_l_balance);
require($new_l_balance >= 0, "unleveraged: new_l_balance=" || $new_l_balance);
$delta_l_balance = $new_l_balance - $l_balances[$L_key].balance;
// log('delta_l_balance', $delta_l_balance);
require($delta_l_balance * $dXn < 0, "unleveraged l-bal should "||$l_bal_direction||", got new " || $new_l_balance || ", delta " || $delta_l_balance);
$l_balances[$L_key].balance = $new_l_balance;
// log('l balances after 2', $l_balances);
$pool.X = $final_X;
$pool.Y = $final_Y;
$pool.Xn = $final_Xn;
$pool.Yn = $final_Yn;
$pool.delta_XL = $pool.delta_XL + $sums.delta_XL;
$pool.XL_denom = $sums.XL_denom + $new_l_balance * ($Leverage-1);
$pool.YL_denom = $sums.YL_denom;
$pool.PL1_ratio = $pool.PL1_ratio * $sums.PL1_ratio;
};
$move_along_X = ($pool, $l_balances, $dXn, $Leverage, $pool_props, $inverted) => {
$Lambda = $pool_props.Lambda;
$a = $inverted ? $pool_props.beta : $pool_props.alpha;
$b = 1 - $a;
$L_key = ($inverted ? -$Leverage : $Leverage) || 'x';
$l_bal_direction = $dXn < 0 ? "grow" : "fall";
$P = $a/$b * $pool.Y / $pool.X;
$final_Xn = $pool.Xn + $dXn;
require($final_Xn > 0, "along X: final_Xn=" || $final_Xn);
$final_X = $Lambda * $final_Xn;
$final_Y = $get_final_y_along_x($pool.X, $pool.Y, $final_X, $pool_props, $inverted);
$delta_Y = $final_Y - $pool.Y;
$delta_Yn = -$a/($b*$Lambda-1)*$delta_Y;
$final_Yn = $pool.Yn + $delta_Yn;
require($final_Yn > 0, "along X: final_Yn=" || $final_Yn);
$final_P = $a/$b * $final_Y / $final_X;
$sums = $update_other_l_balances_and_get_sums($l_balances, $P, $final_P, $Leverage, $inverted);
// update the final balance of our L-pool
$b1 = $sums.initial + $b/($b*$Lambda-1)*$pool.X;
$new_l_balance = $Leverage/($Leverage-1) * ( -$sums.final - $b/($b*$Lambda-1)*$final_X + $b1 * ($final_X/$pool.X)^(1/$b/$Lambda) );
require($new_l_balance >= 0, "along X: new_l_balance=" || $new_l_balance);
$delta_l_balance = $new_l_balance - $l_balances[$L_key].balance;
require($delta_l_balance * $dXn < 0, "along x l-bal should "||$l_bal_direction||", got new " || $new_l_balance || ", delta " || $delta_l_balance);
$l_balances[$L_key].balance = $new_l_balance;
$pool.X = $final_X;
$pool.Y = $final_Y;
$pool.Xn = $final_Xn;
$pool.Yn = $final_Yn;
$pool.delta_XL = $pool.delta_XL + $sums.delta_XL;
$pool.XL_denom = $sums.XL_denom + $new_l_balance * ($Leverage-1);
$pool.YL_denom = $sums.YL_denom;
$pool.PL1_ratio = $pool.PL1_ratio * $sums.PL1_ratio;
};
$move_along_Y = ($pool, $l_balances, $dXn, $Leverage, $pool_props, $inverted) => {
$Lambda = $pool_props.Lambda;
$a = $inverted ? $pool_props.beta : $pool_props.alpha;
$b = 1 - $a;
$L_key = ($inverted ? -$Leverage : $Leverage) || 'x';
$l_bal_direction = $dXn < 0 ? "grow" : "fall";
$P = $a/$b * $pool.Y / $pool.X;
$final_Xn = $pool.Xn + $dXn;
require($final_Xn > 0, "along Y: final_Xn=" || $final_Xn);
$delta_X = -($a*$Lambda-1)/$b * $dXn;
$final_X = $pool.X + $delta_X;
require($final_X > 0, "along Y: final_X=" || $final_X);
$final_Y = $get_final_y_along_y($pool.X, $pool.Y, $final_X, $pool_props, $inverted);
$final_Yn = $final_Y/$Lambda;
$final_P = $a/$b * $final_Y / $final_X;
$sums = $update_other_l_balances_and_get_sums($l_balances, $P, $final_P, $Leverage, $inverted);
// update the final balance of our L-pool
$b2 = $sums.initial - $b/$a/$Lambda*$pool.X;
$new_l_balance = $Leverage/($Leverage-1) * ( -$sums.final + $b/$a/$Lambda*$final_X + $b2 * ($final_X/$pool.X)^(-1/($a*$Lambda-1)) );
require($new_l_balance >= 0, "along Y: new_l_balance=" || $new_l_balance);
$delta_l_balance = $new_l_balance - $l_balances[$L_key].balance;
require($delta_l_balance * $dXn < 0, "along y l-bal should "||$l_bal_direction||", got new " || $new_l_balance || ", delta " || $delta_l_balance);
$l_balances[$L_key].balance = $new_l_balance;
$pool.X = $final_X;
$pool.Y = $final_Y;
$pool.Xn = $final_Xn;
$pool.Yn = $final_Yn;
$pool.delta_XL = $pool.delta_XL + $sums.delta_XL;
$pool.XL_denom = $sums.XL_denom + $new_l_balance * ($Leverage-1);
$pool.YL_denom = $sums.YL_denom;
$pool.PL1_ratio = $pool.PL1_ratio * $sums.PL1_ratio;
};
// delta_Xn < 0: buy L-tokens
// delta_Xn > 0: sell L-tokens
$trade_l_shares = ($balances, $l_balances, $profits, $recent, $x0, $y0, $Leverage, $asset, $delta_Xn, $entry_price, $trigger_initial_address, $pool_props) => {
// require(is_integer($delta_Xn), "delta must be int");
require($asset == $pool_props.x_asset OR $asset == $pool_props.y_asset, "wrong asset");
require($Leverage == 2 OR $Leverage == 5 OR $Leverage == 10 OR $Leverage == 20 OR $Leverage == 50 OR $Leverage == 100, "bad L");
$Lambda = $pool_props.Lambda;
if ($asset == $pool_props.x_asset){
$inverted = false;
$X = $balances.x;
$Y = $balances.y;
$Xn = $balances.xn;
$Yn = $balances.yn;
$X0 = $x0;
$Y0 = $y0;
$a = $pool_props.alpha;
$b = $pool_props.beta;
$token = 'x';
}
else{ // x <-> y swap their roles. Uppercase X, Y, and P refer to invertable values
$inverted = true;
$X = $balances.y;
$Y = $balances.x;
$Xn = $balances.yn;
$Yn = $balances.xn;
$X0 = $y0;
$Y0 = $x0;
$a = $pool_props.beta;
$b = $pool_props.alpha;
$token = 'y';
}
$direct = !$inverted;
require($Xn + $delta_Xn > 0, "Xn balance would be negative");
$L_key = ($inverted ? -$Leverage : $Leverage) || 'x';
$pool = {X: $X, Y: $Y, Xn: $Xn, Yn: $Yn, delta_XL: 0, PL1_ratio: 1};
$initial_l_balance = $l_balances[$L_key].balance;
$initial_shares = $l_balances[$L_key].supply;
// $initial_l_balance might be non-zero after full redemption of all l-shares -- due to rounding of share amounts
// require(!$initial_l_balance == !$initial_shares, "l-balance "||$initial_l_balance||" while shares "||$initial_shares);
$initial_P = $a/$b * ($pool.Y + $Y0) / ($pool.X + $X0);
if ($Lambda == 1)
this_address#3.$move_unleveraged($pool, $l_balances, $X0, $Y0, $delta_Xn, $Leverage, $pool_props, $inverted);
else {
$underleveraged = $Xn > ceil($X/$Lambda);
if (!$underleveraged){ // along X
if ($delta_Xn > 0){ // selling L-shares and X
$delta_Xn_inflection = $X * (( $Lambda/($Lambda-1) * ($a + ($b * $Lambda - 1) * $Yn/$Y) )^($b * $Lambda/($b*$Lambda-1)) - 1) / $Lambda;
$inflected = $delta_Xn > $delta_Xn_inflection;
}
$dXn1 = $inflected ? $delta_Xn_inflection : $delta_Xn;
this_address#3.$move_along_X($pool, $l_balances, $dXn1, $Leverage, $pool_props, $inverted);
if ($inflected)
this_address#3.$move_along_Y($pool, $l_balances, $delta_Xn - $delta_Xn_inflection, $Leverage, $pool_props, $inverted);
}
else{ // along Y
if ($delta_Xn < 0){ // buying L-shares and X
$delta_Xn_inflection = -$b/($Lambda-1) * ($Lambda*$Xn - $X);
$inflected = abs($delta_Xn) > abs($delta_Xn_inflection);
}
$dXn1 = $inflected ? $delta_Xn_inflection : $delta_Xn;
this_address#3.$move_along_Y($pool, $l_balances, $dXn1, $Leverage, $pool_props, $inverted);
if ($inflected)
this_address#3.$move_along_X($pool, $l_balances, $delta_Xn - $delta_Xn_inflection, $Leverage, $pool_props, $inverted);
}
}
$final_l_balance = $l_balances[$L_key].balance;
$delta_l_balance = $final_l_balance - $initial_l_balance;
$final_P = $a/$b * ($pool.Y + $Y0) / ($pool.X + $X0);
// the change in the total X balance of swap pool + all L-pools (positive when buying, negative when selling)
$net_delta = $delta_Xn + $pool.delta_XL + $delta_l_balance;
$final_shares = $initial_shares
? floor($final_l_balance/($initial_l_balance OTHERWISE 0.1*$final_l_balance) / $pool.PL1_ratio * $initial_shares) // if the L-pool got bankrupt before (e.g. the entire balance eaten by interest), its old owners get 10% of shares of the resurrected pool
: round($net_delta); // the initial units for shares are arbitrary
$shares = $final_shares - $initial_shares;
$l_balances[$L_key].supply = $final_shares;
$avg_share_price = $net_delta/$shares;
if ($final_shares == 0){
require($final_l_balance >= 0, "negative final l-balance after redeeming all shares: "||$final_l_balance);
// any remaining l-balance will be a gift to those who buy the l-shares later
// $remainder_fee_X = $final_l_balance;
// $remainder_fee_Y = $final_l_balance * $final_P * ($Leverage-1)/$Leverage;
// $l_balances[$L_key].balance = 0;
}
// we overestimate the utilization ratio here as pool.X and pool.Y here are before adding fees
$denom = 1 - $pool.XL_denom/$b/($pool.X+$X0) - $pool.YL_denom/$a/($pool.Y+$Y0);
// log('denom after L', $denom);
require($denom >= $singularity_threshold, "too close to the singularity point, denom="||$denom||", need more liquidity in order to buy/sell this amount of L-tokens");
$balances.x = $inverted ? $pool.Y : $pool.X;
$balances.y = $inverted ? $pool.X : $pool.Y;
$balances.xn = $inverted ? $pool.Yn : $pool.Xn;
$balances.yn = $inverted ? $pool.Xn : $pool.Yn;
// regular trading fee (%) and arb tax are paid on top
if ($recent.last_trade AND $recent.last_trade.address == $trigger_initial_address AND $recent.last_ts >= timestamp - $trade_merge_period){
$min_P = min($initial_P, $final_P, $direct ? $recent.last_trade.pmin : 1/$recent.last_trade.pmax);
$max_P = max($initial_P, $final_P, $direct ? $recent.last_trade.pmax : 1/$recent.last_trade.pmin);
$recent_traded_amount = $recent.last_trade.amounts[$token];
$recent_paid_tax = $recent.last_trade.paid_taxes[$token];
}
else{
$min_P = min($initial_P, $final_P);
$max_P = max($initial_P, $final_P);
}
$arb_profit_in_Y = ($max_P - $min_P) * ($recent_traded_amount + abs($net_delta)) / 2; // in Y
$arb_profit_in_X = $arb_profit_in_Y / $min_P;
$arb_profit_tax = $arb_profit_in_X * $pool_props.arb_profit_tax - $recent_paid_tax;
require($arb_profit_tax >= 0, "negative arb profit tax");
$swap_fee = abs($net_delta) * $pool_props.swap_fee;
if ($delta_Xn > 0){ // sell
$gross_asset_out = -$net_delta; // gross means before tax and fees
require($gross_asset_out > 0, "asset out must be positive, got " || $gross_asset_out);
if ($entry_price){
// L>0 profit is accounted for in x tokens (in y tokens for L<0) meaning that only profits from the borrowed part of the L-pool are taxed
$l_tax = max(($avg_share_price - $entry_price)*(-$shares)*$pool_props.leverage_profit_tax, 0);
}
else
$l_tax = $gross_asset_out * $pool_props.leverage_token_tax;
}
// For buying, the fee is added on top. For selling (net_delta<0), the fees are subtracted
$subtotal_fee = $arb_profit_tax + $swap_fee + $l_tax;
$gross_delta_exact = $net_delta + $subtotal_fee;
$gross_delta = ceil($gross_delta_exact);
$rounding_fee = $gross_delta - $gross_delta_exact;
$total_fee = $subtotal_fee + $rounding_fee;
// log('balances before', $balances);
$add_net_balance_without_changing_price($balances, $profits, $token, $total_fee, $Lambda);
// log('balances after', $balances);
$update_recent_data($recent, $inverted ? 1/$initial_P : $initial_P, $inverted ? 1/$final_P : $final_P, $trigger_initial_address, $token, abs($net_delta), $arb_profit_tax, $pool_props.period_length);
$event = json_stringify({
type: 'leverage',
token: $token,
L: $Leverage,
shares: $shares,
amount: $gross_delta,
swap_fee: $swap_fee,
arb_profit_tax: $arb_profit_tax,
l_tax: $l_tax,
total_fee: $total_fee,
});
{
shares: $shares,
net_delta: $net_delta,
gross_delta: $gross_delta,
avg_share_price: $avg_share_price,
arb_profit_tax: $arb_profit_tax,
l_tax: $l_tax,
swap_fee: $swap_fee,
total_fee: $total_fee,
initial_price: $initial_P,
final_price: $final_P,
event: $event,
}
};
$handle_trade_l_shares_request = ($pool_aa, $balances, $l_balances, $profits, $recent, $x0, $y0, $trigger_data, $trigger_address, $trigger_outputs, $trigger_initial_address, $pool_props) => {
$x_asset = $pool_props.x_asset;
$y_asset = $pool_props.y_asset;
$asset = $trigger_data.asset == 'x' ? $x_asset : ($trigger_data.asset == 'y' ? $y_asset : $trigger_data.asset);
$L = $trigger_data.L;
$buy = $trigger_data.buy;
$sell = $trigger_data.sell;
$delta = $trigger_data.delta;
$received_amount = $trigger_outputs[$asset];
$min_received_amount = $asset == 'base' ? 10000 : 0;
$net_received_amount = $received_amount - $min_received_amount;
require(!($buy AND $sell), "buy or sell?");
require($delta > 0, "delta must be positive");
if ($buy)
require($net_received_amount > 0, "you forgot to pay");
else
require($net_received_amount == 0, "don't send asset");
$delta_Xn = $buy ? -$delta : $delta; // Xn in the pool
$asset_label = $asset == $x_asset ? 'x' : 'y';
$signedL = $asset_label == 'x' ? $L : -$L;
if ($buy AND $trigger_data.tokens OR $sell AND !$trigger_data.position){
$l_shares_asset = var[$pool_aa]['leveraged_asset' || $signedL];
require($l_shares_asset, "please define an asset for the leveraged token first");
}
if ($sell){
if ($trigger_data.position){
$position = var[$pool_aa][$trigger_data.position];
require($position, "no such position");
require($position.owner == $trigger_address, "you are not the owner of this position");
$parts = split($trigger_data.position, '_');
require(+$parts[1] == $signedL, "wrong L");
$shares_in = $position.shares;
}
else{
$shares_in = $trigger_outputs[$l_shares_asset];
require($shares_in > 0, "no leveraged tokens received");
}
}
$res = $trade_l_shares($balances, $l_balances, $profits, $recent, $x0, $y0, $L, $asset, $delta_Xn, $position.price, $trigger_initial_address, $pool_props);
// log('balances', $balances, 'res', $res);
$shares = $res.shares;
$gross_delta = $res.gross_delta;
if ($buy){
$asset_out = $received_amount - $gross_delta; // the change
require($asset_out >= 0, "expected " || $gross_delta || ", received " || $received_amount);
}
else{
$shares_change = $shares_in + $shares; // shares < 0
require($shares_change >= 0, "expected " || (-$shares) || " shares, received " || $shares_in);
$asset_out = -$gross_delta;
}
$res.signedL = $signedL;
$res.asset_label = $asset_label;
$res.asset = $asset;
$res.l_shares_asset = $l_shares_asset;
$res.position = $position;
$res.shares_change = $shares_change;
$res.asset_out = $asset_out;
$res
};
$validate_and_apply_new_governed_param_value = ($name, $value, $balances, $l_balances, $profits, $lp_shares, $pool_props, $locked_governance) => {
if ($locked_governance)
require(!$locked_governance[$name], "governance is not allowed to change "||$name);
$Lambda = $pool_props.Lambda;
$alpha = $pool_props.alpha;
$beta = $pool_props.beta;
$gamma = $pool_props.gamma;
$mid_price = $pool_props.mid_price;
$mid_price_beta = $pool_props.mid_price_beta;
$s_curve = $lp_shares.linear * $lp_shares.coef;
$x0 = $mid_price ? $s_curve / $mid_price_beta / $gamma : 0;
$y0 = $x0 * $mid_price;
if ($name == 'pool_leverage'){
require($profits.x < 1 AND $profits.y < 1, "profits must be added to the pool before changing pool_leverage"); // only rounding fees are allowed
$profits.x = 0;
$profits.y = 0;
require($alpha != 1/$value, "pool leverage = 1/alpha");
require($beta != 1/$value, "pool leverage = 1/beta");
require($value != $Lambda, "same Lambda");
if ($value > 1)
require(!$mid_price, "price range setting is incompatible with new pool leverage");
$balances.x = $balances.x * $value/$Lambda;
$balances.y = $balances.y * $value/$Lambda;
if ($value == 1){
// move the excessive balances to profits
$profits.x = $profits.x + $balances.xn - $balances.x;
$profits.y = $profits.y + $balances.yn - $balances.y;
$balances.xn = $balances.x;
$balances.yn = $balances.y;
}
// we have modified balances and profits
}
else if ($name == 'mid_price' OR $name == 'price_deviation'){
require($value AND $mid_price, $name||" must be nonzero");
require($alpha == 0.5, "equal weights only");
if ($name == 'price_deviation'){
require($value > 1, "price deviation must be > 1");
$new_p0 = $mid_price;
$new_gamma = $value;
}
else{
$new_p0 = $value;
$new_gamma = $gamma;
}
$sqp = $mid_price_beta; // sqrt(mid_price)
$new_sqp = sqrt($new_p0);
$x = $balances.x;
$y = $balances.y;
// 1. assume we keep all x and decrease y
$new_s1 = 1 / (1/$s_curve + (1/$gamma/$sqp - 1/$new_gamma/$new_sqp) / $x);
$new_y = $new_s1 * ($y/$s_curve + $sqp/$gamma - $new_sqp/$new_gamma);
if ($new_y <= $y){
require($new_y > 0, "new y is negative");
require($new_s1 > 0, "new s1 is negative");
$profits.y = $profits.y + $y - $new_y;
$balances.y = $new_y;
$balances.yn = $new_y;
$lp_shares.coef = $lp_shares.coef * $new_s1/$s_curve;
$new_s = $new_s1;
}
else{ // 2. keep all y and decrease x
$new_s2 = 1 / (1/$s_curve + ($sqp/$gamma - $new_sqp/$new_gamma) / $y);
$new_x = $new_s2 * ($x/$s_curve + 1/$gamma/$sqp - 1/$new_gamma/$new_sqp);
require($new_x <= $x, "can't adjust x and y to keep the price");
require($new_x > 0, "new x is negative");
require($new_s2 > 0, "new s2 is negative");
$profits.x = $profits.x + $x - $new_x;
$balances.x = $new_x;
$balances.xn = $new_x;
$lp_shares.coef = $lp_shares.coef * $new_s2/$s_curve;
$new_s = $new_s2;
}
$new_x0 = $new_s / $new_sqp / $new_gamma;
$new_y0 = $new_s * $new_sqp / $new_gamma;
/*
$sqp = sqrt($new_p0);
$b = ($balances.x/$s_curve*$sqp + $balances.y/$s_curve/$sqp)/$new_gamma;
$a = $balances.x*$balances.y/$s_curve/$s_curve;
$lp_shares.coef = $lp_shares.coef / (-$b + sqrt($b*$b - 4*$a*(1/$new_gamma/$new_gamma-1)))*2*$a;
*/
// we have modified lp_shares, balances, and profits
}
else if ($name == 'alpha'){
require(!$mid_price, "can't change token weights while trading in limited range");
$new_alpha = $value;
$new_beta = 1 - $new_alpha;
require($new_alpha != 1/$Lambda AND $new_beta != 1/$Lambda, "pool leverage = 1/alpha or 1/beta");
// s_coef is unused
// var['s_coef'] *= $balances.xn^$value * $balances.yn^(1-$value) / $s_curve;
// change the balances to preserve the price p = alpha/beta * y/x = const
$new_y2x = $new_beta/$new_alpha * $alpha/$beta * $balances.y/$balances.x;
if ($Lambda > 1){
if ($balances.xn * $Lambda * $new_y2x <= $balances.yn * $Lambda){ // x fully leveraged
$balances.x = $balances.xn * $Lambda;
$balances.y = $balances.x * $new_y2x;
}
else if ($balances.yn * $Lambda / $new_y2x <= $balances.xn * $Lambda){ // y fully leveraged
$balances.y = $balances.yn * $Lambda;
$balances.x = $balances.y / $new_y2x;
}
else
bounce("can't preserve the price"); // should never happen
}
else {
$new_y = $balances.xn * $new_y2x; // assuming x balance stays unchanged
if ($new_y <= $balances.yn){ // excessive y
$profits.y = $profits.y + $balances.yn - $new_y;
$balances.yn = $new_y;
$balances.y = $new_y;
}
else { // excessive x
$new_x = $balances.yn / $new_y2x; // assuming y balance stays unchanged
require($new_x <= $balances.xn, "neither excessive x nor excessive y"); // should never happen
$profits.x = $profits.x + $balances.xn - $new_x;
$balances.xn = $new_x;
$balances.x = $new_x;
}
}
// balances and profits modified
}
// under new balances, we might go over (or too close to) the singularity point
$denom = 1 - $get_utilization_ratio($balances, $l_balances, $new_x0 OTHERWISE $x0, $new_y0 OTHERWISE $y0, $new_alpha OTHERWISE $alpha);
require($denom >= $singularity_threshold, "new balances would bring us too close to the singularity point, denom="||$denom);
};
}",
"messages": [
{
"app": "state",
"state": "{
// $u = $get_utilization_ratio();
// $A = $swap();
// $b = $charge_interest();
// $c = $update_leveraged_balances();
// $d = $trade_l_shares();
$h = $handle_trade_l_shares_request();
// $e = $buy_shares();
// $f = $redeem_shares();
// $g = $validate_and_apply_new_governed_param_value();
// $t = $get_total_balances();
bounce("library only");
}"
}
]
}
]