Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve /settings/nfts and /settings/cycle conflicting reconfigureFundingCyclesOf tx #4068

Merged
merged 13 commits into from
Nov 7, 2023
Merged
13 changes: 4 additions & 9 deletions src/components/announcements/ExampleFeatureAnnouncement.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Trans, t } from '@lingui/macro'
import { JuiceModalProps } from 'components/modals/JuiceModal'
import { NewFeatureAnnouncement } from './NewFeatureAnnouncement'

Expand All @@ -11,17 +10,13 @@ export const ExampleFeatureAnnouncement = (
return (
<NewFeatureAnnouncement
{...props}
title={t`Example Feature`}
title="Example Feature"
position="topRight"
okText={t`Got it`}
okText="Got it"
hideCancelButton
>
<p>
<Trans>This is an example feature announcement.</Trans>
</p>
<p>
<Trans>Use me as a base!</Trans>
</p>
<p>This is an example feature announcement.</p>
<p>Use me as a base!</p>
</NewFeatureAnnouncement>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { t } from '@lingui/macro'
import { AmountInCurrency } from 'components/currency/AmountInCurrency'
import { timeSecondsToDateString } from 'components/v2v3/V2V3Project/ProjectDashboard/utils/timeSecondsToDateString'
import { BigNumber } from 'ethers'
import { V2V3CurrencyOption } from 'models/v2v3/currencyOption'
import { V2V3FundingCycle } from 'models/v2v3/fundingCycle'
import { useMemo } from 'react'
import { fromWad } from 'utils/format/formatNumber'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { getBallotStrategyByAddress } from 'utils/v2v3/ballotStrategies'
import { V2V3CurrencyName } from 'utils/v2v3/currency'
import { MAX_DISTRIBUTION_LIMIT } from 'utils/v2v3/math'
import { ConfigurationPanelDatum } from '../../components/ConfigurationPanel'
import { pairToDatum } from '../../utils/pairToDatum'

export const useFormatConfigurationCyclesSection = ({
fundingCycle,
upcomingFundingCycle,
distributionLimitAmountCurrency,
upcomingDistributionLimitAmountCurrency,
}: {
fundingCycle: V2V3FundingCycle | undefined
upcomingFundingCycle?: V2V3FundingCycle | null
distributionLimitAmountCurrency:
| {
distributionLimit: BigNumber | undefined
currency: BigNumber | undefined
}
| undefined
upcomingDistributionLimitAmountCurrency?: {
distributionLimit: BigNumber | undefined
currency: BigNumber | undefined
} | null
}) => {
const durationDatum: ConfigurationPanelDatum = useMemo(() => {
const formatDuration = (duration: BigNumber | undefined) => {
if (duration === undefined) return undefined
if (duration.eq(0)) return t`Not set`
return timeSecondsToDateString(duration.toNumber(), 'short', 'lower')
}
const currentDuration = formatDuration(fundingCycle?.duration)
if (upcomingFundingCycle === null) {
return pairToDatum(t`Duration`, currentDuration, null)
}
const upcomingDuration = formatDuration(upcomingFundingCycle?.duration)

return pairToDatum(t`Duration`, currentDuration, upcomingDuration)
}, [fundingCycle?.duration, upcomingFundingCycle])

const payoutsDatum: ConfigurationPanelDatum = useMemo(() => {
const formatCurrency = (currency: BigNumber | undefined) => {
if (currency === undefined) return undefined
return currency.toNumber() as V2V3CurrencyOption
}
const formatAmountWad = (
amountWad: BigNumber | undefined,
currency: V2V3CurrencyOption | undefined,
) => {
if (amountWad === undefined) return undefined
if (amountWad.eq(MAX_DISTRIBUTION_LIMIT)) return t`Unlimited`
if (amountWad.eq(0)) return t`Zero (no payouts)`
return formatCurrencyAmount({
amount: Number(fromWad(amountWad)),
currency,
})
}
const { distributionLimit, currency } =
distributionLimitAmountCurrency ?? {}
// const currentPayout = formatAmountWad(
// distributionLimit,
// formatCurrency(currency),
// )
const currentPayout = (
<AmountInCurrency
amount={distributionLimit}
currency={V2V3CurrencyName(formatCurrency(currency))}
/>
)

if (upcomingDistributionLimitAmountCurrency === null) {
return pairToDatum(t`Payouts`, currentPayout, null)
}

const upcomingDistributionLimit =
upcomingDistributionLimitAmountCurrency?.distributionLimit !== undefined
? upcomingDistributionLimitAmountCurrency.distributionLimit
: undefined
const upcomingDistributionLimitCurrency =
upcomingDistributionLimitAmountCurrency?.currency !== undefined
? upcomingDistributionLimitAmountCurrency.currency
: undefined
const upcomingPayout = formatAmountWad(
upcomingDistributionLimit,
formatCurrency(upcomingDistributionLimitCurrency),
)

return pairToDatum(t`Payouts`, currentPayout, upcomingPayout)
}, [distributionLimitAmountCurrency, upcomingDistributionLimitAmountCurrency])

const editDeadlineDatum: ConfigurationPanelDatum = useMemo(() => {
const currentBallotStrategy = fundingCycle?.ballot
? getBallotStrategyByAddress(fundingCycle.ballot)
: undefined
const current = currentBallotStrategy?.name
if (upcomingFundingCycle === null) {
return pairToDatum(t`Edit deadline`, current, null)
}

const upcomingBallotStrategy = upcomingFundingCycle?.ballot
? getBallotStrategyByAddress(upcomingFundingCycle.ballot)
: undefined
const upcoming = upcomingBallotStrategy?.name
return pairToDatum(t`Edit deadline`, current, upcoming)
}, [fundingCycle?.ballot, upcomingFundingCycle])

return useMemo(() => {
return {
duration: durationDatum,
payouts: payoutsDatum,
editDeadline: editDeadlineDatum,
}
}, [durationDatum, editDeadlineDatum, payoutsDatum])
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { NFT_FUNDING_CYCLE_METADATA_OVERRIDES } from 'utils/nftFundingCycleMetad
import { WEIGHT_UNCHANGED, WEIGHT_ZERO } from 'utils/v2v3/fundingCycle'
import { reloadWindow } from 'utils/windowUtils'
import { EditingFundingCycleConfig } from './useEditingFundingCycleConfig'
import { useResolveEditCycleConflicts } from './useResolveEditCycleConflicts'

/**
* Return the value of the `weight` argument to send in the transaction.
Expand All @@ -41,6 +42,13 @@ const getWeightArgument = ({
return newFundingCycleWeight
}

/**
* Return a function to initiate a transaction to reconfigure a project's funding cycle.
*
* @dev Used in two places:
* 1. Edit cycle form
* 2. NFT page - edit dataSource-related attributes of the cycle
*/
export const useReconfigureFundingCycle = ({
editingFundingCycleConfig,
memo,
Expand All @@ -65,6 +73,7 @@ export const useReconfigureFundingCycle = ({
const reconfigureV2V3FundingCycleTx = useReconfigureV2V3FundingCycleTx()
const reconfigureV2V3FundingCycleWithNftsTx =
useReconfigureV2V3FundingCycleWithNftsTx()
const resolveEditCycleConflicts = useResolveEditCycleConflicts()

// If given a latestEditingData, will use that. Else, will use redux store
const reconfigureFundingCycle = useCallback(
Expand Down Expand Up @@ -105,20 +114,22 @@ export const useReconfigureFundingCycle = ({
newFundingCycleWeight: editingFundingCycleData.weight,
})

const reconfigureFundingCycleData: ReconfigureFundingCycleTxParams = {
fundingCycleData: {
...editingFundingCycleData,
weight,
},
fundingCycleMetadata,
fundAccessConstraints: editingFundAccessConstraints,
groupedSplits: [
editingPayoutGroupedSplits,
editingReservedTokensGroupedSplits,
],
memo,
mustStartAtOrAfter: editingMustStartAtOrAfter,
}
const reconfigureFundingCycleData: ReconfigureFundingCycleTxParams =
resolveEditCycleConflicts({
fundingCycleData: {
...editingFundingCycleData,
weight,
},
fundingCycleMetadata,
fundAccessConstraints: editingFundAccessConstraints,
groupedSplits: [
editingPayoutGroupedSplits,
editingReservedTokensGroupedSplits,
],
memo,
mustStartAtOrAfter: editingMustStartAtOrAfter,
launchedNewNfts,
})

const txOpts = {
onDone() {
Expand Down Expand Up @@ -177,6 +188,7 @@ export const useReconfigureFundingCycle = ({
memo,
onComplete,
projectId,
resolveEditCycleConflicts,
],
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext'
import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext'
import useProjectQueuedFundingCycle from 'hooks/v2v3/contractReader/useProjectQueuedFundingCycle'
import { ReconfigureFundingCycleTxParams } from 'hooks/v2v3/transactor/useReconfigureV2V3FundingCycleTx'
import {
V2V3FundingCycleData,
V2V3FundingCycleMetadata,
} from 'models/v2v3/fundingCycle'
import { useContext } from 'react'

/**
* Determines if a "Launch NFTs" (NftDeployer.reconfigureFundingCycleOf) has been called
* in the same cycle as an "Edit cycle" (reconfigureFundingCycleOf) tx.
*
* If so, we need to pass the new delegate and other NFT-related data into the subsequent
* "Edit cycle" tx so they are not overriden and lost.
*
* @param {V2V3FundingCycleMetadata} currentFcMetadata - The current funding cycle metadata.
* @param {V2V3FundingCycleMetadata} queuedFcMetadata - The queued funding cycle metadata.
* @returns {boolean} - Whether a conflict exists or not.
*/
function hasNftConflict(
currentFcMetadata: V2V3FundingCycleMetadata,
queuedFcMetadata: V2V3FundingCycleMetadata,
): boolean {
// if the queued cycle's NFT data has any changes to the current cycle, return true.
const useDataSourceForPayHasDiff =
queuedFcMetadata.useDataSourceForPay !==
currentFcMetadata.useDataSourceForPay
const useDataSourceForRedeemHasDiff =
queuedFcMetadata.useDataSourceForRedeem !==
currentFcMetadata.useDataSourceForRedeem
const useDataSourceHasDiff =
queuedFcMetadata.dataSource !== currentFcMetadata.dataSource

return (
useDataSourceForPayHasDiff ||
useDataSourceForRedeemHasDiff ||
useDataSourceHasDiff
)
}

/**
*
* @returns
*/
export const useResolveEditCycleConflicts = () => {
const { projectId } = useContext(ProjectMetadataContext)
const { fundingCycleMetadata } = useContext(V2V3ProjectContext)

const { data: queuedCycle } = useProjectQueuedFundingCycle({ projectId })

// If no queued cycle, no resolving needs to be done. Return current data
if (!queuedCycle || !fundingCycleMetadata) {
return (data: ReconfigureFundingCycleTxParams) => data
}

const queuedFcData: V2V3FundingCycleData = queuedCycle[0]
const queuedFcMetadata: V2V3FundingCycleMetadata = queuedCycle[1]

return (
data: ReconfigureFundingCycleTxParams & { launchedNewNfts?: boolean },
) => {
// Calling from "Edit cycle" and an NFT tx has be called same cycle: pass that previously queued NFT data into this "Edit cycle" tx
if (hasNftConflict(fundingCycleMetadata, queuedFcMetadata)) {
return {
...data,
fundingCycleMetadata: {
...data.fundingCycleMetadata,
useDataSourceForPay: queuedFcMetadata.useDataSourceForPay,
useDataSourceForRedeem: queuedFcMetadata.useDataSourceForRedeem,
dataSource: queuedFcMetadata.dataSource,
},
}
}
// Calling from "Launch NFTs" and an "Edit cycle" tx has be called same cycle: pass that previously queued data into this "Launch NFTs" tx
if (data.launchedNewNfts) {
return {
...data,
fundingCycleMetadata: {
...data.fundingCycleMetadata,
...queuedFcMetadata,
},
fundingCycleData: {
...data.fundingCycleData,
...queuedFcData,
},
}
// Calling "Edit cycle" when a previously "Edit cycle" tx exists: ignore the old tx data, return the new one
} else return data
}
}
Loading
Loading