Skip to content

Commit

Permalink
Oracle improvements (#2260)
Browse files Browse the repository at this point in the history
* Add oracle feeder improvements

* Add feedback review
  • Loading branch information
kattylucy authored Jul 4, 2024
1 parent 6a4d600 commit 3548a40
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 125 deletions.
72 changes: 72 additions & 0 deletions centrifuge-app/src/pages/IssuerPool/Access/ChangeTreshold.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Box, Select, Shelf, Stack, Text } from '@centrifuge/fabric'
import { ErrorMessage, Field, FieldProps, getIn, useFormikContext } from 'formik'
import { min } from '../../../utils/validation'

type ChangeThresholdProps = {
primaryText: string
secondaryText: string
isEditing: boolean
fieldName: string
signersFieldName: string
minThreshold?: number
validate?: (value: any) => string | undefined
disabled?: boolean
type: string
}

export const ChangeThreshold = ({
primaryText,
secondaryText,
isEditing,
fieldName,
signersFieldName,
minThreshold = 2,
validate,
disabled,
type,
}: ChangeThresholdProps) => {
const form = useFormikContext<any>()
const threshold = getIn(form.values, fieldName)
const signers = getIn(form.values, signersFieldName)

return (
<Stack gap={2}>
<Text as="h3" variant="heading3">
{primaryText}
</Text>
<Text as="p" variant="body2" color="textSecondary">
{secondaryText}
</Text>

<Shelf gap={2}>
{isEditing && (
<Box maxWidth={150}>
<Field name={fieldName} validate={validate ?? min(minThreshold, `Needs at least ${minThreshold} ${type}`)}>
{({ field, form }: FieldProps) => (
<Select
name={fieldName}
onChange={(event) => form.setFieldValue(fieldName, Number(event.target.value))}
onBlur={field.onBlur}
value={field.value}
options={signers.map((_: any, i: number) => ({
value: `${i + 1}`,
label: `${i + 1}`,
disabled: i < minThreshold - 1,
}))}
placeholder=""
disabled={disabled}
/>
)}
</Field>
</Box>
)}
<Text>
{!isEditing && threshold} out of {signers.length} {type}
</Text>
</Shelf>
<Text variant="label2" color="statusCritical">
<ErrorMessage name={fieldName} />
</Text>
</Stack>
)
}
51 changes: 12 additions & 39 deletions centrifuge-app/src/pages/IssuerPool/Access/MultisigForm.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { addressToHex } from '@centrifuge/centrifuge-js'
import { Box, Button, IconMinusCircle, Select, Shelf, Stack, Text } from '@centrifuge/fabric'
import { ErrorMessage, Field, FieldArray, FieldProps, useFormikContext } from 'formik'
import { Button, IconMinusCircle, Stack, Text } from '@centrifuge/fabric'
import { FieldArray, useFormikContext } from 'formik'
import * as React from 'react'
import { DataTable } from '../../../components/DataTable'
import { Identity } from '../../../components/Identity'
import { min } from '../../../utils/validation'
import { AddAddressInput } from '../Configuration/AddAddressInput'
import { ChangeThreshold } from './ChangeTreshold'
import type { PoolManagersInput } from './PoolManagers'

type Row = { address: string; index: number }
Expand Down Expand Up @@ -72,42 +72,15 @@ export function MultisigForm({ isEditing = true, canRemoveFirst = true, isLoadin
)}
</Stack>
<Stack gap={2}>
<Text as="h3" variant="heading3">
Configuration change threshold
</Text>
<Text as="p" variant="body2" color="textSecondary">
For additional security, changing the pool configuration (e.g. the tranche structure or write-off policy)
requires multiple signers. Any such change will require the confirmation of:
</Text>

