diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2731f1568d..30b80ab257 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -110,7 +110,7 @@ jobs: BRANCH=$(echo -n '${{ env.LABEL }}' | cut -d ':' -f2-) fi - URL_BRANCH=$(echo -n "$BRANCH" | tr -d '#' | tr -c '[:alnum:]' '-' | tr '[:upper:]' '[:lower:]') + URL_BRANCH=$(echo -n "$BRANCH" | sed 's/\//-/' | tr -d '/#' | tr -c '[:alnum:]' '-' | tr '[:upper:]' '[:lower:]') SUBDOMAIN="${{ env.PROJECT }}-${{ env.PREFIX }}-$URL_BRANCH-joystream" if [ ${#SUBDOMAIN} -gt 63 ]; then diff --git a/CHANGELOG.md b/CHANGELOG.md index 128e5f66b3..db671e91d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] + +## [3.5.0 (Luxor)][3.5.0] - 2024-03-18 + +### Added +- Decrease council budget proposal. +- Update CRT pallet constraints proposal. +- Set Era Payout Damping Factor proposal. + +### Fixed +- Keep the app usable if an error happen. +- Number input behavior. + ## [3.4.0] - 2024-03-19 ### Fixed @@ -368,7 +380,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.1] - 2022-12-02 -[unreleased]: https://github.com/Joystream/pioneer/compare/v3.4.0...HEAD +[unreleased]: https://github.com/Joystream/pioneer/compare/v3.5.0...HEAD +[3.5.0]: https://github.com/Joystream/pioneer/compare/v3.4.0...v3.5.0 [3.4.0]: https://github.com/Joystream/pioneer/compare/v3.3.1...v3.4.0 [3.3.1]: https://github.com/Joystream/pioneer/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/Joystream/pioneer/compare/v3.2.0...v3.3.0 diff --git a/package.json b/package.json index 7a1ef87d39..478fb11c87 100644 --- a/package.json +++ b/package.json @@ -34,19 +34,19 @@ "@babel/parser": "~7.21.0", "@babel/traverse": "~7.21.0", "@babel/types": "~7.21.0", - "@joystream/types": "4.3.0", - "@polkadot/api": "10.1.4", - "@polkadot/api-contract": "10.1.4", - "@polkadot/api-derive": "10.1.4", - "@polkadot/hw-ledger": "11.1.1", - "@polkadot/keyring": "11.1.1", - "@polkadot/networks": "11.1.1", - "@polkadot/rpc-core": "10.1.4", - "@polkadot/rpc-provider": "10.1.4", - "@polkadot/types": "10.1.4", - "@polkadot/types-known": "10.1.4", - "@polkadot/util": "11.1.1", - "@polkadot/util-crypto": "11.1.1", + "@joystream/types": "4.5.0", + "@polkadot/api": "10.7.1", + "@polkadot/api-contract": "10.7.1", + "@polkadot/api-derive": "10.7.1", + "@polkadot/hw-ledger": "12.2.1", + "@polkadot/keyring": "12.2.1", + "@polkadot/networks": "12.2.1", + "@polkadot/rpc-core": "10.7.1", + "@polkadot/rpc-provider": "10.7.1", + "@polkadot/types": "10.7.1", + "@polkadot/types-known": "10.7.1", + "@polkadot/util": "12.2.1", + "@polkadot/util-crypto": "12.2.1", "bn.js": "^4.11.9" }, "engines": { diff --git a/packages/server/src/common/queries/schema.graphql b/packages/server/src/common/queries/schema.graphql index e31bc5b9f6..ca4f44a16d 100644 --- a/packages/server/src/common/queries/schema.graphql +++ b/packages/server/src/common/queries/schema.graphql @@ -87,6 +87,7 @@ enum EventTypeOptions { CommentPinnedEvent CommentReactedEvent CommentTextUpdatedEvent + CouncilBudgetDecreasedEvent CouncilBudgetFundedEvent CouncilorRewardUpdatedEvent EnglishAuctionSettledEvent @@ -2590,6 +2591,8 @@ union ProposalDetails = | UpdateChannelPayoutsProposalDetails | UpdatePalletFrozenStatusProposalDetails | UpdateGlobalNftLimitProposalDetails + | DecreaseCouncilBudgetProposalDetails + | UpdateTokenPalletTokenConstraintsProposalDetails union ProposalStatus = ProposalStatusDeciding @@ -2976,6 +2979,13 @@ type DataObjectTypeVideoThumbnail { video: Video } +type DecreaseCouncilBudgetProposalDetails { + """ + Proposed amount of token to burn + """ + amount: BigInt! +} + type DecreaseWorkingGroupLeadStakeProposalDetails { """ The lead that should be affected @@ -3553,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) @@ -5261,6 +5318,17 @@ type CommentConnection { pageInfo: PageInfo! } +type CouncilBudgetDecreasedEventEdge { + node: CouncilBudgetDecreasedEvent! + cursor: String! +} + +type CouncilBudgetDecreasedEventConnection { + totalCount: Int! + edges: [CouncilBudgetDecreasedEventEdge!]! + pageInfo: PageInfo! +} + type CouncilBudgetFundedEventEdge { node: CouncilBudgetFundedEvent! cursor: String! @@ -22796,6 +22864,81 @@ input ChannelVisibilitySetByModeratorEventUpdateInput { rationale: String } +input CouncilBudgetDecreasedEventWhereInput { + id_eq: ID + id_in: [ID!] + createdAt_eq: DateTime + createdAt_lt: DateTime + createdAt_lte: DateTime + createdAt_gt: DateTime + createdAt_gte: DateTime + createdById_eq: ID + createdById_in: [ID!] + updatedAt_eq: DateTime + updatedAt_lt: DateTime + updatedAt_lte: DateTime + updatedAt_gt: DateTime + updatedAt_gte: DateTime + updatedById_eq: ID + updatedById_in: [ID!] + deletedAt_all: Boolean + deletedAt_eq: DateTime + deletedAt_lt: DateTime + deletedAt_lte: DateTime + deletedAt_gt: DateTime + deletedAt_gte: DateTime + deletedById_eq: ID + deletedById_in: [ID!] + inExtrinsic_eq: String + inExtrinsic_contains: String + inExtrinsic_startsWith: String + inExtrinsic_endsWith: String + inExtrinsic_in: [String!] + inBlock_eq: Int + inBlock_gt: Int + inBlock_gte: Int + inBlock_lt: Int + inBlock_lte: Int + inBlock_in: [Int!] + network_eq: Network + network_in: [Network!] + indexInBlock_eq: Int + indexInBlock_gt: Int + indexInBlock_gte: Int + indexInBlock_lt: Int + indexInBlock_lte: Int + indexInBlock_in: [Int!] + amount_eq: BigInt + amount_gt: BigInt + amount_gte: BigInt + amount_lt: BigInt + amount_lte: BigInt + amount_in: [BigInt!] + AND: [CouncilBudgetDecreasedEventWhereInput!] + OR: [CouncilBudgetDecreasedEventWhereInput!] + NOT: [CouncilBudgetDecreasedEventWhereInput!] +} + +input CouncilBudgetDecreasedEventWhereUniqueInput { + id: ID! +} + +input CouncilBudgetDecreasedEventCreateInput { + inExtrinsic: String + inBlock: Float! + network: Network! + indexInBlock: Float! + amount: String! +} + +input CouncilBudgetDecreasedEventUpdateInput { + inExtrinsic: String + inBlock: Float + network: Network + indexInBlock: Float + amount: String +} + input CouncilBudgetFundedEventWhereInput { id_eq: ID id_in: [ID!] @@ -29529,6 +29672,47 @@ type BudgetRefillPlannedEvent implements Event & BaseGraphQLObject { nextRefillInBlock: Int! } +type CouncilBudgetDecreasedEvent implements Event & BaseGraphQLObject { + id: ID! + createdAt: DateTime! + createdById: ID! + updatedAt: DateTime + updatedById: ID + deletedAt: DateTime + deletedById: ID + version: Int! + + """ + Hash of the extrinsic which caused the event to be emitted + """ + inExtrinsic: String + + """ + Blocknumber of the block in which the event was emitted. + """ + inBlock: Int! + + """ + Network the block was produced in + """ + network: Network! + + """ + Index of event in block from which it was emitted. + """ + indexInBlock: Int! + + """ + Filtering options for interface implementers + """ + type: EventTypeOptions + + """ + Funding amount. + """ + amount: BigInt! +} + type CouncilBudgetFundedEvent implements Event & BaseGraphQLObject { id: ID! createdAt: DateTime! @@ -31193,6 +31377,23 @@ type Query { where: CommentWhereInput orderBy: [CommentOrderByInput!] ): CommentConnection! + councilBudgetDecreasedEvents( + offset: Int + limit: Int = 50 + where: CouncilBudgetDecreasedEventWhereInput + orderBy: [CouncilBudgetDecreasedEventOrderByInput!] + ): [CouncilBudgetDecreasedEvent!]! + councilBudgetDecreasedEventByUniqueInput( + where: CouncilBudgetDecreasedEventWhereUniqueInput! + ): CouncilBudgetDecreasedEvent + councilBudgetDecreasedEventsConnection( + first: Int + after: String + last: Int + before: String + where: CouncilBudgetDecreasedEventWhereInput + orderBy: [CouncilBudgetDecreasedEventOrderByInput!] + ): CouncilBudgetDecreasedEventConnection! councilBudgetFundedEvents( offset: Int limit: Int = 50 @@ -34940,6 +35141,25 @@ enum CommentOrderByInput { isEdited_DESC } +enum CouncilBudgetDecreasedEventOrderByInput { + createdAt_ASC + createdAt_DESC + updatedAt_ASC + updatedAt_DESC + deletedAt_ASC + deletedAt_DESC + inExtrinsic_ASC + inExtrinsic_DESC + inBlock_ASC + inBlock_DESC + network_ASC + network_DESC + indexInBlock_ASC + indexInBlock_DESC + amount_ASC + amount_DESC +} + enum CouncilBudgetFundedEventOrderByInput { createdAt_ASC createdAt_DESC diff --git a/packages/ui/package.json b/packages/ui/package.json index 1b8a27bb6e..f362158df9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@joystream/pioneer", - "version": "3.4.0", + "version": "3.5.0", "license": "GPL-3.0-only", "scripts": { "build": "node --max_old_space_size=4096 ./build.js", @@ -29,19 +29,19 @@ "@joystream/js": "1.10.0", "@joystream/markdown-editor": "^0.1.0", "@joystream/metadata-protobuf": "2.15.0", - "@joystream/types": "4.3.0", + "@joystream/types": "4.5.0", "@nivo/bar": "^0.79.1", "@nivo/core": "^0.79.0", "@noble/hashes": "^1.1.5", "@oneidentity/zstd-js": "^1.0.3", - "@polkadot/api": "10.1.4", - "@polkadot/extension-dapp": "0.45.3", - "@polkadot/keyring": "11.1.1", - "@polkadot/react-identicon": "3.1.1", - "@polkadot/types": "10.1.4", - "@polkadot/ui-keyring": "3.1.1", - "@polkadot/util": "11.1.1", - "@polkadot/util-crypto": "11.1.1", + "@polkadot/api": "10.7.1", + "@polkadot/extension-dapp": "0.46.3", + "@polkadot/keyring": "12.2.1", + "@polkadot/react-identicon": "3.4.1", + "@polkadot/types": "10.7.1", + "@polkadot/ui-keyring": "3.4.1", + "@polkadot/util": "12.2.1", + "@polkadot/util-crypto": "12.2.1", "@popperjs/core": "^2.10.2", "@types/jest": "^27.0.2", "@types/react": "^17.0.27", diff --git a/packages/ui/src/app/App.tsx b/packages/ui/src/app/App.tsx index 88a1bf82fd..74d1fd58c0 100644 --- a/packages/ui/src/app/App.tsx +++ b/packages/ui/src/app/App.tsx @@ -7,6 +7,7 @@ import '@/services/i18n' import { ImageReportNotification } from '@/app/components/ImageReportNotification' import { OnBoardingOverlay } from '@/app/components/OnboardingOverlay/OnBoardingOverlay' import { CouncilModule } from '@/app/pages/Council/CouncilModule' +import { ErrorPage } from '@/app/pages/ErrorPage' import { NotFound } from '@/app/pages/NotFound' import { BountyRoutes } from '@/bounty/constants' import { ConnectionStatus } from '@/common/components/ConnectionStatus' @@ -21,7 +22,7 @@ import { ProposalsRoutes } from '@/proposals/constants/routes' import { ValidatorsRoutes } from '@/validators/constants/routes' import { WorkingGroupsRoutes } from '@/working-groups/constants/routes' -import { ErrorFallback } from './components/ErrorFallback' +import { ModalErrorFallback, PageErrorFallback } from './components/ErrorFallback' import { ExtensionNotification } from './components/ExtensionWarning' import { NavBar } from './components/NavBar' import { SideBar } from './components/SideBar' @@ -50,7 +51,7 @@ export const App = () => { - + @@ -70,6 +71,7 @@ export const App = () => { + @@ -78,7 +80,7 @@ export const App = () => { - + diff --git a/packages/ui/src/app/components/ErrorFallback.tsx b/packages/ui/src/app/components/ErrorFallback.tsx index f651d0c1ec..7ea3d2778e 100644 --- a/packages/ui/src/app/components/ErrorFallback.tsx +++ b/packages/ui/src/app/components/ErrorFallback.tsx @@ -1,23 +1,42 @@ import React from 'react' +import { useHistory } from 'react-router-dom' -import { ButtonPrimary, ButtonsGroup } from '@/common/components/buttons' +import { ButtonGhost, ButtonPrimary, ButtonsGroup } from '@/common/components/buttons' import { FailureIcon } from '@/common/components/icons' import { Modal, ModalBody, ModalFooterComponent, ModalHeader } from '@/common/components/Modal' +import { useModal } from '@/common/hooks/useModal' interface FallbackProps { error: Error resetErrorBoundary: (...args: Array) => void } -export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => { +export const PageErrorFallback = (props: FallbackProps) => { + const { push } = useHistory() + return push('/error')} /> +} + +export const ModalErrorFallback = (props: FallbackProps) => { + const { hideModal } = useModal() + return +} + +const ErrorFallback = ({ error, resetErrorBoundary, onClose }: FallbackProps & { onClose: () => void }) => { + const handleClose = () => { + onClose() + resetErrorBoundary() + } return ( - } title="Something went wrong" onClick={resetErrorBoundary} /> + } title="Something went wrong" onClick={handleClose} />
{error.message}
+ + Close + Try again diff --git a/packages/ui/src/app/pages/ErrorPage.tsx b/packages/ui/src/app/pages/ErrorPage.tsx new file mode 100644 index 0000000000..ed827575ee --- /dev/null +++ b/packages/ui/src/app/pages/ErrorPage.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +import { PageLayout } from '@/app/components/PageLayout' + +export const ErrorPage = () => { + return Something went wrong} /> +} diff --git a/packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx b/packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx index 2655ce2ace..60c2fcf857 100644 --- a/packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx +++ b/packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx @@ -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: { @@ -794,98 +804,98 @@ export const SpecificParametersFundingRequest: Story = { }), } -export const SpecificParametersMultipleFundingRequest: Story = { - play: specificParametersTest('Funding Request', async ({ args, createProposal, modal, step }) => { - const aliceAddress = alice.controllerAccount - const bobAddress = member('bob').controllerAccount - const charlieAddress = member('charlie').controllerAccount - - await createProposal(async () => { - const nextButton = getButtonByText(modal, 'Create proposal') - expect(nextButton).toBeDisabled() - - await userEvent.click(modal.getByTestId('pay-multiple')) - - const csvField = modal.getByTestId('accounts-amounts') - - // Invalid - await userEvent.clear(csvField) - await userEvent.type(csvField, `${aliceAddress},500${bobAddress},500`) - expect(await modal.findByText(/Not valid CSV format/)) - // ensure its not being open-able while the CSV syntax is valid - const previewButton = getButtonByText(modal, 'Preview and Validate') - expect(previewButton).toBeDisabled() - await waitFor(() => expect(modal.queryByTestId('sidePanel-overlay')).toBeNull()) - expect(nextButton).toBeDisabled() - - // Invalid Accounts error - await userEvent.clear(csvField) - await userEvent.type(csvField, `5GNJqTPy,500\n${bobAddress},500`) - - await waitFor(() => expect(modal.queryByText(/Not valid CSV format/)).toBeNull()) - expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) - expect(nextButton).toBeDisabled() - expect(previewButton).toBeEnabled() - - await userEvent.click(previewButton) - expect(await modal.findByText(/Incorrect destination accounts detected/)) - await userEvent.click(modal.getByTestId('sidePanel-overlay')) - - // Max Amount error - await userEvent.clear(csvField) - await userEvent.type(csvField, `${aliceAddress},166667\n${bobAddress},500`) - expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) - expect(nextButton).toBeDisabled() - await waitFor(() => expect(previewButton).toBeEnabled()) - await waitFor( - async () => { - await userEvent.click(previewButton) - expect(await modal.findByText(/Max payment amount is exceeded/)) - }, - { timeout: 8000 } - ) - await userEvent.click(modal.getByTestId('sidePanel-overlay')) //ensure create proposal is still disabled - expect(nextButton).toBeDisabled() - - // Max Allowed Accounts error - await userEvent.clear(csvField) - await userEvent.type(csvField, `${aliceAddress},400\n${bobAddress},500\n${charlieAddress},500`) - expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) - expect(nextButton).toBeDisabled() - await waitFor(() => expect(previewButton).toBeEnabled()) - await userEvent.click(previewButton) - expect(await modal.findByText(/Maximum allowed accounts exceeded/)) - await userEvent.click(modal.getByTestId('sidePanel-overlay')) //ensure create proposal is still disabled - expect(nextButton).toBeDisabled() - - // delete one account from the list' - await waitFor(() => expect(previewButton).toBeEnabled()) - await userEvent.click(previewButton) - await userEvent.click(modal.getByTestId('removeAccount-2')) - await waitFor(() => expect(modal.queryByText(/Maximum allowed accounts exceeded/)).toBeNull()) - await userEvent.click(modal.getByTestId('sidePanel-overlay')) - - // Valid - await userEvent.clear(csvField) - await userEvent.type(csvField, `${aliceAddress},500\n${bobAddress},500`) - expect(nextButton).toBeDisabled() - - await waitFor(() => expect(previewButton).toBeEnabled()) - await userEvent.click(previewButton) - await userEvent.click(modal.getByTestId('sidePanel-overlay')) - }) - - await step('Transaction parameters', () => { - const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1) - expect(specificParameters.toJSON()).toEqual({ - fundingRequest: [ - { account: aliceAddress, amount: 500_0000000000 }, - { account: bobAddress, amount: 500_0000000000 }, - ], - }) - }) - }), -} +// export const SpecificParametersMultipleFundingRequest: Story = { +// play: specificParametersTest('Funding Request', async ({ args, createProposal, modal, step }) => { +// const aliceAddress = alice.controllerAccount +// const bobAddress = member('bob').controllerAccount +// const charlieAddress = member('charlie').controllerAccount + +// await createProposal(async () => { +// const nextButton = getButtonByText(modal, 'Create proposal') +// expect(nextButton).toBeDisabled() + +// await userEvent.click(modal.getByTestId('pay-multiple')) + +// const csvField = modal.getByTestId('accounts-amounts') + +// // Invalid +// await userEvent.clear(csvField) +// await userEvent.type(csvField, `${aliceAddress},500${bobAddress},500`) +// expect(await modal.findByText(/Not valid CSV format/)) +// // ensure its not being open-able while the CSV syntax is valid +// const previewButton = getButtonByText(modal, 'Preview and Validate') +// expect(previewButton).toBeDisabled() +// await waitFor(() => expect(modal.queryByTestId('sidePanel-overlay')).toBeNull()) +// expect(nextButton).toBeDisabled() + +// // Invalid Accounts error +// await userEvent.clear(csvField) +// await userEvent.type(csvField, `5GNJqTPy,500\n${bobAddress},500`) + +// await waitFor(() => expect(modal.queryByText(/Not valid CSV format/)).toBeNull()) +// expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) +// expect(nextButton).toBeDisabled() +// expect(previewButton).toBeEnabled() + +// await userEvent.click(previewButton) +// expect(await modal.findByText(/Incorrect destination accounts detected/)) +// await userEvent.click(modal.getByTestId('sidePanel-overlay')) + +// // Max Amount error +// await userEvent.clear(csvField) +// await userEvent.type(csvField, `${aliceAddress},166667\n${bobAddress},500`) +// expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) +// expect(nextButton).toBeDisabled() +// await waitFor(() => expect(previewButton).toBeEnabled()) +// await waitFor( +// async () => { +// await userEvent.click(previewButton) +// expect(await modal.findByText(/Max payment amount is exceeded/)) +// }, +// { timeout: 8000 } +// ) +// await userEvent.click(modal.getByTestId('sidePanel-overlay')) //ensure create proposal is still disabled +// expect(nextButton).toBeDisabled() + +// // Max Allowed Accounts error +// await userEvent.clear(csvField) +// await userEvent.type(csvField, `${aliceAddress},400\n${bobAddress},500\n${charlieAddress},500`) +// expect(await modal.findByText(/Please preview and validate the inputs to proceed/)) +// expect(nextButton).toBeDisabled() +// await waitFor(() => expect(previewButton).toBeEnabled()) +// await userEvent.click(previewButton) +// expect(await modal.findByText(/Maximum allowed accounts exceeded/)) +// await userEvent.click(modal.getByTestId('sidePanel-overlay')) //ensure create proposal is still disabled +// expect(nextButton).toBeDisabled() + +// // delete one account from the list' +// await waitFor(() => expect(previewButton).toBeEnabled()) +// await userEvent.click(previewButton) +// await userEvent.click(modal.getByTestId('removeAccount-2')) +// await waitFor(() => expect(modal.queryByText(/Maximum allowed accounts exceeded/)).toBeNull()) +// await userEvent.click(modal.getByTestId('sidePanel-overlay')) + +// // Valid +// await userEvent.clear(csvField) +// await userEvent.type(csvField, `${aliceAddress},500\n${bobAddress},500`) +// expect(nextButton).toBeDisabled() + +// await waitFor(() => expect(previewButton).toBeEnabled()) +// await userEvent.click(previewButton) +// await userEvent.click(modal.getByTestId('sidePanel-overlay')) +// }) + +// await step('Transaction parameters', () => { +// const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1) +// expect(specificParameters.toJSON()).toEqual({ +// fundingRequest: [ +// { account: aliceAddress, amount: 500_0000000000 }, +// { account: bobAddress, amount: 500_0000000000 }, +// ], +// }) +// }) +// }), +// } export const SpecificParametersSetReferralCut: Story = { play: specificParametersTest('Set Referral Cut', async ({ args, createProposal, modal, step }) => { @@ -1543,3 +1553,132 @@ export const SpecificParametersRuntimeUpgrade: Story = { }) }), } + +export const SpecificParametersSetEraPayoutDampingFactor: Story = { + play: specificParametersTest('Set Era Payout Damping Factor', async ({ args, createProposal, modal, step }) => { + await createProposal(async () => { + const nextButton = getButtonByText(modal, 'Create proposal') + expect(nextButton).toBeDisabled() + + // Valid + const factorField = await modal.findByLabelText('Validator reward multiplier') + await userEvent.type(factorField, '60') + await waitFor(() => expect(nextButton).toBeEnabled()) + + // Invalid + await userEvent.clear(factorField) + await userEvent.type(factorField, '200') + await modal.findByText('The value must be between 0 and 100%.') + await waitFor(() => expect(nextButton).toBeDisabled()) + + // Valid again + await userEvent.clear(factorField) + await userEvent.type(factorField, '60') + await waitFor(() => expect(nextButton).toBeEnabled()) + }) + + await step('Transaction parameters', () => { + const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1) + expect(specificParameters.toJSON()).toEqual({ + setEraPayoutDampingFactor: 60, + }) + }) + }), +} + +export const SpecificParametersDecreaseCouncilBudget: Story = { + parameters: { + councilBudget: joy(500), + }, + play: specificParametersTest('Decrease Council Budget', async ({ args, createProposal, modal, step }) => { + await createProposal(async () => { + const nextButton = getButtonByText(modal, 'Create proposal') + expect(nextButton).toBeDisabled() + + const amountField = await modal.findByLabelText('Decrease budget by') + + // Invalid price set to 0 + await userEvent.type(amountField, '0') + expect(await modal.findByText('Amount must be greater than zero')) + expect(nextButton).toBeDisabled() + + // Invalid price set to 0 + await userEvent.clear(amountField) + await userEvent.type(amountField, '600') + expect(await modal.findByText('The current council budget is 500JOY')) + expect(nextButton).toBeDisabled() + + // Valid + await userEvent.clear(amountField) + await userEvent.type(amountField, '8') + }) + + await step('Transaction parameters', () => { + const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1) + expect(specificParameters.toJSON()).toEqual({ decreaseCouncilBudget: 8_0000000000 }) + }) + }), +} + +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)), + }) + }) + } + ), +} diff --git a/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx b/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx index b94aa4b0df..83c3710b9d 100644 --- a/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx +++ b/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx @@ -132,8 +132,20 @@ export default { council: { budget: joy(1000), councilorReward: joy(1), + eraPayoutDampingFactor: 70, }, referendum: { stage: {} }, + projectToken: { + ammBuyTxFees: 1_000, + ammSellTxFees: 2_000, + bloatBond: joy(0.1), + maxYearlyPatronageRate: 500_000, + minAmmSlopeParameter: joy(10), + minRevenueSplitDuration: 100, + minRevenueSplitTimeToStart: 200, + minSaleDuration: 300, + salePlatformFee: 3_000, + }, }, tx: { @@ -218,12 +230,6 @@ export default { // ProposalPreview // ---------------------------------------------------------------------------- -export const AmendConstitution: Story = { - args: { type: 'AmendConstitutionProposalDetails', constitutionality: 2 }, - parameters: { - statuses: ['ProposalStatusDeciding', 'ProposalStatusDormant', 'ProposalStatusDeciding'] satisfies ProposalStatus[], - }, -} export const CancelWorkingGroupLeadOpening: Story = { args: { type: 'CancelWorkingGroupLeadOpeningProposalDetails' }, } @@ -299,12 +305,29 @@ export const UpdateChannelPayouts: Story = { export const UpdatePalletFrozenStatus: Story = { args: { type: 'UpdatePalletFrozenStatusProposalDetails' }, } +export const SetEraPayoutDampingFactor: Story = { + args: { type: 'SetEraPayoutDampingFactorProposalDetails' }, +} export const UpdateWorkingGroupBudget: Story = { args: { type: 'UpdateWorkingGroupBudgetProposalDetails' }, } +export const DecreaseCouncilBudget: Story = { + args: { type: 'DecreaseCouncilBudgetProposalDetails' }, +} +export const UpdateTokenPalletTokenConstraints: Story = { + args: { type: 'UpdateTokenPalletTokenConstraintsProposalDetails' }, +} + +// Disabled proposals export const Veto: Story = { args: { type: 'VetoProposalDetails' }, } +export const AmendConstitution: Story = { + args: { type: 'AmendConstitutionProposalDetails', constitutionality: 2 }, + parameters: { + statuses: ['ProposalStatusDeciding', 'ProposalStatusDormant', 'ProposalStatusDeciding'] satisfies ProposalStatus[], + }, +} // ---------------------------------------------------------------------------- // VoteForProposalModal @@ -664,7 +687,7 @@ export const TestCancelProposalHappy: Story = { }) await step('Confirm', async () => { - expect(await modal.findByText('Your propsal has been cancelled.')) + expect(await modal.findByText('Your proposal has been cancelled.')) expect(onCancel).toHaveBeenLastCalledWith(activeMember.controllerAccount, activeMember.id, PROPOSAL_DATA.id) }) diff --git a/packages/ui/src/common/api/queries/__generated__/baseTypes.generated.ts b/packages/ui/src/common/api/queries/__generated__/baseTypes.generated.ts index a2586e7b1c..569c2219c0 100644 --- a/packages/ui/src/common/api/queries/__generated__/baseTypes.generated.ts +++ b/packages/ui/src/common/api/queries/__generated__/baseTypes.generated.ts @@ -9791,6 +9791,138 @@ export enum Continent { Sa = 'SA', } +export type CouncilBudgetDecreasedEvent = BaseGraphQlObject & + Event & { + __typename: 'CouncilBudgetDecreasedEvent' + /** Funding amount. */ + amount: Scalars['BigInt'] + createdAt: Scalars['DateTime'] + createdById: Scalars['ID'] + deletedAt?: Maybe + deletedById?: Maybe + id: Scalars['ID'] + /** Blocknumber of the block in which the event was emitted. */ + inBlock: Scalars['Int'] + /** Hash of the extrinsic which caused the event to be emitted */ + inExtrinsic?: Maybe + /** Index of event in block from which it was emitted. */ + indexInBlock: Scalars['Int'] + /** Network the block was produced in */ + network: Network + /** Filtering options for interface implementers */ + type?: Maybe + updatedAt?: Maybe + updatedById?: Maybe + version: Scalars['Int'] + } + +export type CouncilBudgetDecreasedEventConnection = { + __typename: 'CouncilBudgetDecreasedEventConnection' + edges: Array + pageInfo: PageInfo + totalCount: Scalars['Int'] +} + +export type CouncilBudgetDecreasedEventCreateInput = { + amount: Scalars['String'] + inBlock: Scalars['Float'] + inExtrinsic?: InputMaybe + indexInBlock: Scalars['Float'] + network: Network +} + +export type CouncilBudgetDecreasedEventEdge = { + __typename: 'CouncilBudgetDecreasedEventEdge' + cursor: Scalars['String'] + node: CouncilBudgetDecreasedEvent +} + +export enum CouncilBudgetDecreasedEventOrderByInput { + AmountAsc = 'amount_ASC', + AmountDesc = 'amount_DESC', + CreatedAtAsc = 'createdAt_ASC', + CreatedAtDesc = 'createdAt_DESC', + DeletedAtAsc = 'deletedAt_ASC', + DeletedAtDesc = 'deletedAt_DESC', + InBlockAsc = 'inBlock_ASC', + InBlockDesc = 'inBlock_DESC', + InExtrinsicAsc = 'inExtrinsic_ASC', + InExtrinsicDesc = 'inExtrinsic_DESC', + IndexInBlockAsc = 'indexInBlock_ASC', + IndexInBlockDesc = 'indexInBlock_DESC', + NetworkAsc = 'network_ASC', + NetworkDesc = 'network_DESC', + UpdatedAtAsc = 'updatedAt_ASC', + UpdatedAtDesc = 'updatedAt_DESC', +} + +export type CouncilBudgetDecreasedEventUpdateInput = { + amount?: InputMaybe + inBlock?: InputMaybe + inExtrinsic?: InputMaybe + indexInBlock?: InputMaybe + network?: InputMaybe +} + +export type CouncilBudgetDecreasedEventWhereInput = { + AND?: InputMaybe> + NOT?: InputMaybe> + OR?: InputMaybe> + amount_eq?: InputMaybe + amount_gt?: InputMaybe + amount_gte?: InputMaybe + amount_in?: InputMaybe> + amount_lt?: InputMaybe + amount_lte?: InputMaybe + createdAt_eq?: InputMaybe + createdAt_gt?: InputMaybe + createdAt_gte?: InputMaybe + createdAt_lt?: InputMaybe + createdAt_lte?: InputMaybe + createdById_eq?: InputMaybe + createdById_in?: InputMaybe> + deletedAt_all?: InputMaybe + deletedAt_eq?: InputMaybe + deletedAt_gt?: InputMaybe + deletedAt_gte?: InputMaybe + deletedAt_lt?: InputMaybe + deletedAt_lte?: InputMaybe + deletedById_eq?: InputMaybe + deletedById_in?: InputMaybe> + id_eq?: InputMaybe + id_in?: InputMaybe> + inBlock_eq?: InputMaybe + inBlock_gt?: InputMaybe + inBlock_gte?: InputMaybe + inBlock_in?: InputMaybe> + inBlock_lt?: InputMaybe + inBlock_lte?: InputMaybe + inExtrinsic_contains?: InputMaybe + inExtrinsic_endsWith?: InputMaybe + inExtrinsic_eq?: InputMaybe + inExtrinsic_in?: InputMaybe> + inExtrinsic_startsWith?: InputMaybe + indexInBlock_eq?: InputMaybe + indexInBlock_gt?: InputMaybe + indexInBlock_gte?: InputMaybe + indexInBlock_in?: InputMaybe> + indexInBlock_lt?: InputMaybe + indexInBlock_lte?: InputMaybe + network_eq?: InputMaybe + network_in?: InputMaybe> + updatedAt_eq?: InputMaybe + updatedAt_gt?: InputMaybe + updatedAt_gte?: InputMaybe + updatedAt_lt?: InputMaybe + updatedAt_lte?: InputMaybe + updatedById_eq?: InputMaybe + updatedById_in?: InputMaybe> +} + +export type CouncilBudgetDecreasedEventWhereUniqueInput = { + id: Scalars['ID'] +} + export type CouncilBudgetFundedEvent = BaseGraphQlObject & Event & { __typename: 'CouncilBudgetFundedEvent' @@ -10784,6 +10916,12 @@ export type DataObjectTypeVideoThumbnail = { video?: Maybe