Skip to content

Commit

Permalink
Merge pull request #55 from 0xProject/feat/abi-encoder-nested-structs
Browse files Browse the repository at this point in the history
Add support for nested user-defined struct types
  • Loading branch information
moodlezoup authored Jan 19, 2022
2 parents 8be1949 + 5a12743 commit 622877c
Show file tree
Hide file tree
Showing 29 changed files with 122 additions and 76 deletions.
2 changes: 1 addition & 1 deletion abi-gen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build:ci": "yarn build",
"test": "run-p run_mocha && yarn test_cli",
"test:circleci": "yarn test:coverage && yarn test_cli",
"run_mocha": "(uname -s | grep -q Darwin && echo 'HACK! skipping mocha run due to https://github.com/0xProject/tools/issues/2000') || mocha --require source-map-support/register --require make-promises-safe lib/test/*_test.js --timeout 100000 --bail --exit",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/*_test.js --timeout 100000 --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"test_cli": "run-p test_cli:test_typescript diff_contract_wrappers test_cli:lint",
Expand Down
9 changes: 3 additions & 6 deletions abi-gen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,9 @@ function registerTypeScriptHelpers(): void {
});

// Check if 0 or false exists
Handlebars.registerHelper(
'isDefined',
(context: any): boolean => {
return context !== undefined;
},
);
Handlebars.registerHelper('isDefined', (context: any): boolean => {
return context !== undefined;
});

// Format docstring for method description
Handlebars.registerHelper(
Expand Down
4 changes: 1 addition & 3 deletions abi-gen/src/python_handlebars_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,7 @@ export function registerPythonHelpers(): void {
for (const pythonTupleName of tuplesToDeclare) {
if (tupleBodies[pythonTupleName]) {
tupleDeclarations.push(
`class ${pythonTupleName}(TypedDict):\n """Python representation of a tuple or struct.\n\n Solidity compiler output does not include the names of structs that appear\n in method definitions. A tuple found in an ABI may have been written in\n Solidity as a literal, anonymous tuple, or it may have been written as a\n named \`struct\`:code:, but there is no way to tell from the compiler\n output. This class represents a tuple that appeared in a method\n definition. Its name is derived from a hash of that tuple's field names,\n and every method whose ABI refers to a tuple with that same list of field\n names will have a generated wrapper method that refers to this class.\n\n Any members of type \`bytes\`:code: should be encoded as UTF-8, which can be\n accomplished via \`str.encode("utf_8")\`:code:\n """${
tupleBodies[pythonTupleName]
}`,
`class ${pythonTupleName}(TypedDict):\n """Python representation of a tuple or struct.\n\n Solidity compiler output does not include the names of structs that appear\n in method definitions. A tuple found in an ABI may have been written in\n Solidity as a literal, anonymous tuple, or it may have been written as a\n named \`struct\`:code:, but there is no way to tell from the compiler\n output. This class represents a tuple that appeared in a method\n definition. Its name is derived from a hash of that tuple's field names,\n and every method whose ABI refers to a tuple with that same list of field\n names will have a generated wrapper method that refers to this class.\n\n Any members of type \`bytes\`:code: should be encoded as UTF-8, which can be\n accomplished via \`str.encode("utf_8")\`:code:\n """${tupleBodies[pythonTupleName]}`,
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions abi-gen/test/utils_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ describe('isOutputFileUpToDate()', () => {
});
});

describe('with an existing output file', () => {
// https://github.com/0xProject/0x-monorepo/issues/2000
describe.skip('with an existing output file', () => {
let outputFile: string;
before(() => {
outputFile = tmp.fileSync(
Expand All @@ -73,7 +74,6 @@ describe('isOutputFileUpToDate()', () => {
it('should return true when output file is newer than abi file', async () => {
expect(utils.isOutputFileUpToDate(outputFile, [abiFile])).to.be.true();
});

it('should return false when output file exists but is older than abi file', () => {
const outFileModTimeMs = fs.statSync(outputFile).mtimeMs;
const abiFileModTimeMs = outFileModTimeMs + 1;
Expand Down
5 changes: 4 additions & 1 deletion assert/test/assert_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ describe('Assertions', () => {
);
});
it('should throw for invalid input', () => {
const invalidInputs = [['hello', 'goodbye'], ['goodbye', 42, false, false]];
const invalidInputs = [
['hello', 'goodbye'],
['goodbye', 42, false, false],
];
invalidInputs.forEach(input =>
expect(assert.hasAtMostOneUniqueValue.bind(assert, input as any, errorMsg)).to.throw(),
);
Expand Down
4 changes: 1 addition & 3 deletions base-contract/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,7 @@ export class BaseContract {
const decoded = rawDecoded[i];
if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) {
throw new Error(
`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${
params.types[i]
}. (Possible type overflow or other encoding error)`,
`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${params.types[i]}. (Possible type overflow or other encoding error)`,
);
}
}
Expand Down
4 changes: 1 addition & 3 deletions base-contract/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ export function linkLibrariesInBytecode(
const libraryAddress = libraryAddresses[libraryName];
if (!libraryAddress) {
throw new Error(
`${
artifact.contractName
} has an unlinked reference library ${libraryName} but no addresses was provided'.`,
`${artifact.contractName} has an unlinked reference library ${libraryName} but no addresses was provided'.`,
);
}
for (const ref of libraryRefs) {
Expand Down
5 changes: 4 additions & 1 deletion base-contract/test/utils_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ describe('Utils tests', () => {
};
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const val = [
[{ to: ZERO_ADDRESS, amount: new BigNumber(1) }, { to: ZERO_ADDRESS, amount: new BigNumber(2) }],
[
{ to: ZERO_ADDRESS, amount: new BigNumber(1) },
{ to: ZERO_ADDRESS, amount: new BigNumber(2) },
],
];
const formatted = formatABIDataItem(abi, val, (type: string, value: any) => {
calls.push({ type, value });
Expand Down
8 changes: 2 additions & 6 deletions monorepo-scripts/src/prepublish_checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ async function checkCurrentVersionMatchesLatestPublishedNPMPackageAsync(
utils.log(`Found version mismatches between package.json and NPM published versions (might be unpublished).`);
_.each(versionMismatches, versionMismatch => {
utils.log(
`${versionMismatch.packageName}: ${versionMismatch.packageJsonVersion} package.json, ${
versionMismatch.npmVersion
} on NPM`,
`${versionMismatch.packageName}: ${versionMismatch.packageJsonVersion} package.json, ${versionMismatch.npmVersion} on NPM`,
);
});
throw new Error(`Please fix the above package.json/NPM inconsistencies.`);
Expand Down Expand Up @@ -114,9 +112,7 @@ async function checkChangelogFormatAsync(updatedPublicPackages: Package[]): Prom
utils.log(`CHANGELOG versions cannot below package.json versions:`);
_.each(changeLogInconsistencies, inconsistency => {
utils.log(
`${inconsistency.packageName}: ${inconsistency.packageJsonVersion} package.json, ${
inconsistency.changelogVersion
} CHANGELOG.json`,
`${inconsistency.packageName}: ${inconsistency.packageJsonVersion} package.json, ${inconsistency.changelogVersion} CHANGELOG.json`,
);
});
throw new Error('Fix the above inconsistencies to continue.');
Expand Down
4 changes: 1 addition & 3 deletions monorepo-scripts/src/utils/changelog_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ export const changelogUtils = {
const lastEntry = changelog[0];
if (semver.lt(lastEntry.version, currentVersion)) {
throw new Error(
`Found CHANGELOG version lower then current package version. ${packageName} current: ${currentVersion}, Changelog: ${
lastEntry.version
}`,
`Found CHANGELOG version lower then current package version. ${packageName} current: ${currentVersion}, Changelog: ${lastEntry.version}`,
);
}
const isLastEntryCurrentVersion = lastEntry.version === currentVersion;
Expand Down
4 changes: 1 addition & 3 deletions monorepo-scripts/src/utils/doc_generate_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,7 @@ export class DocGenerateUtils {
}
if (!_.startsWith(innerExportPath, './')) {
throw new Error(
`GENERATE_DOCS: WARNING - ${
this._packageName
} is exporting one of ${innerExportItems} from a package which is itself exporting from another\
`GENERATE_DOCS: WARNING - ${this._packageName} is exporting one of ${innerExportItems} from a package which is itself exporting from another\
internal package ${innerExportPath}. To fix this, export the dependency directly from ${innerExportPath}\
instead of the intermediate package.`,
);
Expand Down
12 changes: 3 additions & 9 deletions monorepo-scripts/src/utils/docker_hub_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export const dockerHubUtils = {
});
if (response.status !== HTTP_OK_STATUS) {
throw new Error(
`DockerHub user login failed (status code: ${
response.status
}). Make sure you have environment variables 'DOCKER_USERNAME; and 'DOCKER_PASS' set`,
`DockerHub user login failed (status code: ${response.status}). Make sure you have environment variables 'DOCKER_USERNAME; and 'DOCKER_PASS' set`,
);
}
const respPayload = await response.json();
Expand All @@ -44,9 +42,7 @@ export const dockerHubUtils = {
const respPayload = await response.json();
if (response.status !== HTTP_OK_STATUS || respPayload.count === 0) {
throw new Error(
`Failed to fetch org: ${organization}'s list of repos (status code: ${
response.status
}). Make sure your account has been added to the '${organization}' org on DockerHub`,
`Failed to fetch org: ${organization}'s list of repos (status code: ${response.status}). Make sure your account has been added to the '${organization}' org on DockerHub`,
);
}
},
Expand All @@ -56,9 +52,7 @@ export const dockerHubUtils = {
await execAsync(`echo "$DOCKER_PASS" | docker login -u $DOCKER_USERNAME --password-stdin`);
} catch (err) {
throw new Error(
`Failed to log you into the 'docker' commandline tool. Make sure you have the 'docker' commandline tool installed. Full error: ${
err.message
}`,
`Failed to log you into the 'docker' commandline tool. Make sure you have the 'docker' commandline tool installed. Full error: ${err.message}`,
);
}
},
Expand Down
7 changes: 6 additions & 1 deletion monorepo-scripts/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export const utils = {
},
getTopologicallySortedPackages(rootDir: string): Package[] {
const packages = utils.getPackages(rootDir);
const batchedPackages: PackageJSON[] = _.flatten(batchPackages(_.map(packages, pkg => pkg.packageJson), false));
const batchedPackages: PackageJSON[] = _.flatten(
batchPackages(
_.map(packages, pkg => pkg.packageJson),
false,
),
);
const topsortedPackages: Package[] = _.map(
batchedPackages,
(pkg: PackageJSON) => _.find(packages, pkg1 => pkg1.packageJson.name === pkg.name) as Package,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"lcov-result-merger": "^3.0.0",
"lerna": "^3.0.0-beta.25",
"npm-run-all": "^4.1.5",
"prettier": "~1.16.3",
"prettier": "1.19.1",
"source-map-support": "^0.5.6",
"typescript": "4.2.2",
"wsrun": "^2.2.0"
Expand Down
6 changes: 3 additions & 3 deletions sol-compiler/src/utils/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ export async function getSolcJSReleasesAsync(isOfflineMode: boolean): Promise<Bi
await fsWrapper.removeFileAsync(constants.SOLCJS_RELEASES_PATH);
} else {
// Use the cached file otherwise.
return (solcJSReleasesCache = JSON.parse(((await fsWrapper.readFileAsync(
constants.SOLCJS_RELEASES_PATH,
)) as any) as string));
return (solcJSReleasesCache = JSON.parse(
((await fsWrapper.readFileAsync(constants.SOLCJS_RELEASES_PATH)) as any) as string,
));
}
} catch (err) {
if (err.code !== 'ENOENT') {
Expand Down
5 changes: 4 additions & 1 deletion sol-doc/test/utils/random_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export function randomFunctionKind(): FunctionKind {

export function randomParameters(): ParamDocsMap {
const numParams = _.random(0, 7);
return _.zipObject(_.times(numParams, () => randomWord()), _.times(numParams, idx => randomParameter(idx)));
return _.zipObject(
_.times(numParams, () => randomWord()),
_.times(numParams, idx => randomParameter(idx)),
);
}

export function randomParameter(order: number, fields?: Partial<ParamDocs>): ParamDocs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ export class TruffleArtifactAdapter extends AbstractArtifactAdapter {
const compilerVersion = artifact.compiler.version;
if (!compilerVersion.startsWith(this._solcVersion)) {
throw new Error(
`${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${
this._solcVersion
} making it impossible to process traces`,
`${artifact.contractName} was compiled with solidity ${compilerVersion} but specified version is ${this._solcVersion} making it impossible to process traces`,
);
}
}
Expand Down
4 changes: 1 addition & 3 deletions subproviders/src/subproviders/private_key_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
PrivateKeyWalletSubprovider._validateTxParams(txParams);
if (txParams.from !== undefined && txParams.from.toLowerCase() !== this._address.toLowerCase()) {
throw new Error(
`Requested to sign transaction with address: ${txParams.from}, instantiated with address: ${
this._address
}`,
`Requested to sign transaction with address: ${txParams.from}, instantiated with address: ${this._address}`,
);
}
const tx = createTransactionObject(this._common, txParams).sign(this._privateKeyBuffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,7 @@ describe('PrivateKeyWalletSubprovider', () => {
const callback = reportCallbackErrors(done)((err: Error, _response: JSONRPCResponsePayload) => {
expect(err).to.not.be.a('null');
expect(err.message).to.be.equal(
`Requested to sign message with address: ${
fixtureData.TEST_RPC_ACCOUNT_1
}, instantiated with address: ${fixtureData.TEST_RPC_ACCOUNT_0}`,
`Requested to sign message with address: ${fixtureData.TEST_RPC_ACCOUNT_1}, instantiated with address: ${fixtureData.TEST_RPC_ACCOUNT_0}`,
);
done();
});
Expand Down
13 changes: 13 additions & 0 deletions utils/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
[
{
"version": "6.5.0",
"changes": [
{
"note": "Add support for nested user-defined types in AbiEncoder.create",
"pr": 55
},
{
"note": "Fix providerUtils.standardizeOrThrow for web3.js",
"pr": 57
}
]
},
{
"version": "6.4.4",
"changes": [
Expand Down
4 changes: 1 addition & 3 deletions utils/src/abi_encoder/abstract_data_types/types/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ export abstract class AbstractSetDataType extends DataType {
// Sanity check
if (dataItem.components === undefined) {
throw new Error(
`Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${
dataItem.name
}'.`,
`Tried to create a set using key/value pairs, but no components were defined by the input DataItem '${dataItem.name}'.`,
);
}
// Create one member for each component of `dataItem`
Expand Down
29 changes: 27 additions & 2 deletions utils/src/abi_encoder/evm_data_type_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,33 @@ export class EvmDataTypeFactory implements DataTypeFactory {
* @param input A single or set of DataItem or a signature for an EVM data type.
* @return DataType corresponding to input.
*/
export function create(input: DataItem | DataItem[] | string): DataType {
const dataItem = consolidateDataItemsIntoSingle(input);
export function create(input: DataItem | DataItem[] | string, nestedDataItems?: DataItem[]): DataType {
let dataItem = consolidateDataItemsIntoSingle(input);

if (nestedDataItems) {
const nestedTypes = _.keyBy(nestedDataItems, 'internalType');

const replaceTypes = (_dataItem: DataItem) => {
const aliasedType = _dataItem.type;
if (Array.matchType(aliasedType)) {
const [elementType, arrayLength] = Array.decodeElementTypeAndLengthFromType(aliasedType);
if (elementType in nestedTypes) {
_dataItem.type = `${nestedTypes[elementType].type}[${arrayLength ?? ''}]`;
_dataItem.components = nestedTypes[elementType].components;
}
} else if (aliasedType in nestedTypes) {
_dataItem.type = nestedTypes[aliasedType].type;
_dataItem.components = nestedTypes[aliasedType].components;
}
if (_dataItem.components) {
_dataItem.components.map(replaceTypes);
}
};

dataItem = _.cloneDeep(dataItem);
replaceTypes(dataItem);
}

const dataType = EvmDataTypeFactory.getInstance().create(dataItem);
return dataType;
}
Expand Down
4 changes: 2 additions & 2 deletions utils/src/abi_encoder/evm_data_types/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class ArrayDataType extends AbstractSetDataType {
return ArrayDataType._MATCHER.test(type);
}

private static _decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] {
public static decodeElementTypeAndLengthFromType(type: string): [string, undefined | number] {
const matches = ArrayDataType._MATCHER.exec(type);
if (matches === null || matches.length !== 3) {
throw new Error(`Could not parse array: ${type}`);
Expand All @@ -30,7 +30,7 @@ export class ArrayDataType extends AbstractSetDataType {
public constructor(dataItem: DataItem, dataTypeFactory: DataTypeFactory) {
// Construct parent
const isArray = true;
const [arrayElementType, arrayLength] = ArrayDataType._decodeElementTypeAndLengthFromType(dataItem.type);
const [arrayElementType, arrayLength] = ArrayDataType.decodeElementTypeAndLengthFromType(dataItem.type);
super(dataItem, dataTypeFactory, isArray, arrayLength, arrayElementType);
// Set array properties
this._elementType = arrayElementType;
Expand Down
4 changes: 3 additions & 1 deletion utils/src/provider_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ export const providerUtils = {
} else if ((supportedProvider as any).send !== undefined) {
// HACK(fabio): Detect if the `send` method has the old interface `send(payload, cb)` such
// as in versions < Web3.js@1.0.0-beta.37. If so, do a simple re-mapping
if (_.includes((supportedProvider as any).send.toString().replace(' ', ''), 'function(payload,callback)')) {
if (
_.includes((supportedProvider as any).send.toString().replaceAll(' ', ''), 'function(payload,callback)')
) {
provider.sendAsync = (supportedProvider as any).send.bind(supportedProvider);
return provider;
} else {
Expand Down
Loading

0 comments on commit 622877c

Please sign in to comment.