Chore/list asset (#1964)
* chore: add whitelist asset function * feat: add whitelist button * test: fix tests for proposal form * test: add tests for new component * chore: revert incorectly comitted changes * Update apps/token/src/routes/governance/components/proposal/proposal.spec.tsx Co-authored-by: Sam Keen <samuel@vegaprotocol.io> Co-authored-by: Sam Keen <samuel@vegaprotocol.io>
This commit is contained in:
parent
e5432888aa
commit
113eb90469
@ -1,7 +1,7 @@
|
|||||||
# App configuration variables
|
# App configuration variables
|
||||||
NX_VEGA_ENV=TESTNET
|
NX_VEGA_ENV=TESTNET
|
||||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
||||||
NX_VEGA_URL=https://api.n06.testnet.vega.xyz/graphql
|
NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql
|
||||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||||
NX_FAIRGROUND=false
|
NX_FAIRGROUND=false
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useWeb3React } from '@web3-react/core';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
AppStateActionType,
|
||||||
|
useAppState,
|
||||||
|
} from '../../contexts/app-state/app-state-context';
|
||||||
|
|
||||||
|
export const EthWalletContainer = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactElement;
|
||||||
|
}) => {
|
||||||
|
const { account } = useWeb3React();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { appDispatch } = useAppState();
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return (
|
||||||
|
<div className="w-full text-center">
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
appDispatch({
|
||||||
|
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||||
|
isOpen: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('connectEthWallet')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
};
|
1
apps/token/src/components/eth-wallet-container/index.tsx
Normal file
1
apps/token/src/components/eth-wallet-container/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './eth-wallet-container';
|
@ -704,5 +704,8 @@
|
|||||||
"votingEnded": "Voting has ended.",
|
"votingEnded": "Voting has ended.",
|
||||||
"STATUS": "STATUS",
|
"STATUS": "STATUS",
|
||||||
"ProposalMinimumAmounts": "Different proposal types can have different minimum token requirements. You must have the greater of the proposal minimum or spam protection minimum from the table below",
|
"ProposalMinimumAmounts": "Different proposal types can have different minimum token requirements. You must have the greater of the proposal minimum or spam protection minimum from the table below",
|
||||||
"SpamProtectionMin": "Spam protection minimum"
|
"SpamProtectionMin": "Spam protection minimum",
|
||||||
|
"ListAsset": "List Asset",
|
||||||
|
"ListAssetDescription": "This asset needs to be listed on the collateral bridge before it can be used.",
|
||||||
|
"ListAssetAction": "List asset"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
query ProposalAsset($assetId: ID!) {
|
||||||
|
asset(id: $assetId) {
|
||||||
|
status
|
||||||
|
source {
|
||||||
|
... on ERC20 {
|
||||||
|
contractAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query AssetListBundle($assetId: ID!) {
|
||||||
|
erc20ListAssetBundle(assetId: $assetId) {
|
||||||
|
assetSource
|
||||||
|
vegaAssetId
|
||||||
|
nonce
|
||||||
|
signatures
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import { Schema as Types } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import * as Apollo from '@apollo/client';
|
||||||
|
const defaultOptions = {} as const;
|
||||||
|
export type ProposalAssetQueryVariables = Types.Exact<{
|
||||||
|
assetId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ProposalAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } | null };
|
||||||
|
|
||||||
|
export type AssetListBundleQueryVariables = Types.Exact<{
|
||||||
|
assetId: Types.Scalars['ID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type AssetListBundleQuery = { __typename?: 'Query', erc20ListAssetBundle?: { __typename?: 'Erc20ListAssetBundle', assetSource: string, vegaAssetId: string, nonce: string, signatures: string } | null };
|
||||||
|
|
||||||
|
|
||||||
|
export const ProposalAssetDocument = gql`
|
||||||
|
query ProposalAsset($assetId: ID!) {
|
||||||
|
asset(id: $assetId) {
|
||||||
|
status
|
||||||
|
source {
|
||||||
|
... on ERC20 {
|
||||||
|
contractAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useProposalAssetQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useProposalAssetQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useProposalAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useProposalAssetQuery({
|
||||||
|
* variables: {
|
||||||
|
* assetId: // value for 'assetId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useProposalAssetQuery(baseOptions: Apollo.QueryHookOptions<ProposalAssetQuery, ProposalAssetQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<ProposalAssetQuery, ProposalAssetQueryVariables>(ProposalAssetDocument, options);
|
||||||
|
}
|
||||||
|
export function useProposalAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProposalAssetQuery, ProposalAssetQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<ProposalAssetQuery, ProposalAssetQueryVariables>(ProposalAssetDocument, options);
|
||||||
|
}
|
||||||
|
export type ProposalAssetQueryHookResult = ReturnType<typeof useProposalAssetQuery>;
|
||||||
|
export type ProposalAssetLazyQueryHookResult = ReturnType<typeof useProposalAssetLazyQuery>;
|
||||||
|
export type ProposalAssetQueryResult = Apollo.QueryResult<ProposalAssetQuery, ProposalAssetQueryVariables>;
|
||||||
|
export const AssetListBundleDocument = gql`
|
||||||
|
query AssetListBundle($assetId: ID!) {
|
||||||
|
erc20ListAssetBundle(assetId: $assetId) {
|
||||||
|
assetSource
|
||||||
|
vegaAssetId
|
||||||
|
nonce
|
||||||
|
signatures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useAssetListBundleQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useAssetListBundleQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useAssetListBundleQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useAssetListBundleQuery({
|
||||||
|
* variables: {
|
||||||
|
* assetId: // value for 'assetId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useAssetListBundleQuery(baseOptions: Apollo.QueryHookOptions<AssetListBundleQuery, AssetListBundleQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<AssetListBundleQuery, AssetListBundleQueryVariables>(AssetListBundleDocument, options);
|
||||||
|
}
|
||||||
|
export function useAssetListBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AssetListBundleQuery, AssetListBundleQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<AssetListBundleQuery, AssetListBundleQueryVariables>(AssetListBundleDocument, options);
|
||||||
|
}
|
||||||
|
export type AssetListBundleQueryHookResult = ReturnType<typeof useAssetListBundleQuery>;
|
||||||
|
export type AssetListBundleLazyQueryHookResult = ReturnType<typeof useAssetListBundleLazyQuery>;
|
||||||
|
export type AssetListBundleQueryResult = Apollo.QueryResult<AssetListBundleQuery, AssetListBundleQueryVariables>;
|
@ -0,0 +1 @@
|
|||||||
|
export { ListAsset } from './list-asset';
|
@ -0,0 +1,186 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { ListAsset } from './list-asset';
|
||||||
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type {
|
||||||
|
AssetListBundleQuery,
|
||||||
|
ProposalAssetQuery,
|
||||||
|
} from './__generated___/Asset';
|
||||||
|
import { AssetListBundleDocument } from './__generated___/Asset';
|
||||||
|
import { ProposalAssetDocument } from './__generated___/Asset';
|
||||||
|
import { AssetStatus } from '@vegaprotocol/types';
|
||||||
|
import type { useWeb3React } from '@web3-react/core';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
import type { AppState } from '../../../../contexts/app-state/app-state-context';
|
||||||
|
|
||||||
|
const mockUseEthTx = {
|
||||||
|
perform: jest.fn(),
|
||||||
|
Dialog: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/web3', () => {
|
||||||
|
const orig = jest.requireActual('@vegaprotocol/web3');
|
||||||
|
return {
|
||||||
|
...orig,
|
||||||
|
useBridgeContract: jest.fn().mockReturnValue({
|
||||||
|
list_asset: jest.fn(),
|
||||||
|
isNewContract: true,
|
||||||
|
}),
|
||||||
|
useEthereumTransaction: jest.fn(() => mockUseEthTx),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultHookValue = {
|
||||||
|
isActive: false,
|
||||||
|
error: undefined,
|
||||||
|
connector: null,
|
||||||
|
chainId: 3,
|
||||||
|
account: null,
|
||||||
|
} as unknown as ReturnType<typeof useWeb3React>;
|
||||||
|
let mockHookValue: ReturnType<typeof useWeb3React>;
|
||||||
|
|
||||||
|
jest.mock('@web3-react/core', () => {
|
||||||
|
const original = jest.requireActual('@web3-react/core');
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
useWeb3React: jest.fn(() => mockHookValue),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockAppState: AppState = {
|
||||||
|
totalAssociated: new BigNumber('50063005'),
|
||||||
|
decimals: 18,
|
||||||
|
totalSupply: new BigNumber(65000000),
|
||||||
|
balanceFormatted: new BigNumber(0),
|
||||||
|
walletBalance: new BigNumber(0),
|
||||||
|
lien: new BigNumber(0),
|
||||||
|
allowance: new BigNumber(0),
|
||||||
|
tranches: null,
|
||||||
|
vegaWalletOverlay: false,
|
||||||
|
vegaWalletManageOverlay: false,
|
||||||
|
ethConnectOverlay: false,
|
||||||
|
walletAssociatedBalance: null,
|
||||||
|
vestingAssociatedBalance: null,
|
||||||
|
trancheBalances: [],
|
||||||
|
totalLockedBalance: new BigNumber(0),
|
||||||
|
totalVestedBalance: new BigNumber(0),
|
||||||
|
trancheError: null,
|
||||||
|
drawerOpen: false,
|
||||||
|
associationBreakdown: {
|
||||||
|
vestingAssociations: {},
|
||||||
|
stakingAssociations: {},
|
||||||
|
},
|
||||||
|
transactionOverlay: false,
|
||||||
|
bannerMessage: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('../../../contexts/app-state/app-state-context', () => ({
|
||||||
|
useAppState: () => ({
|
||||||
|
appState: mockAppState,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ASSET_ID = 'foo';
|
||||||
|
|
||||||
|
const DEFAULT__ASSET: ProposalAssetQuery = {
|
||||||
|
__typename: 'Query',
|
||||||
|
asset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
status: AssetStatus.STATUS_PENDING_LISTING,
|
||||||
|
source: {
|
||||||
|
__typename: 'ERC20',
|
||||||
|
contractAddress: '0x0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const proposalAssetMock: MockedResponse<ProposalAssetQuery> = {
|
||||||
|
request: {
|
||||||
|
query: ProposalAssetDocument,
|
||||||
|
variables: {
|
||||||
|
assetId: ASSET_ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
...DEFAULT__ASSET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const assetBundleMock: MockedResponse<AssetListBundleQuery> = {
|
||||||
|
request: {
|
||||||
|
query: AssetListBundleDocument,
|
||||||
|
variables: {
|
||||||
|
assetId: ASSET_ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
data: {
|
||||||
|
erc20ListAssetBundle: {
|
||||||
|
assetSource: '0xf00',
|
||||||
|
vegaAssetId: ASSET_ID,
|
||||||
|
nonce: '1',
|
||||||
|
signatures: '0x0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComponent = (assetId: string) => {
|
||||||
|
return render(
|
||||||
|
<MockedProvider mocks={[proposalAssetMock, assetBundleMock]}>
|
||||||
|
<ListAsset
|
||||||
|
assetId={assetId}
|
||||||
|
withdrawalThreshold={'1'}
|
||||||
|
lifetimeLimit={'100'}
|
||||||
|
/>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('Renders connect state if not connected', async () => {
|
||||||
|
mockHookValue = defaultHookValue;
|
||||||
|
renderComponent(ASSET_ID);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByText('Connect Ethereum wallet')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders title, description and button when connected', async () => {
|
||||||
|
mockHookValue = {
|
||||||
|
...defaultHookValue,
|
||||||
|
account: 'foo',
|
||||||
|
};
|
||||||
|
renderComponent(ASSET_ID);
|
||||||
|
expect(await screen.findByText('List asset')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
await screen.findByText(
|
||||||
|
'This asset needs to be listed on the collateral bridge before it can be used.'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(await screen.findByTestId('list-asset')).toHaveTextContent(
|
||||||
|
'List asset'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Sends transaction correctly when button is pressed', async () => {
|
||||||
|
mockHookValue = {
|
||||||
|
...defaultHookValue,
|
||||||
|
account: 'foo',
|
||||||
|
};
|
||||||
|
renderComponent(ASSET_ID);
|
||||||
|
expect(await screen.findByTestId('list-asset')).toHaveTextContent(
|
||||||
|
'List asset'
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByTestId('list-asset'));
|
||||||
|
expect(mockUseEthTx.perform).toBeCalledWith(
|
||||||
|
'0xf00',
|
||||||
|
'0xfoo',
|
||||||
|
'100',
|
||||||
|
'1',
|
||||||
|
'1',
|
||||||
|
'0x0'
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,111 @@
|
|||||||
|
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
||||||
|
import { AssetStatus } from '@vegaprotocol/types';
|
||||||
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
useAssetListBundleQuery,
|
||||||
|
useProposalAssetQuery,
|
||||||
|
} from './__generated___/Asset';
|
||||||
|
import { EthWalletContainer } from '../../../../components/eth-wallet-container';
|
||||||
|
|
||||||
|
const useListAsset = (assetId: string) => {
|
||||||
|
const bridgeContract = useBridgeContract();
|
||||||
|
|
||||||
|
const transaction = useEthereumTransaction<CollateralBridge, 'list_asset'>(
|
||||||
|
bridgeContract,
|
||||||
|
'list_asset'
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading: loadingAsset,
|
||||||
|
error: errorAsset,
|
||||||
|
} = useProposalAssetQuery({
|
||||||
|
variables: {
|
||||||
|
assetId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: assetData,
|
||||||
|
loading: loadingBundle,
|
||||||
|
error: errorBundle,
|
||||||
|
} = useAssetListBundleQuery({
|
||||||
|
variables: {
|
||||||
|
assetId,
|
||||||
|
},
|
||||||
|
skip: !data,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
transaction,
|
||||||
|
data,
|
||||||
|
loadingAsset,
|
||||||
|
errorAsset,
|
||||||
|
assetData,
|
||||||
|
loadingBundle,
|
||||||
|
errorBundle,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ListAssetProps {
|
||||||
|
lifetimeLimit: string;
|
||||||
|
withdrawalThreshold: string;
|
||||||
|
assetId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListAsset = ({
|
||||||
|
assetId,
|
||||||
|
lifetimeLimit,
|
||||||
|
withdrawalThreshold,
|
||||||
|
}: ListAssetProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
transaction,
|
||||||
|
data,
|
||||||
|
loadingAsset,
|
||||||
|
errorAsset,
|
||||||
|
assetData,
|
||||||
|
loadingBundle,
|
||||||
|
errorBundle,
|
||||||
|
} = useListAsset(assetId);
|
||||||
|
const { perform, Dialog } = transaction;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!assetData?.erc20ListAssetBundle ||
|
||||||
|
!assetData.erc20ListAssetBundle ||
|
||||||
|
!data?.asset ||
|
||||||
|
loadingAsset ||
|
||||||
|
loadingBundle
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (data.asset.source.__typename !== 'ERC20') return null;
|
||||||
|
if (data.asset.status !== AssetStatus.STATUS_PENDING_LISTING) return null;
|
||||||
|
if (errorAsset || errorBundle) return null;
|
||||||
|
const { assetSource, signatures, vegaAssetId, nonce } =
|
||||||
|
assetData.erc20ListAssetBundle;
|
||||||
|
return (
|
||||||
|
<div className="mb-8">
|
||||||
|
<h3 className="text-xl mb-2">{t('ListAsset')}</h3>
|
||||||
|
<p className="pr-8">{t('ListAssetDescription')}</p>
|
||||||
|
<EthWalletContainer>
|
||||||
|
<Button
|
||||||
|
data-testid="list-asset"
|
||||||
|
onClick={() =>
|
||||||
|
perform(
|
||||||
|
assetSource,
|
||||||
|
`0x${vegaAssetId}`,
|
||||||
|
lifetimeLimit,
|
||||||
|
withdrawalThreshold,
|
||||||
|
nonce,
|
||||||
|
signatures
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('ListAssetAction')}
|
||||||
|
</Button>
|
||||||
|
</EthWalletContainer>
|
||||||
|
<Dialog />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -34,6 +34,9 @@ jest.mock('../proposal-votes-table', () => ({
|
|||||||
jest.mock('../vote-details', () => ({
|
jest.mock('../vote-details', () => ({
|
||||||
VoteDetails: () => <div data-testid="proposal-vote-details"></div>,
|
VoteDetails: () => <div data-testid="proposal-vote-details"></div>,
|
||||||
}));
|
}));
|
||||||
|
jest.mock('../list-asset', () => ({
|
||||||
|
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||||
|
}));
|
||||||
|
|
||||||
it('Renders with data-testid', async () => {
|
it('Renders with data-testid', async () => {
|
||||||
const proposal = generateProposal();
|
const proposal = generateProposal();
|
||||||
@ -49,4 +52,26 @@ it('renders each section', async () => {
|
|||||||
expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
|
expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders whitelist section if proposal is new asset and source is erc20', async () => {
|
||||||
|
const proposal = generateProposal({
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'NewAsset',
|
||||||
|
name: 'foo',
|
||||||
|
symbol: 'FOO',
|
||||||
|
decimals: 18,
|
||||||
|
quantum: '1',
|
||||||
|
source: {
|
||||||
|
__typename: 'ERC20',
|
||||||
|
lifetimeLimit: '1',
|
||||||
|
withdrawThreshold: '100',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(<Proposal proposal={proposal as Proposal_proposal} />);
|
||||||
|
expect(screen.getByTestId('proposal-list-asset')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import { ProposalChangeTable } from '../proposal-change-table';
|
|||||||
import { ProposalTermsJson } from '../proposal-terms-json';
|
import { ProposalTermsJson } from '../proposal-terms-json';
|
||||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
import { ProposalVotesTable } from '../proposal-votes-table';
|
||||||
import { VoteDetails } from '../vote-details';
|
import { VoteDetails } from '../vote-details';
|
||||||
|
import { ListAsset } from '../list-asset';
|
||||||
|
|
||||||
export enum ProposalType {
|
export enum ProposalType {
|
||||||
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
|
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
|
||||||
@ -15,7 +16,6 @@ export enum ProposalType {
|
|||||||
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
|
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
|
||||||
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
|
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProposalProps {
|
interface ProposalProps {
|
||||||
proposal: Proposal_proposal;
|
proposal: Proposal_proposal;
|
||||||
}
|
}
|
||||||
@ -77,6 +77,15 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
|||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<ProposalChangeTable proposal={proposal} />
|
<ProposalChangeTable proposal={proposal} />
|
||||||
</div>
|
</div>
|
||||||
|
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||||
|
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||||
|
proposal.id ? (
|
||||||
|
<ListAsset
|
||||||
|
assetId={proposal.id}
|
||||||
|
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
|
||||||
|
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<VoteDetails
|
<VoteDetails
|
||||||
proposal={proposal}
|
proposal={proposal}
|
||||||
|
@ -37,6 +37,23 @@ export class CollateralBridge {
|
|||||||
default_withdraw_delay() {
|
default_withdraw_delay() {
|
||||||
return this.contract.default_withdraw_delay();
|
return this.contract.default_withdraw_delay();
|
||||||
}
|
}
|
||||||
|
list_asset(
|
||||||
|
address: string,
|
||||||
|
vegaAssetId: string,
|
||||||
|
lifetimeLimit: string,
|
||||||
|
withdraw_threshold: string,
|
||||||
|
nonce: string,
|
||||||
|
signatures: string
|
||||||
|
) {
|
||||||
|
return this.contract.list_asset(
|
||||||
|
address,
|
||||||
|
vegaAssetId,
|
||||||
|
lifetimeLimit,
|
||||||
|
withdraw_threshold,
|
||||||
|
nonce,
|
||||||
|
signatures
|
||||||
|
);
|
||||||
|
}
|
||||||
withdraw_asset(
|
withdraw_asset(
|
||||||
assetSource: string,
|
assetSource: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
|
Loading…
Reference in New Issue
Block a user