Skip to content

Commit

Permalink
fix: launch projects with payouts and reserved tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
aeolianeth committed Sep 23, 2024
1 parent e699a6d commit d7b93dc
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Form } from 'antd'
import { CURRENCY_METADATA, CurrencyName } from 'constants/currency'
import { BigNumber } from 'ethers'
import { PayoutsTable } from 'packages/v2v3/components/shared/PayoutsTable/PayoutsTable'
import { Split } from 'packages/v2v3/models/splits'
import {
V2V3CurrencyName,
getV2V3CurrencyOption,
} from 'packages/v2v3/utils/currency'
import { MAX_DISTRIBUTION_LIMIT } from 'packages/v2v3/utils/math'
import { allocationToSplit, splitToAllocation } from 'packages/v2v3/utils/splitToAllocation'
import {
allocationToSplit,
splitToAllocation,
} from 'packages/v2v3/utils/splitToAllocation'
import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math'
import { ReactNode } from 'react'
import { useEditingDistributionLimit } from 'redux/hooks/useEditingDistributionLimit'
import { fromWad, parseWad } from 'utils/format/formatNumber'
Expand Down Expand Up @@ -36,7 +40,7 @@ export function CreateFlowPayoutsTable({
const { form, initialValues } = usePayoutsForm()
const distributionLimit = !editingDistributionLimit
? 0
: editingDistributionLimit.amount.eq(MAX_DISTRIBUTION_LIMIT)
: editingDistributionLimit.amount.eq(MAX_PAYOUT_LIMIT)
? undefined
: parseFloat(fromWad(editingDistributionLimit?.amount))

Expand All @@ -45,7 +49,9 @@ export function CreateFlowPayoutsTable({

const setDistributionLimit = (amount: number | undefined) => {
setDistributionLimitAmount(
amount === undefined ? MAX_DISTRIBUTION_LIMIT : parseWad(amount),
amount === undefined
? BigNumber.from(MAX_PAYOUT_LIMIT)
: parseWad(amount),
)
}
const setCurrency = (currency: CurrencyName) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { TokenRedemptionRateGraph } from 'components/TokenRedemptionRateGraph/To
import useMobile from 'hooks/useMobile'
import { formatFundingCycleDuration } from 'packages/v2v3/components/Create/utils/formatFundingCycleDuration'
import { ReservedTokensList } from 'packages/v2v3/components/shared/ReservedTokensList'
import { MAX_DISTRIBUTION_LIMIT, MAX_MINT_RATE } from 'packages/v2v3/utils/math'
import { MAX_MINT_RATE } from 'packages/v2v3/utils/math'
import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math'
import { useAppSelector } from 'redux/hooks/useAppSelector'
import { useEditingDistributionLimit } from 'redux/hooks/useEditingDistributionLimit'
import { inputMustExistRule } from 'utils/antdRules'
Expand Down Expand Up @@ -52,9 +53,7 @@ export const CustomTokenSettings = () => {

const discountRateDisabled = !parseInt(duration)

const redemptionRateDisabled = distributionLimit?.amount.eq(
MAX_DISTRIBUTION_LIMIT,
)
const redemptionRateDisabled = distributionLimit?.amount.eq(MAX_PAYOUT_LIMIT)

const initalMintRateAccessory = (
<span className="mr-5">
Expand Down Expand Up @@ -132,9 +131,10 @@ export const CustomTokenSettings = () => {
<div className="flex flex-col gap-6">
<span>
<Trans>
The issuance rate is reduced by this percentage every ruleset (every{' '}
<strong>{formatFundingCycleDuration(duration)}</strong>). The higher this rate, the
more incentive to pay this project earlier.
The issuance rate is reduced by this percentage every ruleset
(every <strong>{formatFundingCycleDuration(duration)}</strong>).
The higher this rate, the more incentive to pay this project
earlier.
</Trans>
</span>
<Form.Item
Expand Down Expand Up @@ -170,8 +170,8 @@ export const CustomTokenSettings = () => {
) : discountRate === 100 ? (
<Trans>
After {formatFundingCycleDuration(duration)} (your first
ruleset), your project will not issue any tokens unless you edit
the issuance rate.
ruleset), your project will not issue any tokens unless you
edit the issuance rate.
</Trans>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ONE_MILLION } from 'constants/numbers'
import { ProjectTokensSelection } from 'models/projectTokenSelection'
import { AllocationSplit } from 'packages/v2v3/components/shared/Allocation/Allocation'
import {
MAX_DISTRIBUTION_LIMIT,
discountRateFrom,
formatDiscountRate,
formatIssuanceRate,
Expand All @@ -14,14 +13,18 @@ import {
redemptionRateFrom,
reservedRateFrom,
} from 'packages/v2v3/utils/math'
import { allocationToSplit, splitToAllocation } from 'packages/v2v3/utils/splitToAllocation'
import {
allocationToSplit,
splitToAllocation,
} from 'packages/v2v3/utils/splitToAllocation'
import { useDebugValue, useEffect, useMemo } from 'react'
import { useAppDispatch } from 'redux/hooks/useAppDispatch'
import { useAppSelector } from 'redux/hooks/useAppSelector'
import { useEditingDistributionLimit } from 'redux/hooks/useEditingDistributionLimit'
import { useEditingReservedTokensSplits } from 'redux/hooks/useEditingReservedTokensSplits'
import { editingV2ProjectActions } from 'redux/slices/editingV2Project'
import { useFormDispatchWatch } from '../../hooks/useFormDispatchWatch'
import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math'

export type ProjectTokensFormProps = Partial<{
selection: ProjectTokensSelection
Expand Down Expand Up @@ -58,7 +61,7 @@ export const useProjectTokensForm = () => {
const [distributionLimit] = useEditingDistributionLimit()

const redemptionRateDisabled =
!distributionLimit || distributionLimit.amount.eq(MAX_DISTRIBUTION_LIMIT)
!distributionLimit || distributionLimit.amount.eq(MAX_PAYOUT_LIMIT)
const discountRateDisabled = !parseInt(fundingCycleData.duration)

const initialValues: ProjectTokensFormProps | undefined = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ProjectTokensSelection } from 'models/projectTokenSelection'
import { TreasurySelection } from 'models/treasurySelection'
import { useRouter } from 'next/router'
import { ballotStrategiesFn } from 'packages/v2v3/constants/ballotStrategies'
import { MAX_DISTRIBUTION_LIMIT } from 'packages/v2v3/utils/math'
import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math'
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import {
Expand Down Expand Up @@ -49,7 +49,7 @@ const parseCreateFlowStateFromInitialState = (

if (distributionLimit === undefined) {
treasurySelection = undefined
} else if (distributionLimit.eq(MAX_DISTRIBUTION_LIMIT)) {
} else if (distributionLimit.eq(MAX_PAYOUT_LIMIT)) {
treasurySelection = 'unlimited'
} else if (distributionLimit.eq(0)) {
treasurySelection = 'zero'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber } from 'ethers'
import { PayoutsSelection } from 'models/payoutsSelection'
import { MAX_DISTRIBUTION_LIMIT } from 'packages/v2v3/utils/math'
import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math'

export const determineAvailablePayoutsSelections = (
distributionLimit: BigNumber | undefined,
Expand All @@ -11,7 +11,7 @@ export const determineAvailablePayoutsSelections = (
if (distributionLimit.eq(0)) {
return new Set(['amounts'])
}
if (distributionLimit.eq(MAX_DISTRIBUTION_LIMIT)) {
if (distributionLimit.eq(MAX_PAYOUT_LIMIT)) {
return new Set(['percentages'])
}
return new Set(['amounts', 'percentages'])
Expand Down
3 changes: 2 additions & 1 deletion src/packages/v4/hooks/useLaunchProjectTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LaunchV2V3ProjectData } from 'packages/v2v3/hooks/transactor/useLaunchP
import { useCallback, useContext } from 'react'
import { DEFAULT_MUST_START_AT_OR_AFTER } from 'redux/slices/editingV2Project'
import { Address, WaitForTransactionReceiptReturnType } from 'viem'
import { sepolia } from 'viem/chains'
import {
LaunchV2V3ProjectArgs,
transformV2V3CreateArgsToV4,
Expand Down Expand Up @@ -67,7 +68,7 @@ export function useLaunchProjectTx() {
const { writeContractAsync: writeLaunchProject } =
useWriteJbControllerLaunchProjectFor()

const chainId = useCurrentRouteChainId() ?? 84532 // Default to Sepolia.
const chainId = useCurrentRouteChainId() ?? sepolia.id // default to sepolia
const terminalAddress = chainId
? SUPPORTED_JB_MULTITERMINAL_ADDRESS[chainId]
: undefined
Expand Down
32 changes: 17 additions & 15 deletions src/packages/v4/hooks/useV4PayoutSplits.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { JBSplit, SplitPortion } from 'juice-sdk-core'
import {
JBSplit,
NATIVE_TOKEN,
SplitPortion
} from 'juice-sdk-core'
import {
useJBContractContext,
useJBRuleset,
Expand All @@ -10,22 +14,20 @@ export const useV4CurrentPayoutSplits = () => {
const { projectId } = useJBContractContext()
const { data: tokenAddress } = useReadJbTokensTokenOf()
const { data: ruleset } = useJBRuleset()
const rulesetId = BigInt(ruleset?.id ?? 0)
const groupId = BigInt(tokenAddress ?? NATIVE_TOKEN) // contracts say this is: `uint256(uint160(tokenAddress))`

const groupId = BigInt(tokenAddress ?? 0) // contracts say this is: `uint256(uint160(tokenAddress))`
const { data: _splits, isLoading: currentSplitsLoading } =
useReadJbSplitsSplitsOf({
args: [projectId, BigInt(ruleset?.id ?? 0), groupId],
query: {
select(data) {
return data.map(d => ({
return useReadJbSplitsSplitsOf({
args: [projectId, rulesetId, groupId],
query: {
select(data) {
return data.map(
(d): JBSplit => ({
...d,
percent: new SplitPortion(d.percent),
}))
},
}),
)
},
})

const splits: JBSplit[] = _splits ? [..._splits] : []

return { splits, isLoading: currentSplitsLoading }
},
})
}
145 changes: 75 additions & 70 deletions src/packages/v4/utils/launchProject.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { NATIVE_TOKEN, NATIVE_TOKEN_DECIMALS, SplitGroup } from 'juice-sdk-core'
import { V2FundingCycleMetadata } from 'packages/v2/models/fundingCycle'
import {
V2V3FundAccessConstraint,
V2V3FundingCycleData,
} from 'packages/v2v3/models/fundingCycle'
import { GroupedSplits, SplitGroup } from 'packages/v2v3/models/splits'
import { GroupedSplits } from 'packages/v2v3/models/splits'
import { V3FundingCycleMetadata } from 'packages/v3/models/fundingCycle'
import { Address } from 'viem'

export type LaunchV2V3ProjectArgs = [
string, // _owner
Expand All @@ -24,8 +26,8 @@ export function transformV2V3CreateArgsToV4({
currencyTokenAddress,
}: {
v2v3Args: LaunchV2V3ProjectArgs
primaryNativeTerminal: `0x${string}`
currencyTokenAddress: `0x${string}`
primaryNativeTerminal: Address
currencyTokenAddress: Address
}) {
const [
_owner,
Expand All @@ -41,83 +43,86 @@ export function transformV2V3CreateArgsToV4({

const mustStartAtOrAfterNum = parseInt(_mustStartAtOrAfter)

const rulesetConfigurations = [
{
mustStartAtOrAfter: mustStartAtOrAfterNum ?? 0, // 0 denotes start immediately
duration: _data.duration.toNumber(),
weight: _data.weight.toBigInt(),
decayPercent: _data.discountRate.toNumber(),
approvalHook: _data.ballot as `0x${string}`,
const ruleset = {
mustStartAtOrAfter: mustStartAtOrAfterNum ?? 0, // 0 denotes start immediately
duration: _data.duration.toNumber(),
weight: _data.weight.toBigInt(),
decayPercent: _data.discountRate.toNumber(),

metadata: {
reservedPercent: _metadata.reservedRate.toNumber(),
redemptionRate: _metadata.redemptionRate.toNumber(),
baseCurrency: 1, // Not present in v2v3, passing 1 by default
pausePay: _metadata.pausePay,
pauseRedeem: _metadata.pauseRedeem,
pauseCreditTransfers: Boolean(_metadata.global.pauseTransfers),
allowOwnerMinting: _metadata.allowMinting,
allowSetCustomToken: false, // Assuming false by default
allowTerminalMigration: _metadata.allowTerminalMigration,
allowSetTerminals: _metadata.global.allowSetTerminals,
allowSetController: _metadata.global.allowSetController,
allowAddAccountingContext: false, // Not present in v2v3, passing false by default
allowAddPriceFeed: false, // Not present in v2v3, passing false by default
ownerMustSendPayouts: false, // Not present in v2v3, passing false by default
holdFees: _metadata.holdFees,
useTotalSurplusForRedemptions: _metadata.useTotalOverflowForRedemptions,
useDataHookForPay: _metadata.useDataSourceForPay,
useDataHookForRedeem: _metadata.useDataSourceForRedeem,
dataHook: _metadata.dataSource as `0x${string}`,
metadata: 0,
allowCrosschainSuckerExtension: false,
},
approvalHook: _data.ballot as Address,

splitGroups: _groupedSplits.map(group => ({
groupId: BigInt(group.group),
splits: group.splits.map(split => ({
preferAddToBalance: Boolean(split.preferClaimed),
percent: split.percent,
projectId: BigInt(parseInt(split.projectId ?? '0x00', 16)),
beneficiary: split.beneficiary as `0x${string}`,
lockedUntil: split.lockedUntil ?? 0,
hook: split.allocator as `0x${string}`,
})),
})),
metadata: {
reservedPercent: _metadata.reservedRate.toNumber(),
redemptionRate: _metadata.redemptionRate.toNumber(),
baseCurrency: 1, // Not present in v2v3, passing 1 by default
pausePay: _metadata.pausePay,
pauseRedeem: _metadata.pauseRedeem,
pauseCreditTransfers: Boolean(_metadata.global.pauseTransfers),
allowOwnerMinting: _metadata.allowMinting,
allowSetCustomToken: false, // Assuming false by default
allowTerminalMigration: _metadata.allowTerminalMigration,
allowSetTerminals: _metadata.global.allowSetTerminals,
allowSetController: _metadata.global.allowSetController,
allowAddAccountingContext: false, // Not present in v2v3, passing false by default
allowAddPriceFeed: false, // Not present in v2v3, passing false by default
ownerMustSendPayouts: false, // Not present in v2v3, passing false by default
holdFees: _metadata.holdFees,
useTotalSurplusForRedemptions: _metadata.useTotalOverflowForRedemptions,
useDataHookForPay: _metadata.useDataSourceForPay,
useDataHookForRedeem: _metadata.useDataSourceForRedeem,
dataHook: _metadata.dataSource as Address,
metadata: 0,
allowCrosschainSuckerExtension: false,
},

fundAccessLimitGroups: _fundAccessConstraints.map(constraint => ({
terminal: primaryNativeTerminal,
token: currencyTokenAddress,
payoutLimits: [
{
amount: constraint.distributionLimit.toBigInt(),
currency: constraint.distributionLimitCurrency.toNumber(),
},
] as const,
surplusAllowances: [
{
amount: constraint.overflowAllowance.toBigInt(),
currency: constraint.overflowAllowanceCurrency.toNumber(),
},
] as const,
splitGroups: _groupedSplits.map(group => ({
groupId:
group.group === SplitGroup.ETHPayout
? BigInt(NATIVE_TOKEN)
: 1n, // TODO dont hardcode reserved token group as 1n
splits: group.splits.map(split => ({
preferAddToBalance: Boolean(split.preferClaimed),
percent: split.percent,
projectId: BigInt(parseInt(split.projectId ?? '0x00', 16)),
beneficiary: split.beneficiary as Address,
lockedUntil: split.lockedUntil ?? 0,
hook: split.allocator as Address,
})),
},
]
})),

fundAccessLimitGroups: _fundAccessConstraints.map(constraint => ({
terminal: primaryNativeTerminal,
token: currencyTokenAddress,
payoutLimits: [
{
amount: constraint.distributionLimit.toBigInt(),
currency: Number(BigInt(NATIVE_TOKEN)), // TODO support USD somehow
},
],
surplusAllowances: [
{
amount: constraint.overflowAllowance.toBigInt(),
currency: Number(BigInt(NATIVE_TOKEN)),
},
],
})),
}

const rulesetConfigurations = [ruleset]

const terminalConfigurations = _terminals.map(terminal => ({
terminal: terminal as `0x${string}`,
terminal: terminal as Address,
accountingContextsToAccept: [
// @v4todo:
// {
// token: currencyTokenAddress,
// decimals: 18,
// currency: 0
// }
] as const,
{
token: currencyTokenAddress,
decimals: NATIVE_TOKEN_DECIMALS,
currency: Number(BigInt(currencyTokenAddress)),
},
],
}))

return [
_owner as `0x${string}`,
_owner as Address,
_projectMetadata[0],
rulesetConfigurations,
terminalConfigurations,
Expand Down
Loading

0 comments on commit d7b93dc

Please sign in to comment.