From 5135135e79724069aa084272333b3f7027e065a8 Mon Sep 17 00:00:00 2001 From: mantikoros <95266179+mantikoros@users.noreply.github.com> Date: Mon, 9 May 2022 16:04:40 -0500 Subject: [PATCH] Separate out fees (#159) * deduct market ante from profits * display creator fees in stats * show creator earnings in stats * separate out creator, liquidity fees in payouts and deduct from profits --- common/payouts-dpm.ts | 56 +++++++++------ common/payouts-fixed.ts | 32 +++++---- common/payouts.ts | 42 +++++------ common/scoring.ts | 2 +- functions/src/create-contract.ts | 2 +- functions/src/resolve-market.ts | 70 ++++++++++++------- .../src/scripts/pay-out-contract-again.ts | 3 +- functions/src/utils.ts | 8 ++- .../contract/contract-info-dialog.tsx | 5 ++ 9 files changed, 136 insertions(+), 84 deletions(-) diff --git a/common/payouts-dpm.ts b/common/payouts-dpm.ts index 5e6ce209..64c7f90e 100644 --- a/common/payouts-dpm.ts +++ b/common/payouts-dpm.ts @@ -27,7 +27,12 @@ export const getDpmCancelPayouts = ( payout: (bet.amount / betSum) * poolTotal, })) - return [payouts, contract.collectedFees ?? noFees] + return { + payouts, + creatorPayout: 0, + liquidityPayouts: [], + collectedFees: contract.collectedFees ?? noFees, + } } export const getDpmStandardPayouts = ( @@ -59,7 +64,10 @@ export const getDpmStandardPayouts = ( liquidityFee: 0, } - const fees = addObjects(finalFees, contract.collectedFees ?? {}) + const collectedFees = addObjects( + finalFees, + contract.collectedFees ?? {} + ) console.log( 'resolved', @@ -72,11 +80,12 @@ export const getDpmStandardPayouts = ( creatorFee ) - const totalPayouts = payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee - - return [totalPayouts, fees] + return { + payouts: payouts.map(({ userId, payout }) => ({ userId, payout })), + creatorPayout: creatorFee, + liquidityPayouts: [], + collectedFees, + } } export const getDpmMktPayouts = ( @@ -114,7 +123,10 @@ export const getDpmMktPayouts = ( liquidityFee: 0, } - const fees = addObjects(finalFees, contract.collectedFees ?? {}) + const collectedFees = addObjects( + finalFees, + contract.collectedFees ?? {} + ) console.log( 'resolved MKT', @@ -127,11 +139,12 @@ export const getDpmMktPayouts = ( creatorFee ) - const totalPayouts = payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee - - return [totalPayouts, fees] + return { + payouts: payouts.map(({ userId, payout }) => ({ userId, payout })), + creatorPayout: creatorFee, + liquidityPayouts: [], + collectedFees, + } } export const getPayoutsMultiOutcome = ( @@ -169,7 +182,10 @@ export const getPayoutsMultiOutcome = ( liquidityFee: 0, } - const fees = addObjects(finalFees, contract.collectedFees ?? noFees) + const collectedFees = addObjects( + finalFees, + contract.collectedFees ?? noFees + ) console.log( 'resolved', @@ -181,10 +197,10 @@ export const getPayoutsMultiOutcome = ( 'creator fee', creatorFee ) - - const totalPayouts = payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorFee }]) // add creator fee - - return [totalPayouts, fees] + return { + payouts: payouts.map(({ userId, payout }) => ({ userId, payout })), + creatorPayout: creatorFee, + liquidityPayouts: [], + collectedFees, + } } diff --git a/common/payouts-fixed.ts b/common/payouts-fixed.ts index d06a8411..2729d544 100644 --- a/common/payouts-fixed.ts +++ b/common/payouts-fixed.ts @@ -4,6 +4,7 @@ import { Bet } from './bet' import { getProbability } from './calculate' import { getCpmmLiquidityPoolWeights } from './calculate-cpmm' import { Binary, CPMM, FixedPayouts, FullContract } from './contract' +import { noFees } from './fees' import { LiquidityProvision } from './liquidity-provision' export const getFixedCancelPayouts = ( @@ -15,13 +16,16 @@ export const getFixedCancelPayouts = ( payout: lp.amount, })) - return bets + const payouts = bets .filter((b) => !b.isAnte && !b.isLiquidityProvision) .map((bet) => ({ userId: bet.userId, payout: bet.amount, })) - .concat(liquidityPayouts) + + const creatorPayout = 0 + + return { payouts, creatorPayout, liquidityPayouts, collectedFees: noFees } } export const getStandardFixedPayouts = ( @@ -37,7 +41,8 @@ export const getStandardFixedPayouts = ( payout: shares, })) - const creatorPayout = contract.collectedFees.creatorFee + const { collectedFees } = contract + const creatorPayout = collectedFees.creatorFee console.log( 'resolved', @@ -50,10 +55,13 @@ export const getStandardFixedPayouts = ( creatorPayout ) - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee - .concat(getLiquidityPoolPayouts(contract, outcome, liquidities)) + const liquidityPayouts = getLiquidityPoolPayouts( + contract, + outcome, + liquidities + ) + + return { payouts, creatorPayout, liquidityPayouts, collectedFees } } export const getLiquidityPoolPayouts = ( @@ -88,7 +96,8 @@ export const getMktFixedPayouts = ( return { userId, payout: betP * shares } }) - const creatorPayout = contract.collectedFees.creatorFee + const { collectedFees } = contract + const creatorPayout = collectedFees.creatorFee console.log( 'resolved PROB', @@ -101,10 +110,9 @@ export const getMktFixedPayouts = ( creatorPayout ) - return payouts - .map(({ userId, payout }) => ({ userId, payout })) - .concat([{ userId: contract.creatorId, payout: creatorPayout }]) // add creator fee - .concat(getLiquidityPoolProbPayouts(contract, p, liquidities)) + const liquidityPayouts = getLiquidityPoolProbPayouts(contract, p, liquidities) + + return { payouts, creatorPayout, liquidityPayouts, collectedFees } } export const getLiquidityPoolProbPayouts = ( diff --git a/common/payouts.ts b/common/payouts.ts index 57df7257..33f58120 100644 --- a/common/payouts.ts +++ b/common/payouts.ts @@ -38,6 +38,18 @@ export const getLoanPayouts = (bets: Bet[]): Payout[] => { return _.toPairs(loansByUser).map(([userId, payout]) => ({ userId, payout })) } +export const groupPayoutsByUser = (payouts: Payout[]) => { + const groups = _.groupBy(payouts, (payout) => payout.userId) + return _.mapValues(groups, (group) => _.sumBy(group, (g) => g.payout)) +} + +export type PayoutInfo = { + payouts: Payout[] + creatorPayout: number + liquidityPayouts: Payout[] + collectedFees: Fees +} + export const getPayouts = ( outcome: string, resolutions: { @@ -47,16 +59,15 @@ export const getPayouts = ( bets: Bet[], liquidities: LiquidityProvision[], resolutionProbability?: number -): [Payout[], Fees] => { +): PayoutInfo => { if (contract.mechanism === 'cpmm-1' && contract.outcomeType === 'BINARY') { - const payouts = getFixedPayouts( + return getFixedPayouts( outcome, contract, bets, liquidities, resolutionProbability ) - return [payouts, contract.collectedFees] } return getDpmPayouts( @@ -74,7 +85,7 @@ export const getFixedPayouts = ( bets: Bet[], liquidities: LiquidityProvision[], resolutionProbability?: number -): Payout[] => { +) => { switch (outcome) { case 'YES': case 'NO': @@ -100,36 +111,27 @@ export const getDpmPayouts = ( contract: Contract, bets: Bet[], resolutionProbability?: number -) => { +): PayoutInfo => { const openBets = bets.filter((b) => !b.isSold && !b.sale) switch (outcome) { case 'YES': case 'NO': - return getDpmStandardPayouts(outcome, contract, openBets) as [ - Payout[], - Fees - ] + return getDpmStandardPayouts(outcome, contract, openBets) case 'MKT': return contract.outcomeType === 'FREE_RESPONSE' - ? (getPayoutsMultiOutcome( + ? getPayoutsMultiOutcome( resolutions, contract as FullContract, openBets - ) as [Payout[], Fees]) - : (getDpmMktPayouts(contract, openBets, resolutionProbability) as [ - Payout[], - Fees - ]) + ) + : getDpmMktPayouts(contract, openBets, resolutionProbability) case 'CANCEL': - return getDpmCancelPayouts(contract, openBets) as [Payout[], Fees] + return getDpmCancelPayouts(contract, openBets) default: // Outcome is a free response answer id. - return getDpmStandardPayouts(outcome, contract, openBets) as [ - Payout[], - Fees - ] + return getDpmStandardPayouts(outcome, contract, openBets) } } diff --git a/common/scoring.ts b/common/scoring.ts index c4cd4c48..b55a29e5 100644 --- a/common/scoring.ts +++ b/common/scoring.ts @@ -34,7 +34,7 @@ export function scoreUsersByContract( bets, (bet) => bet.isSold || bet.sale ) - const [resolvePayouts] = getPayouts( + const { payouts: resolvePayouts } = getPayouts( resolution, {}, contract, diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index 322579a5..c51222be 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -127,7 +127,7 @@ export const createContract = functions manaLimitPerUser ?? 0 ) - if (!isFree && ante) await chargeUser(creator.id, ante) + if (!isFree && ante) await chargeUser(creator.id, ante, true) await contractRef.create(contract) diff --git a/functions/src/resolve-market.ts b/functions/src/resolve-market.ts index 5a2edec2..efc8e92f 100644 --- a/functions/src/resolve-market.ts +++ b/functions/src/resolve-market.ts @@ -7,7 +7,12 @@ import { User } from '../../common/user' import { Bet } from '../../common/bet' import { getUser, isProd, payUser } from './utils' import { sendMarketResolutionEmail } from './emails' -import { getLoanPayouts, getPayouts } from '../../common/payouts' +import { + getLoanPayouts, + getPayouts, + groupPayoutsByUser, + Payout, +} from '../../common/payouts' import { removeUndefinedProps } from '../../common/util/object' import { LiquidityProvision } from '../../common/liquidity-provision' @@ -89,14 +94,15 @@ export const resolveMarket = functions (doc) => doc.data() as LiquidityProvision ) - const [payouts, collectedFees] = getPayouts( - outcome, - resolutions ?? {}, - contract, - bets, - liquidities, - resolutionProbability - ) + const { payouts, creatorPayout, liquidityPayouts, collectedFees } = + getPayouts( + outcome, + resolutions ?? {}, + contract, + bets, + liquidities, + resolutionProbability + ) await contractDoc.update( removeUndefinedProps({ @@ -115,28 +121,26 @@ export const resolveMarket = functions const openBets = bets.filter((b) => !b.isSold && !b.sale) const loanPayouts = getLoanPayouts(openBets) - if (!isProd) console.log('payouts:', payouts) + if (!isProd) + console.log( + 'payouts:', + payouts, + 'creator payout:', + creatorPayout, + 'liquidity payout:' + ) - const groups = _.groupBy( - [...payouts, ...loanPayouts], - (payout) => payout.userId - ) - const userPayouts = _.mapValues(groups, (group) => - _.sumBy(group, (g) => g.payout) - ) + if (creatorPayout) + await processPayouts( + [{ userId: creatorId, payout: creatorPayout }], + true + ) - const groupsWithoutLoans = _.groupBy(payouts, (payout) => payout.userId) - const userPayoutsWithoutLoans = _.mapValues(groupsWithoutLoans, (group) => - _.sumBy(group, (g) => g.payout) - ) + await processPayouts(liquidityPayouts, true) - const payoutPromises = Object.entries(userPayouts).map( - ([userId, payout]) => payUser(userId, payout) - ) + const result = await processPayouts([...payouts, ...loanPayouts]) - const result = await Promise.all(payoutPromises) - .catch((e) => ({ status: 'error', message: e })) - .then(() => ({ status: 'success' })) + const userPayoutsWithoutLoans = groupPayoutsByUser(payouts) await sendResolutionEmails( openBets, @@ -152,6 +156,18 @@ export const resolveMarket = functions } ) +const processPayouts = async (payouts: Payout[], isDeposit = false) => { + const userPayouts = groupPayoutsByUser(payouts) + + const payoutPromises = Object.entries(userPayouts).map(([userId, payout]) => + payUser(userId, payout, isDeposit) + ) + + return await Promise.all(payoutPromises) + .catch((e) => ({ status: 'error', message: e })) + .then(() => ({ status: 'success' })) +} + const sendResolutionEmails = async ( openBets: Bet[], userPayouts: { [userId: string]: number }, diff --git a/functions/src/scripts/pay-out-contract-again.ts b/functions/src/scripts/pay-out-contract-again.ts index 7672bf7b..916b9efb 100644 --- a/functions/src/scripts/pay-out-contract-again.ts +++ b/functions/src/scripts/pay-out-contract-again.ts @@ -24,7 +24,8 @@ async function checkIfPayOutAgain(contractRef: DocRef, contract: Contract) { if (loanedBets.length && contract.resolution) { const { resolution, resolutions, resolutionProbability } = contract as any - const [payouts] = getPayouts( + + const { payouts } = getPayouts( resolution, resolutions, contract, diff --git a/functions/src/utils.ts b/functions/src/utils.ts index 28ef5445..c0f92f94 100644 --- a/functions/src/utils.ts +++ b/functions/src/utils.ts @@ -79,9 +79,13 @@ export const payUser = (userId: string, payout: number, isDeposit = false) => { return updateUserBalance(userId, payout, isDeposit) } -export const chargeUser = (userId: string, charge: number) => { +export const chargeUser = ( + userId: string, + charge: number, + isAnte?: boolean +) => { if (!isFinite(charge) || charge <= 0) throw new Error('User charge is not positive: ' + charge) - return updateUserBalance(userId, -charge) + return updateUserBalance(userId, -charge, isAnte) } diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx index dca03507..db3fefa6 100644 --- a/web/components/contract/contract-info-dialog.tsx +++ b/web/components/contract/contract-info-dialog.tsx @@ -85,6 +85,11 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) { {formatMoney(contract.volume)} + + Creator earnings + {formatMoney(contract.collectedFees.creatorFee)} + + Traders {tradersCount}