pay back excess liquidity
This commit is contained in:
parent
b082c09ab1
commit
61a2de5ea2
|
@ -1,11 +1,10 @@
|
||||||
import { sum, groupBy, mapValues, sumBy } from 'lodash'
|
import { groupBy, mapValues, sumBy } from 'lodash'
|
||||||
import { LimitBet } from './bet'
|
import { LimitBet } from './bet'
|
||||||
|
|
||||||
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, PLATFORM_FEE } from './fees'
|
||||||
import { LiquidityProvision } from './liquidity-provision'
|
import { LiquidityProvision } from './liquidity-provision'
|
||||||
import { computeFills } from './new-bet'
|
import { computeFills } from './new-bet'
|
||||||
import { binarySearch } from './util/algos'
|
import { binarySearch } from './util/algos'
|
||||||
import { addObjects } from './util/object'
|
|
||||||
|
|
||||||
export type CpmmState = {
|
export type CpmmState = {
|
||||||
pool: { [outcome: string]: number }
|
pool: { [outcome: string]: number }
|
||||||
|
@ -267,48 +266,22 @@ export function addCpmmLiquidity(
|
||||||
return { newPool, liquidity, newP }
|
return { newPool, liquidity, newP }
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateLiquidityDelta = (p: number) => (l: LiquidityProvision) => {
|
export function getCpmmLiquidityPoolWeights(liquidities: LiquidityProvision[]) {
|
||||||
const oldLiquidity = getCpmmLiquidity(l.pool, p)
|
const userAmounts = groupBy(liquidities, (w) => w.userId)
|
||||||
|
const totalAmount = sumBy(liquidities, (w) => w.amount)
|
||||||
|
|
||||||
const newPool = addObjects(l.pool, { YES: l.amount, NO: l.amount })
|
return mapValues(
|
||||||
const newLiquidity = getCpmmLiquidity(newPool, p)
|
userAmounts,
|
||||||
|
(amounts) => sumBy(amounts, (w) => w.amount) / totalAmount
|
||||||
const liquidity = newLiquidity - oldLiquidity
|
|
||||||
return liquidity
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCpmmLiquidityPoolWeights(
|
|
||||||
state: CpmmState,
|
|
||||||
liquidities: LiquidityProvision[],
|
|
||||||
excludeAntes: boolean
|
|
||||||
) {
|
|
||||||
const calcLiqudity = calculateLiquidityDelta(state.p)
|
|
||||||
const liquidityShares = liquidities.map(calcLiqudity)
|
|
||||||
const shareSum = sum(liquidityShares)
|
|
||||||
|
|
||||||
const weights = liquidityShares.map((shares, i) => ({
|
|
||||||
weight: shares / shareSum,
|
|
||||||
providerId: liquidities[i].userId,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const includedWeights = excludeAntes
|
|
||||||
? weights.filter((_, i) => !liquidities[i].isAnte)
|
|
||||||
: weights
|
|
||||||
|
|
||||||
const userWeights = groupBy(includedWeights, (w) => w.providerId)
|
|
||||||
const totalUserWeights = mapValues(userWeights, (userWeight) =>
|
|
||||||
sumBy(userWeight, (w) => w.weight)
|
|
||||||
)
|
)
|
||||||
return totalUserWeights
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserLiquidityShares(
|
export function getUserLiquidityShares(
|
||||||
userId: string,
|
userId: string,
|
||||||
state: CpmmState,
|
state: CpmmState,
|
||||||
liquidities: LiquidityProvision[],
|
liquidities: LiquidityProvision[]
|
||||||
excludeAntes: boolean
|
|
||||||
) {
|
) {
|
||||||
const weights = getCpmmLiquidityPoolWeights(state, liquidities, excludeAntes)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
const userWeight = weights[userId] ?? 0
|
const userWeight = weights[userId] ?? 0
|
||||||
|
|
||||||
return mapValues(state.pool, (shares) => userWeight * shares)
|
return mapValues(state.pool, (shares) => userWeight * shares)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import { Bet } from './bet'
|
import { Bet } from './bet'
|
||||||
import { getProbability } from './calculate'
|
import { getProbability } from './calculate'
|
||||||
import { getCpmmLiquidityPoolWeights } from './calculate-cpmm'
|
import { getCpmmLiquidityPoolWeights } from './calculate-cpmm'
|
||||||
|
@ -56,10 +55,10 @@ export const getLiquidityPoolPayouts = (
|
||||||
outcome: string,
|
outcome: string,
|
||||||
liquidities: LiquidityProvision[]
|
liquidities: LiquidityProvision[]
|
||||||
) => {
|
) => {
|
||||||
const { pool } = contract
|
const { pool, subsidyPool } = contract
|
||||||
const finalPool = pool[outcome]
|
const finalPool = pool[outcome] + subsidyPool
|
||||||
|
|
||||||
const weights = getCpmmLiquidityPoolWeights(contract, liquidities, false)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
|
|
||||||
return Object.entries(weights).map(([providerId, weight]) => ({
|
return Object.entries(weights).map(([providerId, weight]) => ({
|
||||||
userId: providerId,
|
userId: providerId,
|
||||||
|
@ -95,10 +94,10 @@ export const getLiquidityPoolProbPayouts = (
|
||||||
p: number,
|
p: number,
|
||||||
liquidities: LiquidityProvision[]
|
liquidities: LiquidityProvision[]
|
||||||
) => {
|
) => {
|
||||||
const { pool } = contract
|
const { pool, subsidyPool } = contract
|
||||||
const finalPool = p * pool.YES + (1 - p) * pool.NO
|
const finalPool = p * pool.YES + (1 - p) * pool.NO + subsidyPool
|
||||||
|
|
||||||
const weights = getCpmmLiquidityPoolWeights(contract, liquidities, false)
|
const weights = getCpmmLiquidityPoolWeights(liquidities)
|
||||||
|
|
||||||
return Object.entries(weights).map(([providerId, weight]) => ({
|
return Object.entries(weights).map(([providerId, weight]) => ({
|
||||||
userId: providerId,
|
userId: providerId,
|
||||||
|
|
|
@ -9,7 +9,15 @@ import {
|
||||||
RESOLUTIONS,
|
RESOLUTIONS,
|
||||||
} from '../../common/contract'
|
} from '../../common/contract'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getContractPath, getUser, getValues, isProd, log, payUser, revalidateStaticProps } from './utils'
|
import {
|
||||||
|
getContractPath,
|
||||||
|
getUser,
|
||||||
|
getValues,
|
||||||
|
isProd,
|
||||||
|
log,
|
||||||
|
payUser,
|
||||||
|
revalidateStaticProps,
|
||||||
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
getLoanPayouts,
|
getLoanPayouts,
|
||||||
getPayouts,
|
getPayouts,
|
||||||
|
@ -145,6 +153,7 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
|
||||||
resolutions,
|
resolutions,
|
||||||
collectedFees,
|
collectedFees,
|
||||||
}),
|
}),
|
||||||
|
subsidyPool: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
await contractDoc.update(updatedContract)
|
await contractDoc.update(updatedContract)
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
import * as admin from 'firebase-admin'
|
|
||||||
import { z } from 'zod'
|
|
||||||
|
|
||||||
import { CPMMContract } from '../../common/contract'
|
|
||||||
import { User } from '../../common/user'
|
|
||||||
import { subtractObjects } from '../../common/util/object'
|
|
||||||
import { LiquidityProvision } from '../../common/liquidity-provision'
|
|
||||||
import { getUserLiquidityShares } from '../../common/calculate-cpmm'
|
|
||||||
import { Bet } from '../../common/bet'
|
|
||||||
import { getProbability } from '../../common/calculate'
|
|
||||||
import { noFees } from '../../common/fees'
|
|
||||||
|
|
||||||
import { APIError, newEndpoint, validate } from './api'
|
|
||||||
import { redeemShares } from './redeem-shares'
|
|
||||||
|
|
||||||
const bodySchema = z.object({
|
|
||||||
contractId: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const withdrawliquidity = newEndpoint({}, async (req, auth) => {
|
|
||||||
const { contractId } = validate(bodySchema, req.body)
|
|
||||||
|
|
||||||
return await firestore
|
|
||||||
.runTransaction(async (trans) => {
|
|
||||||
const lpDoc = firestore.doc(`users/${auth.uid}`)
|
|
||||||
const lpSnap = await trans.get(lpDoc)
|
|
||||||
if (!lpSnap.exists) throw new APIError(400, 'User not found.')
|
|
||||||
const lp = lpSnap.data() as User
|
|
||||||
|
|
||||||
const contractDoc = firestore.doc(`contracts/${contractId}`)
|
|
||||||
const contractSnap = await trans.get(contractDoc)
|
|
||||||
if (!contractSnap.exists) throw new APIError(400, 'Contract not found.')
|
|
||||||
const contract = contractSnap.data() as CPMMContract
|
|
||||||
|
|
||||||
const liquidityCollection = firestore.collection(
|
|
||||||
`contracts/${contractId}/liquidity`
|
|
||||||
)
|
|
||||||
|
|
||||||
const liquiditiesSnap = await trans.get(liquidityCollection)
|
|
||||||
|
|
||||||
const liquidities = liquiditiesSnap.docs.map(
|
|
||||||
(doc) => doc.data() as LiquidityProvision
|
|
||||||
)
|
|
||||||
|
|
||||||
const userShares = getUserLiquidityShares(
|
|
||||||
auth.uid,
|
|
||||||
contract,
|
|
||||||
liquidities,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
// zero all added amounts for now
|
|
||||||
// can add support for partial withdrawals in the future
|
|
||||||
liquiditiesSnap.docs
|
|
||||||
.filter(
|
|
||||||
(_, i) => !liquidities[i].isAnte && liquidities[i].userId === auth.uid
|
|
||||||
)
|
|
||||||
.forEach((doc) => trans.update(doc.ref, { amount: 0 }))
|
|
||||||
|
|
||||||
const payout = Math.min(...Object.values(userShares))
|
|
||||||
if (payout <= 0) return {}
|
|
||||||
|
|
||||||
const newBalance = lp.balance + payout
|
|
||||||
const newTotalDeposits = lp.totalDeposits + payout
|
|
||||||
trans.update(lpDoc, {
|
|
||||||
balance: newBalance,
|
|
||||||
totalDeposits: newTotalDeposits,
|
|
||||||
} as Partial<User>)
|
|
||||||
|
|
||||||
const newPool = subtractObjects(contract.pool, userShares)
|
|
||||||
|
|
||||||
const minPoolShares = Math.min(...Object.values(newPool))
|
|
||||||
const adjustedTotal = contract.totalLiquidity - payout
|
|
||||||
|
|
||||||
// total liquidity is a bogus number; use minPoolShares to prevent from going negative
|
|
||||||
const newTotalLiquidity = Math.max(adjustedTotal, minPoolShares)
|
|
||||||
|
|
||||||
trans.update(contractDoc, {
|
|
||||||
pool: newPool,
|
|
||||||
totalLiquidity: newTotalLiquidity,
|
|
||||||
})
|
|
||||||
|
|
||||||
const prob = getProbability(contract)
|
|
||||||
|
|
||||||
// surplus shares become user's bets
|
|
||||||
const bets = Object.entries(userShares)
|
|
||||||
.map(([outcome, shares]) =>
|
|
||||||
shares - payout < 1 // don't create bet if less than 1 share
|
|
||||||
? undefined
|
|
||||||
: ({
|
|
||||||
userId: auth.uid,
|
|
||||||
contractId: contract.id,
|
|
||||||
amount:
|
|
||||||
(outcome === 'YES' ? prob : 1 - prob) * (shares - payout),
|
|
||||||
shares: shares - payout,
|
|
||||||
outcome,
|
|
||||||
probBefore: prob,
|
|
||||||
probAfter: prob,
|
|
||||||
createdTime: Date.now(),
|
|
||||||
isLiquidityProvision: true,
|
|
||||||
fees: noFees,
|
|
||||||
} as Omit<Bet, 'id'>)
|
|
||||||
)
|
|
||||||
.filter((x) => x !== undefined)
|
|
||||||
|
|
||||||
for (const bet of bets) {
|
|
||||||
const doc = firestore.collection(`contracts/${contract.id}/bets`).doc()
|
|
||||||
trans.create(doc, { id: doc.id, ...bet })
|
|
||||||
}
|
|
||||||
|
|
||||||
return userShares
|
|
||||||
})
|
|
||||||
.then(async (result) => {
|
|
||||||
// redeem surplus bet with pre-existing bets
|
|
||||||
await redeemShares(auth.uid, contractId)
|
|
||||||
console.log('userid', auth.uid, 'withdraws', result)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
|
|
@ -21,11 +21,6 @@ export const useLiquidity = (contractId: string) => {
|
||||||
export const useUserLiquidity = (contract: CPMMContract, userId: string) => {
|
export const useUserLiquidity = (contract: CPMMContract, userId: string) => {
|
||||||
const liquidities = useLiquidity(contract.id)
|
const liquidities = useLiquidity(contract.id)
|
||||||
|
|
||||||
const userShares = getUserLiquidityShares(
|
const userShares = getUserLiquidityShares(userId, contract, liquidities ?? [])
|
||||||
userId,
|
|
||||||
contract,
|
|
||||||
liquidities ?? [],
|
|
||||||
true
|
|
||||||
)
|
|
||||||
return userShares
|
return userShares
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user