Skip to content

Commit

Permalink
🎛️ Integrate the crt constraints proposal (#4822)
Browse files Browse the repository at this point in the history
* Make the NumberOfBlocks renderer reusable

* Preview CRT constraints proposal

* Create CRT constraints proposal

* Query `UpdateTokenPalletTokenConstraintsProposalDetails`

* Schema and types

* Show current values in the form

* Show current values on the preview page

* Fix the vote on proposal modal

* Fix mismatched values

* Show current values on field inputs sub label instead of message
So the current values remain visible while input values are invalid

* Enable decimal values in `InputNumber`

* Use decimal percent to represent part per million

* Preview decimal percents
  • Loading branch information
thesan authored Apr 8, 2024
1 parent 26ccd79 commit 7577166
Show file tree
Hide file tree
Showing 32 changed files with 12,855 additions and 417 deletions.
12,405 changes: 12,051 additions & 354 deletions .yarn/patches/@joystream-types-npm-4.3.0-542438a0b6.patch

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions packages/server/src/common/queries/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2592,6 +2592,7 @@ union ProposalDetails =
| UpdatePalletFrozenStatusProposalDetails
| UpdateGlobalNftLimitProposalDetails
| DecreaseCouncilBudgetProposalDetails
| UpdateTokenPalletTokenConstraintsProposalDetails

union ProposalStatus =
ProposalStatusDeciding
Expand Down Expand Up @@ -3562,6 +3563,53 @@ type UpdatePalletFrozenStatusProposalDetails {
pallet: String!
}

type UpdateTokenPalletTokenConstraintsProposalDetails {
"""
Proposed maximum patronage yearly interest rate (in part per million)
"""
maxYearlyRate: Int

"""
Proposed minimum value of the slope amm parameter
"""
minAmmSlope: BigInt

"""
Proposed minimum block duration of sales
"""
minSaleDuration: Int

"""
Proposed minimum block duration for a revenue split
"""
minRevenueSplitDuration: Int

"""
Proposed minimum blocks between revenue share issuance block and actual revenue share starting block
"""
minRevenueSplitTimeToStart: Int

"""
Proposed platform fee ratio charged on top of each sale and burned (in part per million)
"""
salePlatformFee: Int

"""
Proposed ratio of fees charged on top of each token purchase from the AMM (in part per million)
"""
ammBuyTxFees: Int

"""
Proposed ratio of fees charged on top of each token sold to the AMM (in part per million)
"""
ammSellTxFees: Int

"""
Proposed bloat bond value used during account creation
"""
bloatBond: BigInt
}

type UpdateWorkingGroupBudgetProposalDetails {
"""
Amount to increase / decrease the working group budget by (will be decudted from / appended to council budget accordingly)
Expand Down
73 changes: 73 additions & 0 deletions packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ export default {
},
projectToken: {
palletFrozen: args.palletFrozen,

ammBuyTxFees: 10_000,
ammSellTxFees: 20_000,
bloatBond: joy(0.1),
maxYearlyPatronageRate: 500_000,
minAmmSlopeParameter: joy(10),
minRevenueSplitDuration: 100,
minRevenueSplitTimeToStart: 200,
minSaleDuration: 300,
salePlatformFee: 30_000,
},
},
tx: {
Expand Down Expand Up @@ -1577,3 +1587,66 @@ export const SpecificParametersDecreaseCouncilBudget: Story = {
})
}),
}

export const SpecificUpdateTokenPalletTokenConstraints: Story = {
play: specificParametersTest(
'Update Token Pallet Token Constraints',
async ({ args, createProposal, modal, step }) => {
await createProposal(async () => {
const nextButton = getButtonByText(modal, 'Create proposal')
expect(nextButton).toBeDisabled()

const maxYearlyRate = await modal.findByLabelText('Maximum yearly rate')
const minAmmSlope = await modal.findByLabelText('Minimum AMM slope')
const minSaleDuration = await modal.findByLabelText('Minimum sale duration')
const minRevenueSplitDuration = await modal.findByLabelText('Minimum revenue split duration')
const minRevenueSplitTimeToStart = await modal.findByLabelText('Minimum revenue split time to start')
const salePlatformFee = await modal.findByLabelText('Sale platform fee')
const ammBuyTxFees = await modal.findByLabelText('AMM buy transaction fees')
const ammSellTxFees = await modal.findByLabelText('AMM sell transaction fees')
const bloatBond = await modal.findByLabelText('Bloat bond')

// Valid
await userEvent.type(maxYearlyRate, '40')
await waitFor(() => expect(nextButton).toBeEnabled())

// Invalid min AMM slope
await userEvent.type(minAmmSlope, '0')
await waitFor(() => expect(nextButton).toBeDisabled())

// Invalid Bloat bond
await userEvent.clear(minAmmSlope)
await userEvent.type(minAmmSlope, '1')
await waitFor(() => expect(nextButton).toBeEnabled())
await userEvent.type(bloatBond, '0')
await waitFor(() => expect(nextButton).toBeDisabled())

// Valid again
await userEvent.clear(bloatBond)
await userEvent.type(bloatBond, '0.01')
await userEvent.type(minSaleDuration, '100')
await userEvent.type(minRevenueSplitDuration, '200')
await userEvent.type(minRevenueSplitTimeToStart, '300')
await userEvent.type(salePlatformFee, '0.1')
await userEvent.type(ammBuyTxFees, '0.2')
await userEvent.type(ammSellTxFees, '0.3')
await waitFor(() => expect(nextButton).toBeEnabled())
})

await step('Transaction parameters', () => {
const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1)
expect(specificParameters.toJSON().updateTokenPalletTokenConstraints).toEqual({
maxYearlyRate: 0.4 * 1_000_000,
minAmmSlope: Number(joy(1)),
minSaleDuration: 100,
minRevenueSplitDuration: 200,
minRevenueSplitTimeToStart: 300,
salePlatformFee: 0.001 * 1_000_000,
ammBuyTxFees: 0.002 * 1_000_000,
ammSellTxFees: 0.003 * 1_000_000,
bloatBond: Number(joy(0.01)),
})
})
}
),
}
14 changes: 14 additions & 0 deletions packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ export default {
councilorReward: joy(1),
},
referendum: { stage: {} },
projectToken: {
ammBuyTxFees: 10_000,
ammSellTxFees: 20_000,
bloatBond: joy(0.1),
maxYearlyPatronageRate: 500_000,
minAmmSlopeParameter: joy(10),
minRevenueSplitDuration: 100,
minRevenueSplitTimeToStart: 200,
minSaleDuration: 300,
salePlatformFee: 30_000,
},
},

tx: {
Expand Down Expand Up @@ -299,6 +310,9 @@ export const UpdateWorkingGroupBudget: Story = {
export const DecreaseCouncilBudget: Story = {
args: { type: 'DecreaseCouncilBudgetProposalDetails' },
}
export const UpdateTokenPalletTokenConstraints: Story = {
args: { type: 'UpdateTokenPalletTokenConstraintsProposalDetails' },
}

// Disabled proposals
export const Veto: Story = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions packages/ui/src/common/api/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2592,6 +2592,7 @@ union ProposalDetails =
| UpdatePalletFrozenStatusProposalDetails
| UpdateGlobalNftLimitProposalDetails
| DecreaseCouncilBudgetProposalDetails
| UpdateTokenPalletTokenConstraintsProposalDetails

union ProposalStatus =
ProposalStatusDeciding
Expand Down Expand Up @@ -3562,6 +3563,53 @@ type UpdatePalletFrozenStatusProposalDetails {
pallet: String!
}

type UpdateTokenPalletTokenConstraintsProposalDetails {
"""
Proposed maximum patronage yearly interest rate (in part per million)
"""
maxYearlyRate: Int

"""
Proposed minimum value of the slope amm parameter
"""
minAmmSlope: BigInt

"""
Proposed minimum block duration of sales
"""
minSaleDuration: Int

"""
Proposed minimum block duration for a revenue split
"""
minRevenueSplitDuration: Int

"""
Proposed minimum blocks between revenue share issuance block and actual revenue share starting block
"""
minRevenueSplitTimeToStart: Int

"""
Proposed platform fee ratio charged on top of each sale and burned (in part per million)
"""
salePlatformFee: Int

"""
Proposed ratio of fees charged on top of each token purchase from the AMM (in part per million)
"""
ammBuyTxFees: Int

"""
Proposed ratio of fees charged on top of each token sold to the AMM (in part per million)
"""
ammSellTxFees: Int

"""
Proposed bloat bond value used during account creation
"""
bloatBond: BigInt
}

type UpdateWorkingGroupBudgetProposalDetails {
"""
Amount to increase / decrease the working group budget by (will be decudted from / appended to council budget accordingly)
Expand Down
20 changes: 3 additions & 17 deletions packages/ui/src/common/components/forms/InputComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ export const InputComponent = React.memo(
</InputLabel>
)}
{sublabel && (
<InputSublabelWrapper>
<InputSublabel>{sublabel}</InputSublabel>
</InputSublabelWrapper>
<label htmlFor={id}>
<TextSmall light>{sublabel}</TextSmall>
</label>
)}
<InputContainer
copy={copy}
Expand Down Expand Up @@ -267,20 +267,6 @@ const InputLabel = styled(Label)<DisabledInputProps>`
color: ${({ disabled }) => (disabled ? Colors.Black[500] : Colors.Black[900])};
`

const InputSublabelWrapper = styled.div`
position: relative;
height: 20px;
margin-bottom: 16px;
`

const InputSublabel = styled(Label)`
font-weight: 400;
font-family: ${Fonts.Inter};
color: ${Colors.Black[700]};
position: absolute;
white-space: nowrap;
`

export const InputIcon = styled.div<DisabledInputProps>`
display: flex;
position: absolute;
Expand Down
57 changes: 57 additions & 0 deletions packages/ui/src/common/components/forms/InputNumber.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Meta, StoryObj } from '@storybook/react'
import React, { FC, useEffect } from 'react'
import { useFormContext } from 'react-hook-form'

import { InputComponent } from './InputComponent'
import { InputNumber } from './InputNumber'

type Args = {
maxAllowedValue: number
placeholder: string
label: string
units: string
decimalScale: number
onChange: (value: number) => void
}
type Story = StoryObj<FC<Args>>

export default {
title: 'Common/Forms/InputNumber',
component: InputNumber as unknown as FC<Args>,

argTypes: {
onChange: { action: 'onChange' },
},

args: {
maxAllowedValue: 10_000,
placeholder: '0',
label: 'Amount',
units: 'per 10 000',
decimalScale: 2,
},
} satisfies Meta<Args>

export const InputNumberStory: Story = {
name: 'InputNumber',
render: ({ label, units, onChange, ...props }) => {
const form = useFormContext()

useEffect(() => {
const subscription = form.watch((data) => onChange(data.input))
return () => subscription.unsubscribe()
}, [form.watch])

return (
<InputComponent
id="input"
label={label}
units={units}
message={`Value must be between 0 and ${props.maxAllowedValue}.`}
tight
>
<InputNumber {...props} id="input" name="input" />
</InputComponent>
)
},
}
Loading

0 comments on commit 7577166

Please sign in to comment.