Skip to content

Commit

Permalink
Merge pull request #10 from haiko-xyz/feat/consume-full-swap-amount
Browse files Browse the repository at this point in the history
feat: solvers - consume full swap amount
  • Loading branch information
parketh authored Oct 4, 2024
2 parents bab501b + 3527a3a commit 3606796
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 32 deletions.
18 changes: 14 additions & 4 deletions models/src/replicating/libraries/SwapLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export const getSwapAmounts = ({
? Decimal.max(thresholdSqrtPrice, scaledLowerSqrtPrice)
: scaledLowerSqrtPrice;

const { amountIn, amountOut, fees, nextSqrtPrice } = computeSwapAmount({
const {
amountIn: netAmountIn,
amountOut,
fees,
nextSqrtPrice,
} = computeSwapAmount({
currSqrtPrice: startSqrtPrice,
targetSqrtPrice,
liquidity,
Expand All @@ -60,19 +65,24 @@ export const getSwapAmounts = ({
exactInput,
});

const grossAmountIn = new Decimal(amountIn).add(fees);
const amountIn =
nextSqrtPrice !== targetSqrtPrice &&
new Decimal(liquidity).gt(0) &&
exactInput
? amount
: new Decimal(netAmountIn).add(fees);

if (thresholdAmount) {
if (exactInput) {
if (amountOut < thresholdAmount)
throw new Error("Threshold amount not met");
} else {
if (grossAmountIn > thresholdAmount)
if (amountIn > thresholdAmount)
throw new Error("Threshold amount exceeded");
}
}

return { amountIn: grossAmountIn, amountOut, fees };
return { amountIn, amountOut, fees };
};

export const computeSwapAmount = ({
Expand Down
120 changes: 120 additions & 0 deletions models/src/replicating/tests/SwapLib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Decimal from "decimal.js";
import { getSwapAmounts } from "../libraries/SwapLib";

type SwapParams = {
isBuy: boolean;
exactInput: boolean;
amount: Decimal.Value;
swapFeeRate: Decimal.Value;
thresholdSqrtPrice: Decimal.Value | null;
thresholdAmount: Decimal.Value | null;
lowerSqrtPrice: Decimal.Value;
upperSqrtPrice: Decimal.Value;
liquidity: Decimal.Value;
baseDecimals: number;
quoteDecimals: number;
};
const testGetSwapAmounts = () => {
const cases: SwapParams[] = [
{
isBuy: true,
exactInput: true,
amount: 1,
swapFeeRate: 0,
thresholdSqrtPrice: null,
thresholdAmount: null,
lowerSqrtPrice: 0.8 ** 0.5,
upperSqrtPrice: 1,
liquidity: 10000,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
exactInput: true,
amount: 1,
swapFeeRate: 0.005,
thresholdSqrtPrice: null,
thresholdAmount: null,
lowerSqrtPrice: 1,
upperSqrtPrice: 1.2 ** 0.5,
liquidity: 0,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: false,
exactInput: true,
amount: 10,
swapFeeRate: 0.005,
thresholdSqrtPrice: 0.95 ** 0.5,
thresholdAmount: null,
lowerSqrtPrice: 0.8 ** 0.5,
upperSqrtPrice: 1,
liquidity: 200,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
exactInput: true,
amount: 10,
swapFeeRate: 0.005,
thresholdSqrtPrice: 1.05 ** 0.5,
thresholdAmount: null,
lowerSqrtPrice: 1,
upperSqrtPrice: 1.2 ** 0.5,
liquidity: 200,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
exactInput: true,
amount: 9.26891,
swapFeeRate: 0.005,
thresholdSqrtPrice: null,
thresholdAmount: null,
lowerSqrtPrice: 0.375 ** 0.5,
upperSqrtPrice: 0.4 ** 0.5,
liquidity: 1,
baseDecimals: 18,
quoteDecimals: 6,
},
];

for (let i = 0; i < cases.length; i++) {
const params = cases[i];
const { amountIn, amountOut, fees } = getSwapAmounts({
isBuy: params.isBuy,
exactInput: params.exactInput,
amount: params.amount,
swapFeeRate: params.swapFeeRate,
thresholdSqrtPrice: params.thresholdSqrtPrice,
thresholdAmount: params.thresholdAmount,
lowerSqrtPrice: params.lowerSqrtPrice,
upperSqrtPrice: params.upperSqrtPrice,
liquidity: params.liquidity,
baseDecimals: params.baseDecimals,
quoteDecimals: params.quoteDecimals,
});
console.log(`Case ${i + 1}`);
const inDecimals = params.isBuy
? params.quoteDecimals
: params.baseDecimals;
const outDecimals = params.isBuy
? params.baseDecimals
: params.quoteDecimals;
console.log({
amountIn: new Decimal(amountIn)
.mul(new Decimal(10).pow(inDecimals))
.toFixed(0),
amountOut: new Decimal(amountOut)
.mul(new Decimal(10).pow(outDecimals))
.toFixed(0),
fees: new Decimal(fees).mul(new Decimal(10).pow(inDecimals)).toFixed(0),
});
}
};

testGetSwapAmounts();
18 changes: 14 additions & 4 deletions models/src/reversion/libraries/SwapLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export const getSwapAmounts = ({
? Decimal.max(thresholdSqrtPrice, scaledLowerSqrtPrice)
: scaledLowerSqrtPrice;

const { amountIn, amountOut, fees, nextSqrtPrice } = computeSwapAmount({
const {
amountIn: netAmountIn,
amountOut,
fees,
nextSqrtPrice,
} = computeSwapAmount({
currSqrtPrice: startSqrtPrice,
targetSqrtPrice,
liquidity,
Expand All @@ -60,19 +65,24 @@ export const getSwapAmounts = ({
exactInput,
});

const grossAmountIn = new Decimal(amountIn).add(fees);
const amountIn =
nextSqrtPrice !== targetSqrtPrice &&
new Decimal(liquidity).gt(0) &&
exactInput
? amount
: new Decimal(netAmountIn).add(fees);

if (thresholdAmount) {
if (exactInput) {
if (amountOut < thresholdAmount)
throw new Error("Threshold amount not met");
} else {
if (grossAmountIn > thresholdAmount)
if (amountIn > thresholdAmount)
throw new Error("Threshold amount exceeded");
}
}

return { amountIn: grossAmountIn, amountOut, fees };
return { amountIn, amountOut, fees };
};

export const computeSwapAmount = ({
Expand Down
68 changes: 49 additions & 19 deletions models/src/reversion/tests/SwapLib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type SwapParams = {
lowerSqrtPrice: Decimal.Value;
upperSqrtPrice: Decimal.Value;
liquidity: Decimal.Value;
baseDecimals: number;
quoteDecimals: number;
};
const testGetSwapAmounts = () => {
const cases: SwapParams[] = [
Expand All @@ -24,6 +26,8 @@ const testGetSwapAmounts = () => {
lowerSqrtPrice: 0.8 ** 0.5,
upperSqrtPrice: 1,
liquidity: 10000,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
Expand All @@ -35,6 +39,8 @@ const testGetSwapAmounts = () => {
lowerSqrtPrice: 1,
upperSqrtPrice: 1.2 ** 0.5,
liquidity: 0,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: false,
Expand All @@ -46,6 +52,8 @@ const testGetSwapAmounts = () => {
lowerSqrtPrice: 0.8 ** 0.5,
upperSqrtPrice: 1,
liquidity: 200,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
Expand All @@ -57,32 +65,54 @@ const testGetSwapAmounts = () => {
lowerSqrtPrice: 1,
upperSqrtPrice: 1.2 ** 0.5,
liquidity: 200,
baseDecimals: 18,
quoteDecimals: 18,
},
{
isBuy: true,
exactInput: true,
amount: 9.26891,
swapFeeRate: 0.005,
thresholdSqrtPrice: null,
thresholdAmount: null,
lowerSqrtPrice: 0.375 ** 0.5,
upperSqrtPrice: 0.4 ** 0.5,
liquidity: 1,
baseDecimals: 18,
quoteDecimals: 6,
},
];

const baseDecimals = 18;
const quoteDecimals = 18;

for (let i = 0; i < cases.length; i++) {
const params = cases[i];
const { amountIn, amountOut, fees } = getSwapAmounts(
params.isBuy,
params.exactInput,
params.amount,
params.swapFeeRate,
params.thresholdSqrtPrice,
params.thresholdAmount,
params.lowerSqrtPrice,
params.upperSqrtPrice,
params.liquidity,
baseDecimals,
quoteDecimals
);
const { amountIn, amountOut, fees } = getSwapAmounts({
isBuy: params.isBuy,
exactInput: params.exactInput,
amount: params.amount,
swapFeeRate: params.swapFeeRate,
thresholdSqrtPrice: params.thresholdSqrtPrice,
thresholdAmount: params.thresholdAmount,
lowerSqrtPrice: params.lowerSqrtPrice,
upperSqrtPrice: params.upperSqrtPrice,
liquidity: params.liquidity,
baseDecimals: params.baseDecimals,
quoteDecimals: params.quoteDecimals,
});
console.log(`Case ${i + 1}`);
const inDecimals = params.isBuy
? params.quoteDecimals
: params.baseDecimals;
const outDecimals = params.isBuy
? params.baseDecimals
: params.quoteDecimals;
console.log({
amountIn: new Decimal(amountIn).mul(1e18).toFixed(0),
amountOut: new Decimal(amountOut).mul(1e18).toFixed(0),
fees: new Decimal(fees).mul(1e18).toFixed(0),
amountIn: new Decimal(amountIn)
.mul(new Decimal(10).pow(inDecimals))
.toFixed(0),
amountOut: new Decimal(amountOut)
.mul(new Decimal(10).pow(outDecimals))
.toFixed(0),
fees: new Decimal(fees).mul(new Decimal(10).pow(inDecimals)).toFixed(0),
});
}
};
Expand Down
16 changes: 13 additions & 3 deletions packages/replicating/src/libraries/swap_lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub fn get_swap_amounts(
};

// Compute swap amounts.
let (amount_in, amount_out, fees, _) = compute_swap_amounts(
let (net_amount_in, amount_out, fees, next_sqrt_price) = compute_swap_amounts(
start_sqrt_price,
target_sqrt_price,
position.liquidity,
Expand All @@ -58,7 +58,17 @@ pub fn get_swap_amounts(
swap_params.exact_input,
);

(amount_in + fees, amount_out, fees)
// If liquidity is sufficient to fill entire swap amount, we want to make sure the
// requested amount is fully consumed for exact input case.
let amount_in = if next_sqrt_price != target_sqrt_price
&& position.liquidity != 0
&& swap_params.exact_input {
swap_params.amount
} else {
net_amount_in + fees
};

(amount_in, amount_out, fees)
}

// Compute amounts swapped and new price after swapping between two prices.
Expand All @@ -72,7 +82,7 @@ pub fn get_swap_amounts(
// * `exact_input` - whether swap amount is exact input or output
//
// # Returns
// * `amount_in` - amount of tokens swapped in
// * `net_amount_in` - net amount of tokens swapped in
// * `amount_out` - amount of tokens swapped out
// * `fee_amount` - amount of fees
// * `next_sqrt_price` - next sqrt price
Expand Down
18 changes: 18 additions & 0 deletions packages/replicating/src/tests/libraries/test_swap_lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,24 @@ fn test_get_swap_amounts_ask_threshold_sqrt_price() {
);
}

#[test]
fn test_swap_amount_always_consumes_full_input_if_sufficient_liquidity() {
let swap_params = SwapParams {
is_buy: true,
amount: 9268910,
exact_input: true,
threshold_sqrt_price: Option::None(()),
threshold_amount: Option::None(()),
deadline: Option::None(()),
};
let position = PositionInfo {
lower_sqrt_price: to_e18(6100), upper_sqrt_price: to_e18(6500), liquidity: to_e18_u128(1),
};
let swap_fee_rate = 50;
let (amount_in, _, _) = get_swap_amounts(swap_params, swap_fee_rate, position);
assert(amount_in == swap_params.amount, 'Swap amount');
}

////////////////////////////////
// TESTS - compute_swap_amounts
////////////////////////////////
Expand Down
Loading

0 comments on commit 3606796

Please sign in to comment.