diff --git a/common/calculate-cpmm.ts b/common/calculate-cpmm.ts index 3470088a..493b5fa9 100644 --- a/common/calculate-cpmm.ts +++ b/common/calculate-cpmm.ts @@ -272,16 +272,16 @@ export function getCpmmLiquidityPoolWeights( const liquidityShares = liquidities.map(calcLiqudity) const shareSum = sum(liquidityShares) - const includedLiquidities = excludeAntes - ? liquidityShares.filter((_, i) => !liquidities[i].isAnte) - : liquidityShares - - const weights = includedLiquidities.map((s, i) => ({ - weight: s / shareSum, + const weights = liquidityShares.map((shares, i) => ({ + weight: shares / shareSum, providerId: liquidities[i].userId, })) - const userWeights = groupBy(weights, (w) => w.providerId) + const includedWeights = excludeAntes + ? weights.filter((_, i) => !liquidities[i].isAnte) + : weights + + const userWeights = groupBy(includedWeights, (w) => w.providerId) const totalUserWeights = mapValues(userWeights, (userWeight) => sumBy(userWeight, (w) => w.weight) ) diff --git a/functions/src/index.ts b/functions/src/index.ts index d3e9b42f..0d0de3ba 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,7 +3,6 @@ import * as admin from 'firebase-admin' admin.initializeApp() // v1 -export * from './transact' export * from './stripe' export * from './create-user' export * from './on-create-bet' @@ -28,6 +27,7 @@ export * from './on-create-txn' // v2 export * from './health' +export * from './transact' export * from './change-user-info' export * from './create-answer' export * from './place-bet' diff --git a/functions/src/stripe.ts b/functions/src/stripe.ts index 67309aa8..450bbe35 100644 --- a/functions/src/stripe.ts +++ b/functions/src/stripe.ts @@ -1,4 +1,4 @@ -import * as functions from 'firebase-functions' +import { onRequest } from 'firebase-functions/v2/https' import * as admin from 'firebase-admin' import Stripe from 'stripe' @@ -42,9 +42,9 @@ const manticDollarStripePrice = isProd() 10000: 'price_1K8bEiGdoFKoCJW7Us4UkRHE', } -export const createCheckoutSession = functions - .runWith({ minInstances: 1, secrets: ['STRIPE_APIKEY'] }) - .https.onRequest(async (req, res) => { +export const createcheckoutsession = onRequest( + { minInstances: 1, secrets: ['STRIPE_APIKEY'] }, + async (req, res) => { const userId = req.query.userId?.toString() const manticDollarQuantity = req.query.manticDollarQuantity?.toString() @@ -86,14 +86,15 @@ export const createCheckoutSession = functions }) res.redirect(303, session.url || '') - }) + } +) -export const stripeWebhook = functions - .runWith({ +export const stripewebhook = onRequest( + { minInstances: 1, secrets: ['MAILGUN_KEY', 'STRIPE_APIKEY', 'STRIPE_WEBHOOKSECRET'], - }) - .https.onRequest(async (req, res) => { + }, + async (req, res) => { const stripe = initStripe() let event @@ -115,7 +116,8 @@ export const stripeWebhook = functions } res.status(200).send('success') - }) + } +) const issueMoneys = async (session: StripeSession) => { const { id: sessionId } = session diff --git a/functions/src/transact.ts b/functions/src/transact.ts index cd091b83..113afc0b 100644 --- a/functions/src/transact.ts +++ b/functions/src/transact.ts @@ -1,40 +1,40 @@ -import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' import { User } from '../../common/user' import { Txn } from '../../common/txn' import { removeUndefinedProps } from '../../common/util/object' +import { APIError, newEndpoint } from './api' export type TxnData = Omit -export const transact = functions - .runWith({ minInstances: 1 }) - .https.onCall(async (data: TxnData, context) => { - const userId = context?.auth?.uid - if (!userId) return { status: 'error', message: 'Not authorized' } +// TODO: We totally fail to validate most of the input to this function, +// so anyone can spam our database with malformed transactions. - const { amount, fromType, fromId } = data +export const transact = newEndpoint({}, async (req, auth) => { + const data = req.body + const { amount, fromType, fromId } = data - if (fromType !== 'USER') - return { - status: 'error', - message: "From type is only implemented for type 'user'.", - } + if (fromType !== 'USER') + throw new APIError(400, "From type is only implemented for type 'user'.") - if (fromId !== userId) - return { - status: 'error', - message: 'Must be authenticated with userId equal to specified fromId.', - } + if (fromId !== auth.uid) + throw new APIError( + 403, + 'Must be authenticated with userId equal to specified fromId.' + ) - if (isNaN(amount) || !isFinite(amount)) - return { status: 'error', message: 'Invalid amount' } + if (isNaN(amount) || !isFinite(amount)) + throw new APIError(400, 'Invalid amount') - // Run as transaction to prevent race conditions. - return await firestore.runTransaction(async (transaction) => { - await runTxn(transaction, data) - }) + // Run as transaction to prevent race conditions. + return await firestore.runTransaction(async (transaction) => { + const result = await runTxn(transaction, data) + if (result.status == 'error') { + throw new APIError(500, result.message ?? 'An unknown error occurred.') + } + return result }) +}) export async function runTxn( fbTransaction: admin.firestore.Transaction, diff --git a/web/components/tipper.tsx b/web/components/tipper.tsx index 6f7dfbcb..e4b6580f 100644 --- a/web/components/tipper.tsx +++ b/web/components/tipper.tsx @@ -11,7 +11,7 @@ import { debounce, sum } from 'lodash' import { useEffect, useRef, useState } from 'react' import { CommentTips } from 'web/hooks/use-tip-txns' import { useUser } from 'web/hooks/use-user' -import { transact } from 'web/lib/firebase/fn-call' +import { transact } from 'web/lib/firebase/api-call' import { track } from 'web/lib/service/analytics' import { Row } from './layout/row' import { Tooltip } from './tooltip' diff --git a/web/lib/firebase/api-call.ts b/web/lib/firebase/api-call.ts index 695117f9..94da9f09 100644 --- a/web/lib/firebase/api-call.ts +++ b/web/lib/firebase/api-call.ts @@ -53,6 +53,11 @@ export function getFunctionUrl(name: string) { export function createAnswer(params: any) { return call(getFunctionUrl('createanswer'), 'POST', params) } + +export function transact(params: any) { + return call(getFunctionUrl('transact'), 'POST', params) +} + export function changeUserInfo(params: any) { return call(getFunctionUrl('changeuserinfo'), 'POST', params) } diff --git a/web/lib/firebase/fn-call.ts b/web/lib/firebase/fn-call.ts index 27a5e8f3..2f299aea 100644 --- a/web/lib/firebase/fn-call.ts +++ b/web/lib/firebase/fn-call.ts @@ -1,5 +1,4 @@ import { httpsCallable } from 'firebase/functions' -import { Txn } from 'common/txn' import { User } from 'common/user' import { randomString } from 'common/util/random' import './init' @@ -9,11 +8,6 @@ import { safeLocalStorage } from '../util/local' export const cloudFunction = (name: string) => httpsCallable(functions, name) -export const transact = cloudFunction< - Omit, - { status: 'error' | 'success'; message?: string; txn?: Txn } ->('transact') - export const createUser: () => Promise = () => { const local = safeLocalStorage() let deviceToken = local?.getItem('device-token') diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index e72fe141..7f007031 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -39,6 +39,9 @@ import { filterDefined } from 'common/util/array' import { addUserToGroupViaSlug } from 'web/lib/firebase/groups' import { removeUndefinedProps } from 'common/util/object' import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) + import { track } from '@amplitude/analytics-browser' export const users = coll('users') diff --git a/web/lib/service/stripe.ts b/web/lib/service/stripe.ts index 395f7093..bedd68aa 100644 --- a/web/lib/service/stripe.ts +++ b/web/lib/service/stripe.ts @@ -1,12 +1,11 @@ -import { PROJECT_ID } from 'common/envs/constants' +import { getFunctionUrl } from 'web/lib/firebase/api-call' export const checkoutURL = ( userId: string, manticDollarQuantity: number, referer = '' ) => { - const endpoint = `https://us-central1-${PROJECT_ID}.cloudfunctions.net/createCheckoutSession` - + const endpoint = getFunctionUrl('createcheckoutsession') return `${endpoint}?userId=${userId}&manticDollarQuantity=${manticDollarQuantity}&referer=${encodeURIComponent( referer )}` diff --git a/web/pages/charity/[charitySlug].tsx b/web/pages/charity/[charitySlug].tsx index 7c3ce51b..c3e0912a 100644 --- a/web/pages/charity/[charitySlug].tsx +++ b/web/pages/charity/[charitySlug].tsx @@ -10,7 +10,7 @@ import { Spacer } from 'web/components/layout/spacer' import { User } from 'common/user' import { useUser } from 'web/hooks/use-user' import { Linkify } from 'web/components/linkify' -import { transact } from 'web/lib/firebase/fn-call' +import { transact } from 'web/lib/firebase/api-call' import { charities, Charity } from 'common/charity' import { useRouter } from 'next/router' import Custom404 from '../404'