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