Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/bypass liquidity check #30

Merged
merged 2 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/core/src/prices/__tests__/priceVaultBuy.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { formatEther, parseEther } from 'viem';
import { makePriceVaultBuy } from '../priceVaultBuy';
import { WeiPerEther, Zero } from '@nftx/constants';
import { InsufficientLiquidityError } from '@nftx/errors';

type PriceVaultBuy = ReturnType<typeof makePriceVaultBuy>;
let priceVaultBuy: PriceVaultBuy;
Expand All @@ -11,6 +12,7 @@ let quoteVaultBuy: jest.Mock;
let tokenIds: `${number}`[];
let vault: Parameters<PriceVaultBuy>[0]['vault'];
let holdings: Parameters<PriceVaultBuy>[0]['holdings'];
let bypassLiquidityCheck: boolean;

beforeEach(() => {
fetchAmmQuote = jest.fn(({ buyAmount }: { buyAmount: bigint }) => ({
Expand Down Expand Up @@ -65,6 +67,7 @@ beforeEach(() => {
quantity: 1n,
},
];
bypassLiquidityCheck = false;

priceVaultBuy = makePriceVaultBuy({ fetchAmmQuote, quoteVaultBuy });
run = () =>
Expand All @@ -74,6 +77,7 @@ beforeEach(() => {
tokenIds,
vault,
provider: null as any,
bypassLiquidityCheck,
});
});

Expand Down Expand Up @@ -129,6 +133,35 @@ describe('when there are more than 5 token ids', () => {
expect(Number(formatEther(result.price))).toBeLessThan(20);
});
});

describe('when there is no price', () => {
beforeEach(() => {
fetchAmmQuote.mockResolvedValue({ price: 0n });
});

it('throw an insufficient liquidity error', async () => {
const promise = run();

await expect(promise).rejects.toThrow(InsufficientLiquidityError);
});

describe('when bypassLiquidityCheck is true', () => {
beforeEach(() => {
bypassLiquidityCheck = true;
});

it('returns a zero value price', async () => {
const promise = run();

await expect(promise).resolves.toBeTruthy();

const result = await promise;

expect(`${result.price}`).toBe('150000000000000000');
expect(`${result.vTokenPrice}`).toBe('0');
});
});
});
});

describe('when there are 5 or less token ids', () => {
Expand Down Expand Up @@ -168,6 +201,51 @@ describe('when there are 5 or less token ids', () => {
expect(Number(formatEther(result.price))).toBeLessThan(7.1);
});
});

describe('when there is no price stored on the vault', () => {
beforeEach(() => {
vault.prices = undefined as any;
});

it('returns a rough price estimate', async () => {
const result = await run();

expect(`${result.price}`).toBe('2050000000000000000');
expect(fetchAmmQuote).toBeCalled();
});
});

describe('when there is no price', () => {
beforeEach(() => {
vault.prices[1].redeem.price = Zero;
vault.prices[1].redeem.vTokenPrice = Zero;
vault.prices[1].redeem.feePrice = Zero;
vault.prices[1].redeem.premiumPrice = Zero;
});

it('throws an insufficient liquidity error', async () => {
const promise = run();

await expect(promise).rejects.toThrow(InsufficientLiquidityError);
});

describe('when bypassLiquidityCheck is true', () => {
beforeEach(() => {
bypassLiquidityCheck = true;
});

it('returns a zero value price', async () => {
const promise = run();

await expect(promise).resolves.toBeTruthy();

const result = await promise;

expect(`${result.price}`).toBe('0');
expect(`${result.vTokenPrice}`).toBe('0');
});
});
});
});

