Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate liquidity docs to ADD_SUBSIDY txns #2552

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 11 additions & 14 deletions backend/api/src/add-subsidy.ts
Expand Up @@ -5,6 +5,7 @@ import { getNewLiquidityProvision } from 'common/add-liquidity'
import { APIError, type APIHandler } from './helpers/endpoint'
import { SUBSIDY_FEE } from 'common/economy'
import { runTxn } from 'shared/txn/run-txn'
import { type Txn } from 'common/txn'

export const addLiquidity: APIHandler<
'market/:contractId/add-liquidity'
Expand Down Expand Up @@ -35,21 +36,14 @@ export const addLiquidity: APIHandler<

if (user.balance < amount) throw new APIError(403, 'Insufficient balance')

const newLiquidityProvisionDoc = firestore
.collection(`contracts/${contractId}/liquidity`)
.doc()

const subsidyAmount = (1 - SUBSIDY_FEE) * amount

const { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } =
getNewLiquidityProvision(
user.id,
subsidyAmount,
contract,
newLiquidityProvisionDoc.id
)
const { newTotalLiquidity, newSubsidyPool } = getNewLiquidityProvision(
subsidyAmount,
contract
)

await runTxn(transaction, {
const { status, message, txn } = await runTxn(transaction, {
fromId: user.id,
amount: amount,
toId: contractId,
Expand All @@ -59,6 +53,10 @@ export const addLiquidity: APIHandler<
fromType: 'USER',
})

if (status === 'error' || !txn) {
throw new APIError(500, message ?? 'Unknown error')
}

transaction.update(contractDoc, {
subsidyPool: newSubsidyPool,
totalLiquidity: newTotalLiquidity,
Expand All @@ -70,8 +68,7 @@ export const addLiquidity: APIHandler<
throw new APIError(500, 'Invalid user balance for ' + user.username)
}

transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
return newLiquidityProvision
return txn as Txn
})
}

Expand Down
15 changes: 7 additions & 8 deletions backend/api/src/create-answer-cpmm.ts
Expand Up @@ -17,11 +17,13 @@ import { isAdminId } from 'common/envs/constants'
import { Bet } from 'common/bet'
import { floatingEqual } from 'common/util/math'
import { noFees } from 'common/fees'
import { getCpmmInitialLiquidity } from 'common/antes'
import { getCpmmInitialLiquidityTxn } from 'common/antes'
import { addUserToContractFollowers } from 'shared/follow-market'
import { log } from 'shared/utils'
import { createNewAnswerOnContractNotification } from 'shared/create-notification'
import { removeUndefinedProps } from 'common/util/object'
import { addHouseSubsidyToAnswer } from 'shared/helpers/add-house-subsidy'
import { runTxn } from 'shared/txn/run-txn'

