From 3858f8fbeaf11ec96e6d881d1acbc3ac799bea26 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Thu, 9 Jun 2022 12:46:47 -0500 Subject: [PATCH] withdraw liquidity: redeem surplus shares, adjust total depostits, adjust amount for current prob, set isLiquidityProvision for surplus bets, error handling --- functions/src/withdraw-liquidity.ts | 159 ++++++++++++++++------------ 1 file changed, 90 insertions(+), 69 deletions(-) diff --git a/functions/src/withdraw-liquidity.ts b/functions/src/withdraw-liquidity.ts index a3b402a9..cee3002e 100644 --- a/functions/src/withdraw-liquidity.ts +++ b/functions/src/withdraw-liquidity.ts @@ -11,6 +11,7 @@ import { getProbability } from '../../common/calculate' import { noFees } from '../../common/fees' import { APIError } from './api' +import { redeemShares } from './redeem-shares' export const withdrawLiquidity = functions .runWith({ minInstances: 1 }) @@ -28,83 +29,103 @@ export const withdrawLiquidity = functions if (!contractId) return { status: 'error', message: 'Missing contract id' } - const result = await firestore.runTransaction(async (trans) => { - const lpDoc = firestore.doc(`users/${userId}`) - const lpSnap = await trans.get(lpDoc) - if (!lpSnap.exists) throw new APIError(400, 'User not found.') - const lp = lpSnap.data() as User + return await firestore + .runTransaction(async (trans) => { + const lpDoc = firestore.doc(`users/${userId}`) + 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 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(userId, contract, liquidities) - - // 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 === userId + const liquidityCollection = firestore.collection( + `contracts/${contractId}/liquidity` ) - .forEach((doc) => trans.update(doc.ref, { amount: 0 })) - const payout = Math.min(...Object.values(userShares)) - if (payout <= 0) return {} + const liquiditiesSnap = await trans.get(liquidityCollection) - const newBalance = lp.balance + payout - trans.update(lpDoc, { balance: newBalance }) + const liquidities = liquiditiesSnap.docs.map( + (doc) => doc.data() as LiquidityProvision + ) - const newPool = subtractObjects(contract.pool, userShares) - const newTotalLiquidity = contract.totalLiquidity - payout - trans.update(contractDoc, { - pool: newPool, - totalLiquidity: newTotalLiquidity, + const userShares = getUserLiquidityShares( + userId, + contract, + liquidities + ) + + // 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 === userId + ) + .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) + + const newPool = subtractObjects(contract.pool, userShares) + const newTotalLiquidity = contract.totalLiquidity - payout + 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: userId, + 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) + ) + .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(userId, contractId) - 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: userId, - contractId: contract.id, - amount: shares - payout, - shares: shares - payout, - outcome, - probBefore: prob, - probAfter: prob, - createdTime: Date.now(), - fees: noFees, - } as Omit) - ) - .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 - }) - - console.log('userid', userId, 'withdraws', result) - return { status: 'success', userShares: result } + console.log('userid', userId, 'withdraws', result) + return { status: 'success', userShares: result } + }) + .catch((e) => { + return { status: 'error', message: e.message } + }) } )