Skip to content

Commit

Permalink
Merge pull request #54 from Parifi/add-fixes
Browse files Browse the repository at this point in the history
[PFT-941] Add fixes and more functionality to the sdk
  • Loading branch information
sudeepb02 authored May 23, 2024
2 parents c4632d8 + d80a03c commit c9e13e9
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 24 deletions.
5 changes: 4 additions & 1 deletion src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ export const PYTH_USDC_USD_PRICE_ID_BETA = '0x41f3625971ca2ed2263e78573fe5ce23e1
export const PYTH_USDC_USD_PRICE_ID_STABLE = '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a';

// Constants for Pimlico relayer
export const FACTORY_ADDRESS_SIMPLE_ACCOUNT = '0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985'
export const FACTORY_ADDRESS_SIMPLE_ACCOUNT = '0x91E60e0613810449d098b0b5Ec8b51A0FE8c8985'

// Temporary constants
export const SUBGRAPH_HELPER_ADDRESS = "0xf012d32505df6853187170F00C7b789A8ecC41c2"
4 changes: 4 additions & 0 deletions src/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const getDiff = (a: Decimal, b: Decimal): Decimal => {
return a.gt(b) ? a.minus(b) : b.minus(a);
};

export const addDelay = async (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

export const getUniqueValuesFromArray = (originalArray: string[]): string[] => {
const uniqueArray: string[] = [];
const seenValues = new Set<string>();
Expand Down
1 change: 1 addition & 0 deletions src/common/subgraphMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ export const mapVaultPositionToInterface = (response: any): VaultPosition | unde
avgMintPriceDec: response.avgMintPriceDec,
realizedPNL: response.realizedPNL,
realizedPNLInUsd: response.realizedPNLInUsd,
unrealizedPNL: response.unrealizedPNL,
timestamp: response.timestamp,
cooldownInitiatedTimestamp: response.cooldownInitiatedTimestamp,
cooldownEnd: response.cooldownEnd,
Expand Down
10 changes: 10 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
canBeSettled,
canBeSettledPriceId,
checkIfOrderCanBeSettledId,
getLiquidationPrice,
getNetProfitOrLossInCollateral,
getOrderManagerInstance,
getProfitOrLossInUsd,
Expand Down Expand Up @@ -233,6 +234,15 @@ export class Core {
);
};

getLiquidationPrice = (
position: Position,
market: Market,
normalizedMarketPrice: Decimal,
normalizedCollateralPrice: Decimal,
): Decimal => {
return getLiquidationPrice(position, market, normalizedMarketPrice, normalizedCollateralPrice);
};

////////////////////////////////////////////////////////////////
////////////////////// PARIFI UTILS //////////////////////
////////////////////////////////////////////////////////////////
Expand Down
56 changes: 54 additions & 2 deletions src/core/order-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import {
PRECISION_MULTIPLIER,
} from '../../common/constants';
import { getAccruedBorrowFeesInMarket, getMarketUtilization } from '../data-fabric';
import { convertMarketAmountToCollateral } from '../price-feed';
import { convertCollateralAmountToUsd, convertMarketAmountToCollateral, convertMarketAmountToUsd } from '../price-feed';
import { Chain } from '@parifi/references';
import { contracts as parifiContracts } from '@parifi/references';
import { Contract, ethers } from 'ethers';
import { AxiosInstance } from 'axios';
import { getOrderById, getPythPriceIdsForOrderIds, getPythPriceIdsForPositionIds } from '../../subgraph';
import {
getOrderById,
getPythPriceIdsForOrderIds,
getPythPriceIdsForPositionIds,
getTotalUnrealizedPnlInUsd,
} from '../../subgraph';
import { getLatestPricesFromPyth, getVaaPriceUpdateData, normalizePythPriceForParifi } from '../../pyth/pyth';
import { getPriceIdsForCollaterals } from '../../common';
import { executeTxUsingGelato } from '../../relayers/gelato/gelato-function';
Expand Down Expand Up @@ -451,3 +456,50 @@ export const settleOrderUsingGelato = async (
console.log('Task ID:', taskId);
return { gelatoTaskId: taskId };
};

// Returns the liquidation price of a Position
// A position is liquidated when the loss of a position in USD goes above the USD value
// of liquidation threshold times the deposited collateral value
export const getLiquidationPrice = (
position: Position,
market: Market,
normalizedMarketPrice: Decimal,
normalizedCollateralPrice: Decimal,
): Decimal => {
const collateral = new Decimal(position.positionCollateral ?? '0');

// Decimal digits for market and collateral token
const collateralDecimals = new Decimal(market.depositToken?.decimals ?? '18');
const marketDecimals = new Decimal(market.marketDecimals ?? '18');

// Total fees for the position taking into account closing fee and liquidation fee
const accruedBorrowFeesInMarket = getAccruedBorrowFeesInMarket(position, market);
const fixedFeesInMarket = new Decimal(position.positionSize ?? '0')
.times(new Decimal(market.openingFee ?? '0').add(market.liquidationFee ?? '0'))
.div(MAX_FEE);

const totalFeesInUsd = convertMarketAmountToUsd(
accruedBorrowFeesInMarket.add(fixedFeesInMarket),
marketDecimals,
normalizedMarketPrice,
);

const collateralInUsd = convertCollateralAmountToUsd(collateral, collateralDecimals, normalizedCollateralPrice);
const maxLossLimitInUsd = collateralInUsd.times(market.liquidationThreshold ?? '0').div(PRECISION_MULTIPLIER);

const lossLimitAfterFees = maxLossLimitInUsd.sub(totalFeesInUsd);

// @todo Revisit this
// If loss is already more than the max loss, the position can be liquidated at the current price
if (lossLimitAfterFees.lessThan(DECIMAL_ZERO)) return normalizedMarketPrice;

const lossPerToken = lossLimitAfterFees
.times(new Decimal('10').pow(marketDecimals))
.div(position.positionSize ?? '1');

if (position.isLong) {
return new Decimal(position.avgPrice ?? 0).sub(lossPerToken);
} else {
return new Decimal(position.avgPrice ?? 0).add(lossPerToken);
}
};
16 changes: 12 additions & 4 deletions src/core/pages/poolPage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Decimal from 'decimal.js';
import { getAllVaults, getUserVaultData } from '../../subgraph/vaults';
import { MAX_FEE } from '../../common';
import { DECIMAL_10, MAX_FEE } from '../../common';
import { UserVaultData } from '../../interfaces/sdkTypes';

// Add Vault and Position data for a user address. If the user has no vault deposits/withdrawals, it will
// Add Vault and Position data for a user address. If the user has no vault deposits/withdrawals, it will
// return the user specific fields with 0 values. Other global values for vaults will be populated
export const getPoolPageData = async (subgraphEndpoint: string, userAddress: string): Promise<UserVaultData[]> => {
const userVaultData: UserVaultData[] = [];
Expand All @@ -17,6 +17,14 @@ export const getPoolPageData = async (subgraphEndpoint: string, userAddress: str
(BigInt(vaultPosition?.sharesBalance ?? 0) * BigInt(vault.assetsPerShare ?? 0)) /
BigInt(10 ** (vault.vaultDecimals ?? 1));

// Calculate the PNL for each token based on the average mint price and the current price
// of assets per share
const pnlPerToken = new Decimal(vault.assetsPerShare ?? 0).minus(vaultPosition?.avgMintPrice ?? 0);

const currentUnrealizedPNL = new Decimal(vaultPosition?.sharesBalance ?? 0)
.times(pnlPerToken)
.div(DECIMAL_10.pow(vault.vaultDecimals ?? 0));

const userData: UserVaultData = {
vaultId: vault.id ?? '0x',
vaultSymbol: vault.vaultSymbol ?? 'pfERC20',
Expand All @@ -32,9 +40,9 @@ export const getPoolPageData = async (subgraphEndpoint: string, userAddress: str
cooldownStarted: BigInt(vaultPosition?.cooldownInitiatedTimestamp ?? 0),
cooldownWindowInSeconds: BigInt(vault.cooldownPeriod ?? '0'),
withdrawalWindowInSeconds: BigInt(vault.withdrawalWindow ?? '0'),
totalAssetsGain: new Decimal(vaultPosition?.realizedPNL ?? 0).add(vaultPosition?.unrealizedPNL ?? 0),
totalAssetsGain: currentUnrealizedPNL.add(vaultPosition?.realizedPNL ?? 0),
realizedPNL: new Decimal(vaultPosition?.realizedPNL ?? 0),
unrealizedPNL: new Decimal(vaultPosition?.unrealizedPNL ?? 0),
unrealizedPNL: currentUnrealizedPNL,
realizedPNLInUsd: new Decimal(vaultPosition?.realizedPNLInUsd ?? 0),
};

Expand Down
43 changes: 43 additions & 0 deletions src/core/subgraph-helper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Contract, ethers } from 'ethers';
import { Chain } from '@parifi/references';
import { contracts as parifiContracts } from '@parifi/references';

import { SUBGRAPH_HELPER_ADDRESS } from '../../common';

const subgraphHelperAbi = [
{
anonymous: false,
inputs: [{ indexed: false, internalType: 'bytes32[]', name: 'orderIds', type: 'bytes32[]' }],
name: 'OrderUpdateRequest',
type: 'event',
},
{
anonymous: false,
inputs: [{ indexed: false, internalType: 'bytes32[]', name: 'positionIds', type: 'bytes32[]' }],
name: 'PositionUpdateRequest',
type: 'event',
},
{
inputs: [{ internalType: 'bytes32[]', name: 'orderIds', type: 'bytes32[]' }],
name: 'triggerOrderUpdate',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'bytes32[]', name: 'positionIds', type: 'bytes32[]' }],
name: 'triggerPositionUpdate',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];

// Returns Subgraph Helper contract instance without signer
export const getSubgraphHelperInstance = (chain: Chain): Contract => {
try {
return new ethers.Contract(SUBGRAPH_HELPER_ADDRESS, subgraphHelperAbi);
} catch (error) {
throw error;
}
};
8 changes: 4 additions & 4 deletions src/subgraph/accounts/subgraphQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { gql } from 'graphql-request';
// for vaults and positions
export const fetchRealizedPnlData = (userAddress: string) => gql`
{
account(id: "${userAddress}") {
account(id: "${userAddress.toLowerCase()}") {
id
totalRealizedPnlPositions
totalRealizedPnlVaults
Expand All @@ -20,7 +20,7 @@ export const fetchRealizedPnlData = (userAddress: string) => gql`
/// 4. Deposited collateral of all open positions - positions.positionCollateral
export const fetchPortfolioData = (userAddresses: string[]) => gql`
{
accounts(where: {id_in: [${userAddresses.map((id) => `"${id}"`).join(', ')}]}) {
accounts(where: {id_in: [${userAddresses.map((id) => `"${id.toLowerCase()}"`).join(', ')}]}) {
id
totalRealizedPnlPositions
totalRealizedPnlVaults
Expand All @@ -30,7 +30,7 @@ export const fetchPortfolioData = (userAddresses: string[]) => gql`
orderBy: positionCollateral
orderDirection: desc
where: {
user_in: [${userAddresses.map((id) => `"${id}"`).join(', ')}],
user_in: [${userAddresses.map((id) => `"${id.toLowerCase()}"`).join(', ')}],
status: OPEN
}
) {
Expand All @@ -54,7 +54,7 @@ export const fetchPortfolioData = (userAddresses: string[]) => gql`
first: 1000
orderBy: sharesBalance
orderDirection: desc
where: {user_in: [${userAddresses.map((id) => `"${id}"`).join(', ')}]}
where: {user_in: [${userAddresses.map((id) => `"${id.toLowerCase()}"`).join(', ')}]}
) {
user {
id
Expand Down
2 changes: 1 addition & 1 deletion src/subgraph/markets/subgraphQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const fetchAllMarketsDataQuery = gql`
// Fetch all details of a market by ID
export const fetchMarketByIdQuery = (marketId: string) => gql`
{
market(id: "${marketId}") {
market(id: "${marketId.toLowerCase()}") {
id
vaultAddress
depositToken {
Expand Down
4 changes: 2 additions & 2 deletions src/subgraph/orders/subgraphQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const fetchOrdersByUserQuery = (userAddress: string, count: number = 10,
orders(
first: ${count}
skip: ${skip}
where: {user: "${userAddress}"}
where: {user: "${userAddress.toLowerCase()}"}
orderBy: createdTimestamp
orderDirection: desc
) {
Expand Down Expand Up @@ -156,7 +156,7 @@ export const fetchPartnerRewards = (partnerAddress: string, count: number = 20,
skip: ${skip}
orderBy: timestamp
orderDirection: desc
where: { partner: "${partnerAddress}" }
where: { partner: "${partnerAddress.toLowerCase()}" }
) {
id
partner { id }
Expand Down
6 changes: 3 additions & 3 deletions src/subgraph/positions/subgraphQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const fetchPositionsByUserQuery = (userAddress: string, count: number = 1
skip: ${skip}
orderBy: createdTimestamp
orderDirection: desc
where: {user: "${userAddress}"}
where: {user: "${userAddress.toLowerCase()}"}
) {
id
market {
Expand Down Expand Up @@ -61,7 +61,7 @@ export const fetchPositionsByUserQueryAndStatus = (
orderBy: createdTimestamp
orderDirection: desc
where: {
user: "${userAddress}"
user: "${userAddress.toLowerCase()}"
status: "${status}"
}
) {
Expand Down Expand Up @@ -204,7 +204,7 @@ export const fetchAllPositionsUnrealizedPnl = (userAddress: string) => gql`
first: 1000
orderBy: positionCollateral
orderDirection: desc
where: { user: "${userAddress}", status: OPEN }
where: { user: "${userAddress.toLowerCase()}", status: OPEN }
) {
id
netUnrealizedPnlInUsd
Expand Down
6 changes: 3 additions & 3 deletions src/subgraph/vaults/subgraphQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const fetchUserVaultPositionsQuery = (user: string) => gql`
first: 1000
orderBy: sharesBalance
orderDirection: desc
where: {user: "${user}"}
where: {user: "${user.toLowerCase()}"}
) {
id
user {
Expand Down Expand Up @@ -85,7 +85,7 @@ export const fetchVaultAprDetails = (vaultId: string) => gql`
first: 30
orderBy: startTimestamp
orderDirection: desc
where: { vault: "${vaultId}" }
where: { vault: "${vaultId.toLowerCase()}" }
) {
vault { allTimeApr }
apr
Expand All @@ -99,7 +99,7 @@ export const fetchCooldownDetails = (user: string) => gql`
orderBy: timestamp
orderDirection: desc
first: 10
where: {user: "${user}"}
where: {user: "${user.toLowerCase()}"}
) {
id
user {
Expand Down
40 changes: 39 additions & 1 deletion test/core/orderManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ethers } from 'ethers';
import { getParifiSdkInstanceForTesting } from '..';
import { TEST_SETTLE_ORDER_ID } from '../common/constants';
import { TEST_MARKET_ID1, TEST_POSITION_ID1, TEST_SETTLE_ORDER_ID } from '../common/constants';
import { DECIMAL_ZERO, getNormalizedPriceByIdFromPriceIdArray } from '../../src';

describe('Order Manager tests', () => {
it('should liquidate a single position', async () => {
Expand Down Expand Up @@ -38,4 +39,41 @@ describe('Order Manager tests', () => {
const tx = await parifiSdk.core.batchSettleOrdersUsingWallet(orderIds, priceUpdateData, wallet);
console.log(tx);
});

it('should return valid liquidation price', async () => {
const parifiSdk = await getParifiSdkInstanceForTesting();
const position = await parifiSdk.subgraph.getPositionById(
'0x9a1b314246e76d5f912020961f95d44077df4be8450d25e2c7dddd98582b5b66',
);
const market = await parifiSdk.subgraph.getMarketById(position.market?.id ?? TEST_MARKET_ID1);

const normalizedPrice = await parifiSdk.pyth.getLatestPricesNormalized([
market.depositToken?.pyth?.id ?? '0x',
market.pyth?.id ?? '0x',
]);

const normalizedCollateralPrice = normalizedPrice.find(
(p) => p.priceId === market.depositToken?.pyth?.id,
)?.normalizedPrice;

const normalizedMarketPrice =
normalizedPrice.find((p) => p.priceId === market.pyth?.id)?.normalizedPrice ?? DECIMAL_ZERO;

console.log('normalizedCollateralPrice', normalizedCollateralPrice);
console.log('normalizedMarketPrice', normalizedMarketPrice);

const liquidationPrice = await parifiSdk.core.getLiquidationPrice(
position,
market,
normalizedMarketPrice ?? DECIMAL_ZERO,
normalizedCollateralPrice ?? DECIMAL_ZERO,
);

console.log('liquidationPrice', liquidationPrice);
if (position.isLong) {
expect(liquidationPrice.toNumber()).toBeLessThan(Number(position.avgPrice));
} else {
expect(liquidationPrice.toNumber()).toBeGreaterThan(Number(position.avgPrice));
}
});
});
1 change: 0 additions & 1 deletion test/core/poolPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('Stats tests', () => {
const parifiSdk = await getParifiSdkInstanceForTesting();

const userPoolData = await parifiSdk.core.getPoolPageData(ethers.ZeroAddress);
console.log(userPoolData);
expect(userPoolData.length).not.toBe(0);
userPoolData.forEach((data) => {
expect(data.assetBalance).toBe(BIGINT_ZERO);
Expand Down
Loading

0 comments on commit c9e13e9

Please sign in to comment.