From d3cb3896f42aa1ecf24253783766c3b8900b0046 Mon Sep 17 00:00:00 2001
From: Sam Keen
Date: Fri, 11 Nov 2022 14:30:03 +0000
Subject: [PATCH] fix(1837): account for proposal vote and enactment deadlines
being uncoupled (#2005)
* Fix/1837: Remove 2 seconds from proposal vote deadline on submission to ensure the deadline is always slightly below the maximum the API accepts
* fix(1837): Adjust for vote deadline and enactment deadline being decoupled in the API
* fix(1837): Removed unnecessary dependencies
* fix(1837): A couple of extra tests for get-enactment-timestamp
* fix(1837): propose-update-asset.tsx tweaked to ensure max enactment button works properly
---
apps/token/src/i18n/translations/dev.json | 3 +-
...-form-vote-and-enactment-deadline.spec.tsx | 54 ++++++----
...posal-form-vote-and-enactment-deadline.tsx | 99 +++++++++++++------
.../propose/freeform/propose-freeform.tsx | 9 +-
.../propose-network-parameter.tsx | 30 ++++--
.../propose/new-asset/propose-new-asset.tsx | 37 +++++--
.../propose/new-market/propose-new-market.tsx | 30 ++++--
.../update-asset/propose-update-asset.tsx | 31 ++++--
.../update-market/propose-update-market.tsx | 30 ++++--
.../src/utils/deadline-helpers.spec.ts | 41 +++++++-
libs/governance/src/utils/deadline-helpers.ts | 19 +++-
.../src/utils/get-closing-timestamp.spec.ts | 29 +++++-
.../src/utils/get-closing-timestamp.ts | 24 +++--
.../src/utils/get-enactment-timestamp.spec.ts | 47 +++++++--
.../src/utils/get-enactment-timestamp.ts | 26 ++---
.../utils/get-validation-timestamp.spec.ts | 31 +++++-
.../src/utils/get-validation-timestamp.ts | 24 +++--
17 files changed, 421 insertions(+), 143 deletions(-)
diff --git a/apps/token/src/i18n/translations/dev.json b/apps/token/src/i18n/translations/dev.json
index c13143e0d..b1f2cf0ae 100644
--- a/apps/token/src/i18n/translations/dev.json
+++ b/apps/token/src/i18n/translations/dev.json
@@ -675,13 +675,14 @@
"ProposalVoteTitle": "Vote deadline",
"ProposalVoteAndEnactmentTitle": "Vote deadline and enactment",
"ProposalVoteDeadline": "Time till voting closes",
- "ProposalEnactmentDeadline": "Time till enactment (after vote close)",
+ "ProposalEnactmentDeadline": "Time till enactment (must be equal to or after vote close)",
"ProposalValidationDeadline": "Time till ERC-20 asset validation. Maximum value is affected by the vote deadline.",
"ThisWillSetVotingDeadlineTo": "This will set the voting deadline to",
"ThisWillSetEnactmentDeadlineTo": "This will set the enactment date to",
"ThisWillSetValidationDeadlineTo": "This will set the validation deadline to",
"Hours": "hours",
"ThisWillAdd2MinutesToAllowTimeToConfirmInWallet": "Note: we add 2 minutes of extra time when you choose the minimum value. This gives you time to confirm the proposal in your wallet.",
+ "ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline": "Proposal will fail if enactment is earlier than the voting deadline",
"SelectAMarketToChange": "Select a market to change",
"MarketName": "Market name",
"MarketCode": "Market code",
diff --git a/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.spec.tsx b/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.spec.tsx
index 2d2824f5f..0e04c8c7e 100644
--- a/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.spec.tsx
+++ b/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.spec.tsx
@@ -20,7 +20,7 @@ afterEach(() => {
const minVoteDeadline = '1h0m0s';
const maxVoteDeadline = '5h0m0s';
const minEnactDeadline = '1h0m0s';
-const maxEnactDeadline = '4h0m0s';
+const maxEnactDeadline = '5h0m0s';
/**
* Formats date according to locale.
@@ -118,7 +118,7 @@ describe('Proposal form vote, validation and enactment deadline', () => {
const maxButton = screen.getByTestId('max-enactment');
const minButton = screen.getByTestId('min-enactment');
fireEvent.click(maxButton);
- expect(enactmentDeadlineInput).toHaveValue(4);
+ expect(enactmentDeadlineInput).toHaveValue(5);
fireEvent.click(minButton);
expect(enactmentDeadlineInput).toHaveValue(1);
});
@@ -154,7 +154,24 @@ describe('Proposal form vote, validation and enactment deadline', () => {
'2022-01-01T00:02:00.000Z'
);
expect(screen.getByTestId('enactment-date')).toHaveTextContent(
- '2022-01-01T02:00:00.000Z'
+ '2022-01-01T01:02:00.000Z'
+ );
+ // When max values are used, the deadlines should have 2 seconds subtracted
+ // from them to account any delays
+ const voteDeadlineMaxButton = screen.getByTestId('max-vote');
+ const enactmentDeadlineMaxButton = screen.getByTestId('max-enactment');
+ const validationDeadlineMaxButton = screen.getByTestId('max-validation');
+ fireEvent.click(voteDeadlineMaxButton);
+ fireEvent.click(enactmentDeadlineMaxButton);
+ fireEvent.click(validationDeadlineMaxButton);
+ expect(screen.getByTestId('voting-date')).toHaveTextContent(
+ '2022-01-01T04:59:58.000Z'
+ );
+ expect(screen.getByTestId('validation-date')).toHaveTextContent(
+ '2022-01-01T04:59:58.000Z'
+ );
+ expect(screen.getByTestId('enactment-date')).toHaveTextContent(
+ '2022-01-01T04:59:58.000Z'
);
});
@@ -171,19 +188,7 @@ describe('Proposal form vote, validation and enactment deadline', () => {
'2022-01-01T00:02:30.000Z'
);
expect(screen.getByTestId('enactment-date')).toHaveTextContent(
- '2022-01-01T02:00:30.000Z'
- );
- });
-
- it('update the vote deadline date and the enactment deadline date when the vote deadline is changed', () => {
- renderComponent();
- const voteDeadlineInput = screen.getByTestId('proposal-vote-deadline');
- fireEvent.change(voteDeadlineInput, { target: { value: 2 } });
- expect(screen.getByTestId('voting-date')).toHaveTextContent(
- '2022-01-01T02:00:00.000Z'
- );
- expect(screen.getByTestId('enactment-date')).toHaveTextContent(
- '2022-01-01T03:00:00.000Z'
+ '2022-01-01T01:02:30.000Z'
);
});
@@ -198,7 +203,7 @@ describe('Proposal form vote, validation and enactment deadline', () => {
fireEvent.click(voteDeadlineMaxButton);
fireEvent.click(validationDeadlineMaxButton);
expect(screen.getByTestId('validation-date')).toHaveTextContent(
- '2022-01-01T05:00:00.000Z'
+ '2022-01-01T04:59:58.000Z'
);
expect(validationDeadlineInput).toHaveValue(5);
fireEvent.click(voteDeadlineMinButton);
@@ -207,4 +212,19 @@ describe('Proposal form vote, validation and enactment deadline', () => {
);
expect(validationDeadlineInput).toHaveValue(1);
});
+
+ it('displays error text if the vote deadline is set later than the enactment deadline', () => {
+ renderComponent();
+ const voteDeadlineInput = screen.getByTestId('proposal-vote-deadline');
+ const enactmentDeadlineInput = screen.getByTestId(
+ 'proposal-enactment-deadline'
+ );
+ fireEvent.change(voteDeadlineInput, { target: { value: 5 } });
+ fireEvent.change(enactmentDeadlineInput, { target: { value: 2 } });
+ expect(
+ screen.getByTestId('enactment-before-voting-deadline')
+ ).toHaveTextContent(
+ 'Proposal will fail if enactment is earlier than the voting deadline'
+ );
+ });
});
diff --git a/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.tsx b/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.tsx
index a1da96410..d8f98996a 100644
--- a/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.tsx
+++ b/apps/token/src/routes/governance/components/propose/proposal-form-vote-and-enactment-deadline.tsx
@@ -7,10 +7,12 @@ import {
InputError,
} from '@vegaprotocol/ui-toolkit';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
-import { addHours, addMinutes } from 'date-fns';
+import { addHours } from 'date-fns';
import {
+ addTwoMinutes,
deadlineToSeconds,
secondsToRoundedHours,
+ subtractTwoSeconds,
} from '@vegaprotocol/governance';
import { ProposalFormSubheader } from './proposal-form-subheader';
import type { UseFormRegisterReturn } from 'react-hook-form';
@@ -218,6 +220,22 @@ const EnactmentForm = ({
{getDateTimeFormat().format(deadlineDates.enactment)}
+ {deadlines.enactment === minEnactmentHours && (
+
+ {t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
+
+ )}
+ {deadlines.enactment && deadlines.enactment < deadlines.vote && (
+
+ {t('ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline')}
+
+ )}
)}
@@ -302,16 +320,13 @@ export function ProposalFormVoteAndEnactmentDeadline({
});
const [deadlineDates, setDeadlineDates] = useState({
- vote:
- deadlines.vote === minVoteHours
- ? addHours(addMinutes(new Date(), 2), deadlines.vote)
- : addHours(new Date(), deadlines.vote),
+ vote: addHours(addTwoMinutes(), deadlines.vote),
enactment: deadlines.enactment
- ? addHours(new Date(), deadlines.vote + deadlines.enactment)
+ ? addHours(addTwoMinutes(), deadlines.enactment)
: undefined,
validation:
deadlines.validation === 0
- ? addHours(addMinutes(new Date(), 2), deadlines.validation)
+ ? addHours(addTwoMinutes(), deadlines.validation)
: addHours(new Date(), deadlines.validation),
});
@@ -319,21 +334,40 @@ export function ProposalFormVoteAndEnactmentDeadline({
const interval = setInterval(() => {
setDeadlineDates((prev) => ({
...prev,
- vote:
- deadlines.vote === minVoteHours
- ? addHours(addMinutes(new Date(), 2), deadlines.vote)
- : addHours(new Date(), deadlines.vote),
+ vote: addHours(
+ (deadlines.vote === minVoteHours && addTwoMinutes()) ||
+ (deadlines.vote === maxVoteHours && subtractTwoSeconds()) ||
+ new Date(),
+ deadlines.vote
+ ),
enactment: deadlines.enactment
- ? addHours(new Date(), deadlines.vote + deadlines.enactment)
+ ? addHours(
+ (deadlines.enactment === minEnactmentHours && addTwoMinutes()) ||
+ (deadlines.enactment === maxEnactmentHours &&
+ subtractTwoSeconds()) ||
+ new Date(),
+ deadlines.enactment
+ )
: undefined,
validation:
deadlines.validation === 0
- ? addHours(addMinutes(new Date(), 2), deadlines.validation)
- : addHours(new Date(), deadlines.validation),
+ ? addHours(addTwoMinutes(), deadlines.validation)
+ : addHours(
+ (deadlines.validation === maxVoteHours &&
+ subtractTwoSeconds()) ||
+ new Date(),
+ deadlines.validation
+ ),
}));
}, 1000);
return () => clearInterval(interval);
- }, [deadlines, minVoteHours]);
+ }, [
+ deadlines,
+ maxEnactmentHours,
+ maxVoteHours,
+ minEnactmentHours,
+ minVoteHours,
+ ]);
const updateVoteDeadlineAndDate = (hours: number) => {
// Validation, when needed, can only happen within the voting period. Therefore, if the
@@ -345,22 +379,24 @@ export function ProposalFormVoteAndEnactmentDeadline({
validation: prev.validation && Math.min(prev.validation, hours),
}));
- // If the vote deadline is set to minimum, add 2 mins to the date as we do
+ // If the vote deadlines are set to minimum, add 2 mins to the date as we do
// this on submission to allow time to confirm in the wallet. Amending the
// vote deadline also changes the enactment date and potentially the validation
// date.
// The validation deadline date cannot be after the vote deadline date. Therefore,
// if the vote deadline is changed, the validation deadline must potentially
// be changed to be within the new vote deadline.
+ // Whilst it's not ideal, currently enactment deadlines are uncoupled from
+ // vote deadlines in the API. Therefore, the UI currently is too, so updating
+ // the vote deadline does not update the enactment deadline.
setDeadlineDates((prev) => ({
...prev,
- vote:
- hours === minVoteHours
- ? addHours(addMinutes(new Date(), 2), hours)
- : addHours(new Date(), hours),
- enactment: deadlines.enactment
- ? addHours(new Date(), hours + deadlines.enactment)
- : undefined,
+ vote: addHours(
+ (hours === minVoteHours && addTwoMinutes()) ||
+ (hours === maxVoteHours && subtractTwoSeconds()) ||
+ new Date(),
+ hours
+ ),
validation: addHours(new Date(), Math.min(hours, deadlines.validation)),
}));
};
@@ -373,7 +409,12 @@ export function ProposalFormVoteAndEnactmentDeadline({
setDeadlineDates((prev) => ({
...prev,
- enactment: addHours(deadlineDates.vote, hours),
+ enactment: addHours(
+ (hours === minEnactmentHours && addTwoMinutes()) ||
+ (hours === maxEnactmentHours && subtractTwoSeconds()) ||
+ new Date(),
+ hours
+ ),
}));
};
@@ -385,10 +426,12 @@ export function ProposalFormVoteAndEnactmentDeadline({
setDeadlineDates((prev) => ({
...prev,
- validation:
- hours === 0
- ? addHours(addMinutes(new Date(), 2), hours)
- : addHours(new Date(), hours),
+ validation: addHours(
+ (hours === 0 && addTwoMinutes()) ||
+ (hours === maxVoteHours && subtractTwoSeconds()) ||
+ new Date(),
+ hours
+ ),
}));
};
diff --git a/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx b/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx
index 932cec2b7..1ee29fbd6 100644
--- a/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx
+++ b/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx
@@ -56,6 +56,12 @@ export const ProposeFreeform = () => {
params.governance_proposal_freeform_minClose
).toString();
+ const isVoteDeadlineAtMaximum =
+ fields.proposalVoteDeadline ===
+ deadlineToRoundedHours(
+ params.governance_proposal_freeform_maxClose
+ ).toString();
+
await submit({
rationale: {
title: fields.proposalTitle,
@@ -65,7 +71,8 @@ export const ProposeFreeform = () => {
newFreeform: {},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
},
});
diff --git a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx
index 4a8d0d5cf..04fe118eb 100644
--- a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx
+++ b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx
@@ -9,7 +9,7 @@ import {
getClosingTimestamp,
getEnactmentTimestamp,
useProposalSubmit,
- deadlineToRoundedHours,
+ doesValueEquateToParam,
} from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import {
@@ -103,11 +103,22 @@ export const ProposeNetworkParameter = () => {
.split('_')
.join('.');
- const isVoteDeadlineAtMinimum =
- fields.proposalVoteDeadline ===
- deadlineToRoundedHours(
- params.governance_proposal_updateNetParam_minClose
- ).toString();
+ const isVoteDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateNetParam_minClose
+ );
+ const isVoteDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateNetParam_maxClose
+ );
+ const isEnactmentDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateNetParam_minEnact
+ );
+ const isEnactmentDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateNetParam_maxEnact
+ );
await submit({
rationale: {
@@ -123,12 +134,13 @@ export const ProposeNetworkParameter = () => {
},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
enactmentTimestamp: getEnactmentTimestamp(
- fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline,
- isVoteDeadlineAtMinimum
+ isEnactmentDeadlineAtMinimum,
+ isEnactmentDeadlineAtMaximum
),
},
});
diff --git a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx
index f81098084..176633e0e 100644
--- a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx
+++ b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx
@@ -5,7 +5,7 @@ import {
getEnactmentTimestamp,
getValidationTimestamp,
useProposalSubmit,
- deadlineToRoundedHours,
+ doesValueEquateToParam,
} from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import {
@@ -65,11 +65,26 @@ export const ProposeNewAsset = () => {
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewAssetProposalFormFields) => {
- const isVoteDeadlineAtMinimum =
- fields.proposalVoteDeadline ===
- deadlineToRoundedHours(
- params.governance_proposal_asset_minClose
- ).toString();
+ const isVoteDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_asset_minClose
+ );
+ const isVoteDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_asset_maxClose
+ );
+ const isEnactmentDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_asset_minEnact
+ );
+ const isEnactmentDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_asset_maxEnact
+ );
+ const isValidationDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalValidationDeadline,
+ params.governance_proposal_asset_maxClose
+ );
await submit({
rationale: {
@@ -82,15 +97,17 @@ export const ProposeNewAsset = () => {
},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
enactmentTimestamp: getEnactmentTimestamp(
- fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline,
- isVoteDeadlineAtMinimum
+ isEnactmentDeadlineAtMinimum,
+ isEnactmentDeadlineAtMaximum
),
validationTimestamp: getValidationTimestamp(
- fields.proposalValidationDeadline
+ fields.proposalValidationDeadline,
+ isValidationDeadlineAtMaximum
),
},
});
diff --git a/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx b/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx
index 83aa3a592..33c244649 100644
--- a/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx
+++ b/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx
@@ -4,7 +4,7 @@ import {
getClosingTimestamp,
getEnactmentTimestamp,
useProposalSubmit,
- deadlineToRoundedHours,
+ doesValueEquateToParam,
} from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import {
@@ -63,11 +63,22 @@ export const ProposeNewMarket = () => {
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: NewMarketProposalFormFields) => {
- const isVoteDeadlineAtMinimum =
- fields.proposalVoteDeadline ===
- deadlineToRoundedHours(
- params.governance_proposal_market_minClose
- ).toString();
+ const isVoteDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_market_minClose
+ );
+ const isVoteDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_market_maxClose
+ );
+ const isEnactmentDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_market_minEnact
+ );
+ const isEnactmentDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_market_maxEnact
+ );
await submit({
rationale: {
@@ -80,12 +91,13 @@ export const ProposeNewMarket = () => {
},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
enactmentTimestamp: getEnactmentTimestamp(
- fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline,
- isVoteDeadlineAtMinimum
+ isEnactmentDeadlineAtMinimum,
+ isEnactmentDeadlineAtMaximum
),
},
});
diff --git a/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx
index 54275e74c..f944b2b47 100644
--- a/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx
+++ b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx
@@ -4,7 +4,7 @@ import {
getClosingTimestamp,
getEnactmentTimestamp,
useProposalSubmit,
- deadlineToRoundedHours,
+ doesValueEquateToParam,
} from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import {
@@ -63,11 +63,22 @@ export const ProposeUpdateAsset = () => {
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateAssetProposalFormFields) => {
- const isVoteDeadlineAtMinimum =
- fields.proposalVoteDeadline ===
- deadlineToRoundedHours(
- params.governance_proposal_updateAsset_minClose
- ).toString();
+ const isVoteDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateAsset_minClose
+ );
+ const isVoteDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateAsset_maxClose
+ );
+ const isEnactmentDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateAsset_minEnact
+ );
+ const isEnactmentDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateAsset_maxEnact
+ );
await submit({
rationale: {
@@ -80,12 +91,13 @@ export const ProposeUpdateAsset = () => {
},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
enactmentTimestamp: getEnactmentTimestamp(
- fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline,
- isVoteDeadlineAtMinimum
+ isEnactmentDeadlineAtMinimum,
+ isEnactmentDeadlineAtMaximum
),
},
});
@@ -171,6 +183,7 @@ export const ProposeUpdateAsset = () => {
voteErrorMessage={errors?.proposalVoteDeadline?.message}
voteMinClose={params.governance_proposal_updateAsset_minClose}
voteMaxClose={params.governance_proposal_updateAsset_maxClose}
+ onEnactMinMax={setValue}
enactmentRegister={register('proposalEnactmentDeadline', {
required: t('Required'),
})}
diff --git a/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx b/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx
index d07ba855b..67131e6b0 100644
--- a/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx
+++ b/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx
@@ -6,7 +6,7 @@ import {
getClosingTimestamp,
getEnactmentTimestamp,
useProposalSubmit,
- deadlineToRoundedHours,
+ doesValueEquateToParam,
} from '@vegaprotocol/governance';
import { useEnvironment } from '@vegaprotocol/environment';
import {
@@ -123,11 +123,22 @@ export const ProposeUpdateMarket = () => {
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const onSubmit = async (fields: UpdateMarketProposalFormFields) => {
- const isVoteDeadlineAtMinimum =
- fields.proposalVoteDeadline ===
- deadlineToRoundedHours(
- params.governance_proposal_updateMarket_minClose
- ).toString();
+ const isVoteDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateMarket_minClose
+ );
+ const isVoteDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalVoteDeadline,
+ params.governance_proposal_updateMarket_maxClose
+ );
+ const isEnactmentDeadlineAtMinimum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateMarket_minEnact
+ );
+ const isEnactmentDeadlineAtMaximum = doesValueEquateToParam(
+ fields.proposalEnactmentDeadline,
+ params.governance_proposal_updateMarket_maxEnact
+ );
await submit({
rationale: {
@@ -143,12 +154,13 @@ export const ProposeUpdateMarket = () => {
},
closingTimestamp: getClosingTimestamp(
fields.proposalVoteDeadline,
- isVoteDeadlineAtMinimum
+ isVoteDeadlineAtMinimum,
+ isVoteDeadlineAtMaximum
),
enactmentTimestamp: getEnactmentTimestamp(
- fields.proposalVoteDeadline,
fields.proposalEnactmentDeadline,
- isVoteDeadlineAtMinimum
+ isEnactmentDeadlineAtMinimum,
+ isEnactmentDeadlineAtMaximum
),
},
});
diff --git a/libs/governance/src/utils/deadline-helpers.spec.ts b/libs/governance/src/utils/deadline-helpers.spec.ts
index eb31151a3..cd4cf4f50 100644
--- a/libs/governance/src/utils/deadline-helpers.spec.ts
+++ b/libs/governance/src/utils/deadline-helpers.spec.ts
@@ -1,4 +1,9 @@
-import { deadlineToSeconds, secondsToRoundedHours } from './deadline-helpers';
+import {
+ deadlineToSeconds,
+ secondsToRoundedHours,
+ addTwoMinutes,
+ subtractTwoSeconds,
+} from './deadline-helpers';
describe('deadlineToSeconds', () => {
it('should throw an error if the deadline does not match the format "XhXmXs"', () => {
@@ -39,3 +44,37 @@ describe('secondsToRoundedHours', () => {
expect(secondsToRoundedHours(9000)).toEqual(3);
});
});
+
+describe('addTwoMinutes', () => {
+ it('should add two minutes to the current time', () => {
+ const now = new Date();
+ const twoMinutesLater = new Date(now.getTime() + 2 * 60 * 1000);
+ expect(addTwoMinutes(now)).toEqual(twoMinutesLater);
+ });
+
+ it('will use the current time if no date is provided', () => {
+ const now = new Date();
+ const twoMinutesLater = new Date(now.getTime() + 2 * 60 * 1000);
+ expect(addTwoMinutes()).toEqual(twoMinutesLater);
+ });
+
+ it('should add two minutes to a given time', () => {
+ const date = new Date(2020, 0, 1);
+ const twoMinutesLater = new Date(date.getTime() + 2 * 60 * 1000);
+ expect(addTwoMinutes(date)).toEqual(twoMinutesLater);
+ });
+});
+
+describe('subtractTwoSeconds', () => {
+ it('should subtract two seconds to the current time', () => {
+ const now = new Date();
+ const twoSecondsEarlier = new Date(now.getTime() - 2 * 1000);
+ expect(subtractTwoSeconds(now)).toEqual(twoSecondsEarlier);
+ });
+
+ it('should subtract two seconds from a given time', () => {
+ const date = new Date(2020, 0, 1);
+ const twoSecondsEarlier = new Date(date.getTime() - 2 * 1000);
+ expect(subtractTwoSeconds(date)).toEqual(twoSecondsEarlier);
+ });
+});
diff --git a/libs/governance/src/utils/deadline-helpers.ts b/libs/governance/src/utils/deadline-helpers.ts
index 621d224de..62c9dde5a 100644
--- a/libs/governance/src/utils/deadline-helpers.ts
+++ b/libs/governance/src/utils/deadline-helpers.ts
@@ -1,10 +1,15 @@
import { parse as ISO8601Parse, toSeconds } from 'iso8601-duration';
+import { addMinutes, subSeconds } from 'date-fns';
+
+const deadlineRegexChecker = (deadline: string) => {
+ // check that the deadline string matches the format "XhXmXs"
+ const regex = /^(\d+h)?(\d+m)?(\d+s)?$/;
+ return regex.test(deadline);
+};
// Converts API deadlines ("XhXmXs") to seconds
export const deadlineToSeconds = (deadline: string) => {
- // check that the deadline string matches the format "XhXmXs"
- const regex = /^(\d+h)?(\d+m)?(\d+s)?$/;
- if (!regex.test(deadline)) {
+ if (!deadlineRegexChecker(deadline)) {
throw new Error(
`Invalid deadline format, expected format "XhXmXs", got "${deadline}"`
);
@@ -20,3 +25,11 @@ export const secondsToRoundedHours = (seconds: number) => {
export const deadlineToRoundedHours = (deadline: string) =>
secondsToRoundedHours(deadlineToSeconds(deadline));
+
+export const doesValueEquateToParam = (value: string, param: string) =>
+ value === deadlineToRoundedHours(param).toString();
+
+export const addTwoMinutes = (date?: Date) => addMinutes(date || new Date(), 2);
+
+export const subtractTwoSeconds = (date?: Date) =>
+ subSeconds(date || new Date(), 2);
diff --git a/libs/governance/src/utils/get-closing-timestamp.spec.ts b/libs/governance/src/utils/get-closing-timestamp.spec.ts
index 1ff61d547..944365422 100644
--- a/libs/governance/src/utils/get-closing-timestamp.spec.ts
+++ b/libs/governance/src/utils/get-closing-timestamp.spec.ts
@@ -1,5 +1,5 @@
import { getClosingTimestamp } from './get-closing-timestamp';
-import { addHours, addMinutes, getTime } from 'date-fns';
+import { addHours, addMinutes, getTime, subSeconds } from 'date-fns';
beforeEach(() => {
jest.useFakeTimers();
@@ -14,6 +14,7 @@ describe('getClosingTimestamp', () => {
it('should return the correct timestamp if the proposalVoteDeadline is set to minimum (when 2 mins are added)', () => {
const proposalVoteDeadline = '1';
const isMinimumDeadlineSelected = true;
+ const isMaximumDeadlineSelected = false;
const expected = Math.floor(
getTime(
addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline))
@@ -21,20 +22,40 @@ describe('getClosingTimestamp', () => {
);
const actual = getClosingTimestamp(
proposalVoteDeadline,
- isMinimumDeadlineSelected
+ isMinimumDeadlineSelected,
+ isMaximumDeadlineSelected
);
expect(actual).toEqual(expected);
});
- it('should return the correct timestamp if the proposalVoteDeadline is not set to minimum (when no extra mins are added)', () => {
+ it('should return the correct timestamp if the proposalVoteDeadline is not set to minimum or maximum (no extra time added or subtracted)', () => {
const proposalVoteDeadline = '2';
const isMinimumDeadlineSelected = false;
+ const isMaximumDeadlineSelected = false;
const expected = Math.floor(
getTime(addHours(new Date(), Number(proposalVoteDeadline))) / 1000
);
const actual = getClosingTimestamp(
proposalVoteDeadline,
- isMinimumDeadlineSelected
+ isMinimumDeadlineSelected,
+ isMaximumDeadlineSelected
+ );
+ expect(actual).toEqual(expected);
+ });
+
+ it('should return the correct timestamp if the proposalVoteDeadline is set to maximum (when 2 secs are subtracted)', () => {
+ const proposalVoteDeadline = '3';
+ const isMinimumDeadlineSelected = false;
+ const isMaximumDeadlineSelected = true;
+ const expected = Math.floor(
+ getTime(
+ addHours(subSeconds(new Date(), 2), Number(proposalVoteDeadline))
+ ) / 1000
+ );
+ const actual = getClosingTimestamp(
+ proposalVoteDeadline,
+ isMinimumDeadlineSelected,
+ isMaximumDeadlineSelected
);
expect(actual).toEqual(expected);
});
diff --git a/libs/governance/src/utils/get-closing-timestamp.ts b/libs/governance/src/utils/get-closing-timestamp.ts
index b0af3dc3d..ab5c59d68 100644
--- a/libs/governance/src/utils/get-closing-timestamp.ts
+++ b/libs/governance/src/utils/get-closing-timestamp.ts
@@ -1,17 +1,25 @@
-import { addHours, addMinutes, getTime } from 'date-fns';
+import { addHours, getTime } from 'date-fns';
+import { addTwoMinutes, subtractTwoSeconds } from './deadline-helpers';
-// If proposaVoteDeadline is at its minimum, then we add
-// 2 extra minutes to the closing timestamp to ensure that there's time
-// to confirm in the wallet.
+// If the vote deadline is at its minimum, then we add 2 extra minutes to the
+// closing timestamp to ensure that there's time to confirm in the wallet.
+
+// If it's at its maximum, remove a couple of seconds to ensure rounding errors
+// and communication delays don't cause the deadline to be slightly
+// later than the API can accept.
export const getClosingTimestamp = (
proposalVoteDeadline: string,
- minimumDeadlineSelected: boolean
+ minimumDeadlineSelected: boolean,
+ maximumDeadlineSelected: boolean
) =>
Math.floor(
getTime(
- minimumDeadlineSelected
- ? addHours(addMinutes(new Date(), 2), Number(proposalVoteDeadline))
- : addHours(new Date(), Number(proposalVoteDeadline))
+ addHours(
+ (minimumDeadlineSelected && addTwoMinutes()) ||
+ (maximumDeadlineSelected && subtractTwoSeconds()) ||
+ new Date(),
+ Number(proposalVoteDeadline)
+ )
) / 1000
);
diff --git a/libs/governance/src/utils/get-enactment-timestamp.spec.ts b/libs/governance/src/utils/get-enactment-timestamp.spec.ts
index da2da6e3b..a31c6996e 100644
--- a/libs/governance/src/utils/get-enactment-timestamp.spec.ts
+++ b/libs/governance/src/utils/get-enactment-timestamp.spec.ts
@@ -1,5 +1,5 @@
import { getEnactmentTimestamp } from './get-enactment-timestamp';
-import { addHours, getTime } from 'date-fns';
+import { addHours, addMinutes, getTime, subSeconds } from 'date-fns';
beforeEach(() => {
jest.useFakeTimers();
@@ -12,21 +12,48 @@ afterEach(() => {
describe('getEnactmentTimestamp', () => {
it('should return the correct timestamp', () => {
- const proposalVoteDeadline = '2';
const isMinimumVoteDeadlineSelected = false;
+ const isMaximumVoteDeadlineSelected = false;
const enactmentDeadline = '1';
const expected = Math.floor(
- getTime(
- addHours(
- new Date(),
- Number(proposalVoteDeadline) + Number(enactmentDeadline)
- )
- ) / 1000
+ getTime(addHours(new Date(), Number(enactmentDeadline))) / 1000
);
const actual = getEnactmentTimestamp(
- proposalVoteDeadline,
enactmentDeadline,
- isMinimumVoteDeadlineSelected
+ isMinimumVoteDeadlineSelected,
+ isMaximumVoteDeadlineSelected
+ );
+ expect(actual).toEqual(expected);
+ });
+
+ it('should return the correct timestamp when minimum vote deadline is selected', () => {
+ const isMinimumVoteDeadlineSelected = true;
+ const isMaximumVoteDeadlineSelected = false;
+ const enactmentDeadline = '1';
+ const expected = Math.floor(
+ getTime(addMinutes(addHours(new Date(), Number(enactmentDeadline)), 2)) /
+ 1000
+ );
+ const actual = getEnactmentTimestamp(
+ enactmentDeadline,
+ isMinimumVoteDeadlineSelected,
+ isMaximumVoteDeadlineSelected
+ );
+ expect(actual).toEqual(expected);
+ });
+
+ it('should return the correct timestamp when maximum vote deadline is selected', () => {
+ const isMinimumVoteDeadlineSelected = false;
+ const isMaximumVoteDeadlineSelected = true;
+ const enactmentDeadline = '1';
+ const expected = Math.floor(
+ getTime(subSeconds(addHours(new Date(), Number(enactmentDeadline)), 2)) /
+ 1000
+ );
+ const actual = getEnactmentTimestamp(
+ enactmentDeadline,
+ isMinimumVoteDeadlineSelected,
+ isMaximumVoteDeadlineSelected
);
expect(actual).toEqual(expected);
});
diff --git a/libs/governance/src/utils/get-enactment-timestamp.ts b/libs/governance/src/utils/get-enactment-timestamp.ts
index 22ed4d258..2fe4f6acc 100644
--- a/libs/governance/src/utils/get-enactment-timestamp.ts
+++ b/libs/governance/src/utils/get-enactment-timestamp.ts
@@ -1,22 +1,24 @@
-import { addHours, fromUnixTime, getTime } from 'date-fns';
-import { getClosingTimestamp } from './get-closing-timestamp';
+import { addHours, getTime } from 'date-fns';
+import { addTwoMinutes, subtractTwoSeconds } from './deadline-helpers';
+
+// If the enactment deadline is at its minimum, then we add 2 extra minutes to the
+// closing timestamp to ensure that there's time to confirm in the wallet.
+
+// If it's at its maximum, remove a couple of seconds to ensure rounding errors
+// and communication delays don't cause the deadline to be slightly
+// later than the API can accept.
export const getEnactmentTimestamp = (
- proposalVoteDeadline: string,
enactmentDeadline: string,
- minimumVoteDeadlineSelected: boolean
+ minimumDeadlineSelected: boolean,
+ maximumDeadlineSelected: boolean
) =>
Math.floor(
getTime(
addHours(
- new Date(
- fromUnixTime(
- getClosingTimestamp(
- proposalVoteDeadline,
- minimumVoteDeadlineSelected
- )
- )
- ),
+ (minimumDeadlineSelected && addTwoMinutes()) ||
+ (maximumDeadlineSelected && subtractTwoSeconds()) ||
+ new Date(),
Number(enactmentDeadline)
)
) / 1000
diff --git a/libs/governance/src/utils/get-validation-timestamp.spec.ts b/libs/governance/src/utils/get-validation-timestamp.spec.ts
index 1c4210e6e..303d26172 100644
--- a/libs/governance/src/utils/get-validation-timestamp.spec.ts
+++ b/libs/governance/src/utils/get-validation-timestamp.spec.ts
@@ -1,4 +1,4 @@
-import { addHours, addMinutes, getTime } from 'date-fns';
+import { addHours, addMinutes, getTime, subSeconds } from 'date-fns';
import { getValidationTimestamp } from './get-validation-timestamp';
beforeEach(() => {
@@ -13,21 +13,44 @@ afterEach(() => {
describe('getValidationTimestamp', () => {
it('should return the correct timestamp if the proposalValidationDeadline is 0 (when 2 mins are added)', () => {
const proposalValidationDeadline = '0';
+ const isMaximumDeadlineSelected = false;
const expected = Math.floor(
getTime(
addHours(addMinutes(new Date(), 2), Number(proposalValidationDeadline))
) / 1000
);
- const actual = getValidationTimestamp(proposalValidationDeadline);
+ const actual = getValidationTimestamp(
+ proposalValidationDeadline,
+ isMaximumDeadlineSelected
+ );
expect(actual).toEqual(expected);
});
- it('should return the correct timestamp if the proposalValidationDeadline is 1 (when no extra mins are added)', () => {
+ it('should return the correct timestamp if the proposalValidationDeadline is neither maximum nor minimum (when no extra mins are added)', () => {
const proposalValidationDeadline = '1';
+ const isMaximumDeadlineSelected = false;
const expected = Math.floor(
getTime(addHours(new Date(), Number(proposalValidationDeadline))) / 1000
);
- const actual = getValidationTimestamp(proposalValidationDeadline);
+ const actual = getValidationTimestamp(
+ proposalValidationDeadline,
+ isMaximumDeadlineSelected
+ );
+ expect(actual).toEqual(expected);
+ });
+
+ it('should return the correct timestamp if the proposalValidationDeadline is maximum (when 2 secs are subtracted)', () => {
+ const proposalValidationDeadline = '2';
+ const isMaximumDeadlineSelected = true;
+ const expected = Math.floor(
+ getTime(
+ addHours(subSeconds(new Date(), 2), Number(proposalValidationDeadline))
+ ) / 1000
+ );
+ const actual = getValidationTimestamp(
+ proposalValidationDeadline,
+ isMaximumDeadlineSelected
+ );
expect(actual).toEqual(expected);
});
});
diff --git a/libs/governance/src/utils/get-validation-timestamp.ts b/libs/governance/src/utils/get-validation-timestamp.ts
index 36a7a7011..945779396 100644
--- a/libs/governance/src/utils/get-validation-timestamp.ts
+++ b/libs/governance/src/utils/get-validation-timestamp.ts
@@ -1,17 +1,25 @@
-import { addHours, addMinutes, getTime } from 'date-fns';
+import { addHours, getTime } from 'date-fns';
+import { addTwoMinutes, subtractTwoSeconds } from './deadline-helpers';
// If proposalValidationDeadline is at its minimum of 0 hours, then we add
// 2 extra minutes to the validation timestamp to ensure that there's time
// to confirm in the wallet.
-export const getValidationTimestamp = (proposalValidationDeadline: string) =>
+// If it's at its maximum, remove a couple of seconds to ensure rounding errors
+// and communication delays don't cause the proposal deadline to be slightly
+// later than the API can accept.
+
+export const getValidationTimestamp = (
+ proposalValidationDeadline: string,
+ maximumDeadlineSelected: boolean
+) =>
Math.floor(
getTime(
- proposalValidationDeadline === '0'
- ? addHours(
- addMinutes(new Date(), 2),
- Number(proposalValidationDeadline)
- )
- : addHours(new Date(), Number(proposalValidationDeadline))
+ addHours(
+ (proposalValidationDeadline === '0' && addTwoMinutes()) ||
+ (maximumDeadlineSelected && subtractTwoSeconds()) ||
+ new Date(),
+ Number(proposalValidationDeadline)
+ )
) / 1000
);