From 46d03826cc127f19e410e717069de4bed1a9fe87 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Wed, 5 Oct 2022 16:07:42 +0100 Subject: [PATCH] Feat/1446: Add UpdateAsset proposal form (#1558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat/1446: Add UpdateAsset proposal form * Feat/1446: Formatting fix * Feat/1446: changes from PR comments * Feat/1446: Fix for mockWalletContext shape and lint fix * Feat/1446: Fix more tests reliant on the updated mockPubkey shape change * Feat/1446: Adding the update asset proposal terms interface into the new location Co-authored-by: Bartłomiej Głownia Co-authored-by: Dexter --- apps/token/src/i18n/translations/dev.json | 2 + .../proposals-list-item-details.spec.tsx | 6 +- .../freeform/propose-freeform.spec.tsx | 12 +- .../propose/freeform/propose-freeform.tsx | 6 +- .../propose-network-parameter.spec.tsx | 22 +-- .../propose-network-parameter.tsx | 6 +- .../new-asset/propose-new-asset.spec.tsx | 10 +- .../propose/new-asset/propose-new-asset.tsx | 20 +- .../new-market/propose-new-market.spec.tsx | 6 +- .../propose/new-market/propose-new-market.tsx | 20 +- .../governance/propose/propose.spec.tsx | 1 + .../src/routes/governance/propose/propose.tsx | 10 + .../governance/propose/raw/propose-raw.tsx | 12 +- .../governance/propose/update-asset/index.tsx | 4 + .../propose-update-asset.spec.tsx | 102 ++++++++++ .../update-asset/propose-update-asset.tsx | 182 ++++++++++++++++++ .../propose-update-market.spec.tsx | 12 +- .../update-market/propose-update-market.tsx | 20 +- .../routes/governance/test-helpers/mocks.ts | 8 +- apps/token/src/routes/router-config.tsx | 11 ++ .../src/hooks/use-network-params.ts | 10 + libs/react-helpers/src/lib/validate/index.ts | 9 + libs/wallet/src/connectors/vega-connector.ts | 18 +- 23 files changed, 410 insertions(+), 99 deletions(-) create mode 100644 apps/token/src/routes/governance/propose/update-asset/index.tsx create mode 100644 apps/token/src/routes/governance/propose/update-asset/propose-update-asset.spec.tsx create mode 100644 apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx diff --git a/apps/token/src/i18n/translations/dev.json b/apps/token/src/i18n/translations/dev.json index 5f96f96fa..077b19080 100644 --- a/apps/token/src/i18n/translations/dev.json +++ b/apps/token/src/i18n/translations/dev.json @@ -636,6 +636,7 @@ "NewMarketProposal": "New market proposal", "UpdateMarketProposal": "Update market proposal", "NewAssetProposal": "New asset proposal", + "UpdateAssetProposal": "Update asset proposal", "NewFreeformProposal": "New freeform proposal", "NewRawProposal": "New raw proposal", "MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a proposal", @@ -647,6 +648,7 @@ "NewMarket": "New market", "UpdateMarket": "Update market", "NewAsset": "New asset", + "UpdateAsset": "Update asset", "Freeform": "Freeform", "RawProposal": "Let me choose (raw proposal)", "UseMin": "Use minimum", diff --git a/apps/token/src/routes/governance/components/proposals-list-item/proposals-list-item-details.spec.tsx b/apps/token/src/routes/governance/components/proposals-list-item/proposals-list-item-details.spec.tsx index c5e5545dd..1aed2d962 100644 --- a/apps/token/src/routes/governance/components/proposals-list-item/proposals-list-item-details.spec.tsx +++ b/apps/token/src/routes/governance/components/proposals-list-item/proposals-list-item-details.spec.tsx @@ -26,7 +26,7 @@ import { lastWeek, nextWeek, } from '../../test-helpers/mocks'; -import type { ProposalsConnection_proposalsConnection_edges_node as ProposalNode } from '@vegaprotocol/governance'; +import type { Proposals_proposalsConnection_edges_node as ProposalNode } from '../../proposals/__generated__/Proposals'; const renderComponent = ( proposal: ProposalNode, @@ -172,7 +172,7 @@ describe('Proposals list item details', () => { datetime: lastWeek.toString(), party: { __typename: 'Party', - id: mockPubkey, + id: mockPubkey.publicKey, stakingSummary: { __typename: 'StakingSummary', currentStakeAvailable: '1000', @@ -210,7 +210,7 @@ describe('Proposals list item details', () => { datetime: lastWeek.toString(), party: { __typename: 'Party', - id: mockPubkey, + id: mockPubkey.publicKey, stakingSummary: { __typename: 'StakingSummary', currentStakeAvailable: '1000', diff --git a/apps/token/src/routes/governance/propose/freeform/propose-freeform.spec.tsx b/apps/token/src/routes/governance/propose/freeform/propose-freeform.spec.tsx index de6b8ad57..4bf639d83 100644 --- a/apps/token/src/routes/governance/propose/freeform/propose-freeform.spec.tsx +++ b/apps/token/src/routes/governance/propose/freeform/propose-freeform.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { ProposeFreeform } from './propose-freeform'; import { MockedProvider } from '@apollo/client/testing'; import { mockWalletContext } from '../../test-helpers/mocks'; @@ -81,16 +81,14 @@ describe('Propose Freeform', () => { it('should render the title', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByText('New freeform proposal')).toBeInTheDocument() - ); + expect( + await screen.findByText('New freeform proposal') + ).toBeInTheDocument(); }); it('should render the form components', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('freeform-proposal-form')).toBeTruthy() - ); + expect(await screen.findByTestId('freeform-proposal-form')).toBeTruthy(); expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy(); expect(screen.getByTestId('proposal-docs-link')).toBeTruthy(); expect(screen.getByTestId('proposal-title')).toBeTruthy(); diff --git a/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx b/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx index 75b598b64..6f82bd335 100644 --- a/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx +++ b/apps/token/src/routes/governance/propose/freeform/propose-freeform.tsx @@ -27,7 +27,7 @@ export interface FreeformProposalFormFields { proposalReference: string; } -const docsLink = 'freeform-proposal'; +const DOCS_LINK = 'freeform-proposal'; export const ProposeFreeform = () => { const { params, loading, error } = useNetworkParams([ @@ -76,9 +76,9 @@ export const ProposeFreeform = () => {

{t('ProposalTermsText')} {`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`} + >{`${VEGA_DOCS_URL}/tutorials/proposals/${DOCS_LINK}`}

)} diff --git a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.spec.tsx b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.spec.tsx index d6f9fe915..146466482 100644 --- a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.spec.tsx +++ b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.spec.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { ProposeNetworkParameter } from './propose-network-parameter'; import { MockedProvider } from '@apollo/client/testing'; import { mockWalletContext } from '../../test-helpers/mocks'; @@ -81,16 +81,16 @@ describe('Propose Network Parameter', () => { it('should render the correct title', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByText('Update network parameter proposal')).toBeTruthy() - ); + expect( + await screen.findByText('Update network parameter proposal') + ).toBeTruthy(); }); it('should render the form components', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('network-parameter-proposal-form')).toBeTruthy() - ); + expect( + await screen.findByTestId('network-parameter-proposal-form') + ).toBeTruthy(); expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy(); expect(screen.getByTestId('proposal-docs-link')).toBeTruthy(); expect(screen.getByTestId('proposal-title')).toBeTruthy(); @@ -103,15 +103,15 @@ describe('Propose Network Parameter', () => { it('should render the network param select element with no initial value', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('proposal-parameter-select')).toHaveValue('') + expect(await screen.findByTestId('proposal-parameter-select')).toHaveValue( + '' ); }); it('should render the current param value and a new value input when the network param select element is changed', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('proposal-parameter-select')).toHaveValue('') + expect(await screen.findByTestId('proposal-parameter-select')).toHaveValue( + '' ); fireEvent.change(screen.getByTestId('proposal-parameter-select'), { diff --git a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx index d07090e89..73fd0caec 100644 --- a/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx +++ b/apps/token/src/routes/governance/propose/network-parameter/propose-network-parameter.tsx @@ -69,7 +69,7 @@ export interface NetworkParameterProposalFormFields { proposalReference: string; } -const docsLink = '/network-parameter-proposal'; +const DOCS_LINK = '/network-parameter-proposal'; export const ProposeNetworkParameter = () => { const [selectedNetworkParam, setSelectedNetworkParam] = useState< @@ -141,9 +141,9 @@ export const ProposeNetworkParameter = () => {

{t('ProposalTermsText')} {`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`} + >{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}

)} diff --git a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.spec.tsx b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.spec.tsx index 1ccd0f05e..e5d747ded 100644 --- a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.spec.tsx +++ b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.spec.tsx @@ -1,6 +1,6 @@ import { MockedProvider } from '@apollo/client/testing'; import { MemoryRouter as Router } from 'react-router-dom'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { VegaWalletContext } from '@vegaprotocol/wallet'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { mockWalletContext } from '../../test-helpers/mocks'; @@ -81,16 +81,12 @@ describe('Propose New Asset', () => { it('should render the title', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByText('New asset proposal')).toBeTruthy() - ); + expect(await screen.findByText('New asset proposal')).toBeTruthy(); }); it('should render the form components', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('new-asset-proposal-form')).toBeTruthy() - ); + expect(await screen.findByTestId('new-asset-proposal-form')).toBeTruthy(); expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy(); expect(screen.getByTestId('proposal-docs-link')).toBeTruthy(); expect(screen.getByTestId('proposal-title')).toBeTruthy(); diff --git a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx index 07ddf9461..6e63d3ace 100644 --- a/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx +++ b/apps/token/src/routes/governance/propose/new-asset/propose-new-asset.tsx @@ -7,6 +7,7 @@ import { getValidationTimestamp, } from '@vegaprotocol/governance'; import { useEnvironment } from '@vegaprotocol/environment'; +import { validateJson } from '@vegaprotocol/react-helpers'; import { ProposalFormMinRequirements, ProposalFormTitle, @@ -32,7 +33,7 @@ export interface NewAssetProposalFormFields { proposalReference: string; } -const docsLink = '/new-asset-proposal'; +const DOCS_LINK = '/new-asset-proposal'; export const ProposeNewAsset = () => { const { @@ -100,9 +101,9 @@ export const ProposeNewAsset = () => {

{t('ProposalTermsText')} {`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`} + >{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}

)} @@ -141,20 +142,11 @@ export const ProposeNewAsset = () => { { - try { - JSON.parse(value); - return true; - } catch (e) { - return t('Must be valid JSON'); - } - }, - }, + validate: (value) => validateJson(value), })} labelOverride={'Terms.newAsset (JSON format)'} errorMessage={errors?.proposalTerms?.message} - customDocLink={docsLink} + customDocLink={DOCS_LINK} /> { it('should render the form components', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByTestId('new-market-proposal-form')).toBeTruthy() - ); + expect(await screen.findByTestId('new-market-proposal-form')).toBeTruthy(); expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy(); expect(screen.getByTestId('proposal-docs-link')).toBeTruthy(); expect(screen.getByTestId('proposal-title')).toBeTruthy(); diff --git a/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx b/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx index 89c0f419c..4aa0bf0fa 100644 --- a/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx +++ b/apps/token/src/routes/governance/propose/new-market/propose-new-market.tsx @@ -6,6 +6,7 @@ import { getEnactmentTimestamp, } from '@vegaprotocol/governance'; import { useEnvironment } from '@vegaprotocol/environment'; +import { validateJson } from '@vegaprotocol/react-helpers'; import { ProposalFormMinRequirements, ProposalFormTitle, @@ -30,7 +31,7 @@ export interface NewMarketProposalFormFields { proposalReference: string; } -const docsLink = '/new-market-proposal'; +const DOCS_LINK = '/new-market-proposal'; export const ProposeNewMarket = () => { const { @@ -95,9 +96,9 @@ export const ProposeNewMarket = () => {

{t('ProposalTermsText')} {`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`} + >{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}

)} @@ -136,20 +137,11 @@ export const ProposeNewMarket = () => { { - try { - JSON.parse(value); - return true; - } catch (e) { - return t('Must be valid JSON'); - } - }, - }, + validate: (value) => validateJson(value), })} labelOverride={'Terms.newMarket (JSON format)'} errorMessage={errors?.proposalTerms?.message} - customDocLink={docsLink} + customDocLink={DOCS_LINK} /> { expect(screen.getByText('New market')).toBeTruthy(); expect(screen.getByText('Update market')).toBeTruthy(); expect(screen.getByText('New asset')).toBeTruthy(); + expect(screen.getByText('Update asset')).toBeTruthy(); expect(screen.getByText('Freeform')).toBeTruthy(); expect(screen.getByText('Let me choose (raw proposal)')).toBeTruthy(); }); diff --git a/apps/token/src/routes/governance/propose/propose.tsx b/apps/token/src/routes/governance/propose/propose.tsx index 7b0ba9070..47a1d5372 100644 --- a/apps/token/src/routes/governance/propose/propose.tsx +++ b/apps/token/src/routes/governance/propose/propose.tsx @@ -79,6 +79,16 @@ export const Propose = () => {

+
  • +

    + + {t('UpdateAsset')} + +

    +
  • { data-testid="proposal-data" {...register('rawProposalData', { required: t('Required'), - validate: { - validateJson: (value) => { - try { - JSON.parse(value); - return true; - } catch (e) { - return t('Must be valid JSON'); - } - }, - }, + validate: (value) => validateJson(value), })} /> {errors.rawProposalData?.message && ( diff --git a/apps/token/src/routes/governance/propose/update-asset/index.tsx b/apps/token/src/routes/governance/propose/update-asset/index.tsx new file mode 100644 index 000000000..06b069cbd --- /dev/null +++ b/apps/token/src/routes/governance/propose/update-asset/index.tsx @@ -0,0 +1,4 @@ +export { + ProposeUpdateAsset, + ProposeUpdateAsset as default, +} from './propose-update-asset'; diff --git a/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.spec.tsx b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.spec.tsx new file mode 100644 index 000000000..fd5e453e0 --- /dev/null +++ b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.spec.tsx @@ -0,0 +1,102 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { MemoryRouter as Router } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import { VegaWalletContext } from '@vegaprotocol/wallet'; +import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; +import { mockWalletContext } from '../../test-helpers/mocks'; +import { ProposeUpdateAsset } from './propose-update-asset'; +import type { NetworkParamsQuery } from '@vegaprotocol/web3'; +import type { MockedResponse } from '@apollo/client/testing'; +import { NETWORK_PARAMETERS_QUERY } from '@vegaprotocol/react-helpers'; + +jest.mock('@vegaprotocol/environment', () => ({ + useEnvironment: () => ({ + VEGA_DOCS_URL: 'https://docs.vega.xyz', + }), +})); + +const updateAssetNetworkParamsQueryMock: MockedResponse = { + request: { + query: NETWORK_PARAMETERS_QUERY, + }, + result: { + data: { + networkParameters: [ + { + __typename: 'NetworkParameter', + key: 'governance.proposal.updateAsset.maxClose', + value: '8760h0m0s', + }, + { + __typename: 'NetworkParameter', + key: 'governance.proposal.updateAsset.maxEnact', + value: '8760h0m0s', + }, + { + __typename: 'NetworkParameter', + key: 'governance.proposal.updateAsset.minClose', + value: '1h0m0s', + }, + { + __typename: 'NetworkParameter', + key: 'governance.proposal.updateAsset.minEnact', + value: '2h0m0s', + }, + { + __typename: 'NetworkParameter', + key: 'governance.proposal.updateAsset.minProposerBalance', + value: '1', + }, + { + __typename: 'NetworkParameter', + key: 'spam.protection.proposal.min.tokens', + value: '1000000000000000000', + }, + ], + }, + }, +}; + +const renderComponent = () => + render( + + + + + + + + + + ); + +// Note: form submission is tested in propose-raw.spec.tsx. Reusable form +// components are tested in their own directory. + +describe('Propose Update Asset', () => { + it('should render successfully', async () => { + const { baseElement } = renderComponent(); + await expect(baseElement).toBeTruthy(); + }); + + it('should render the title', async () => { + renderComponent(); + expect(await screen.findByText('Update asset proposal')).toBeTruthy(); + }); + + it('should render the form components', async () => { + renderComponent(); + expect( + await screen.findByTestId('update-asset-proposal-form') + ).toBeTruthy(); + expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy(); + expect(screen.getByTestId('proposal-docs-link')).toBeTruthy(); + expect(screen.getByTestId('proposal-title')).toBeTruthy(); + expect(screen.getByTestId('proposal-description')).toBeTruthy(); + expect(screen.getByTestId('proposal-terms')).toBeTruthy(); + expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy(); + expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy(); + expect(screen.getByTestId('proposal-submit')).toBeTruthy(); + expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy(); + }); +}); diff --git a/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx new file mode 100644 index 000000000..ad9009f43 --- /dev/null +++ b/apps/token/src/routes/governance/propose/update-asset/propose-update-asset.tsx @@ -0,0 +1,182 @@ +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { + useProposalSubmit, + getClosingTimestamp, + getEnactmentTimestamp, +} from '@vegaprotocol/governance'; +import { useEnvironment } from '@vegaprotocol/environment'; +import { validateJson } from '@vegaprotocol/react-helpers'; +import { + ProposalFormMinRequirements, + ProposalFormTitle, + ProposalFormDescription, + ProposalFormTerms, + ProposalFormSubmit, + ProposalFormTransactionDialog, + ProposalFormSubheader, + ProposalFormVoteAndEnactmentDeadline, +} from '../../components/propose'; +import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit'; +import { Heading } from '../../../../components/heading'; +import { VegaWalletContainer } from '../../../../components/vega-wallet-container'; +import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers'; + +export interface UpdateAssetProposalFormFields { + proposalVoteDeadline: string; + proposalEnactmentDeadline: string; + proposalTitle: string; + proposalDescription: string; + proposalTerms: string; + proposalReference: string; +} + +const DOCS_LINK = '/update-asset-proposal'; + +export const ProposeUpdateAsset = () => { + const { + params, + loading: networkParamsLoading, + error: networkParamsError, + } = useNetworkParams([ + NetworkParams.governance_proposal_updateAsset_minClose, + NetworkParams.governance_proposal_updateAsset_maxClose, + NetworkParams.governance_proposal_updateAsset_minEnact, + NetworkParams.governance_proposal_updateAsset_maxEnact, + NetworkParams.governance_proposal_updateAsset_minProposerBalance, + NetworkParams.spam_protection_proposal_min_tokens, + ]); + + const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment(); + const { t } = useTranslation(); + const { + register, + handleSubmit, + formState: { isSubmitting, errors }, + } = useForm(); + const { finalizedProposal, submit, Dialog } = useProposalSubmit(); + + const onSubmit = async (fields: UpdateAssetProposalFormFields) => { + await submit({ + rationale: { + title: fields.proposalTitle, + description: fields.proposalDescription, + }, + terms: { + updateAsset: { + ...JSON.parse(fields.proposalTerms), + }, + closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline), + enactmentTimestamp: getEnactmentTimestamp( + fields.proposalVoteDeadline, + fields.proposalEnactmentDeadline + ), + }, + }); + }; + + return ( + + + + {() => ( + <> + + + {VEGA_DOCS_URL && ( +

    + {t('ProposalTermsText')} + {`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`} +

    + )} + + {VEGA_EXPLORER_URL && ( +

    + {t('MoreAssetsInfo')}{' '} + {`${VEGA_EXPLORER_URL}/assets`} +

    + )} + +
    +
    + + {t('ProposalRationale')} + + + + + + + + {t('UpdateAsset')} + + + validateJson(value), + })} + labelOverride={'Terms.updateAsset (JSON format)'} + errorMessage={errors?.proposalTerms?.message} + customDocLink={DOCS_LINK} + /> + + + + + + +
    + + )} + + + ); +}; diff --git a/apps/token/src/routes/governance/propose/update-market/propose-update-market.spec.tsx b/apps/token/src/routes/governance/propose/update-market/propose-update-market.spec.tsx index cd5a2c5ef..ab88929a6 100644 --- a/apps/token/src/routes/governance/propose/update-market/propose-update-market.spec.tsx +++ b/apps/token/src/routes/governance/propose/update-market/propose-update-market.spec.tsx @@ -178,17 +178,17 @@ describe('Propose Update Market', () => { it('should render the select element with no initial value', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByText('Update market proposal')).toBeInTheDocument() - ); + expect( + await screen.findByText('Update market proposal') + ).toBeInTheDocument(); expect(screen.getByTestId('proposal-market-select')).toHaveValue(''); }); it('should render the correct market details when the market select is used', async () => { renderComponent(); - await waitFor(() => - expect(screen.getByText('Update market proposal')).toBeInTheDocument() - ); + expect( + await screen.findByText('Update market proposal') + ).toBeInTheDocument(); fireEvent.change(screen.getByTestId('proposal-market-select'), { target: { value: diff --git a/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx b/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx index c8e32652d..303101da5 100644 --- a/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx +++ b/apps/token/src/routes/governance/propose/update-market/propose-update-market.tsx @@ -8,6 +8,7 @@ import { getEnactmentTimestamp, } from '@vegaprotocol/governance'; import { useEnvironment } from '@vegaprotocol/environment'; +import { validateJson } from '@vegaprotocol/react-helpers'; import { ProposalFormSubheader, ProposalFormMinRequirements, @@ -60,7 +61,7 @@ export interface UpdateMarketProposalFormFields { proposalReference: string; } -const docsLink = '/update-market-proposal'; +const DOCS_LINK = '/update-market-proposal'; export const ProposeUpdateMarket = () => { const { @@ -158,9 +159,9 @@ export const ProposeUpdateMarket = () => {

    {t('ProposalTermsText')} {`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`} + >{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}

    )} @@ -255,20 +256,11 @@ export const ProposeUpdateMarket = () => { { - try { - JSON.parse(value); - return true; - } catch (e) { - return t('Must be valid JSON'); - } - }, - }, + validate: (value) => validateJson(value), })} labelOverride={t('ProposeUpdateMarketTerms')} errorMessage={errors?.proposalTerms?.message} - customDocLink={docsLink} + customDocLink={DOCS_LINK} /> + import( + /* webpackChunkName: "route-governance-propose-update-asset", webpackPrefetch: true */ './governance/propose/update-asset' + ) +); + const LazyGovernanceProposeFreeform = React.lazy( () => import( @@ -273,6 +280,10 @@ const routerConfig = [ element: , }, { path: 'new-asset', element: }, + { + path: 'update-asset', + element: , + }, { path: 'freeform', element: }, { path: 'raw', element: }, ], diff --git a/libs/react-helpers/src/hooks/use-network-params.ts b/libs/react-helpers/src/hooks/use-network-params.ts index 5bc3f59d4..a4c2e14d0 100644 --- a/libs/react-helpers/src/hooks/use-network-params.ts +++ b/libs/react-helpers/src/hooks/use-network-params.ts @@ -43,6 +43,14 @@ export const NetworkParams = { governance_proposal_asset_maxClose: 'governance_proposal_asset_maxClose', governance_proposal_asset_minEnact: 'governance_proposal_asset_minEnact', governance_proposal_asset_maxEnact: 'governance_proposal_asset_maxEnact', + governance_proposal_updateAsset_minClose: + 'governance_proposal_updateAsset_minClose', + governance_proposal_updateAsset_maxClose: + 'governance_proposal_updateAsset_maxClose', + governance_proposal_updateAsset_minEnact: + 'governance_proposal_updateAsset_minEnact', + governance_proposal_updateAsset_maxEnact: + 'governance_proposal_updateAsset_maxEnact', governance_proposal_updateNetParam_minClose: 'governance_proposal_updateNetParam_minClose', governance_proposal_updateNetParam_maxClose: @@ -71,6 +79,8 @@ export const NetworkParams = { 'governance_proposal_asset_requiredParticipation', governance_proposal_asset_minProposerBalance: 'governance_proposal_asset_minProposerBalance', + governance_proposal_updateAsset_minProposerBalance: + 'governance_proposal_updateAsset_minProposerBalance', governance_proposal_updateNetParam_requiredMajority: 'governance_proposal_updateNetParam_requiredMajority', governance_proposal_updateNetParam_requiredParticipation: diff --git a/libs/react-helpers/src/lib/validate/index.ts b/libs/react-helpers/src/lib/validate/index.ts index cae0791c4..acbdeb957 100644 --- a/libs/react-helpers/src/lib/validate/index.ts +++ b/libs/react-helpers/src/lib/validate/index.ts @@ -45,3 +45,12 @@ export const suitableForSyntaxHighlighter = (str: string) => { return false; } }; + +export const validateJson = (value: string) => { + try { + JSON.parse(value); + return true; + } catch (e) { + return t('Must be valid JSON'); + } +}; diff --git a/libs/wallet/src/connectors/vega-connector.ts b/libs/wallet/src/connectors/vega-connector.ts index a83a7c495..d428cce9b 100644 --- a/libs/wallet/src/connectors/vega-connector.ts +++ b/libs/wallet/src/connectors/vega-connector.ts @@ -168,6 +168,21 @@ interface ProposalNewAssetTerms { validationTimestamp: number; } +interface ProposalUpdateAssetTerms { + updateAsset: { + assetId: string; + changes: { + quantum: string; + erc20: { + withdrawThreshold: string; + lifetimeLimit: string; + }; + }; + }; + closingTimestamp: number; + enactmentTimestamp: number; +} + interface OracleSpecBinding { settlementPriceProperty: string; tradingTerminationProperty: string; @@ -227,7 +242,8 @@ export interface ProposalSubmission { | ProposalNewMarketTerms | ProposalUpdateMarketTerms | ProposalNetworkParameterTerms - | ProposalNewAssetTerms; + | ProposalNewAssetTerms + | ProposalUpdateAssetTerms; } export interface ProposalSubmissionBody {