Skip to content

Commit

Permalink
Merge pull request #653 from subquery/feat/new-allocation-design
Browse files Browse the repository at this point in the history
feat: new allocation design
  • Loading branch information
HuberTRoy authored Feb 20, 2024
2 parents 5f65b3a + c665598 commit 817e5c2
Show file tree
Hide file tree
Showing 19 changed files with 916 additions and 570 deletions.
15 changes: 11 additions & 4 deletions src/components/DeploymentInfo/DeploymentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useGetIfUnsafeDeployment } from '@hooks/useGetIfUnsafeDeployment';
import { Spinner, Typography } from '@subql/components';
import { ProjectType } from '@subql/contract-sdk/types';
import { Tooltip } from 'antd';
import { clsx } from 'clsx';

import { useProjectMetadata } from '../../containers';
import { useAsyncMemo } from '../../hooks';
Expand Down Expand Up @@ -42,7 +43,11 @@ export const DeploymentInfo: React.FC<Props> = ({ project, deploymentId, type })
<div className={styles.projectTextInfo}>
<div style={{ display: 'flex', height: 22 }}>
{project?.name && (
<Typography variant="large" style={{ marginRight: 10 }}>
<Typography
className="overflowEllipsis"
variant="large"
style={{ marginRight: 10, width: '100%', maxWidth: '200px' }}
>
{project?.name}
</Typography>
)}
Expand All @@ -58,13 +63,15 @@ export const DeploymentInfo: React.FC<Props> = ({ project, deploymentId, type })
</div>
)}
<div className={project?.name ? '' : styles.deployment}>
<Typography variant="small" className={styles.text}>
<Typography variant="small" className={clsx(styles.text, 'overflowEllipsis')} style={{ maxWidth: 200 }}>
{versionHeader}
</Typography>

<Copy value={deploymentId}>
<Copy value={deploymentId} position="flex-start">
<Typography variant="small" className={styles.text}>
{deploymentId ?? '-'}
{deploymentId
? `${deploymentId.slice(0, 5)}...${deploymentId.slice(deploymentId.length - 5, deploymentId.length)}`
: '-'}
</Typography>
</Copy>
</div>
Expand Down
138 changes: 86 additions & 52 deletions src/components/DoAllocate/DoAllocate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { SQNetworks } from '@subql/network-config';
import { ProjectType } from '@subql/network-query';
import { useAsyncMemo, useGetIndexerAllocationSummaryLazyQuery } from '@subql/react-hooks';
import { parseError, TOKEN } from '@utils';
import { Button, Form } from 'antd';
import { Button, Form, Radio } from 'antd';
import { useForm, useWatch } from 'antd/es/form/Form';
import BigNumber from 'bignumber.js';
import { useAccount } from 'wagmi';
Expand All @@ -31,9 +31,11 @@ interface IProps {
deploymentId?: string;
actionBtn?: React.ReactNode;
onSuccess?: () => void;
initialStatus?: 'Add' | 'Remove';
disabled?: boolean;
}

const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess }) => {
const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess, initialStatus, disabled }) => {
const { address: account } = useAccount();
const project = useProjectFromQuery(projectId ?? '');
const { data: deploymentMetadata } = useDeploymentMetadata(deploymentId);
Expand All @@ -45,6 +47,7 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
const { contracts } = useWeb3Store();
const [open, setOpen] = useState(false);
const [currentRewardsPerToken, setCurrentRewardsPerToken] = useState(BigNumber(0));
const [addOrRemove, setAddOrRemove] = useState<'Add' | 'Remove'>(initialStatus || 'Add');

const runnerAllocation = useAsyncMemo(async () => {
if (!account)
Expand All @@ -62,6 +65,8 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
};
}, [account]);

console.warn(runnerAllocation.data?.total.toString(), runnerAllocation.data?.used.toString());

