diff --git a/backend/api/src/on-create-bet.ts b/backend/api/src/on-create-bet.ts index 4bfa82ad0e..76ffb3acff 100644 --- a/backend/api/src/on-create-bet.ts +++ b/backend/api/src/on-create-bet.ts @@ -5,7 +5,7 @@ import { getBettingStreakResetTimeBeforeNow, getUser, } from 'shared/utils' -import { Bet, LimitBet } from 'common/bet' +import { Bet, LimitBet, maker } from 'common/bet' import { CPMMContract, CPMMMultiContract, @@ -31,7 +31,6 @@ import { SupabaseDirectClient, } from 'shared/supabase/init' import { convertBet } from 'common/supabase/bets' -import { maker } from 'api/place-bet' import { BOT_USERNAMES } from 'common/envs/constants' import { addUserToContractFollowers } from 'shared/follow-market' import { updateUserInterestEmbedding } from 'shared/helpers/embeddings' diff --git a/backend/api/src/place-bet.ts b/backend/api/src/place-bet.ts index 7f7a2886ea..050686d2b1 100644 --- a/backend/api/src/place-bet.ts +++ b/backend/api/src/place-bet.ts @@ -8,7 +8,7 @@ import { uniqBy, } from 'lodash' import { APIError, type APIHandler } from './helpers/endpoint' -import { CPMM_MIN_POOL_QTY, Contract, MarketContract } from 'common/contract' +import { Contract, CPMM_MIN_POOL_QTY, MarketContract } from 'common/contract' import { User } from 'common/user' import { BetInfo, @@ -17,7 +17,7 @@ import { getNewMultiCpmmBetInfo, } from 'common/new-bet' import { removeUndefinedProps } from 'common/util/object' -import { Bet, LimitBet } from 'common/bet' +import { Bet, LimitBet, maker } from 'common/bet' import { floatingEqual } from 'common/util/math' import { getContract, getUser, log, metrics } from 'shared/utils' import { Answer } from 'common/answer' @@ -28,9 +28,9 @@ import { BLESSED_BANNED_USER_IDS } from 'common/envs/constants' import * as crypto from 'crypto' import { formatMoneyWithDecimals } from 'common/util/format' import { + createSupabaseDirectClient, SupabaseDirectClient, SupabaseTransaction, - createSupabaseDirectClient, } from 'shared/supabase/init' import { bulkIncrementBalances, incrementBalance } from 'shared/supabase/users' import { runShortTrans } from 'shared/short-transaction' @@ -55,24 +55,32 @@ import { filterDefined } from 'common/util/array' export const placeBet: APIHandler<'bet'> = async (props, auth) => { const isApi = auth.creds.kind === 'key' - const { user, contract, answers, unfilledBets, balanceByUserId } = - await fetchContractBetDataAndValidate( - createSupabaseDirectClient(), + let simulatedMakerIds: string[] = [] + if (props.deps === undefined) { + const { user, contract, answers, unfilledBets, balanceByUserId } = + await fetchContractBetDataAndValidate( + createSupabaseDirectClient(), + props, + auth.uid, + isApi + ) + // Simulate bet to see whose limit orders you match. + const simulatedResult = calculateBetResult( props, - auth.uid, - isApi + user, + contract, + answers, + unfilledBets, + balanceByUserId ) - // Simulate bet to see whose limit orders you match. - const simulatedResult = calculateBetResult( - props, - user, - contract, - answers, - unfilledBets, - balanceByUserId - ) - const simulatedMakerIds = getMakerIdsFromBetResult(simulatedResult) - const deps = [auth.uid, contract.id, ...simulatedMakerIds] + simulatedMakerIds = getMakerIdsFromBetResult(simulatedResult) + } + + const deps = [ + auth.uid, + props.contractId, + ...(props.deps ?? simulatedMakerIds), + ] return await betsQueue.enqueueFn( () => placeBetMain(props, auth.uid, isApi), @@ -665,13 +673,6 @@ export const validateBet = async ( return user } -export type maker = { - bet: LimitBet - amount: number - shares: number - timestamp: number -} - export async function bulkUpdateLimitOrders( db: SupabaseDirectClient, updates: Array<{ diff --git a/backend/shared/src/short-transaction.ts b/backend/shared/src/short-transaction.ts index acb8aca7f0..ca6c15c368 100644 --- a/backend/shared/src/short-transaction.ts +++ b/backend/shared/src/short-transaction.ts @@ -1,12 +1,12 @@ import { SupabaseTransaction, createSupabaseDirectClient, + SERIAL_MODE, } from './supabase/init' -import { DEFAULT_QUEUE_TIME_LIMIT } from 'shared/helpers/fn-queue' export const runShortTrans = async ( callback: (trans: SupabaseTransaction) => Promise ) => { const pg = createSupabaseDirectClient() - return await pg.timeout(DEFAULT_QUEUE_TIME_LIMIT / 2, callback, false) + return await pg.tx({ mode: SERIAL_MODE }, callback) } diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index 832745caa0..08cd0c69d8 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -161,6 +161,7 @@ export const API = (_apiTypeCheck = { //Multi answerId: z.string().optional(), dryRun: z.boolean().optional(), + deps: z.array(z.string()).optional(), }) .strict(), }, diff --git a/common/src/bet.ts b/common/src/bet.ts index 04a0cfe458..ad38da722d 100644 --- a/common/src/bet.ts +++ b/common/src/bet.ts @@ -93,3 +93,9 @@ export const calculateMultiBets = ( ) ) } +export type maker = { + bet: LimitBet + amount: number + shares: number + timestamp: number +} diff --git a/common/src/calculate-cpmm-arbitrage.ts b/common/src/calculate-cpmm-arbitrage.ts index f5bc94be10..c312436b2f 100644 --- a/common/src/calculate-cpmm-arbitrage.ts +++ b/common/src/calculate-cpmm-arbitrage.ts @@ -1,6 +1,6 @@ import { Dictionary, first, groupBy, mapValues, sum, sumBy } from 'lodash' import { Answer } from './answer' -import { Bet, LimitBet } from './bet' +import { Bet, LimitBet, maker } from './bet' import { calculateAmountToBuySharesFixedP, getCpmmProbability, @@ -23,8 +23,8 @@ const noFillsReturn = ( outcome, answer, takers: [], - makers: [], - ordersToCancel: [], + makers: [] as maker[], + ordersToCancel: [] as LimitBet[], cpmmState: { pool: { YES: answer.poolYes, NO: answer.poolNo }, p: 0.5, diff --git a/web/components/bet/bet-panel.tsx b/web/components/bet/bet-panel.tsx index cc9ddda6a3..f157da794e 100644 --- a/web/components/bet/bet-panel.tsx +++ b/web/components/bet/bet-panel.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { sumBy } from 'lodash' import toast from 'react-hot-toast' import { CheckIcon } from '@heroicons/react/solid' @@ -55,6 +55,7 @@ import { FeeDisplay } from './fees' import { floatingEqual } from 'common/util/math' import { getTierFromLiquidity } from 'common/tier' import { getAnswerColor } from '../charts/contract/choice' +import { LimitBet } from 'common/bet' export type BinaryOutcomes = 'YES' | 'NO' | undefined @@ -234,6 +235,7 @@ export const BuyPanelBody = (props: { const [error, setError] = useState() const [isSubmitting, setIsSubmitting] = useState(false) + const betDeps = useRef() const [inputRef, focusAmountInput] = useFocus() @@ -285,6 +287,7 @@ export const BuyPanelBody = (props: { contractId: contract.id, answerId: multiProps?.answerToBuy.id, replyToCommentId, + deps: betDeps.current?.map((b) => b.userId), }) ) .then((r) => { @@ -373,6 +376,11 @@ export const BuyPanelBody = (props: { fees = getFeeTotal(newBetResult.totalFees) + sumBy(otherBetResults, (result) => getFeeTotal(result.totalFees)) + betDeps.current = newBetResult.makers + .map((m) => m.bet) + .concat(otherBetResults.flatMap((r) => r.makers.map((m) => m.bet))) + .concat(newBetResult.ordersToCancel) + .concat(otherBetResults.flatMap((r) => r.ordersToCancel)) } else { const cpmmState = isCpmmMulti ? { @@ -403,6 +411,9 @@ export const BuyPanelBody = (props: { probBefore = result.probBefore probAfter = result.probAfter fees = getFeeTotal(result.fees) + betDeps.current = result.makers + .map((m) => m.bet) + .concat(result.ordersToCancel) } } catch (err: any) { console.error('Error in calculateCpmmMultiArbitrageBet:', err) diff --git a/web/components/bet/limit-order-panel.tsx b/web/components/bet/limit-order-panel.tsx index 83017cdbe1..ae8bb3b1a9 100644 --- a/web/components/bet/limit-order-panel.tsx +++ b/web/components/bet/limit-order-panel.tsx @@ -1,6 +1,6 @@ import dayjs from 'dayjs' import { clamp, sumBy } from 'lodash' -import { useState } from 'react' +import { useRef, useState } from 'react' import { Answer } from 'common/answer' import { LimitBet } from 'common/bet' @@ -91,7 +91,7 @@ export default function LimitOrderPanel(props: { const [error, setError] = useState() const [inputError, setInputError] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) - + const betDeps = useRef() // Expiring orders const [addExpiration, setAddExpiration] = usePersistentInMemoryState( false, @@ -185,6 +185,7 @@ export default function LimitOrderPanel(props: { answerId, limitProb: limitProb, expiresAt, + deps: betDeps.current?.map((b) => b.userId), }) ) .then((r) => { @@ -238,6 +239,7 @@ export default function LimitOrderPanel(props: { orderAmount = result.orderAmount filledAmount = result.amount fees = result.fees + betDeps.current = result.betDeps } catch (err: any) { console.error('Error in calculateCpmmMultiArbitrageBet:', err) setError( @@ -467,6 +469,7 @@ const getBetReturns = ( let amount: number let shares: number let fees: Fees + let betDeps: LimitBet[] if (arbitrageProps) { const { answers, answerToBuy } = arbitrageProps const { newBetResult, otherBetResults } = calculateCpmmMultiArbitrageBet( @@ -481,6 +484,11 @@ const getBetReturns = ( ) amount = sumBy(newBetResult.takers, 'amount') shares = sumBy(newBetResult.takers, 'shares') + betDeps = newBetResult.makers + .map((m) => m.bet) + .concat(otherBetResults.flatMap((r) => r.makers.map((m) => m.bet))) + .concat(newBetResult.ordersToCancel) + .concat(otherBetResults.flatMap((r) => r.ordersToCancel)) fees = addObjects( newBetResult.totalFees, otherBetResults.reduce( @@ -489,7 +497,7 @@ const getBetReturns = ( ) ) } else { - ;({ amount, shares, fees } = computeCpmmBet( + const result = computeCpmmBet( cpmmState, outcome, betAmount, @@ -497,7 +505,11 @@ const getBetReturns = ( unfilledBets, balanceByUserId, !arbitrageProps && { max: MAX_CPMM_PROB, min: MIN_CPMM_PROB } - )) + ) + amount = result.amount + shares = result.shares + fees = result.fees + betDeps = result.makers.map((m) => m.bet).concat(result.ordersToCancel) } const remainingMatched = limitProb @@ -507,5 +519,13 @@ const getBetReturns = ( const currentPayout = shares + remainingMatched const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 - return { orderAmount, amount, shares, currentPayout, currentReturn, fees } + return { + orderAmount, + amount, + shares, + currentPayout, + currentReturn, + fees, + betDeps, + } }