<Shelf gap={2}>
{isEditing && (
<Box maxWidth={150}>
<Field name="adminMultisig.threshold" validate={min(2, 'Multisig needs at least two signers')}>
{({ field, form }: FieldProps) => (
<Select
name="adminMultisig.threshold"
onChange={(event) => form.setFieldValue('adminMultisig.threshold', Number(event.target.value))}
onBlur={field.onBlur}
value={field.value}
options={adminMultisig.signers.map((_, i) => ({
value: `${i + 1}`,
label: `${i + 1}`,
disabled: i === 0,
}))}
placeholder=""
/>
)}
</Field>
</Box>
)}
<Text>
{!isEditing && adminMultisig.threshold} out of {adminMultisig.signers.length} managers
</Text>
</Shelf>
<Text variant="label2" color="statusCritical">
<ErrorMessage name="adminMultisig.threshold" />
</Text>
<ChangeThreshold
secondaryText=" For additional security, changing the pool configuration (e.g. the tranche structure or write-off policy)
requires multiple signers. Any such change will require the confirmation of:"
primaryText="Configuration change threshold"
isEditing={isEditing}
fieldName="adminMultisig.threshold"
signersFieldName="adminMultisig.signers"
type="managers"
/>
</Stack>
</Stack>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ export function WriteOffGroups() {
)}
</Shelf>
}
subtitle="At least one write-off activity is required"
headerRight={
<>
{isEditing ? (
Expand Down
2 changes: 0 additions & 2 deletions centrifuge-app/src/pages/IssuerPool/Configuration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { EpochAndTranches } from './EpochAndTranches'
import { Issuer } from './Issuer'
import { LoanTemplates } from './LoanTemplates'
import { PoolConfig } from './PoolConfig'
import { WriteOffGroups } from './WriteOffGroups'

export function IssuerPoolConfigurationPage() {
return (
Expand All @@ -36,7 +35,6 @@ function IssuerPoolConfiguration() {
<Details />
<Issuer />
<EpochAndTranches />
<WriteOffGroups />
<LoanTemplates />
{editPoolConfig && <PoolConfig poolId={poolId} />}
</>
Expand Down
184 changes: 101 additions & 83 deletions centrifuge-app/src/pages/IssuerPool/Pricing/OracleFeeders.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { addressToHex } from '@centrifuge/centrifuge-js'
import { useCentrifugeApi, useCentrifugeQuery, useCentrifugeTransaction } from '@centrifuge/centrifuge-react'
import { Box, Button, IconMinusCircle, NumberInput, Stack, Text } from '@centrifuge/fabric'
import { Box, Button, IconMinusCircle, Stack, Text } from '@centrifuge/fabric'
import { blake2AsHex } from '@polkadot/util-crypto'
import { FieldArray, Form, FormikProvider, useFormik } from 'formik'
import * as React from 'react'
import { map } from 'rxjs'
import { ButtonGroup } from '../../../components/ButtonGroup'
import { DataTable } from '../../../components/DataTable'
import { FieldWithErrorMessage } from '../../../components/FieldWithErrorMessage'
import { Identity } from '../../../components/Identity'
import { PageSection } from '../../../components/PageSection'
import { usePoolAdmin } from '../../../utils/usePermissions'
import { positiveNumber } from '../../../utils/validation'
import { ChangeThreshold } from '../Access/ChangeTreshold'
import { AddAddressInput } from '../Configuration/AddAddressInput'
import { WriteOffGroups } from '../Configuration/WriteOffGroups'

type FormValues = {
feeders: string[]
Expand All @@ -30,7 +31,7 @@ export function OracleFeeders({ poolId }: { poolId: string }) {

const api = useCentrifugeApi()
const { execute, isLoading } = useCentrifugeTransaction(
'Set oracle prices',
'Set oracle providers',
(cent) => (args: [values: FormValues], options) => {
const [values] = args
const info = {
Expand Down Expand Up @@ -85,91 +86,108 @@ export function OracleFeeders({ poolId }: { poolId: string }) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialValues, isEditing])

// Use useEffect to update threshold value when signers array changes
React.useEffect(() => {
if (form.values.feeders.length > 0 && form.values.minFeeders === 0) {
form.setFieldValue('minFeeders', 1)
}
}, [form.values.feeders, form.values.minFeeders, form.setFieldValue])

const rows = React.useMemo(() => form.values.feeders.map((a, i) => ({ address: a, index: i })), [form.values.feeders])

return (
<FormikProvider value={form}>
<Form>
<PageSection
title="Oracle feeders"
headerRight={
isEditing ? (
<ButtonGroup variant="small">
<Button variant="secondary" onClick={() => setIsEditing(false)} small>
Cancel
</Button>
<Button
type="submit"
small
loading={isLoading}
loadingMessage={isLoading ? 'Pending...' : undefined}
key="done"
disabled={!admin}
>
Done
<Box>
<FormikProvider value={form}>
<Form>
<PageSection
title="Oracle providers"
headerRight={
isEditing ? (
<ButtonGroup variant="small">
<Button variant="secondary" onClick={() => setIsEditing(false)} small>
Cancel
</Button>
<Button
type="submit"
small
loading={isLoading}
loadingMessage={isLoading ? 'Pending...' : undefined}
key="done"
disabled={!admin}
>
Done
</Button>
</ButtonGroup>
) : (
<Button variant="secondary" onClick={() => setIsEditing(true)} small key="edit">
Edit
</Button>
</ButtonGroup>
) : (
<Button variant="secondary" onClick={() => setIsEditing(true)} small key="edit">
Edit
</Button>
)
}
>
<Stack gap={2}>
<Box width={200}>
<FieldWithErrorMessage
as={NumberInput}
label="Minimum feeders"
name="minFeeders"
validate={positiveNumber()}
disabled={!isEditing}
/>
</Box>
<FieldArray name="feeders">
{(fldArr) => (
<Stack gap={3}>
<DataTable
data={rows}
columns={[
{
align: 'left',
header: 'Address(es)',
cell: (row: Row) => (
<Text variant="body2">
<Identity address={row.address} clickToCopy showIcon labelForConnectedAddress={false} />
</Text>
),
},
{
header: '',
cell: (row: Row) =>
isEditing && (
<Button
variant="tertiary"
icon={IconMinusCircle}
onClick={() => fldArr.remove(row.index)}
disabled={isLoading}
/>
)
}
>
<Stack gap={2}>
<Text as="p" variant="body2" color="textSecondary">
Add or remove addresses that can provide oracle updates for the onchain NAV.
</Text>
<FieldArray name="feeders">
{(fldArr) => (
<Stack gap={3}>
<DataTable
data={rows}
columns={[
{
align: 'left',
header: 'Address(es)',
cell: (row: Row) => (
<Text variant="body2">
<Identity address={row.address} clickToCopy showIcon labelForConnectedAddress={false} />
</Text>
),
width: '72px',
},
]}
/>
{isEditing && !isLoading && (
<AddAddressInput
existingAddresses={form.values.feeders}
onAdd={(address) => {
fldArr.push(addressToHex(address))
}}
},
{
header: '',
cell: (row: Row) =>
isEditing && (
<Button
variant="tertiary"
icon={IconMinusCircle}
onClick={() => fldArr.remove(row.index)}
disabled={isLoading}
/>
),
width: '72px',
},
]}
/>
)}
</Stack>
)}
</FieldArray>
</Stack>
</PageSection>
</Form>
</FormikProvider>
{isEditing && !isLoading && (
<AddAddressInput
existingAddresses={form.values.feeders}
onAdd={(address) => {
fldArr.push(addressToHex(address))
}}
/>
)}
</Stack>
)}
</FieldArray>
<Box>
<ChangeThreshold
primaryText="Oracle update threshold"
secondaryText="Determine how many oracle providers are required before a pricing update is finalized and will become reflected in the NAV."
isEditing={isEditing}
fieldName="minFeeders"
signersFieldName="feeders"
validate={positiveNumber()}
disabled={!isEditing}
minThreshold={1}
type="providers"
/>
</Box>
</Stack>
</PageSection>
</Form>
</FormikProvider>
<WriteOffGroups />
</Box>
)
}

0 comments on commit 3548a40

Please sign in to comment.