diff --git a/common/add-liquidity.ts b/common/add-liquidity.ts index 9271bbbf..47b3c1e9 100644 --- a/common/add-liquidity.ts +++ b/common/add-liquidity.ts @@ -1,4 +1,4 @@ -import { addCpmmLiquidity, getCpmmLiquidity } from './calculate-cpmm' +import { getCpmmLiquidity } from './calculate-cpmm' import { CPMMContract } from './contract' import { LiquidityProvision } from './liquidity-provision' @@ -8,25 +8,23 @@ export const getNewLiquidityProvision = ( contract: CPMMContract, newLiquidityProvisionId: string ) => { - const { pool, p, totalLiquidity } = contract + const { pool, p, totalLiquidity, subsidyPool } = contract - const { newPool, newP } = addCpmmLiquidity(pool, p, amount) - - const liquidity = - getCpmmLiquidity(newPool, newP) - getCpmmLiquidity(pool, newP) + const liquidity = getCpmmLiquidity(pool, p) const newLiquidityProvision: LiquidityProvision = { id: newLiquidityProvisionId, userId: userId, contractId: contract.id, amount, - pool: newPool, - p: newP, + pool, + p, liquidity, createdTime: Date.now(), } const newTotalLiquidity = (totalLiquidity ?? 0) + amount + const newSubsidyPool = (subsidyPool ?? 0) + amount - return { newLiquidityProvision, newPool, newP, newTotalLiquidity } + return { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } } diff --git a/common/contract.ts b/common/contract.ts index 2656b5d5..dd2aa70a 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -91,7 +91,8 @@ export type CPMM = { mechanism: 'cpmm-1' pool: { [outcome: string]: number } p: number // probability constant in y^p * n^(1-p) = k - totalLiquidity: number // in M$ + totalLiquidity: number // for historical reasons, this the total subsidy amount added in M$ + subsidyPool: number // current value of subsidy pool in M$ prob: number probChanges: { day: number diff --git a/common/new-contract.ts b/common/new-contract.ts index 9a73e2ea..241f0a0f 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -111,6 +111,7 @@ const getBinaryCpmmProps = (initialProb: number, ante: number) => { mechanism: 'cpmm-1', outcomeType: 'BINARY', totalLiquidity: ante, + subsidyPool: 0, initialProbability: p, p, pool: pool, diff --git a/functions/src/add-subsidy.ts b/functions/src/add-subsidy.ts new file mode 100644 index 00000000..b3ed1895 --- /dev/null +++ b/functions/src/add-subsidy.ts @@ -0,0 +1,78 @@ +import * as admin from 'firebase-admin' +import { z } from 'zod' + +import { Contract, CPMMContract } from '../../common/contract' +import { User } from '../../common/user' +import { getNewLiquidityProvision } from '../../common/add-liquidity' +import { APIError, newEndpoint, validate } from './api' + +const bodySchema = z.object({ + contractId: z.string(), + amount: z.number().gt(0), +}) + +export const addsubsidy = newEndpoint({}, async (req, auth) => { + const { amount, contractId } = validate(bodySchema, req.body) + + if (!isFinite(amount) || amount < 1) throw new APIError(400, 'Invalid amount') + + // run as transaction to prevent race conditions + return await firestore.runTransaction(async (transaction) => { + const userDoc = firestore.doc(`users/${auth.uid}`) + const userSnap = await transaction.get(userDoc) + if (!userSnap.exists) throw new APIError(400, 'User not found') + const user = userSnap.data() as User + + const contractDoc = firestore.doc(`contracts/${contractId}`) + const contractSnap = await transaction.get(contractDoc) + if (!contractSnap.exists) throw new APIError(400, 'Invalid contract') + const contract = contractSnap.data() as Contract + if ( + contract.mechanism !== 'cpmm-1' || + (contract.outcomeType !== 'BINARY' && + contract.outcomeType !== 'PSEUDO_NUMERIC') + ) + throw new APIError(400, 'Invalid contract') + + const { closeTime } = contract + if (closeTime && Date.now() > closeTime) + throw new APIError(400, 'Trading is closed') + + if (user.balance < amount) throw new APIError(400, 'Insufficient balance') + + const newLiquidityProvisionDoc = firestore + .collection(`contracts/${contractId}/liquidity`) + .doc() + + const { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } = + getNewLiquidityProvision( + user.id, + amount, + contract, + newLiquidityProvisionDoc.id + ) + + transaction.update(contractDoc, { + subsidyPool: newSubsidyPool, + totalLiquidity: newTotalLiquidity, + } as Partial) + + const newBalance = user.balance - amount + const newTotalDeposits = user.totalDeposits - amount + + if (!isFinite(newBalance)) { + throw new APIError(500, 'Invalid user balance for ' + user.username) + } + + transaction.update(userDoc, { + balance: newBalance, + totalDeposits: newTotalDeposits, + }) + + transaction.create(newLiquidityProvisionDoc, newLiquidityProvision) + + return newLiquidityProvision + }) +}) + +const firestore = admin.firestore() diff --git a/functions/src/index.ts b/functions/src/index.ts index 763fd8bb..622b7e4a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -44,8 +44,6 @@ export * from './sell-bet' export * from './sell-shares' export * from './claim-manalink' export * from './create-market' -export * from './add-liquidity' -export * from './withdraw-liquidity' export * from './create-group' export * from './resolve-market' export * from './unsubscribe' @@ -53,6 +51,7 @@ export * from './stripe' export * from './mana-bonus-email' export * from './close-market' export * from './update-comment-bounty' +export * from './add-subsidy' import { health } from './health' import { transact } from './transact'