Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 12 additions & 44 deletions cypress/e2e/contracts/erc20.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ describe('ERC20 Contract ', () => {
});

it('contract file uploads', () => {
// TODO: In the contract, replaced Address with H160. Fix https://github.com/use-ink/contracts-ui/issues/582
assertUpload('erc20.contract');
});

Expand Down Expand Up @@ -54,67 +53,36 @@ describe('ERC20 Contract ', () => {

it(`transfers ${transferValue} Units to another account`, () => {
selectMessage('transfer', 3);
cy.get('.form-field.to')
.find("input[type='text']")
.clear()
.type('0x60afa252b554aabc4b3253ca2be60dc1d536ec10')
.should('have.value', '0x60afa252b554aabc4b3253ca2be60dc1d536ec10');
cy.get('.form-field.value').find('input[type="number"]').type(`${transferValue}`);
cy.get('.form-field.to').find('.dropdown').click().find('.dropdown__option').eq(3).click();
cy.get('.form-field.value').find('input[type="number"]').eq(0).type(`${transferValue}`);
assertCall();
selectMessage('balanceOf', 1);
console.log(initialSupply - transferValue);
cy.get('.form-field.owner').find('.dropdown').click().find('.dropdown__option').eq(0).click();

cy.get('.form-field.owner')
.find("input[type='text']")
.clear()
.type('0x9621dde636de098b43efb0fa9b61facfe328f99d')
.should('have.value', '0x9621dde636de098b43efb0fa9b61facfe328f99d');
assertReturnValue('balanceOf', `${initialSupply - transferValue}`);
});

it(`successfully approves allowance`, () => {
selectMessage('approve', 4);
cy.get('.form-field.spender')
.find("input[type='text']")
.clear()
.type('0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01')
.should('have.value', '0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01');
cy.get('.form-field.value').find('input[type="number"]').type(`${allowance}`);
cy.get('.form-field.spender').find('.dropdown').click().find('.dropdown__option').eq(2).click();
cy.get('.form-field.value').find('input[type="number"]').eq(0).type(`${allowance}`);
assertCall();
selectMessage('allowance', 2);
cy.get('.form-field.owner')
.find("input[type='text']")
.clear()
.type('0x9621dde636de098b43efb0fa9b61facfe328f99d')
.should('have.value', '0x9621dde636de098b43efb0fa9b61facfe328f99d');
cy.get('.form-field.spender')
.find("input[type='text']")
.clear()
.type('0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01')
.should('have.value', '0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01');
cy.get('.form-field.owner').find('.dropdown').click().find('.dropdown__option').eq(0).click();
cy.get('.form-field.spender').find('.dropdown').click().find('.dropdown__option').eq(2).click();
assertReturnValue('allowance', `${allowance}`);
});

it(`transfers ${transferValue} on behalf of alice`, () => {
cy.get('.form-field.caller').click().find('.dropdown__option').eq(2).click();
selectMessage('transferFrom', 5);
cy.get('.form-field.from')
.find("input[type='text']")
.clear()
.type('0x9621dde636de098b43efb0fa9b61facfe328f99d')
.should('have.value', '0x9621dde636de098b43efb0fa9b61facfe328f99d');
cy.get('.form-field.to')
.find("input[type='text']")
.clear()
.type('0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01')
.should('have.value', '0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01');
cy.get('.form-field.value').find('input[type="number"]').type(`${transferValue}`);
cy.get('.form-field.from').find('.dropdown').click().find('.dropdown__option').eq(0).click();
cy.get('.form-field.to').find('.dropdown').click().find('.dropdown__option').eq(2).click();
cy.get('.form-field.value').find('input[type="number"]').eq(0).type(`${transferValue}`);
assertCall();
selectMessage('balanceOf', 1);
cy.get('.form-field.owner')
.find("input[type='text']")
.clear()
.type('0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01')
.should('have.value', '0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01');
cy.get('.form-field.owner').find('.dropdown').click().find('.dropdown__option').eq(2).click();
assertReturnValue('balanceOf', `${transferValue}`);
});
});
6 changes: 2 additions & 4 deletions cypress/e2e/contracts/mother.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
assertMoveToStep3,
assertContractRedirect,
assertInstantiate,
selectAccount,
} from '../../support/util';