export const createAnswerCPMM: APIHandler<'market/:contractId/answer'> = async (
props,
Expand Down Expand Up @@ -167,18 +169,15 @@ export const createAnswerCpmmMain = async (
transaction.update(contractDoc, {
totalLiquidity: FieldValue.increment(ANSWER_COST),
})
const liquidityDoc = firestore
.collection(`contracts/${contract.id}/liquidity`)
.doc()
const lp = getCpmmInitialLiquidity(

const lp = getCpmmInitialLiquidityTxn(
user.id,
contract,
liquidityDoc.id,
ANSWER_COST,
createdTime,
newAnswer.id
)
transaction.create(liquidityDoc, lp)

await runTxn(transaction, lp)
}

return { newAnswerId: newAnswer.id, contract, user }
Expand Down
16 changes: 6 additions & 10 deletions backend/api/src/create-market.ts
@@ -1,6 +1,6 @@
import * as admin from 'firebase-admin'
import { FieldValue, Transaction } from 'firebase-admin/firestore'
import { getCpmmInitialLiquidity } from 'common/antes'
import { getCpmmInitialLiquidityTxn } from 'common/antes'
import {
add_answers_mode,
Contract,
Expand Down Expand Up @@ -593,18 +593,14 @@ async function generateAntes(
outcomeType === 'MULTIPLE_CHOICE' ||
outcomeType === 'NUMBER'
) {
const liquidityDoc = firestore
.collection(`contracts/${contract.id}/liquidity`)
.doc()

const lp = getCpmmInitialLiquidity(
const lp = getCpmmInitialLiquidityTxn(
providerId,
contract as CPMMBinaryContract | CPMMMultiContract,
liquidityDoc.id,
ante,
contract.createdTime
ante
)

await liquidityDoc.set(lp)
await firestore.runTransaction(async (transaction) => {
runTxn(transaction, lp)
})
}
}
63 changes: 24 additions & 39 deletions backend/functions/src/triggers/on-create-liquidity-provision.ts
@@ -1,45 +1,30 @@
import * as functions from 'firebase-functions'
import { getContract, getUser, log } from 'shared/utils'
import { getContract, getUser } from 'shared/utils'
import { createFollowOrMarketSubsidizedNotification } from 'shared/create-notification'
import { LiquidityProvision } from 'common/liquidity-provision'
import { addUserToContractFollowers } from 'shared/follow-market'
import {
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
HOUSE_LIQUIDITY_PROVIDER_ID,
} from 'common/antes'
import { secrets } from 'common/secrets'
import { AddSubsidyTxn } from 'common/txn'

export const onCreateLiquidityProvision = functions
.runWith({ secrets })
.firestore.document('contracts/{contractId}/liquidity/{liquidityId}')
.onCreate(async (change, context) => {
const liquidity = change.data() as LiquidityProvision
const { eventId } = context
// TODO: add this as continuation of add liquidity instances

// Ignore Manifold Markets liquidity for now - users see a notification for free market liquidity provision
if (
liquidity.isAnte ||
liquidity.userId === HOUSE_LIQUIDITY_PROVIDER_ID ||
liquidity.userId === DEV_HOUSE_LIQUIDITY_PROVIDER_ID
)
return
export const onCreateLiquidityProvision = async (txn: AddSubsidyTxn) => {
// Ignore Manifold Markets liquidity for now - users see a notification for free market liquidity provision
if (txn.fromType === 'BANK' || txn.data.isAnte) {
return
}

log(`onCreateLiquidityProvision: ${JSON.stringify(liquidity)}`)
const contract = await getContract(txn.toId)
if (!contract)
throw new Error('Could not find contract corresponding with liquidity')

const contract = await getContract(liquidity.contractId)
if (!contract)
throw new Error('Could not find contract corresponding with liquidity')

const liquidityProvider = await getUser(liquidity.userId)
if (!liquidityProvider) throw new Error('Could not find liquidity provider')
await addUserToContractFollowers(contract.id, liquidityProvider.id)
await createFollowOrMarketSubsidizedNotification(
contract.id,
'liquidity',
'created',
liquidityProvider,
eventId,
liquidity.amount.toString(),
{ contract }
)
})
const liquidityProvider = await getUser(txn.fromId)
if (!liquidityProvider) throw new Error('Could not find liquidity provider')
await addUserToContractFollowers(contract.id, liquidityProvider.id)
await createFollowOrMarketSubsidizedNotification(
contract.id,
'liquidity',
'created',
liquidityProvider,
txn.id,
txn.amount.toString(),
{ contract }
)
}
72 changes: 33 additions & 39 deletions backend/shared/src/helpers/add-house-subsidy.ts
@@ -1,44 +1,42 @@
import * as admin from 'firebase-admin'
import { FieldValue } from 'firebase-admin/firestore'

import { CPMMContract, CPMMMultiContract } from 'common/contract'
import { isProd } from 'shared/utils'
import {
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
HOUSE_LIQUIDITY_PROVIDER_ID,
} from 'common/antes'
import { getNewLiquidityProvision } from 'common/add-liquidity'
import { APIError } from 'common/api/utils'
import { runTxnFromBank } from 'shared/txn/run-txn'

const firestore = admin.firestore()

export const addHouseSubsidy = (contractId: string, amount: number) => {
return firestore.runTransaction(async (transaction) => {
const newLiquidityProvisionDoc = firestore
.collection(`contracts/${contractId}/liquidity`)
.doc()

const providerId = isProd()
? HOUSE_LIQUIDITY_PROVIDER_ID
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID

const contractDoc = firestore.doc(`contracts/${contractId}`)
const snap = await transaction.get(contractDoc)
const contract = snap.data() as CPMMContract | CPMMMultiContract

const { newLiquidityProvision, newTotalLiquidity, newSubsidyPool } =
getNewLiquidityProvision(
providerId,
amount,
contract,
newLiquidityProvisionDoc.id
)
const { newTotalLiquidity, newSubsidyPool } = getNewLiquidityProvision(
amount,
contract
)

const { status, message, txn } = await runTxnFromBank(transaction, {
fromType: 'BANK',
amount,
toId: contractId,
toType: 'CONTRACT',
category: 'ADD_SUBSIDY',
token: 'M$',
})

if (status === 'error') {
throw new APIError(500, message ?? 'Unknown error')
}

transaction.update(contractDoc, {
subsidyPool: newSubsidyPool,
totalLiquidity: newTotalLiquidity,
} as Partial<CPMMContract>)

transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
return txn
})
}

Expand All @@ -48,25 +46,21 @@ export const addHouseSubsidyToAnswer = (
amount: number
) => {
return firestore.runTransaction(async (transaction) => {
const newLiquidityProvisionDoc = firestore
.collection(`contracts/${contractId}/liquidity`)
.doc()

const providerId = isProd()
? HOUSE_LIQUIDITY_PROVIDER_ID
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID

const contractDoc = firestore.doc(`contracts/${contractId}`)
const snap = await transaction.get(contractDoc)
const contract = snap.data() as CPMMContract | CPMMMultiContract

const { newLiquidityProvision } = getNewLiquidityProvision(
providerId,
const { status, message, txn } = await runTxnFromBank(transaction, {
fromType: 'BANK',
amount,
contract,
newLiquidityProvisionDoc.id,
answerId
)
toId: contractId,
toType: 'CONTRACT',
category: 'ADD_SUBSIDY',
token: 'M$',
data: { answerId },
})

if (status === 'error') {
throw new APIError(500, message ?? 'Unknown error')
}

transaction.update(contractDoc, {
totalLiquidity: FieldValue.increment(amount),
Expand All @@ -80,6 +74,6 @@ export const addHouseSubsidyToAnswer = (
subsidyPool: FieldValue.increment(amount),
})

transaction.create(newLiquidityProvisionDoc, newLiquidityProvision)
return txn
})
}
26 changes: 19 additions & 7 deletions backend/shared/src/resolve-market-helpers.ts
Expand Up @@ -35,6 +35,7 @@ import { recordContractEdit } from 'shared/record-contract-edit'
import { createSupabaseDirectClient } from './supabase/init'
import { Answer } from 'common/answer'
import { acquireLock, releaseLock } from './firestore-lock'
import { convertTxn } from 'common/supabase/txns'

export type ResolutionParams = {
outcome: string
Expand Down Expand Up @@ -255,14 +256,26 @@ export const getDataAndPayoutInfo = async (
answerId: string | undefined
) => {
const { id: contractId, creatorId, outcomeType } = unresolvedContract
const liquiditiesSnap = await firestore
.collection(`contracts/${contractId}/liquidity`)
.get()

const liquidityDocs = liquiditiesSnap.docs.map(
(doc) => doc.data() as LiquidityProvision
const pg = createSupabaseDirectClient()

const liquidityTxns = await pg.map(
`select * from txns
where category = 'ADD_SUBSIDY'
and to_id = $1`,
[contractId],
convertTxn
)

const liquidityDocs: LiquidityProvision[] = liquidityTxns.map((txn) => ({
id: txn.id,
userId: txn.fromId,
contractId,
createdTime: txn.createdTime,
isAnte: txn.data?.isAnte,
answerId: txn.data?.answerId,
amount: txn.amount,
}))

const liquidities =
unresolvedContract.mechanism === 'cpmm-multi-1' &&
outcomeType !== 'NUMBER' &&
Expand All @@ -278,7 +291,6 @@ export const getDataAndPayoutInfo = async (
) {
// Load bets from supabase as an optimization.
// This type of multi choice generates a lot of extra bets that have shares = 0.
const pg = createSupabaseDirectClient()
bets = await pg.map(
`select * from contract_bets
where contract_id = $1
Expand Down