describe('when vault is an 1155', () => {
Expand Down
31 changes: 25 additions & 6 deletions packages/core/src/prices/priceVaultBuy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const checkLiquidity = <P extends { vTokenPrice: bigint }>(price: P) => {
if (!price.vTokenPrice) {
throw new InsufficientLiquidityError();
}
return price;
};

const getIndexedPrice = ({
Expand All @@ -32,19 +31,21 @@ const getIndexedPrice = ({
vault,
now,
network,
bypassLiquidityCheck,
}: {
vault: Pick<Vault, 'vTokenToEth' | 'prices'>;
tokenIds: TokenIds;
holdings: Pick<VaultHolding, 'dateAdded' | 'tokenId'>[];
now: number;
network: number;
bypassLiquidityCheck: boolean | undefined;
}) => {
// We store the prices for buying up to 5 NFTs
// so we can save ourselves from having to make additional calls/calculations
// We just pull out the stored price, and add a premium if necessary
const totalTokenIds = getTotalTokenIds(tokenIds);
const { vTokenToEth } = vault;
const price = vault.prices?.[totalTokenIds - 1]?.redeem;
let price = vault.prices?.[totalTokenIds - 1]?.redeem;
if (!price) {
return;
}
Expand All @@ -56,25 +57,33 @@ const getIndexedPrice = ({
network,
});

return {
price = {
...price,
premiumPrice,
price: price.price + premiumPrice,
};

if (!bypassLiquidityCheck) {
checkLiquidity(price);
}

return price;
};

const getRoughPrice = async ({
holdings,
network,
tokenIds,
vault,
bypassLiquidityCheck,
fetchAmmQuote,
now,
}: {
tokenIds: TokenIds;
holdings: Pick<VaultHolding, 'tokenId' | 'dateAdded'>[];
vault: Pick<Vault, 'vTokenToEth' | 'fees' | 'id'>;
network: number;
bypassLiquidityCheck: boolean | undefined;
fetchAmmQuote: FetchAmmQuote;
now: number;
}) => {
Expand All @@ -94,6 +103,7 @@ const getRoughPrice = async ({
buyToken: vault.id,
buyAmount,
sellToken: 'ETH',
throwOnError: !bypassLiquidityCheck,
});
const feePrice = calculateTotalFeePrice(
vault.fees.redeemFee,
Expand Down Expand Up @@ -124,6 +134,10 @@ const getRoughPrice = async ({
routeString,
};

if (!bypassLiquidityCheck) {
checkLiquidity(result);
}

return result;
};

Expand All @@ -142,6 +156,7 @@ export const makePriceVaultBuy =
bypassIndexedPrice,
holdings: allHoldings,
provider,
bypassLiquidityCheck,
}: {
network: number;
tokenIds: TokenIds;
Expand All @@ -152,6 +167,7 @@ export const makePriceVaultBuy =
bypassIndexedPrice?: boolean;
holdings: Pick<VaultHolding, 'tokenId' | 'dateAdded' | 'quantity'>[];
provider: Provider;
bypassLiquidityCheck?: boolean;
}): Promise<MarketplacePrice> => {
const now = Math.floor(Date.now() / 1000);
const totalTokenIds = getTotalTokenIds(tokenIds);
Expand Down Expand Up @@ -180,7 +196,8 @@ export const makePriceVaultBuy =
userAddress: '0x',
vault,
network,
}).then(checkLiquidity);
bypassLiquidityCheck,
});
}
}
}
Expand All @@ -192,9 +209,10 @@ export const makePriceVaultBuy =
vault,
now,
network,
bypassLiquidityCheck,
});
if (result) {
return checkLiquidity(result);
return result;
}
}

Expand All @@ -205,7 +223,8 @@ export const makePriceVaultBuy =
vault,
fetchAmmQuote: fetchAmmQuote,
now,
}).then(checkLiquidity);
bypassLiquidityCheck,
});
};

