Inga/admin rules resolve (#880)

* Giving admin permission to resolve all markets that have closed after 7 days.
This commit is contained in:
ingawei 2022-09-14 22:28:40 -05:00 committed by GitHub
parent 9aa56dd193
commit ccf02bdba8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 29 deletions

View File

@ -21,7 +21,10 @@ export function isWhitelisted(email?: string) {
}
// TODO: Before open sourcing, we should turn these into env vars
export function isAdmin(email: string) {
export function isAdmin(email?: string) {
if (!email) {
return false
}
return ENV_CONFIG.adminEmails.includes(email)
}

View File

@ -74,6 +74,7 @@ export const PROD_CONFIG: EnvConfig = {
'iansphilips@gmail.com', // Ian
'd4vidchee@gmail.com', // D4vid
'federicoruizcassarino@gmail.com', // Fede
'ingawei@gmail.com', //Inga
],
visibility: 'PUBLIC',

View File

@ -14,7 +14,8 @@ service cloud.firestore {
'manticmarkets@gmail.com',
'iansphilips@gmail.com',
'd4vidchee@gmail.com',
'federicoruizcassarino@gmail.com'
'federicoruizcassarino@gmail.com',
'ingawei@gmail.com'
]
}

View File

@ -16,7 +16,7 @@ import {
groupPayoutsByUser,
Payout,
} from '../../common/payouts'
import { isManifoldId } from '../../common/envs/constants'
import { isAdmin, isManifoldId } from '../../common/envs/constants'
import { removeUndefinedProps } from '../../common/util/object'
import { LiquidityProvision } from '../../common/liquidity-provision'
import { APIError, newEndpoint, validate } from './api'
@ -76,13 +76,18 @@ export const resolvemarket = newEndpoint(opts, async (req, auth) => {
throw new APIError(404, 'No contract exists with the provided ID')
const contract = contractSnap.data() as Contract
const { creatorId, closeTime } = contract
const firebaseUser = await admin.auth().getUser(auth.uid)
const { value, resolutions, probabilityInt, outcome } = getResolutionParams(
contract,
req.body
)
if (creatorId !== auth.uid && !isManifoldId(auth.uid))
if (
creatorId !== auth.uid &&
!isManifoldId(auth.uid) &&
!isAdmin(firebaseUser.email)
)
throw new APIError(403, 'User is not creator of contract')
if (contract.resolution) throw new APIError(400, 'Contract already resolved')

View File

@ -11,6 +11,8 @@ import { ResolveConfirmationButton } from '../confirmation-button'
import { removeUndefinedProps } from 'common/util/object'
export function AnswerResolvePanel(props: {
isAdmin: boolean
isCreator: boolean
contract: FreeResponseContract | MultipleChoiceContract
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
setResolveOption: (
@ -18,7 +20,14 @@ export function AnswerResolvePanel(props: {
) => void
chosenAnswers: { [answerId: string]: number }
}) {
const { contract, resolveOption, setResolveOption, chosenAnswers } = props
const {
contract,
resolveOption,
setResolveOption,
chosenAnswers,
isAdmin,
isCreator,
} = props
const answers = Object.keys(chosenAnswers)
const [isSubmitting, setIsSubmitting] = useState(false)
@ -76,7 +85,14 @@ export function AnswerResolvePanel(props: {
return (
<Col className="gap-4 rounded">
<div>Resolve your market</div>
<Row className="justify-between">
<div>Resolve your market</div>
{isAdmin && !isCreator && (
<span className="rounded bg-red-200 p-1 text-xs text-red-600">
ADMIN
</span>
)}
</Row>
<Col className="gap-4 sm:flex-row sm:items-center">
<ChooseCancelSelector
className="sm:!flex-row sm:items-center"

View File

@ -24,10 +24,13 @@ import { Linkify } from 'web/components/linkify'
import { BuyButton } from 'web/components/yes-no-selector'
import { UserLink } from 'web/components/user-link'
import { Button } from 'web/components/button'
import { useAdmin } from 'web/hooks/use-admin'
import { needsAdminToResolve } from 'web/pages/[username]/[contractSlug]'
export function AnswersPanel(props: {
contract: FreeResponseContract | MultipleChoiceContract
}) {
const isAdmin = useAdmin()
const { contract } = props
const { creatorId, resolution, resolutions, totalBets, outcomeType } =
contract
@ -154,17 +157,20 @@ export function AnswersPanel(props: {
<CreateAnswerPanel contract={contract} />
)}
{user?.id === creatorId && !resolution && (
<>
<Spacer h={2} />
<AnswerResolvePanel
contract={contract}
resolveOption={resolveOption}
setResolveOption={setResolveOption}
chosenAnswers={chosenAnswers}
/>
</>
)}
{(user?.id === creatorId || (isAdmin && needsAdminToResolve(contract))) &&
!resolution && (
<>
<Spacer h={2} />
<AnswerResolvePanel
isAdmin={isAdmin}
isCreator={user?.id === creatorId}
contract={contract}
resolveOption={resolveOption}
setResolveOption={setResolveOption}
chosenAnswers={chosenAnswers}
/>
</>
)}
</Col>
)
}

View File

@ -12,11 +12,13 @@ import { BucketInput } from './bucket-input'
import { getPseudoProbability } from 'common/pseudo-numeric'
export function NumericResolutionPanel(props: {
isAdmin: boolean
isCreator: boolean
creator: User
contract: NumericContract | PseudoNumericContract
className?: string
}) {
const { contract, className } = props
const { contract, className, isAdmin, isCreator } = props
const { min, max, outcomeType } = contract
const [outcomeMode, setOutcomeMode] = useState<
@ -78,10 +80,20 @@ export function NumericResolutionPanel(props: {
: 'btn-disabled'
return (
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
<div className="mb-6 whitespace-nowrap text-2xl">Resolve market</div>
<Col
className={clsx(
'relative w-full rounded-md bg-white px-8 py-6',
className
)}
>
{isAdmin && !isCreator && (
<span className="absolute right-4 top-4 rounded bg-red-200 p-1 text-xs text-red-600">
ADMIN
</span>
)}
<div className="whitespace-nowrap text-2xl">Resolve market</div>
<div className="mb-3 text-sm text-gray-500">Outcome</div>
<div className="my-3 text-sm text-gray-500">Outcome</div>
<Spacer h={4} />

View File

@ -12,11 +12,13 @@ import { getProbability } from 'common/calculate'
import { BinaryContract, resolution } from 'common/contract'
export function ResolutionPanel(props: {
isAdmin: boolean
isCreator: boolean
creator: User
contract: BinaryContract
className?: string
}) {
const { contract, className } = props
const { contract, className, isAdmin, isCreator } = props
// const earnedFees =
// contract.mechanism === 'dpm-2'
@ -66,7 +68,12 @@ export function ResolutionPanel(props: {
: 'btn-disabled'
return (
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
<Col className={clsx('relative rounded-md bg-white px-8 py-6', className)}>
{isAdmin && !isCreator && (
<span className="absolute right-4 top-4 rounded bg-red-200 p-1 text-xs text-red-600">
ADMIN
</span>
)}
<div className="mb-6 whitespace-nowrap text-2xl">Resolve market</div>
<div className="mb-3 text-sm text-gray-500">Outcome</div>

View File

@ -45,6 +45,8 @@ import {
import { ContractsGrid } from 'web/components/contract/contracts-grid'
import { Title } from 'web/components/title'
import { usePrefetch } from 'web/hooks/use-prefetch'
import { useAdmin } from 'web/hooks/use-admin'
import dayjs from 'dayjs'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: {
@ -110,19 +112,28 @@ export default function ContractPage(props: {
)
}
// requires an admin to resolve a week after market closes
export function needsAdminToResolve(contract: Contract) {
return !contract.isResolved && dayjs().diff(contract.closeTime, 'day') > 7
}
export function ContractPageSidebar(props: {
user: User | null | undefined
contract: Contract
}) {
const { contract, user } = props
const { creatorId, isResolved, outcomeType } = contract
const isCreator = user?.id === creatorId
const isBinary = outcomeType === 'BINARY'
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
const isNumeric = outcomeType === 'NUMERIC'
const allowTrade = tradingAllowed(contract)
const allowResolve = !isResolved && isCreator && !!user
const isAdmin = useAdmin()
const allowResolve =
!isResolved &&
(isCreator || (needsAdminToResolve(contract) && isAdmin)) &&
!!user
const hasSidePanel =
(isBinary || isNumeric || isPseudoNumeric) && (allowTrade || allowResolve)
@ -139,9 +150,19 @@ export function ContractPageSidebar(props: {
))}
{allowResolve &&
(isNumeric || isPseudoNumeric ? (
<NumericResolutionPanel creator={user} contract={contract} />
<NumericResolutionPanel
isAdmin={isAdmin}
creator={user}
isCreator={isCreator}
contract={contract}
/>
) : (
<ResolutionPanel creator={user} contract={contract} />
<ResolutionPanel
isAdmin={isAdmin}
creator={user}
isCreator={isCreator}
contract={contract}
/>
))}
</Col>
) : null
@ -154,10 +175,8 @@ export function ContractPageContent(
}
) {
const { backToHome, comments, user } = props
const contract = useContractWithPreload(props.contract) ?? props.contract
usePrefetch(user?.id)
useTracking(
'view market',
{