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
This commit is contained in:
mantikoros 2022-05-09 16:04:40 -05:00 committed by GitHub
parent a5b0372a6e
commit 5135135e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 84 deletions

View File

@ -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<Fees>(finalFees, contract.collectedFees ?? {})
const collectedFees = addObjects<Fees>(
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<Fees>(finalFees, contract.collectedFees ?? {})
const collectedFees = addObjects<Fees>(
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<Fees>(finalFees, contract.collectedFees ?? noFees)
const collectedFees = addObjects<Fees>(
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,
}
}

View File

@ -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 = (

View File

@ -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<DPM, Multi | FreeResponse>,
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)
}
}

View File

@ -34,7 +34,7 @@ export function scoreUsersByContract(
bets,
(bet) => bet.isSold || bet.sale
)
const [resolvePayouts] = getPayouts(
const { payouts: resolvePayouts } = getPayouts(
resolution,
{},
contract,

View File

@ -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)

View File

@ -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 },

View File

@ -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,

View File

@ -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)
}

View File

@ -85,6 +85,11 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
<td>{formatMoney(contract.volume)}</td>
</tr>
<tr>
<td>Creator earnings</td>
<td>{formatMoney(contract.collectedFees.creatorFee)}</td>
</tr>
<tr>
<td>Traders</td>
<td>{tradersCount}</td>