Support contract bundles (#4019)

* Bump API

* Rebuild i18n

* Fix InputAddress

* Pass through registry where applicable

* Support contract bundles
This commit is contained in:
Jaco Greeff 2020-11-12 17:08:27 +01:00 committed by GitHub
parent 9fef98a300
commit 3000f5b7a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 31 deletions

View File

@ -27,7 +27,7 @@ function Upload ({ onClose }: Props): React.ReactElement {
const [uploadTx, setUploadTx] = useState<SubmittableExtrinsic<'promise'> | 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 (
<Modal header={t('Upload WASM')}>
@ -80,31 +93,39 @@ function Upload ({ onClose }: Props): React.ReactElement {
type='account'
value={accountId}
/>
<InputFile
help={t<string>('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<string>('compiled contract WASM')}
onChange={_onAddWasm}
placeholder={
wasm && !isWasmValid
? t<string>('The code is not recognized as being in valid WASM format')
: null
}
/>
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError || !isAbiSupplied}
isError={invalidAbi}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
label={t<string>('json for either ABI or .contract bundle')}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withWasm
/>
{!invalidAbi && contractAbi && (
<>
{!contractAbi.project.source.wasm.length && (
<InputFile
help={t<string>('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<string>('compiled contract WASM')}
onChange={_onAddWasm}
placeholder={
wasm && !isWasmValid
? t<string>('The code is not recognized as being in valid WASM format')
: null
}
/>
)}
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
</>
)}
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton

View File

@ -19,7 +19,7 @@ interface Props {
isValid?: boolean;
isSupplied?: boolean;
label?: React.ReactNode;
onChange: (u8a: Uint8Array) => 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<Props> {
function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, label, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true, withWasm }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (contractAbi && isValid)
@ -39,7 +40,7 @@ function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull,
<Labelled
className={className}
help={t<string>('This is the ABI as supplied. Any calls to the contract will use this information for encoding.')}
label={t<string>('contract ABI')}
label={label || t<string>('contract ABI')}
labelExtra={onRemove && (
<IconLink
icon='trash'
@ -55,6 +56,7 @@ function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull,
onSelectConstructor={onSelectConstructor}
withConstructors={withConstructors}
withMessages={withMessages}
withWasm={withWasm}
/>
</Labelled>
)
@ -65,9 +67,9 @@ function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull,
isDisabled={isDisabled}
isError={isError}
isFull={isFull}
label={t<string>('contract ABI')}
label={label || t<string>('contract ABI')}
onChange={onChange}
placeholder={errorText || t<string>('click to select or drag and drop a JSON ABI file')}
placeholder={errorText || t<string>('click to select or drag and drop a JSON file')}
/>
</div>
);

View File

@ -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<Props> {
function Messages ({ className = '', contract, contractAbi: { constructors, messages, project: { source } }, isLabelled, isWatching, onSelect, onSelectConstructor, withConstructors, withMessages, withWasm } : Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const optInfo = useCall<Option<ContractInfo>>(contract && api.query.contracts.contractInfoOf, [contract?.address]);
@ -110,6 +112,9 @@ function Messages ({ className = '', contract, contractAbi: { constructors, mess
))}
</Expander>
)}
{withWasm && source.wasm.length !== 0 && (
<div>{t<string>('{{size}} WASM bytes', { replace: { size: formatNumber(source.wasm.length) } })}</div>
)}
</div>
);
}

View File

@ -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,