const priceVaultBuy = makePriceVaultBuy({
Expand Down
24 changes: 20 additions & 4 deletions packages/core/src/prices/priceVaultSell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,38 @@ const checkLiquidity = (price: MarketplacePrice) => {
if (price.price < Zero) {
throw new MintFeeExceedsValueError();
}
return price;
};

const getIndexedPrice = ({
tokenIds,
vault,
bypassLiquidityCheck,
}: {
tokenIds: TokenIds;
vault: Pick<Vault, 'prices'>;
bypassLiquidityCheck: boolean | undefined;
}) => {
const totalTokenIds = getTotalTokenIds(tokenIds);
const price = vault.prices?.[totalTokenIds - 1]?.mint;
// We don't need to worry about premium pricing on sells
// Check the price has enough liquidity
if (price && !bypassLiquidityCheck) {
checkLiquidity(price);
}
return price;
};

const getRoughPrice = async ({
network,
tokenIds,
vault,
bypassLiquidityCheck,
fetchAmmQuote,
}: {
network: number;
tokenIds: TokenIds;
vault: Pick<Vault, 'vTokenToEth' | 'id' | 'fees'>;
bypassLiquidityCheck: boolean | undefined;
fetchAmmQuote: FetchAmmQuote;
}) => {
const totalTokenIds = getTotalTokenIds(tokenIds);
Expand All @@ -59,6 +66,7 @@ const getRoughPrice = async ({
sellAmount,
buyToken: 'ETH',
network,
throwOnError: !bypassLiquidityCheck,
});
const feePrice = calculateTotalFeePrice(
vault.fees.mintFee,
Expand All @@ -81,6 +89,11 @@ const getRoughPrice = async ({
routeString,
};

// Check the price has enough liquidity
if (!bypassLiquidityCheck) {
checkLiquidity(result);
}

return result;
};

Expand All @@ -91,11 +104,13 @@ export const makePriceVaultSell =
network,
tokenIds,
vault,
bypassLiquidityCheck,
}: {
network: number;
tokenIds: TokenIds;
vault: Pick<Vault, 'id' | 'prices' | 'vTokenToEth' | 'fees'>;
bypassIndexedPrice?: boolean;
bypassLiquidityCheck?: boolean;
}) => {
const totalTokenIds = getTotalTokenIds(tokenIds);

Expand All @@ -104,9 +119,9 @@ export const makePriceVaultSell =
});

if (bypassIndexedPrice !== true && totalTokenIds <= 5) {
const result = getIndexedPrice({ tokenIds, vault });
const result = getIndexedPrice({ tokenIds, vault, bypassLiquidityCheck });
if (result) {
return checkLiquidity(result);
return result;
}
}

Expand All @@ -115,7 +130,8 @@ export const makePriceVaultSell =
tokenIds,
vault,
fetchAmmQuote,
}).then(checkLiquidity);
bypassLiquidityCheck,
});
};

export default makePriceVaultSell({ fetchAmmQuote });
3 changes: 3 additions & 0 deletions packages/core/src/prices/priceVaultSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const makePriceVaultSwap =
holdings: allHoldings,
vault,
bypassIndexedPrice,
bypassLiquidityCheck,
}: {
network: number;
provider: Provider;
Expand All @@ -114,6 +115,7 @@ export const makePriceVaultSwap =
buyTokenIds: TokenIds;
holdings: Pick<VaultHolding, 'dateAdded' | 'tokenId' | 'quantity'>[];
bypassIndexedPrice?: boolean;
bypassLiquidityCheck?: boolean;
}) => {
const now = Math.floor(Date.now() / 1000);
const totalIn = getTotalTokenIds(sellTokenIds);
Expand Down Expand Up @@ -153,6 +155,7 @@ export const makePriceVaultSwap =
sellTokenIds,
userAddress: '0x',
vault,
bypassLiquidityCheck,
});
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/prices/quoteVaultBuy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const makeQuoteVaultBuy =
vault,
holdings: allHoldings,
slippagePercentage,
bypassLiquidityCheck,
}: {
vault: Pick<Vault, 'fees' | 'id' | 'vaultId' | 'is1155'>;
tokenIds: TokenIds;
Expand All @@ -113,6 +114,7 @@ export const makeQuoteVaultBuy =
provider: Provider;
holdings: Pick<VaultHolding, 'dateAdded' | 'tokenId'>[];
slippagePercentage?: number;
bypassLiquidityCheck?: boolean;
}) => {
const totalTokenIds = getTotalTokenIds(tokenIds);
const buyAmount = parseEther(`${totalTokenIds}`);
Expand Down Expand Up @@ -143,6 +145,7 @@ export const makeQuoteVaultBuy =
sellToken: 'WETH',
userAddress: getChainConstant(MARKETPLACE_ZAP, network),
slippagePercentage,
throwOnError: !bypassLiquidityCheck,
});

const items = await Promise.all(
Expand Down
Loading
Loading