From 51866c86125758fe348470c3ad419f948f169634 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 6 May 2022 14:42:59 -0400 Subject: [PATCH] create numeric contracts --- common/answer.ts | 2 +- common/antes.ts | 38 +++++++++++++++++++++++++++- common/contract.ts | 4 +-- common/new-contract.ts | 43 +++++++++++++++++++++++++++++++- common/user.ts | 2 +- functions/src/create-contract.ts | 30 ++++++++++++++++++++-- 6 files changed, 111 insertions(+), 8 deletions(-) diff --git a/common/answer.ts b/common/answer.ts index 87e7d05f..9dcc3828 100644 --- a/common/answer.ts +++ b/common/answer.ts @@ -9,7 +9,7 @@ export type Answer = { userId: string username: string name: string - avatarUrl: string + avatarUrl?: string text: string } diff --git a/common/antes.ts b/common/antes.ts index c77308f4..5ff92ce4 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -1,9 +1,17 @@ import { Bet } from './bet' import { getDpmProbability } from './calculate-dpm' -import { Binary, CPMM, DPM, FreeResponse, FullContract } from './contract' +import { + Binary, + CPMM, + DPM, + FreeResponse, + FullContract, + Numeric, +} from './contract' import { User } from './user' import { LiquidityProvision } from './liquidity-provision' import { noFees } from './fees' +import * as _ from 'lodash' export const FIXED_ANTE = 100 @@ -106,3 +114,31 @@ export function getFreeAnswerAnte( return anteBet } + +export function getNumericAntes( + creator: User, + contract: FullContract, + ante: number +) { + const { bucketCount, createdTime } = contract + + const betAnte = ante / bucketCount + const betShares = Math.sqrt(ante ** 2 / bucketCount) + + return _.range(0, bucketCount).map((i) => { + const anteBet: Omit = { + userId: creator.id, + contractId: contract.id, + amount: betAnte, + shares: betShares, + outcome: i.toString(), + probBefore: 0, + probAfter: 1 / bucketCount, + createdTime, + isAnte: true, + fees: noFees, + } + + return anteBet + }) +} diff --git a/common/contract.ts b/common/contract.ts index 1d407cbd..c35d1660 100644 --- a/common/contract.ts +++ b/common/contract.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import * as _ from 'lodash' import { Answer } from './answer' import { Fees } from './fees' @@ -12,7 +12,7 @@ export type FullContract< creatorId: string creatorName: string creatorUsername: string - creatorAvatarUrl: string + creatorAvatarUrl?: string question: string description: string // More info about what the contract is about diff --git a/common/new-contract.ts b/common/new-contract.ts index b86ebb71..5a20742d 100644 --- a/common/new-contract.ts +++ b/common/new-contract.ts @@ -1,3 +1,5 @@ +import * as _ from 'lodash' + import { PHANTOM_ANTE } from './antes' import { Binary, @@ -5,6 +7,7 @@ import { CPMM, DPM, FreeResponse, + Numeric, outcomeType, } from './contract' import { User } from './user' @@ -22,7 +25,12 @@ export function getNewContract( initialProb: number, ante: number, closeTime: number, - extraTags: string[] + extraTags: string[], + + // used for numeric markets + bucketCount: number, + min: number, + max: number ) { const tags = parseTags( `${question} ${description} ${extraTags.map((tag) => `#${tag}`).join(' ')}` @@ -32,6 +40,8 @@ export function getNewContract( const propsByOutcomeType = outcomeType === 'BINARY' ? getBinaryCpmmProps(initialProb, ante) // getBinaryDpmProps(initialProb, ante) + : outcomeType === 'NUMERIC' + ? getNumericProps(ante, bucketCount, min, max) : getFreeAnswerProps(ante) const volume = outcomeType === 'BINARY' ? 0 : ante @@ -115,6 +125,37 @@ const getFreeAnswerProps = (ante: number) => { return system } +const getNumericProps = ( + ante: number, + bucketCount: number, + min: number, + max: number +) => { + const buckets = _.range(0, bucketCount).map((i) => i.toString()) + + const betAnte = ante / bucketCount + const pool = Object.fromEntries(buckets.map((answer) => [answer, betAnte])) + const totalBets = pool + + const betShares = Math.sqrt(ante ** 2 / bucketCount) + const totalShares = Object.fromEntries( + buckets.map((answer) => [answer, betShares]) + ) + + const system: DPM & Numeric = { + mechanism: 'dpm-2', + outcomeType: 'NUMERIC', + pool, + totalBets, + totalShares, + bucketCount, + min, + max, + } + + return system +} + const getMultiProps = ( outcomes: string[], initialProbs: number[], diff --git a/common/user.ts b/common/user.ts index ce586774..8f8e6d0d 100644 --- a/common/user.ts +++ b/common/user.ts @@ -4,7 +4,7 @@ export type User = { name: string username: string - avatarUrl: string + avatarUrl?: string // For their user page bio?: string diff --git a/functions/src/create-contract.ts b/functions/src/create-contract.ts index dfc8128d..b748eca6 100644 --- a/functions/src/create-contract.ts +++ b/functions/src/create-contract.ts @@ -12,6 +12,7 @@ import { MAX_DESCRIPTION_LENGTH, MAX_QUESTION_LENGTH, MAX_TAG_LENGTH, + Numeric, outcomeType, } from '../../common/contract' import { slugify } from '../../common/util/slugify' @@ -22,6 +23,7 @@ import { getAnteBets, getCpmmInitialLiquidity, getFreeAnswerAnte, + getNumericAntes, HOUSE_LIQUIDITY_PROVIDER_ID, MINIMUM_ANTE, } from '../../common/antes' @@ -63,7 +65,9 @@ export const createContract = functions tags = tags?.map((tag) => tag.toString().slice(0, MAX_TAG_LENGTH)) let outcomeType = data.outcomeType ?? 'BINARY' - if (!['BINARY', 'MULTI', 'FREE_RESPONSE'].includes(outcomeType)) + if ( + !['BINARY', 'MULTI', 'FREE_RESPONSE', 'NUMERIC'].includes(outcomeType) + ) return { status: 'error', message: 'Invalid outcomeType' } if ( @@ -115,7 +119,10 @@ export const createContract = functions initialProb, ante, closeTime, - tags ?? [] + tags ?? [], + 1000, + 1, + 1000 ) if (!isFree && ante) await chargeUser(creator.id, ante) @@ -174,6 +181,25 @@ export const createContract = functions anteBetDoc.id ) await anteBetDoc.set(anteBet) + } else if (outcomeType === 'NUMERIC') { + const antes = getNumericAntes( + creator, + contract as FullContract, + ante + ) + + await firestore.runTransaction(async (transaction) => { + for (let anteBet of antes) { + const anteBetDoc = firestore + .collection(`contracts/${contract.id}/bets`) + .doc() + + await transaction.set(anteBetDoc, { + id: anteBetDoc.id, + ...anteBet, + }) + } + }) } }