From 3000f5b7a458a3a9ed6e1a160f2eb73480d1aacf Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 12 Nov 2020 17:08:27 +0100 Subject: [PATCH] Support contract bundles (#4019) * Bump API * Rebuild i18n * Fix InputAddress * Pass through registry where applicable * Support contract bundles --- packages/page-contracts/src/Codes/Upload.tsx | 57 +++++++++++++------ packages/page-contracts/src/shared/ABI.tsx | 12 ++-- .../page-contracts/src/shared/Messages.tsx | 7 ++- packages/page-contracts/src/useAbi.ts | 16 +++--- 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/packages/page-contracts/src/Codes/Upload.tsx b/packages/page-contracts/src/Codes/Upload.tsx index 1c34c018f8..a37b0767fd 100644 --- a/packages/page-contracts/src/Codes/Upload.tsx +++ b/packages/page-contracts/src/Codes/Upload.tsx @@ -27,7 +27,7 @@ function Upload ({ onClose }: Props): React.ReactElement { const [uploadTx, setUploadTx] = useState | null>(null); const [[wasm, isWasmValid], setWasm] = useState<[Uint8Array | null, boolean]>([null, false]); const [name, isNameValid, setName] = useNonEmptyString(); - const { contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi(); + const { abiName, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi(); const code = useMemo( () => isAbiValid && isWasmValid && wasm && contractAbi @@ -40,6 +40,18 @@ function Upload ({ onClose }: Props): React.ReactElement { setUploadTx(() => code ? code.createBlueprint() : null); }, [code]); + useEffect((): void => { + setWasm( + contractAbi && isWasm(contractAbi.project.source.wasm) + ? [contractAbi.project.source.wasm, true] + : [null, false] + ); + }, [contractAbi]); + + useEffect((): void => { + abiName && setName(abiName); + }, [abiName, setName]); + const _onAddWasm = useCallback( (wasm: Uint8Array, name: string): void => { setWasm([wasm, isWasm(wasm)]); @@ -62,6 +74,7 @@ function Upload ({ onClose }: Props): React.ReactElement { ); const isSubmittable = !!accountId && (!isNull(name) && isNameValid) && isWasmValid && isAbiSupplied && isAbiValid && !!uploadTx; + const invalidAbi = isAbiError || !isAbiSupplied; return ( @@ -80,31 +93,39 @@ function Upload ({ onClose }: Props): React.ReactElement { type='account' value={accountId} /> - ('The compiled WASM for the contract that you wish to deploy. Each unique code blob will be attached with a code hash that can be used to create new instances.')} - isError={!isWasmValid} - label={t('compiled contract WASM')} - onChange={_onAddWasm} - placeholder={ - wasm && !isWasmValid - ? t('The code is not recognized as being in valid WASM format') - : null - } - /> - ('json for either ABI or .contract bundle')} onChange={onChangeAbi} onRemove={onRemoveAbi} + withWasm /> + {!invalidAbi && contractAbi && ( + <> + {!contractAbi.project.source.wasm.length && ( + ('The compiled WASM for the contract that you wish to deploy. Each unique code blob will be attached with a code hash that can be used to create new instances.')} + isError={!isWasmValid} + label={t('compiled contract WASM')} + onChange={_onAddWasm} + placeholder={ + wasm && !isWasmValid + ? t('The code is not recognized as being in valid WASM format') + : null + } + /> + )} + + + )} void; + onChange: (u8a: Uint8Array, name: string) => void; onRemove?: () => void; onRemoved?: () => void; onSelect?: () => void; @@ -27,11 +27,12 @@ interface Props { withConstructors?: boolean; withLabel?: boolean; withMessages?: boolean; + withWasm?: boolean; } const NOOP = (): void => undefined; -function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true }: Props): React.ReactElement { +function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, label, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true, withWasm }: Props): React.ReactElement { const { t } = useTranslation(); return (contractAbi && isValid) @@ -39,7 +40,7 @@ function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, ('This is the ABI as supplied. Any calls to the contract will use this information for encoding.')} - label={t('contract ABI')} + label={label || t('contract ABI')} labelExtra={onRemove && ( ) @@ -65,9 +67,9 @@ function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isDisabled={isDisabled} isError={isError} isFull={isFull} - label={t('contract ABI')} + label={label || t('contract ABI')} onChange={onChange} - placeholder={errorText || t('click to select or drag and drop a JSON ABI file')} + placeholder={errorText || t('click to select or drag and drop a JSON file')} /> ); diff --git a/packages/page-contracts/src/shared/Messages.tsx b/packages/page-contracts/src/shared/Messages.tsx index 37788c2234..d29be7d5dc 100644 --- a/packages/page-contracts/src/shared/Messages.tsx +++ b/packages/page-contracts/src/shared/Messages.tsx @@ -11,6 +11,7 @@ import { Abi, ContractPromise } from '@polkadot/api-contract'; import { Expander } from '@polkadot/react-components'; import { useApi, useCall } from '@polkadot/react-hooks'; import { Option } from '@polkadot/types'; +import { formatNumber } from '@polkadot/util'; import Message from './Message'; import { useTranslation } from '../translate'; @@ -25,6 +26,7 @@ export interface Props { onSelectConstructor?: (constructorIndex: number) => void; withConstructors?: boolean; withMessages?: boolean; + withWasm?: boolean; } const READ_ADDR = '0x'.padEnd(66, '0'); @@ -41,7 +43,7 @@ function sortMessages (messages: AbiMessage[]): [AbiMessage, number][] { ); } -function Messages ({ className = '', contract, contractAbi: { constructors, messages }, isLabelled, isWatching, onSelect, onSelectConstructor, withConstructors, withMessages } : Props): React.ReactElement { +function Messages ({ className = '', contract, contractAbi: { constructors, messages, project: { source } }, isLabelled, isWatching, onSelect, onSelectConstructor, withConstructors, withMessages, withWasm } : Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); const optInfo = useCall>(contract && api.query.contracts.contractInfoOf, [contract?.address]); @@ -110,6 +112,9 @@ function Messages ({ className = '', contract, contractAbi: { constructors, mess ))} )} + {withWasm && source.wasm.length !== 0 && ( +
{t('{{size}} WASM bytes', { replace: { size: formatNumber(source.wasm.length) } })}
+ )} ); } diff --git a/packages/page-contracts/src/useAbi.ts b/packages/page-contracts/src/useAbi.ts index 006cda1875..06c4e5aba2 100644 --- a/packages/page-contracts/src/useAbi.ts +++ b/packages/page-contracts/src/useAbi.ts @@ -12,38 +12,39 @@ import store from './store'; interface UseAbi { abi: string | null; + abiName: string | null; contractAbi: Abi | null; errorText: string | null; isAbiError: boolean; isAbiValid: boolean; isAbiSupplied: boolean; - onChangeAbi: (u8a: Uint8Array) => void; + onChangeAbi: (u8a: Uint8Array, name: string) => void; onRemoveAbi: () => void; } export default function useAbi (initialValue: [string | null | undefined, Abi | null | undefined] = [null, null], codeHash: StringOrNull = null, isRequired = false): UseAbi { - const [[abi, contractAbi, isAbiSupplied, isAbiValid], setAbi] = useState<[string | null | undefined, Abi | null | undefined, boolean, boolean]>([initialValue[0], initialValue[1], !!initialValue[1], !isRequired || !!initialValue[1]]); + const [[abi, contractAbi, isAbiSupplied, isAbiValid, abiName], setAbi] = useState<[string | null | undefined, Abi | null | undefined, boolean, boolean, string | null]>([initialValue[0], initialValue[1], !!initialValue[1], !isRequired || !!initialValue[1], null]); const [[isAbiError, errorText], setError] = useState<[boolean, string | null]>([false, null]); useEffect( (): void => { - initialValue[0] && abi !== initialValue[0] && setAbi([initialValue[0], initialValue[1], !!initialValue[1], !isRequired || !!initialValue[1]]); + initialValue[0] && abi !== initialValue[0] && setAbi([initialValue[0], initialValue[1], !!initialValue[1], !isRequired || !!initialValue[1], null]); }, [abi, initialValue, isRequired] ); const onChangeAbi = useCallback( - (u8a: Uint8Array): void => { + (u8a: Uint8Array, name: string): void => { const json = u8aToString(u8a); try { - setAbi([json, new Abi(json, api.registry.getChainProperties()), true, true]); + setAbi([json, new Abi(json, api.registry.getChainProperties()), true, true, name.replace('.contract', '').replace('.json', '').replace('_', ' ')]); codeHash && store.saveCode(codeHash, { abi: json }); } catch (error) { console.error(error); - setAbi([null, null, false, false]); + setAbi([null, null, false, false, null]); setError([true, (error as Error).message]); } }, @@ -52,7 +53,7 @@ export default function useAbi (initialValue: [string | null | undefined, Abi | const onRemoveAbi = useCallback( (): void => { - setAbi([null, null, false, false]); + setAbi([null, null, false, false, null]); setError([false, null]); codeHash && store.saveCode(codeHash, { abi: null }); @@ -62,6 +63,7 @@ export default function useAbi (initialValue: [string | null | undefined, Abi | return { abi: abi || null, + abiName, contractAbi: contractAbi || null, errorText, isAbiError,