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
This commit is contained in:
Sam Keen 2022-11-11 14:30:03 +00:00 committed by GitHub
parent 266f87be8f
commit d3cb3896f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 421 additions and 143 deletions

View File

@ -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",

View File

@ -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'
);
});
});

View File

@ -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 = ({
<span data-testid="enactment-date" className="pl-2">
{getDateTimeFormat().format(deadlineDates.enactment)}
</span>
{deadlines.enactment === minEnactmentHours && (
<span
data-testid="enactment-2-mins-extra"
className="block mt-4 font-light"
>
{t('ThisWillAdd2MinutesToAllowTimeToConfirmInWallet')}
</span>
)}
{deadlines.enactment && deadlines.enactment < deadlines.vote && (
<span
data-testid="enactment-before-voting-deadline"
className="block mt-4 text-vega-red-dark"
>
{t('ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline')}
</span>
)}
</p>
)}
</FormGroup>
@ -302,16 +320,13 @@ export function ProposalFormVoteAndEnactmentDeadline({
});
const [deadlineDates, setDeadlineDates] = useState<DeadlineDatesProps>({
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
),
}));
};

View File

@ -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
),
},
});

View File

@ -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
),
},
});

View File

@ -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
),
},
});

View File

@ -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
),
},
});

View File

@ -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'),
})}

View File

@ -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
),
},
});

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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);
});

View File

@ -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
);

View File

@ -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);
});

View File

@ -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

View File

@ -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);
});
});

View File

@ -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
);