describe('Mother Contract ', () => {
Expand Down Expand Up @@ -76,12 +77,9 @@ describe('Mother Contract ', () => {
.within(() => {
cy.get('[data-cy="switch-button"]').click();
cy.contains('0: H160').should('be.visible');
cy.get("input[type='text']")
.clear()
.type('0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01')
.should('have.value', '0x41dccbd49b26c50d34355ed86ff0fa9e489d1e01');
cy.contains('1: u128').should('be.visible');
cy.get("input[type='number']").should('have.lengthOf', 1).type('99999');
selectAccount('bob', 2);
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion cypress/fixtures/erc20.contract

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/lib/callOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function transformUserInput(
messageArgs: AbiParam[],
values?: Record<string, unknown>,
): unknown[] {
return messageArgs.map(({ name, type: { type } }) => {
return messageArgs.map(({ name, type: { type, displayName } }) => {
const value = values ? values[name] : null;

if (type === 'Balance') {
Expand All @@ -67,6 +67,11 @@ export function transformUserInput(
return registry.createType('U256', value);
}

// H160 and Address types need explicit type creation
if (type === 'H160' || type === 'Address' || displayName?.includes('Address')) {
return registry.createType('H160', value);
}

return value;
});
}
36 changes: 27 additions & 9 deletions src/lib/initValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,52 @@ import type { Registry, TypeDef } from '@polkadot/types/types';

import { getTypeDef } from '@polkadot/types';
import { TypeDefInfo } from '@polkadot/types/types';
import { decodeAddress } from '@polkadot/keyring';
import { BN_ZERO, isBn } from './bn';
import { Account } from 'types';
import { toEthAddress } from './address';
import type { InkVersion } from 'ui/contexts/VersionContext';

const warnList: string[] = [];

export function getInitValue(registry: Registry, accounts: Account[], def: TypeDef): unknown {
export function getInitValue(
registry: Registry,
accounts: Account[],
def: TypeDef,
version?: InkVersion,
): unknown {
if (def.info === TypeDefInfo.Si) {
const lookupTypeDef = registry.lookup.getTypeDef(def.lookupIndex as number);

return getInitValue(registry, accounts, lookupTypeDef);
return getInitValue(registry, accounts, lookupTypeDef, version);
} else if (def.info === TypeDefInfo.Option) {
return null;
} else if (def.info === TypeDefInfo.Vec) {
return [getInitValue(registry, accounts, def.sub as TypeDef)];
return [getInitValue(registry, accounts, def.sub as TypeDef, version)];
} else if (def.info === TypeDefInfo.VecFixed) {
const value = [];
const length = def.length as number;

for (let i = 0; i < length; i++) {
value.push(getInitValue(registry, accounts, def.sub as TypeDef));
value.push(getInitValue(registry, accounts, def.sub as TypeDef, version));
}

return value;
} else if (def.info === TypeDefInfo.Tuple) {
return Array.isArray(def.sub) ? def.sub.map(def => getInitValue(registry, accounts, def)) : [];
return Array.isArray(def.sub)
? def.sub.map(def => getInitValue(registry, accounts, def, version))
: [];
} else if (def.info === TypeDefInfo.Struct) {
return Array.isArray(def.sub)
? def.sub.reduce((result: Record<string, unknown>, def): Record<string, unknown> => {
result[def.name as string] = getInitValue(registry, accounts, def);
result[def.name as string] = getInitValue(registry, accounts, def, version);

return result;
}, {})
: {};
} else if (def.info === TypeDefInfo.Enum) {
return Array.isArray(def.sub)
? { [def.sub[0].name as string]: getInitValue(registry, accounts, def.sub[0]) }
? { [def.sub[0].name as string]: getInitValue(registry, accounts, def.sub[0], version) }
: {};
}

Expand Down Expand Up @@ -117,8 +127,16 @@ export function getInitValue(registry: Registry, accounts: Account[], def: TypeD
return '';
}

case 'AccountIdOf':
case 'Address':
try {
const address = accounts[0].address;
// ink v6 uses H160 (Ethereum-style) addresses
return version === 'v6' ? toEthAddress(decodeAddress(address)) : address;
} catch (e) {
return '';
}

case 'AccountIdOf':
case 'Call':
case 'CandidateReceipt':
case 'Digest':
Expand Down Expand Up @@ -151,7 +169,7 @@ export function getInitValue(registry: Registry, accounts: Account[], def: TypeD
} else if ([TypeDefInfo.Struct].includes(raw.info)) {
return undefined;
} else if ([TypeDefInfo.Enum, TypeDefInfo.Tuple].includes(raw.info)) {
return getInitValue(registry, accounts, raw);
return getInitValue(registry, accounts, raw, version);
}
} catch (e) {
error = (e as Error).message;
Expand Down
7 changes: 4 additions & 3 deletions src/ui/components/account/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Dropdown } from '../common/Dropdown';
import { Account } from './Account';
import { createAccountOptions } from 'ui/util/dropdown';
import type { DropdownOption, DropdownProps, ValidFormField } from 'types';
import { useApi, useDatabase } from 'ui/contexts';
import { useApi, useDatabase, useVersion } from 'ui/contexts';
import { classes } from 'lib/util';
import { useDbQuery } from 'ui/hooks';

Expand Down Expand Up @@ -52,6 +52,7 @@ export function AccountSelect({ placeholder = 'Select account', ...props }: Prop
export function AddressSelect({ placeholder = 'Select account', onChange, ...props }: Props) {
const { accounts } = useApi();
const { db } = useDatabase();
const { version } = useVersion();
const [contracts] = useDbQuery(() => db.contracts.toArray(), [db]);
const [recent, setRecent] = useState<DropdownOption<string>[]>([]);

Expand All @@ -63,7 +64,7 @@ export function AddressSelect({ placeholder = 'Select account', onChange, ...pro
},
{
label: 'My Accounts',
options: createAccountOptions(accounts || []),
options: createAccountOptions(accounts || [], version),
},
{
label: 'Uploaded Contracts',
Expand All @@ -73,7 +74,7 @@ export function AddressSelect({ placeholder = 'Select account', onChange, ...pro
})),
},
];
}, [accounts, contracts, recent]);
}, [accounts, contracts, recent, version]);

const handleCreate = (inputValue: string) => {
setRecent([...recent, { label: inputValue, value: inputValue }]);
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/form/InputBn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function getMinMax(type: string): [bigint, bigint] {
}

export function InputBn({ onChange, typeDef: { type } }: Props): React.ReactElement {
const [displayValue, setDisplayValue] = useState('0');
const [displayValue, setDisplayValue] = useState('');
const [min, max] = getMinMax(type);

const handleChange = useCallback(
Expand Down
1 change: 1 addition & 0 deletions src/ui/components/form/findComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export function findComponent(
switch (type.type) {
case 'AccountId':
case 'Address':
case 'H160':
return AddressSelect;

case 'Balance':
Expand Down
35 changes: 27 additions & 8 deletions src/ui/hooks/useArgValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@
// SPDX-License-Identifier: GPL-3.0-only

import { useEffect, useMemo, useRef, useState } from 'react';
import { useApi } from 'ui/contexts/ApiContext';
import { useApi, useVersion } from 'ui/contexts';
import { AbiMessage, AbiParam, Account, Registry, SetState } from 'types';
import { getInitValue } from 'lib/initValue';
import { transformUserInput } from 'lib/callOptions';
import type { InkVersion } from 'ui/contexts/VersionContext';

type ArgValues = Record<string, unknown>;

function fromArgs(registry: Registry, accounts: Account[], args: AbiParam[]): ArgValues {
function fromArgs(
registry: Registry,
accounts: Account[],
args: AbiParam[],
version?: InkVersion,
): ArgValues {
const result: ArgValues = {};

args?.forEach(({ name, type }) => {
result[name] = getInitValue(registry, accounts, type);
result[name] = getInitValue(registry, accounts, type, version);
});

return result;
Expand All @@ -24,27 +30,40 @@ export function useArgValues(
registry: Registry,
): [ArgValues, SetState<ArgValues>, Uint8Array | undefined] {
const { accounts } = useApi();
const { version } = useVersion();
const [value, setValue] = useState<ArgValues>(
accounts && message ? fromArgs(registry, accounts, message.args) : {},
accounts && message ? fromArgs(registry, accounts, message.args, version) : {},
);
const argsRef = useRef(message?.args ?? []);

const inputData = useMemo(() => {
let data: Uint8Array | undefined;
try {
data = message?.toU8a(transformUserInput(registry, message.args, value));
if (version === 'v6' && message) {
const patchedArgs = message.args.map(arg => {
if (arg.type.type === 'Address') {
return { ...arg, type: { ...arg.type, type: 'H160' } };
}
return arg;
});

const patchedMessage = { ...message, args: patchedArgs };
data = patchedMessage.toU8a(transformUserInput(registry, patchedArgs, value));
} else {
data = message?.toU8a(transformUserInput(registry, message.args, value));
}
} catch (e) {
console.error(e);
}
return data;
}, [value, registry, message]);
}, [value, registry, message, version]);

useEffect((): void => {
if (accounts && message && argsRef.current !== message.args) {
setValue(fromArgs(registry, accounts, message.args));
setValue(fromArgs(registry, accounts, message.args, version));
argsRef.current = message.args;
}
}, [accounts, message, registry]);
}, [accounts, message, registry, version]);

return [value, setValue, inputData];
}
18 changes: 16 additions & 2 deletions src/ui/hooks/useStoredContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import { useLiveQuery } from 'dexie-react-hooks';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import { useApi, useDatabase } from 'ui/contexts';
import { useApi, useDatabase, useVersion } from 'ui/contexts';
import { ContractDocument, ContractPromise, UIContract } from 'types';

export function useStoredContract(address: string): UIContract | undefined {
const navigate = useNavigate();
const { api } = useApi();
const { db } = useDatabase();
const { version } = useVersion();
const [contract, setContract] = useState<ContractPromise>();
const [document, setDocument] = useState<ContractDocument>();

Expand All @@ -24,10 +25,23 @@ export function useStoredContract(address: string): UIContract | undefined {
navigate('/');
} else {
const c = new ContractPromise(api, d.abi, address);

// TODO: Temporary workaround: ink v6 uses Address as a type alias for H160, but polkadot.js
// encodes them differently. Patch the ABI to use H160 for consistent encoding.
if (version === 'v6') {
c.abi.messages.forEach(message => {
message.args.forEach(arg => {
if (arg.type.type === 'Address') {
arg.type.type = 'H160';
}
});
});
}

setDocument(d);
setContract(c);
}
}, [address]);
}, [address, version]);

if (!document || !contract) return undefined;

Expand Down
Loading