const allocationRewardsRate = useAsyncMemo(async () => {
const rewards = await contracts?.rewardsBooster.boosterQueryRewardRate(
project.data?.type === ProjectType.RPC ? 1 : 0,
Expand Down Expand Up @@ -102,40 +107,55 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess

const avaibleStakeAmount = useMemo(() => {
const leftAllocation = runnerAllocation.data?.left ? BigNumber(runnerAllocation.data?.left) : BigNumber(0);
const haveAllocated = formatSQT(
BigNumber(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0').toString(),
);

return leftAllocation.plus(haveAllocated).toString();
return leftAllocation.toString();
}, [allocatedStake, runnerAllocation.data?.left]);

const currentAllocatedTokensOfThisDeployment = useMemo(() => {
return formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount || '0');
}, [allocatedStake.data]);

const estimatedRewardsPerTokenOneEra = useMemo(() => {
// 2s one block
// 7 days one era
return currentRewardsPerToken.multipliedBy(NETWORK_NAME === SQNetworks.TESTNET ? 1800 : 1800 * 24 * 7);
}, [currentRewardsPerToken]);

const estimatedRewardsAfterInput = useMemo(() => {
// lack div all tokens
// to know all tokens that already allocated is not very easy.
if (estimatedRewardsPerTokenOneEra.eq(0) || !formAllocateVal) {
return 'Unkonwn';
}

return formatNumber(
formatSQT(
estimatedRewardsPerTokenOneEra
.multipliedBy(
BigNumber(formAllocateVal)
.minus(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0'))
.abs() || 0,
)
.toString(),
),
);
}, [estimatedRewardsPerTokenOneEra, formAllocateVal, addOrRemove]);

const updateAllocate = async () => {
if (!deploymentId || !account) return;

await form.validateFields();

const addOrRemoveFunc = BigNumber(form.getFieldValue('allocateVal')).gt(
formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0'),
)
? contracts?.stakingAllocation.addAllocation
: contracts?.stakingAllocation.removeAllocation;
const addOrRemoveFunc =
addOrRemove === 'Add'
? contracts?.stakingAllocation.addAllocation
: contracts?.stakingAllocation.removeAllocation;

try {
const res = await addOrRemoveFunc?.(
cidToBytes32(deploymentId),
account,
parseEther(
BigNumber(form.getFieldValue('allocateVal'))
.minus(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0'))
.abs()
.toFixed(),
),
parseEther(BigNumber(form.getFieldValue('allocateVal')).toFixed()),
);

await res?.wait();
Expand All @@ -155,7 +175,8 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
};

useEffect(() => {
if (open && account && deploymentId) {
if (open && account && deploymentId && !disabled) {
setAddOrRemove(initialStatus || 'Add');
getAllocatedStake({
variables: {
id: `${deploymentId}:${account}`,
Expand All @@ -165,14 +186,16 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
getAllocateRewardsPerBlock();
runnerAllocation.refetch();
}
}, [open, account, deploymentId]);
}, [open, account, deploymentId, disabled]);

return (
<div className={styles.doAllocate}>
{actionBtn ? (
<div
onClick={() => {
setOpen(true);
if (!disabled) {
setOpen(true);
}
}}
>
{actionBtn}
Expand Down Expand Up @@ -227,34 +250,31 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
</div>
</div>

<div style={{ height: 1, width: '100%', background: 'var(--sq-gray400)', margin: '24px 0' }}></div>

<div className="col-flex" style={{ gap: 8 }}>
<div className="flex">
<Typography variant="medium" type="secondary">
Current allocated stake to {project.data?.metadata.name}
</Typography>
<span style={{ flex: 1 }}></span>
<Typography variant="medium">
{formatNumber(formatSQT(allocatedStake.data?.indexerAllocationSummary?.totalAmount || '0'))} {TOKEN}
</Typography>
</div>

<div className="flex">
<Typography variant="medium" type="secondary">
Available stake to allocate {TOKEN}
</Typography>
<span style={{ flex: 1 }}></span>
<Typography variant="medium">
{avaibleStakeAmount} {TOKEN}
</Typography>
</div>
<div>
<Radio.Group
value={addOrRemove}
onChange={(val) => {
setAddOrRemove(val.target.value);
}}
style={{ display: 'flex', flexDirection: 'column', gap: 16, margin: '24px 0 0 0' }}
>
<Radio value="Add">Add Allocation</Radio>
<Radio
value="Remove"
disabled={(allocatedStake.data?.indexerAllocationSummary?.totalAmount.toString() || '0') === '0'}
>
Remove Allocation
</Radio>
</Radio.Group>
</div>

<div style={{ height: 1, width: '100%', background: 'var(--sq-gray400)', margin: '24px 0' }}></div>

<div>
<Form layout="vertical" form={form}>
<Typography style={{ marginTop: 24 }}>New allocation amount</Typography>
<Typography>New allocation amount</Typography>
<Form.Item
style={{ marginBottom: 0 }}
name="allocateVal"
rules={[
{
Expand All @@ -269,9 +289,9 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
>
<NumberInput
description=""
maxAmount={avaibleStakeAmount}
maxAmount={addOrRemove === 'Add' ? avaibleStakeAmount : currentAllocatedTokensOfThisDeployment}
inputParams={{
max: avaibleStakeAmount,
max: addOrRemove === 'Add' ? avaibleStakeAmount : currentAllocatedTokensOfThisDeployment,
value: form.getFieldValue('allocateVal'),
onChange: (value) => {
form.setFieldsValue({ allocateVal: value });
Expand All @@ -283,15 +303,29 @@ const DoAllocate: FC<IProps> = ({ projectId, deploymentId, actionBtn, onSuccess
></NumberInput>
</Form.Item>
</Form>
<div className="col-flex" style={{ gap: 8, marginBottom: 24 }}>
<div className="flex">
<Typography variant="medium" type="secondary">
Current allocated stake to {project.data?.metadata.name}
</Typography>
<span style={{ flex: 1 }}></span>
<Typography variant="medium">
{formatNumber(currentAllocatedTokensOfThisDeployment)} {TOKEN}
</Typography>
</div>

<div className="flex">
<Typography variant="medium" type="secondary">
Available stake to allocate {TOKEN}
</Typography>
<span style={{ flex: 1 }}></span>
<Typography variant="medium">
{avaibleStakeAmount} {TOKEN}
</Typography>
</div>
</div>
<Typography variant="medium">
Estimated allocation rewards after update:{' '}
{estimatedRewardsPerTokenOneEra.eq(0)
? 'Unknown'
: formatNumber(
formatSQT(estimatedRewardsPerTokenOneEra.multipliedBy(formAllocateVal || 0).toString()),
)}{' '}
{TOKEN} Per era
Estimated allocation rewards after update: {estimatedRewardsAfterInput} {TOKEN} Per era
</Typography>
</div>
</Modal>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProjectCard/ProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const ProjectCard: React.FC<Props> = ({ project, onClick }) => {
Last updated
</Typography>
<Typography variant="small" style={{ marginLeft: 8 }}>
{dayjs(project.updatedTimestamp).fromNow()}
{dayjs(project.updatedTimestamp).utc().fromNow()}
</Typography>
</div>
)}
Expand Down
2 changes: 0 additions & 2 deletions src/components/ProjectHeader/ProjectHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import * as React from 'react';
import { useTranslation } from 'react-i18next';
import DoAllocate from '@components/DoAllocate/DoAllocate';
import UnsafeWarn from '@components/UnsafeWarn';
import { Manifest } from '@hooks/useGetDeploymentManifest';
import { ProjectDetailsQuery } from '@hooks/useProjectFromQuery';
Expand Down Expand Up @@ -84,7 +83,6 @@ const ProjectHeader: React.FC<Props> = ({
{/* <Button type="primary" shape="round" size="large">
Get RPC Endpoint
</Button> */}
<DoAllocate projectId={project.id} deploymentId={currentVersion}></DoAllocate>
</div>
<Address address={project.owner} size="small" />

Expand Down
2 changes: 1 addition & 1 deletion src/components/Status/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const deploymentStatus: { [key: string]: StatusColor } = {
INDEXING: StatusColor.blue,
STARTED: StatusColor.blue,
READY: StatusColor.green,
NOTINDEXING: StatusColor.red,
NOTINDEXING: StatusColor.gray,
TERMINATED: StatusColor.red,
};

Expand Down
Loading

0 comments on commit 817e5c2

Please sign in to comment.