From aacb3e550c5d7c1a58ac49ac2dba6fe046e2ae06 Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 24 Jul 2020 09:07:06 -0400 Subject: [PATCH 1/5] basic fix --- src/lib/proposalHelpers.ts | 21 +++++++++++---------- src/lib/proposalUtils.ts | 8 ++++---- src/lib/util.ts | 28 +++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/lib/proposalHelpers.ts b/src/lib/proposalHelpers.ts index 3942ae5c7..cd23a6b0f 100644 --- a/src/lib/proposalHelpers.ts +++ b/src/lib/proposalHelpers.ts @@ -1,6 +1,7 @@ import * as moment from "moment"; import { IProposalOutcome, IProposalStage, IProposalState } from "@daostack/arc.js"; +import { safeMoment } from "lib/util"; export interface IRedemptionState { accountAddress: string; @@ -18,23 +19,23 @@ export interface IRedemptionState { voterReputation: number; } -export const closingTime = (proposal: IProposalState) => { +export const closingTime = (proposal: IProposalState): moment.Moment => { switch (proposal.stage) { case IProposalStage.ExpiredInQueue: case IProposalStage.Queued: - return moment((Number(proposal.createdAt.toString()) + proposal.genesisProtocolParams.queuedVotePeriodLimit) * 1000); + return safeMoment(proposal.createdAt).add(proposal.genesisProtocolParams.queuedVotePeriodLimit, "seconds"); case IProposalStage.PreBoosted: - return moment((proposal.preBoostedAt + proposal.genesisProtocolParams.preBoostedVotePeriodLimit) * 1000); + return safeMoment(proposal.preBoostedAt).add(proposal.genesisProtocolParams.preBoostedVotePeriodLimit, "seconds"); case IProposalStage.Boosted: - return moment((proposal.boostedAt + proposal.genesisProtocolParams.boostedVotePeriodLimit) * 1000); + return safeMoment(proposal.boostedAt).add(proposal.genesisProtocolParams.boostedVotePeriodLimit, "seconds"); case IProposalStage.QuietEndingPeriod: - return moment((proposal.quietEndingPeriodBeganAt + proposal.genesisProtocolParams.quietEndingPeriod) * 1000); + return safeMoment(proposal.quietEndingPeriodBeganAt).add(proposal.genesisProtocolParams.quietEndingPeriod, "seconds"); case IProposalStage.Executed: - return moment(proposal.executedAt * 1000); + return safeMoment(proposal.executedAt); } }; -export function proposalExpired(proposal: IProposalState) { +export function proposalExpired(proposal: IProposalState): boolean { const res = ( (proposal.stage === IProposalStage.ExpiredInQueue) || (proposal.stage === IProposalStage.Queued && closingTime(proposal) <= moment()) @@ -42,20 +43,20 @@ export function proposalExpired(proposal: IProposalState) { return res; } -export function proposalEnded(proposal: IProposalState) { +export function proposalEnded(proposal: IProposalState): boolean { const res = ( (proposal.stage === IProposalStage.Executed) || proposalExpired(proposal)); return res; } -export function proposalPassed(proposal: IProposalState) { +export function proposalPassed(proposal: IProposalState): boolean { const res = ( (proposal.stage === IProposalStage.Executed && proposal.winningOutcome === IProposalOutcome.Pass) ); return res; } -export function proposalFailed(proposal: IProposalState) { +export function proposalFailed(proposal: IProposalState): boolean { const res = ( (proposal.stage === IProposalStage.Executed && proposal.winningOutcome === IProposalOutcome.Fail) || proposalExpired(proposal) diff --git a/src/lib/proposalUtils.ts b/src/lib/proposalUtils.ts index 69d1ce07c..5a6db9d38 100644 --- a/src/lib/proposalUtils.ts +++ b/src/lib/proposalUtils.ts @@ -3,7 +3,7 @@ import moment = require("moment-timezone"); const cloneDeep = require("clone-deep"); -export function importUrlValues(defaultValues: Values) { +export function importUrlValues(defaultValues: Values): any { const { search } = window.location; const params = new URLSearchParams(search); const initialFormValues: any = cloneDeep([defaultValues])[0]; @@ -41,14 +41,14 @@ export function importUrlValues(defaultValues: Values) { return initialFormValues; } -export const exportUrl = (values: any) => { - const setQueryString = (key: string) => { +export const exportUrl = (values: unknown): void => { + const setQueryString = (key: keyof typeof values): string => { if (values[key] === undefined) { return ""; } if (typeof values[key] === "object") { if (moment.isMoment(values[key])) { - return `${key}=${values[key].toString()}`; + return `${key}=${(values[key] as moment.Moment).toString()}`; } else { return `${key}=${JSON.stringify(values[key])}`; } diff --git a/src/lib/util.ts b/src/lib/util.ts index 8403edad1..a355a9657 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -301,7 +301,7 @@ export async function getNetworkId(web3Provider?: Web3Provider): Promise return network.chainId.toString(); } else { const promise = new Promise((resolve, reject) => { - web3Provider.send("net_version", (error, response) => { + web3Provider.send("net_version", (error: any, response: any) => { if (error) { reject(error); } else { @@ -634,3 +634,29 @@ export function ethBalance(address: Address): Observable { const arc = getArc(); return arc.ethBalance(address); } + +/** + * arc.js is inconsistent in how it returns datetimes. + * Convert all possibilities safely to a `moment`. + * Is also OK when the dateSpecifier is already a moment + * @param dateSpecifier + */ +export function safeMoment(dateSpecifier: moment.Moment | Date | number | string | undefined): moment.Moment | null { + if (!dateSpecifier) { + return null; + } + + switch (typeof dateSpecifier) { + case "object": + if (moment.isMoment(dateSpecifier)) { + return dateSpecifier; + } + // else assume is a Date, fallthrough + case "string": + return moment(dateSpecifier); + case "number": + return moment.unix(dateSpecifier); + default: + throw new Error(`safeMoment: unknown type: ${typeof dateSpecifier}`); + } +} From 9b2970b4319801cce29edf53f87a20abad747fac Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 24 Jul 2020 09:10:18 -0400 Subject: [PATCH 2/5] add more use of safeMoment --- .../ContributionRewardExtRewarders/Competition/utils.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Plugin/ContributionRewardExtRewarders/Competition/utils.ts b/src/components/Plugin/ContributionRewardExtRewarders/Competition/utils.ts index 691d054a9..79b2b4fab 100644 --- a/src/components/Plugin/ContributionRewardExtRewarders/Competition/utils.ts +++ b/src/components/Plugin/ContributionRewardExtRewarders/Competition/utils.ts @@ -8,6 +8,7 @@ import { operationNotifierObserver } from "actions/arcActions"; import { IRootState } from "reducers"; import { Observable, of } from "rxjs"; import { map, mergeMap, toArray, first } from "rxjs/operators"; +import { safeMoment } from "lib/util"; /** * Defined in the order that Competition cards should be sorted in the List component. @@ -72,10 +73,10 @@ export class CompetitionStatus { export const competitionStatus = (competition: ICompetitionProposalState): CompetitionStatus => { const now = moment(); - const startTime = moment(competition.startTime); - const submissionsEndTime = moment(competition.suggestionsEndTime); - const votingStartTime = moment(competition.votingStartTime); - const endTime = moment(competition.endTime); + const startTime = safeMoment(competition.startTime); + const submissionsEndTime = safeMoment(competition.suggestionsEndTime); + const votingStartTime = safeMoment(competition.votingStartTime); + const endTime = safeMoment(competition.endTime); const hasSubmissions = !!competition.totalSuggestions; const hasWinners = !!competition.numberOfWinningSuggestions; let status: CompetitionStatusEnum; From 629e1915924cbcd2f58ecb7e364080fe32d9b0cf Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 24 Jul 2020 09:17:08 -0400 Subject: [PATCH 3/5] a couple more --- src/components/Proposal/Voting/VoteButtons.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Proposal/Voting/VoteButtons.tsx b/src/components/Proposal/Voting/VoteButtons.tsx index c509d6c37..9ab225a40 100644 --- a/src/components/Proposal/Voting/VoteButtons.tsx +++ b/src/components/Proposal/Voting/VoteButtons.tsx @@ -7,7 +7,7 @@ import classNames from "classnames"; import Reputation from "components/Account/Reputation"; import { ActionTypes, default as PreTransactionModal } from "components/Shared/PreTransactionModal"; import Analytics from "lib/analytics"; -import { fromWei, targetedNetwork } from "lib/util"; +import { fromWei, targetedNetwork, safeMoment } from "lib/util"; import { Page } from "pages"; import * as React from "react"; import { connect } from "react-redux"; @@ -105,7 +105,7 @@ class VoteButtons extends React.Component { (proposalState.stage === IProposalStage.Boosted && expired) || (proposalState.stage === IProposalStage.QuietEndingPeriod && expired) || (currentAccountState && currentAccountState.reputation.eq(new BN(0))) || - (currentAccountState && (proposalState.createdAt < currentAccountState.createdAt) && + (currentAccountState && (safeMoment(proposalState.createdAt) < safeMoment(currentAccountState.createdAt)) && //this is a workaround till https://github.com/daostack/subgraph/issues/548 (targetedNetwork() !== "ganache")) || currentVote === IProposalOutcome.Pass || @@ -133,7 +133,7 @@ class VoteButtons extends React.Component { * 5) when `currentAccount` is not found in the subgraph, then a fake `currentAccountState` is created with * rep == 0 */ - (currentAccountState && (proposalState.createdAt < currentAccountState.createdAt)) ? + (currentAccountState && (safeMoment(proposalState.createdAt) < safeMoment(currentAccountState.createdAt))) ? "Must have had reputation in this DAO when the proposal was created" : proposalState.stage === IProposalStage.ExpiredInQueue || (proposalState.stage === IProposalStage.Boosted && expired) || From 0f4882eec71ade954ed0ba2c3bf09b496b538c04 Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 24 Jul 2020 09:22:02 -0400 Subject: [PATCH 4/5] clean up safeMoment --- src/lib/util.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/util.ts b/src/lib/util.ts index a355a9657..02ccbbf1d 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -638,14 +638,11 @@ export function ethBalance(address: Address): Observable { /** * arc.js is inconsistent in how it returns datetimes. * Convert all possibilities safely to a `moment`. - * Is also OK when the dateSpecifier is already a moment + * Is OK when the dateSpecifier is already a moment. + * If is a string, must be ISO-conformant. * @param dateSpecifier */ -export function safeMoment(dateSpecifier: moment.Moment | Date | number | string | undefined): moment.Moment | null { - if (!dateSpecifier) { - return null; - } - +export function safeMoment(dateSpecifier: moment.Moment | Date | number | string | undefined): moment.Moment { switch (typeof dateSpecifier) { case "object": if (moment.isMoment(dateSpecifier)) { From cf509d7a38df33eeae69fe63e0b93cfd04e84049 Mon Sep 17 00:00:00 2001 From: Doug Kent Date: Fri, 24 Jul 2020 09:24:32 -0400 Subject: [PATCH 5/5] comment --- src/lib/util.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/util.ts b/src/lib/util.ts index 02ccbbf1d..a2543ac30 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -652,6 +652,7 @@ export function safeMoment(dateSpecifier: moment.Moment | Date | number | string case "string": return moment(dateSpecifier); case "number": + // then should be a count of seconds in UNIX epoch return moment.unix(dateSpecifier); default: throw new Error(`safeMoment: unknown type: ${typeof dateSpecifier}`);