From e2c0feb8b04b1cf9a43a7098c29049f0542e56bb Mon Sep 17 00:00:00 2001 From: Alfetopito Date: Fri, 17 Jan 2025 17:26:03 +0000 Subject: [PATCH] feat: veeeery dirty way of reusing getEstimatedExecutionPrice --- .../src/legacy/state/orders/utils.ts | 127 ++++++++++-------- .../updaters/ExecutionPriceUpdater/index.tsx | 40 ++++-- 2 files changed, 103 insertions(+), 64 deletions(-) diff --git a/apps/cowswap-frontend/src/legacy/state/orders/utils.ts b/apps/cowswap-frontend/src/legacy/state/orders/utils.ts index b84691ec50..7f80e64776 100644 --- a/apps/cowswap-frontend/src/legacy/state/orders/utils.ts +++ b/apps/cowswap-frontend/src/legacy/state/orders/utils.ts @@ -1,6 +1,6 @@ import type { LatestAppDataDocVersion } from '@cowprotocol/app-data' import { ONE_HUNDRED_PERCENT, PENDING_ORDERS_BUFFER, ZERO_FRACTION } from '@cowprotocol/common-const' -import { bpsToPercent, buildPriceFromCurrencyAmounts, isSellOrder } from '@cowprotocol/common-utils' +import { bpsToPercent, buildPriceFromCurrencyAmounts, getWrappedToken, isSellOrder } from '@cowprotocol/common-utils' import { EnrichedOrder, OrderKind, OrderStatus } from '@cowprotocol/cow-sdk' import { UiOrderType } from '@cowprotocol/types' import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' @@ -12,7 +12,6 @@ import { decodeAppData } from 'modules/appData/utils/decodeAppData' import { getIsComposableCowParentOrder } from 'utils/orderUtils/getIsComposableCowParentOrder' import { getOrderSurplus } from 'utils/orderUtils/getOrderSurplus' import { getUiOrderType } from 'utils/orderUtils/getUiOrderType' -import type { ParsedOrder } from 'utils/orderUtils/parseOrder' import { Order, updateOrder, UpdateOrderParams as UpdateOrderParamsAction } from './actions' import { OUT_OF_MARKET_PRICE_DELTA_PERCENTAGE } from './consts' @@ -35,7 +34,10 @@ export type OrderTransitionStatus = * or all buyAmount has been bought, for buy orders */ export function isOrderFulfilled( - order: Pick + order: Pick< + EnrichedOrder, + 'buyAmount' | 'sellAmount' | 'executedBuyAmount' | 'executedSellAmountBeforeFees' | 'kind' + >, ): boolean { const { buyAmount, sellAmount, executedBuyAmount, executedSellAmountBeforeFees, kind } = order @@ -98,7 +100,7 @@ export function classifyOrder( | 'kind' | 'signingScheme' | 'status' - > | null + > | null, ): OrderTransitionStatus { if (!order) { console.debug(`[state::orders::classifyOrder] unknown order`) @@ -133,7 +135,7 @@ export function classifyOrder( export function isOrderUnfillable( order: Order, orderPrice: Price, - executionPrice: Price + executionPrice: Price, ): boolean { // Calculate the percentage of the current price in regards to the order price const percentageDifference = ONE_HUNDRED_PERCENT.subtract(executionPrice.divide(orderPrice)) @@ -143,7 +145,7 @@ export function isOrderUnfillable( orderPrice.toSignificant(10), executionPrice.toSignificant(10), `${percentageDifference.toFixed(4)}%`, - percentageDifference.greaterThan(OUT_OF_MARKET_PRICE_DELTA_PERCENTAGE) + percentageDifference.greaterThan(OUT_OF_MARKET_PRICE_DELTA_PERCENTAGE), ) // Example. Consider the pair X-Y: @@ -175,7 +177,7 @@ export function getOrderMarketPrice(order: Order, quotedAmount: string, feeAmoun order.outputToken, // For sell orders, the market price has the fee subtracted from the sell amount JSBI.subtract(JSBI.BigInt(remainingAmount), JSBI.BigInt(feeAmount)), - quotedAmount + quotedAmount, ) } @@ -221,32 +223,75 @@ const EXECUTION_PRICE_FEE_COEFFICIENT = new Percent(5, 100) * @param fillPrice AKA MarketPrice * @param fee Estimated fee in inputToken atoms, as string */ +export function getEstimatedExecutionPrice( + order: undefined, // there's no order when calling this way + fillPrice: Price, + fee: string, + inputAmount: CurrencyAmount, + outputAmount: CurrencyAmount, + kind: OrderKind, + fullAppData: EnrichedOrder['fullAppData'], +): Price | null export function getEstimatedExecutionPrice( order: Order, fillPrice: Price, - fee: string + fee: string, +): Price | null +export function getEstimatedExecutionPrice( + order: Order | undefined, + fillPrice: Price, + fee: string, + inputAmount?: CurrencyAmount, + outputAmount?: CurrencyAmount, + kind?: OrderKind, + fullAppData?: EnrichedOrder['fullAppData'], ): Price | null { - // Build CurrencyAmount and Price instances - const feeAmount = CurrencyAmount.fromRawAmount(order.inputToken, fee) - // Take partner fee into account when calculating the limit price - const limitPrice = getOrderLimitPriceWithPartnerFee(order) + let inputToken: Token + let outputToken: Token + let limitPrice: Price + let sellAmount: string + + if (order) { + inputToken = order.inputToken + outputToken = order.outputToken + limitPrice = getOrderLimitPriceWithPartnerFee(order) + + if (getUiOrderType(order) === UiOrderType.SWAP) { + return limitPrice + } - if (getUiOrderType(order) === UiOrderType.SWAP) { - return limitPrice - } + // Parent TWAP order, ignore + if (getIsComposableCowParentOrder(order)) { + return null + } - // Parent TWAP order, ignore - if (getIsComposableCowParentOrder(order)) { - return null + sellAmount = getRemainderAmountsWithoutSurplus(order).sellAmount + } else { + sellAmount = inputAmount!.quotient.toString() + inputToken = getWrappedToken(inputAmount!.currency) + outputToken = getWrappedToken(outputAmount!.currency) + limitPrice = getOrderLimitPriceWithPartnerFee({ + inputToken, + outputToken, + sellAmount, + buyAmount: outputAmount!.quotient.toString(), + kind: kind as OrderKind, + fullAppData, + }) } + const feeAmount = CurrencyAmount.fromRawAmount(inputToken, fee) + + // Build CurrencyAmount and Price instances + // const feeAmount = CurrencyAmount.fromRawAmount(order.inputToken, feeAmount) + // Take partner fee into account when calculating the limit price + // Check what's left to sell, discounting the surplus, if any - const { sellAmount } = getRemainderAmountsWithoutSurplus(order) - const remainingSellAmount = CurrencyAmount.fromRawAmount(order.inputToken, sellAmount) + const remainingSellAmount = CurrencyAmount.fromRawAmount(inputToken, sellAmount) // When fee > amount, return 0 price if (!remainingSellAmount.greaterThan(ZERO_FRACTION)) { - return new Price(order.inputToken, order.outputToken, '0', '0') + return new Price(inputToken, outputToken, '0', '0') } const feeWithMargin = feeAmount.add(feeAmount.multiply(EXECUTION_PRICE_FEE_COEFFICIENT)) @@ -255,7 +300,7 @@ export function getEstimatedExecutionPrice( // Just in case when the denominator is <= 0 after subtraction the fee if (!denominator.greaterThan(ZERO_FRACTION)) { - return new Price(order.inputToken, order.outputToken, '0', '0') + return new Price(inputToken, outputToken, '0', '0') } /** @@ -267,37 +312,11 @@ export function getEstimatedExecutionPrice( * Fee with margin: 0.002 + 5% = 0.0021 WETH * Executes at: 182000 / (100 - 0.0021) = 1820.038 USDC per 1 WETH */ - const feasibleExecutionPrice = new Price( - order.inputToken, - order.outputToken, - denominator.quotient, - numerator.quotient - ) + const feasibleExecutionPrice = new Price(inputToken, outputToken, denominator.quotient, numerator.quotient) // Pick the MAX between FEP and FP const estimatedExecutionPrice = fillPrice.greaterThan(feasibleExecutionPrice) ? fillPrice : feasibleExecutionPrice - // TODO: remove debug statement - console.debug(`getEstimatedExecutionPrice`, { - 'Amount (A)': - remainingSellAmount.toFixed(remainingSellAmount.currency.decimals) + ' ' + remainingSellAmount.currency.symbol, - 'Fee (F)': feeAmount.toFixed(feeAmount.currency.decimals) + ' ' + feeAmount.currency.symbol, - 'Limit Price (LP)': `${limitPrice.toFixed(8)} ${limitPrice.quoteCurrency.symbol} per ${ - limitPrice.baseCurrency.symbol - } (${limitPrice.numerator.toString()}/${limitPrice.denominator.toString()})`, - 'Feasible Execution Price (FEP)': `${feasibleExecutionPrice.toFixed(18)} ${ - feasibleExecutionPrice.quoteCurrency.symbol - } per ${feasibleExecutionPrice.baseCurrency.symbol}`, - 'Fill Price (FP)': `${fillPrice.toFixed(8)} ${fillPrice.quoteCurrency.symbol} per ${ - fillPrice.baseCurrency.symbol - } (${fillPrice.numerator.toString()}/${fillPrice.denominator.toString()})`, - 'Est.Execution Price (EEP)': `${estimatedExecutionPrice.toFixed(8)} ${ - estimatedExecutionPrice.quoteCurrency.symbol - } per ${estimatedExecutionPrice.baseCurrency.symbol}`, - id: order.id.slice(0, 8), - class: order.class, - }) - return estimatedExecutionPrice } @@ -378,14 +397,16 @@ export function partialOrderUpdate({ chainId, order, isSafeWallet }: UpdateOrder } export function getOrderVolumeFee( - fullAppData: EnrichedOrder['fullAppData'] + fullAppData: EnrichedOrder['fullAppData'], ): LatestAppDataDocVersion['metadata']['partnerFee'] | undefined { const appData = decodeAppData(fullAppData) as LatestAppDataDocVersion return appData?.metadata?.partnerFee } -export function getOrderLimitPriceWithPartnerFee(order: Order | ParsedOrder): Price { +type LimitPriceOrder = Pick + +export function getOrderLimitPriceWithPartnerFee(order: LimitPriceOrder): Price { const inputAmount = CurrencyAmount.fromRawAmount(order.inputToken, order.sellAmount.toString()) const outputAmount = CurrencyAmount.fromRawAmount(order.outputToken, order.buyAmount.toString()) @@ -393,7 +414,7 @@ export function getOrderLimitPriceWithPartnerFee(order: Order | ParsedOrder): Pr order.fullAppData, inputAmount, outputAmount, - isSellOrder(order.kind) + isSellOrder(order.kind), ) return buildPriceFromCurrencyAmounts(inputCurrencyAmount, outputCurrencyAmount) @@ -403,7 +424,7 @@ function getOrderAmountsWithPartnerFee( fullAppData: EnrichedOrder['fullAppData'], sellAmount: CurrencyAmount, buyAmount: CurrencyAmount, - isSellOrder: boolean + isSellOrder: boolean, ): { inputCurrencyAmount: CurrencyAmount; outputCurrencyAmount: CurrencyAmount } { const volumeFee = getOrderVolumeFee(fullAppData) diff --git a/apps/cowswap-frontend/src/modules/limitOrders/updaters/ExecutionPriceUpdater/index.tsx b/apps/cowswap-frontend/src/modules/limitOrders/updaters/ExecutionPriceUpdater/index.tsx index 96f43f9322..b197cde0f1 100644 --- a/apps/cowswap-frontend/src/modules/limitOrders/updaters/ExecutionPriceUpdater/index.tsx +++ b/apps/cowswap-frontend/src/modules/limitOrders/updaters/ExecutionPriceUpdater/index.tsx @@ -1,27 +1,45 @@ import { useAtomValue, useSetAtom } from 'jotai' -import { useEffect } from 'react' +import { FractionUtils, getWrappedToken } from '@cowprotocol/common-utils' + +import { getEstimatedExecutionPrice } from 'legacy/state/orders/utils' + +import { useAppData } from 'modules/appData' import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState' import { executionPriceAtom } from 'modules/limitOrders/state/executionPriceAtom' import { limitRateAtom } from 'modules/limitOrders/state/limitRateAtom' -import { calculateExecutionPrice } from 'utils/orderUtils/calculateExecutionPrice' +import { useSafeEffect } from 'common/hooks/useSafeMemo' export function ExecutionPriceUpdater() { const { marketRate, feeAmount } = useAtomValue(limitRateAtom) const { inputCurrencyAmount, outputCurrencyAmount, orderKind } = useLimitOrdersDerivedState() const setExecutionPrice = useSetAtom(executionPriceAtom) + const { fullAppData } = useAppData() || {} + + const inputToken = inputCurrencyAmount?.currency && getWrappedToken(inputCurrencyAmount.currency) + const outputToken = outputCurrencyAmount?.currency && getWrappedToken(outputCurrencyAmount.currency) + + const marketPrice = + marketRate && inputToken && outputToken && FractionUtils.toPrice(marketRate, inputToken, outputToken) + + const fee = feeAmount?.quotient.toString() - const price = calculateExecutionPrice({ - inputCurrencyAmount, - outputCurrencyAmount, - feeAmount, - marketRate, - orderKind, - }) + const price = + marketPrice && + fee && + getEstimatedExecutionPrice( + undefined, + marketPrice, + fee, + inputCurrencyAmount, + outputCurrencyAmount, + orderKind, + fullAppData, + ) - useEffect(() => { - setExecutionPrice(price) + useSafeEffect(() => { + price && price.greaterThan(0) && setExecutionPrice(price) }, [price, setExecutionPrice]) return null