aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/ethereum-types/src/index.ts2
-rw-r--r--packages/react-docs/src/components/documentation.tsx6
-rw-r--r--packages/react-docs/src/components/interface.tsx4
-rw-r--r--packages/react-docs/src/components/signature.tsx19
-rw-r--r--packages/react-docs/src/components/signature_block.tsx22
-rw-r--r--packages/react-docs/src/components/type.tsx10
-rw-r--r--packages/react-docs/src/components/type_definition.tsx4
-rw-r--r--packages/react-docs/src/docs_info.ts10
-rw-r--r--packages/sol-doc/package.json3
-rw-r--r--packages/sol-doc/src/cli.ts17
-rw-r--r--packages/sol-doc/src/index.ts2
-rw-r--r--packages/sol-doc/src/sol_doc.ts500
-rw-r--r--packages/sol-doc/src/solidity_doc_generator.ts306
-rw-r--r--packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol18
-rw-r--r--packages/sol-doc/test/solidity_doc_generator_test.ts50
-rw-r--r--packages/types/src/index.ts1
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx2
-rw-r--r--packages/website/ts/containers/smart_contracts_documentation.ts60
-rw-r--r--packages/website/ts/pages/documentation/doc_page.tsx6
19 files changed, 674 insertions, 368 deletions
diff --git a/packages/ethereum-types/src/index.ts b/packages/ethereum-types/src/index.ts
index 916661638..2f3140a58 100644
--- a/packages/ethereum-types/src/index.ts
+++ b/packages/ethereum-types/src/index.ts
@@ -497,4 +497,4 @@ export interface CompilerOptions {
compilerSettings?: CompilerSettings;
contracts?: string[] | '*';
solcVersion?: string;
-}
+} // tslint:disable:max-file-line-count
diff --git a/packages/react-docs/src/components/documentation.tsx b/packages/react-docs/src/components/documentation.tsx
index 3cd14923c..a23111297 100644
--- a/packages/react-docs/src/components/documentation.tsx
+++ b/packages/react-docs/src/components/documentation.tsx
@@ -218,11 +218,11 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
_.isEmpty(docSection.events);
const sortedTypes = _.sortBy(docSection.types, 'name');
- const typeDefs = _.map(sortedTypes, customType => {
+ const typeDefs = _.map(sortedTypes, (customType, i) => {
return (
<TypeDefinition
sectionName={sectionName}
- key={`type-${customType.name}`}
+ key={`type-${customType.name}-${i}`}
customType={customType}
docsInfo={this.props.docsInfo}
typeDefinitionByName={typeDefinitionByName}
@@ -353,7 +353,7 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
key={`badge-${networkName}-${sectionName}`}
href={linkIfExists}
target="_blank"
- style={{ color: colors.white, textDecoration: 'none' }}
+ style={{ color: colors.white, textDecoration: 'none', marginTop: 8 }}
>
<Badge title={networkName} backgroundColor={networkNameToColor[networkName]} />
</a>
diff --git a/packages/react-docs/src/components/interface.tsx b/packages/react-docs/src/components/interface.tsx
index 9f0800d71..cad7d6c46 100644
--- a/packages/react-docs/src/components/interface.tsx
+++ b/packages/react-docs/src/components/interface.tsx
@@ -20,9 +20,9 @@ export interface InterfaceProps {
export const Interface: React.SFC<InterfaceProps> = (props: InterfaceProps): any => {
const type = props.type;
- const properties = _.map(type.children, property => {
+ const properties = _.map(type.children, (property, i) => {
return (
- <span key={`property-${property.name}-${property.type}-${type.name}`}>
+ <span key={`property-${property.name}-${property.type}-${type.name}-${i}`}>
{property.name}:{' '}
{property.type && !_.isUndefined(property.type.method) ? (
<Signature
diff --git a/packages/react-docs/src/components/signature.tsx b/packages/react-docs/src/components/signature.tsx
index a690a1f03..1f3dd0ee8 100644
--- a/packages/react-docs/src/components/signature.tsx
+++ b/packages/react-docs/src/components/signature.tsx
@@ -19,12 +19,14 @@ export interface SignatureProps {
callPath?: string;
docsInfo: DocsInfo;
isInPopover: boolean;
+ isFallback?: boolean;
}
const defaultProps = {
shouldHideMethodName: false,
shouldUseArrowSyntax: false,
callPath: '',
+ isFallback: false,
};
export const Signature: React.SFC<SignatureProps> = (props: SignatureProps) => {
@@ -34,6 +36,7 @@ export const Signature: React.SFC<SignatureProps> = (props: SignatureProps) => {
props.docsInfo,
sectionName,
props.isInPopover,
+ props.name,
props.typeDefinitionByName,
);
const paramStringArray: any[] = [];
@@ -75,7 +78,7 @@ export const Signature: React.SFC<SignatureProps> = (props: SignatureProps) => {
return (
<span style={{ fontSize: 15 }}>
{props.callPath}
- {methodName}
+ {props.isFallback ? '' : methodName}
{typeParameterIfExists}({hasMoreThenTwoParams && <br />}
{paramStringArray})
{props.returnType && (
@@ -101,9 +104,10 @@ function renderParameters(
docsInfo: DocsInfo,
sectionName: string,
isInPopover: boolean,
+ name: string,
typeDefinitionByName?: TypeDefinitionByName,
): React.ReactNode[] {
- const params = _.map(parameters, (p: Parameter) => {
+ const params = _.map(parameters, (p: Parameter, i: number) => {
const isOptional = p.isOptional;
const hasDefaultValue = !_.isUndefined(p.defaultValue);
const type = (
@@ -116,9 +120,14 @@ function renderParameters(
/>
);
return (
- <span key={`param-${p.type}-${p.name}`}>
- {p.name}
- {isOptional && '?'}: {type}
+ <span key={`param-${JSON.stringify(p.type)}-${name}-${i}`}>
+ {!_.isEmpty(p.name) && (
+ <span>
+ {p.name}
+ {isOptional && '?'}:{' '}
+ </span>
+ )}
+ {type}
{hasDefaultValue && ` = ${p.defaultValue}`}
</span>
);
diff --git a/packages/react-docs/src/components/signature_block.tsx b/packages/react-docs/src/components/signature_block.tsx
index 1ea0ea28c..5ec82983a 100644
--- a/packages/react-docs/src/components/signature_block.tsx
+++ b/packages/react-docs/src/components/signature_block.tsx
@@ -32,7 +32,6 @@ export interface SignatureBlockState {
const styles: Styles = {
chip: {
fontSize: 13,
- backgroundColor: colors.lightBlueA700,
color: colors.white,
height: 11,
borderRadius: 14,
@@ -50,6 +49,8 @@ export class SignatureBlock extends React.Component<SignatureBlockProps, Signatu
public render(): React.ReactNode {
const method = this.props.method;
+ const isFallback = (method as SolidityMethod).isFallback;
+ const hasExclusivelyNamedParams = !_.isUndefined(_.find(method.parameters, p => !_.isEmpty(p.name)));
return (
<div
id={`${this.props.sectionName}-${method.name}`}
@@ -63,10 +64,11 @@ export class SignatureBlock extends React.Component<SignatureBlockProps, Signatu
{(method as TypescriptMethod).isStatic && this._renderChip('Static')}
{(method as SolidityMethod).isConstant && this._renderChip('Constant')}
{(method as SolidityMethod).isPayable && this._renderChip('Payable')}
+ {isFallback && this._renderChip('Fallback', colors.lightGreenA700)}
<div style={{ lineHeight: 1.3 }}>
<AnchorTitle
headerSize={HeaderSizes.H3}
- title={method.name}
+ title={isFallback ? '' : method.name}
id={`${this.props.sectionName}-${method.name}`}
shouldShowAnchor={this.state.shouldShowAnchor}
/>
@@ -84,6 +86,7 @@ export class SignatureBlock extends React.Component<SignatureBlockProps, Signatu
typeDefinitionByName={this.props.typeDefinitionByName}
docsInfo={this.props.docsInfo}
isInPopover={false}
+ isFallback={isFallback}
/>
</code>
{(method as TypescriptMethod).source && (
@@ -95,12 +98,13 @@ export class SignatureBlock extends React.Component<SignatureBlockProps, Signatu
)}
{method.comment && <Comment comment={method.comment} className="py2" />}
{method.parameters &&
- !_.isEmpty(method.parameters) && (
+ !_.isEmpty(method.parameters) &&
+ hasExclusivelyNamedParams && (
<div>
<h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}>
ARGUMENTS
</h4>
- {this._renderParameterDescriptions(method.parameters)}
+ {this._renderParameterDescriptions(method.parameters, method.name)}
</div>
)}
{method.returnComment && (
@@ -114,19 +118,19 @@ export class SignatureBlock extends React.Component<SignatureBlockProps, Signatu
</div>
);
}
- private _renderChip(text: string): React.ReactNode {
+ private _renderChip(text: string, backgroundColor: string = colors.lightBlueA700): React.ReactNode {
return (
- <div className="p1 mr1" style={styles.chip}>
+ <div className="p1 mr1" style={{ ...styles.chip, backgroundColor }}>
{text}
</div>
);
}
- private _renderParameterDescriptions(parameters: Parameter[]): React.ReactNode {
- const descriptions = _.map(parameters, parameter => {
+ private _renderParameterDescriptions(parameters: Parameter[], name: string): React.ReactNode {
+ const descriptions = _.map(parameters, (parameter: Parameter, i: number) => {
const isOptional = parameter.isOptional;
return (
<div
- key={`param-description-${parameter.name}`}
+ key={`param-description-${parameter.name}-${name}-${i}`}
className="flex pb1 mb2"
style={{ borderBottom: '1px solid #f0f4f7' }}
>
diff --git a/packages/react-docs/src/components/type.tsx b/packages/react-docs/src/components/type.tsx
index 156a3496d..5c018f5dd 100644
--- a/packages/react-docs/src/components/type.tsx
+++ b/packages/react-docs/src/components/type.tsx
@@ -7,12 +7,12 @@ import { Link as ScrollLink } from 'react-scroll';
import * as ReactTooltip from 'react-tooltip';
import { DocsInfo } from '../docs_info';
-import { constants } from '../utils/constants';
import { Signature } from './signature';
import { TypeDefinition } from './type_definition';
const basicJsTypes = ['string', 'number', 'undefined', 'null', 'boolean'];
+const basicSolidityTypes = ['bytes', 'bytes4', 'bytes32', 'uint8', 'uint256', 'address'];
const defaultProps = {};
@@ -80,7 +80,7 @@ export const Type: React.SFC<TypeProps> = (props: TypeProps): any => {
case TypeDocTypes.Array:
typeName = type.elementType.name;
- if (_.includes(basicJsTypes, typeName)) {
+ if (_.includes(basicJsTypes, typeName) || _.includes(basicSolidityTypes, typeName)) {
typeNameColor = colors.orange;
}
break;
@@ -168,10 +168,10 @@ export const Type: React.SFC<TypeProps> = (props: TypeProps): any => {
break;
case TypeDocTypes.Tuple:
- const tupleTypes = _.map(type.tupleElements, t => {
+ const tupleTypes = _.map(type.tupleElements, (t, i) => {
return (
<Type
- key={`type-tuple-${t.name}-${t.typeDocType}`}
+ key={`type-tuple-${t.name}-${t.typeDocType}-${i}`}
type={t}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
@@ -221,7 +221,7 @@ export const Type: React.SFC<TypeProps> = (props: TypeProps): any => {
const id = Math.random().toString();
const typeDefinitionAnchorId = isExportedClassReference
? props.type.name
- : `${constants.TYPES_SECTION_NAME}-${typeName}`;
+ : `${props.docsInfo.typeSectionName}-${typeName}`;
typeName = (
<ScrollLink
to={typeDefinitionAnchorId}
diff --git a/packages/react-docs/src/components/type_definition.tsx b/packages/react-docs/src/components/type_definition.tsx
index 09cb3ff74..9a3e50a1b 100644
--- a/packages/react-docs/src/components/type_definition.tsx
+++ b/packages/react-docs/src/components/type_definition.tsx
@@ -5,7 +5,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import { DocsInfo } from '../docs_info';
-import { KindString } from '../types';
+import { KindString, SupportedDocJson } from '../types';
import { constants } from '../utils/constants';
import { Comment } from './comment';
@@ -46,7 +46,7 @@ export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDef
let codeSnippet: React.ReactNode;
switch (customType.kindString) {
case KindString.Interface:
- typePrefix = 'Interface';
+ typePrefix = this.props.docsInfo.type === SupportedDocJson.SolDoc ? 'Struct' : 'Interface';
codeSnippet = (
<Interface
type={customType}
diff --git a/packages/react-docs/src/docs_info.ts b/packages/react-docs/src/docs_info.ts
index 6355a2f88..092a8c266 100644
--- a/packages/react-docs/src/docs_info.ts
+++ b/packages/react-docs/src/docs_info.ts
@@ -18,6 +18,7 @@ export class DocsInfo {
public packageName: string;
public packageUrl: string;
public menu: DocsMenu;
+ public typeSectionName: string;
public sections: SectionsMap;
public sectionNameToMarkdownByVersion: SectionNameToMarkdownByVersion;
public contractsByVersionByNetworkId?: ContractsByVersionByNetworkId;
@@ -28,6 +29,7 @@ export class DocsInfo {
this.displayName = config.displayName;
this.packageName = config.packageName;
this.packageUrl = config.packageUrl;
+ this.typeSectionName = config.type === SupportedDocJson.SolDoc ? 'structs' : 'types';
this.sections = config.markdownSections;
this.sectionNameToMarkdownByVersion = config.sectionNameToMarkdownByVersion;
this.contractsByVersionByNetworkId = config.contractsByVersionByNetworkId;
@@ -53,7 +55,7 @@ export class DocsInfo {
_.isEmpty(docSection.properties) &&
_.isEmpty(docSection.events);
- if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) {
+ if (sectionName === this.typeSectionName) {
const sortedTypesNames = _.sortBy(docSection.types, 'name');
const typeNames = _.map(sortedTypesNames, t => t.name);
menuSubsectionsBySection[sectionName] = typeNames;
@@ -82,12 +84,12 @@ export class DocsInfo {
return menuSubsectionsBySection;
}
public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat): { [name: string]: TypeDefinitionByName } {
- if (_.isUndefined(this.sections.types)) {
+ if (_.isUndefined(docAgnosticFormat[this.typeSectionName])) {
return {};
}
- const typeDocSection = docAgnosticFormat[this.sections.types];
- const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name') as any;
+ const section = docAgnosticFormat[this.typeSectionName];
+ const typeDefinitionByName = _.keyBy(section.types, 'name') as any;
return typeDefinitionByName;
}
}
diff --git a/packages/sol-doc/package.json b/packages/sol-doc/package.json
index 2f8525ff4..2de4443d6 100644
--- a/packages/sol-doc/package.json
+++ b/packages/sol-doc/package.json
@@ -13,7 +13,7 @@
"lint": "tslint --project . --format stylish",
"clean": "shx rm -rf lib",
"generate-v1-protocol-docs": "(cd ../contracts/src/1.0.0; node ../../../../node_modules/.bin/sol-doc --contracts-dir . --contracts Exchange/Exchange_v1.sol TokenRegistry/TokenRegistry.sol TokenTransferProxy/TokenTransferProxy_v1.sol) > v1.0.0.json",
- "generate-v2-protocol-docs": "(cd ../contracts/src/2.0.0; node ../../../../node_modules/.bin/sol-doc --contracts-dir . --contracts $(cd protocol; ls -C1 */*.sol */interfaces/*.sol) ) > v2.0.0.json",
+ "generate-v2-protocol-docs": "(cd ../contracts/src/2.0.0; node ../../../../node_modules/.bin/sol-doc --contracts-dir . --contracts Exchange/Exchange.sol AssetProxy/ERC20Proxy.sol AssetProxy/ERC721Proxy.sol OrderValidator/OrderValidator.sol Forwarder/Forwarder.sol AssetProxyOwner/AssetProxyOwner.sol) > v2.0.0.json",
"deploy-v2-protocol-docs": "aws --profile 0xproject s3 cp --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json v2.0.0.json s3://staging-doc-jsons/contracts/",
"deploy-v1-protocol-docs": "aws --profile 0xproject s3 cp --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json v1.0.0.json s3://staging-doc-jsons/contracts/"
},
@@ -28,6 +28,7 @@
"@0xproject/types": "^1.1.2",
"@0xproject/utils": "^2.0.0",
"ethereum-types": "^1.0.9",
+ "ethereumjs-util": "^5.1.1",
"lodash": "^4.17.10",
"yargs": "^12.0.2"
},
diff --git a/packages/sol-doc/src/cli.ts b/packages/sol-doc/src/cli.ts
index eb0f00bf6..11e638785 100644
--- a/packages/sol-doc/src/cli.ts
+++ b/packages/sol-doc/src/cli.ts
@@ -3,7 +3,7 @@ import * as yargs from 'yargs';
import { logUtils } from '@0xproject/utils';
-import { generateSolDocAsync } from './solidity_doc_generator';
+import { SolDoc } from './sol_doc';
const JSON_TAB_WIDTH = 4;
@@ -20,7 +20,20 @@ const JSON_TAB_WIDTH = 4;
.demandOption('contracts-dir')
.array('contracts')
.help().argv;
- const doc = await generateSolDocAsync(argv.contractsDir, argv.contracts);
+ // Unfortunately, the only way to currently retrieve the declared structs within Solidity contracts
+ // is to tease them out of the params/return values included in the ABI. These structures do
+ // not include the structs actual name, so we need a mapping to assign the proper name to a
+ // struct. If the name is not in this mapping, the structs name will default to the param/return value
+ // name (which mostly coincide).
+ const customTypeHashToName: { [hash: string]: string } = {
+ '52d4a768701076c7bac06e386e430883975eb398732eccba797fd09dd064a60e': 'Order',
+ '46f7e8c4d144d11a72ce5338458ea37b933500d7a65e740cbca6d16e350eaa48': 'FillResults',
+ c22239cf0d29df1e6cf1be54f21692a8c0b3a48b9367540d4ffff4608b331ce9: 'OrderInfo',
+ c21e9ff31a30941c22e1cb43752114bb467c34dea58947f98966c9030fc8e4a9: 'TraderInfo',
+ '07c2bddc165e0b5005e6244dd4a9771fa61c78c4f42abd687d57567b0768136c': 'MatchedFillResults',
+ };
+ const solDoc = new SolDoc();
+ const doc = await solDoc.generateSolDocAsync(argv.contractsDir, argv.contracts, customTypeHashToName);
process.stdout.write(JSON.stringify(doc, null, JSON_TAB_WIDTH));
})().catch(err => {
logUtils.warn(err);
diff --git a/packages/sol-doc/src/index.ts b/packages/sol-doc/src/index.ts
index 03f3c9de6..521668cc8 100644
--- a/packages/sol-doc/src/index.ts
+++ b/packages/sol-doc/src/index.ts
@@ -1 +1 @@
-export { generateSolDocAsync } from './solidity_doc_generator';
+export { SolDoc } from './sol_doc';
diff --git a/packages/sol-doc/src/sol_doc.ts b/packages/sol-doc/src/sol_doc.ts
new file mode 100644
index 000000000..4b7cbe77c
--- /dev/null
+++ b/packages/sol-doc/src/sol_doc.ts
@@ -0,0 +1,500 @@
+import * as path from 'path';
+
+import {
+ AbiDefinition,
+ ConstructorAbi,
+ DataItem,
+ DevdocOutput,
+ EventAbi,
+ EventParameter,
+ FallbackAbi,
+ MethodAbi,
+ StandardContractOutput,
+} from 'ethereum-types';
+import ethUtil = require('ethereumjs-util');
+import * as _ from 'lodash';
+
+import { Compiler, CompilerOptions } from '@0xproject/sol-compiler';
+import {
+ CustomType,
+ CustomTypeChild,
+ DocAgnosticFormat,
+ DocSection,
+ Event,
+ EventArg,
+ ObjectMap,
+ Parameter,
+ SolidityMethod,
+ Type,
+ TypeDocTypes,
+} from '@0xproject/types';
+
+export class SolDoc {
+ private _customTypeHashToName: ObjectMap<string> | undefined;
+ private static _genEventDoc(abiDefinition: EventAbi): Event {
+ const eventDoc: Event = {
+ name: abiDefinition.name,
+ eventArgs: SolDoc._genEventArgsDoc(abiDefinition.inputs),
+ };
+ return eventDoc;
+ }
+ private static _devdocMethodDetailsIfExist(
+ methodSignature: string,
+ devdocIfExists: DevdocOutput | undefined,
+ ): string | undefined {
+ let details;
+ if (!_.isUndefined(devdocIfExists)) {
+ const devdocMethodsIfExist = devdocIfExists.methods;
+ if (!_.isUndefined(devdocMethodsIfExist)) {
+ const devdocMethodIfExists = devdocMethodsIfExist[methodSignature];
+ if (!_.isUndefined(devdocMethodIfExists)) {
+ const devdocMethodDetailsIfExist = devdocMethodIfExists.details;
+ if (!_.isUndefined(devdocMethodDetailsIfExist)) {
+ details = devdocMethodDetailsIfExist;
+ }
+ }
+ }
+ }
+ return details;
+ }
+ private static _genFallbackDoc(
+ abiDefinition: FallbackAbi,
+ devdocIfExists: DevdocOutput | undefined,
+ ): SolidityMethod {
+ const methodSignature = `()`;
+ const comment = SolDoc._devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
+
+ const returnComment =
+ _.isUndefined(devdocIfExists) || _.isUndefined(devdocIfExists.methods[methodSignature])
+ ? undefined
+ : devdocIfExists.methods[methodSignature].return;
+
+ const methodDoc: SolidityMethod = {
+ isConstructor: false,
+ name: 'fallback',
+ callPath: '',
+ parameters: [],
+ returnType: { name: 'void', typeDocType: TypeDocTypes.Intrinsic },
+ returnComment,
+ isConstant: true,
+ isPayable: abiDefinition.payable,
+ isFallback: true,
+ comment: _.isEmpty(comment)
+ ? 'The fallback function. It is executed on a call to the contract if none of the other functions match the given public identifier (or if no data was supplied at all).'
+ : comment,
+ };
+ return methodDoc;
+ }
+ private static _genEventArgsDoc(args: EventParameter[]): EventArg[] {
+ const eventArgsDoc: EventArg[] = [];
+
+ for (const arg of args) {
+ const name = arg.name;
+
+ const type: Type = {
+ name: arg.type,
+ typeDocType: TypeDocTypes.Intrinsic,
+ };
+
+ const eventArgDoc: EventArg = {
+ isIndexed: arg.indexed,
+ name,
+ type,
+ };
+
+ eventArgsDoc.push(eventArgDoc);
+ }
+ return eventArgsDoc;
+ }
+ private static _dedupStructs(customTypes: CustomType[]): CustomType[] {
+ const uniqueCustomTypes: CustomType[] = [];
+ const seenTypes: { [hash: string]: boolean } = {};
+ _.each(customTypes, customType => {
+ const hash = SolDoc._generateCustomTypeHash(customType);
+ if (!seenTypes[hash]) {
+ uniqueCustomTypes.push(customType);
+ seenTypes[hash] = true;
+ }
+ });
+ return uniqueCustomTypes;
+ }
+ private static _capitalize(text: string): string {
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
+ }
+ private static _generateCustomTypeHash(customType: CustomType): string {
+ const customTypeWithoutName = _.cloneDeep(customType);
+ delete customTypeWithoutName.name;
+ const customTypeWithoutNameStr = JSON.stringify(customTypeWithoutName);
+ const hash = ethUtil.sha256(customTypeWithoutNameStr).toString('hex');
+ return hash;
+ }
+ private static _makeCompilerOptions(contractsDir: string, contractsToCompile?: string[]): CompilerOptions {
+ const compilerOptions: CompilerOptions = {
+ contractsDir,
+ contracts: '*',
+ compilerSettings: {
+ outputSelection: {
+ ['*']: {
+ ['*']: ['abi', 'devdoc'],
+ },
+ },
+ },
+ };
+
+ const shouldOverrideCatchAllContractsConfig =
+ !_.isUndefined(contractsToCompile) && contractsToCompile.length > 0;
+ if (shouldOverrideCatchAllContractsConfig) {
+ compilerOptions.contracts = contractsToCompile;
+ }
+
+ return compilerOptions;
+ }
+ /**
+ * Invoke the Solidity compiler and transform its ABI and devdoc outputs into a
+ * JSON format easily consumed by documentation rendering tools.
+ * @param contractsToDocument list of contracts for which to generate doc objects
+ * @param contractsDir the directory in which to find the `contractsToCompile` as well as their dependencies.
+ * @return doc object for use with documentation generation tools.
+ */
+ public async generateSolDocAsync(
+ contractsDir: string,
+ contractsToDocument?: string[],
+ customTypeHashToName?: ObjectMap<string>,
+ ): Promise<DocAgnosticFormat> {
+ this._customTypeHashToName = customTypeHashToName;
+ const docWithDependencies: DocAgnosticFormat = {};
+ const compilerOptions = SolDoc._makeCompilerOptions(contractsDir, contractsToDocument);
+ const compiler = new Compiler(compilerOptions);
+ const compilerOutputs = await compiler.getCompilerOutputsAsync();
+ let structs: CustomType[] = [];
+ for (const compilerOutput of compilerOutputs) {
+ const contractFileNames = _.keys(compilerOutput.contracts);
+ for (const contractFileName of contractFileNames) {
+ const contractNameToOutput = compilerOutput.contracts[contractFileName];
+
+ const contractNames = _.keys(contractNameToOutput);
+ for (const contractName of contractNames) {
+ const compiledContract = contractNameToOutput[contractName];
+ if (_.isUndefined(compiledContract.abi)) {
+ throw new Error('compiled contract did not contain ABI output');
+ }
+ docWithDependencies[contractName] = this._genDocSection(compiledContract, contractName);
+ structs = [...structs, ...this._extractStructs(compiledContract)];
+ }
+ }
+ }
+ structs = SolDoc._dedupStructs(structs);
+ structs = this._overwriteStructNames(structs);
+
+ let doc: DocAgnosticFormat = {};
+ if (_.isUndefined(contractsToDocument) || contractsToDocument.length === 0) {
+ doc = docWithDependencies;
+ } else {
+ for (const contractToDocument of contractsToDocument) {
+ const contractBasename = path.basename(contractToDocument);
+ const contractName =
+ contractBasename.lastIndexOf('.sol') === -1
+ ? contractBasename
+ : contractBasename.substring(0, contractBasename.lastIndexOf('.sol'));
+ doc[contractName] = docWithDependencies[contractName];
+ }
+ }
+
+ if (structs.length > 0) {
+ doc.structs = {
+ comment: '',
+ constructors: [],
+ methods: [],
+ properties: [],
+ types: structs,
+ functions: [],
+ events: [],
+ };
+ }
+
+ delete this._customTypeHashToName; // Clean up instance state
+ return doc;
+ }
+ private _getCustomTypeFromDataItem(inputOrOutput: DataItem): CustomType {
+ const customType: CustomType = {
+ name: _.capitalize(inputOrOutput.name),
+ kindString: 'Interface',
+ children: [],
+ };
+ _.each(inputOrOutput.components, (component: DataItem) => {
+ const childType = this._getTypeFromDataItem(component);
+ const customTypeChild = {
+ name: component.name,
+ type: childType,
+ };
+ // (fabio): Not sure why this type casting is necessary. Seems TS doesn't
+ // deduce that `customType.children` cannot be undefined anymore after being
+ // set to `[]` above.
+ (customType.children as CustomTypeChild[]).push(customTypeChild);
+ });
+ return customType;
+ }
+ private _getNameFromDataItemIfExists(dataItem: DataItem): string | undefined {
+ if (_.isUndefined(dataItem.components)) {
+ return undefined;
+ }
+ const customType = this._getCustomTypeFromDataItem(dataItem);
+ const hash = SolDoc._generateCustomTypeHash(customType);
+ if (_.isUndefined(this._customTypeHashToName) || _.isUndefined(this._customTypeHashToName[hash])) {
+ return undefined;
+ }
+ return this._customTypeHashToName[hash];
+ }
+ private _getTypeFromDataItem(dataItem: DataItem): Type {
+ const typeDocType = !_.isUndefined(dataItem.components) ? TypeDocTypes.Reference : TypeDocTypes.Intrinsic;
+ let typeName: string;
+ if (typeDocType === TypeDocTypes.Reference) {
+ const nameIfExists = this._getNameFromDataItemIfExists(dataItem);
+ typeName = _.isUndefined(nameIfExists) ? SolDoc._capitalize(dataItem.name) : nameIfExists;
+ } else {
+ typeName = dataItem.type;
+ }
+
+ const isArrayType = _.endsWith(dataItem.type, '[]');
+ let type: Type;
+ if (isArrayType) {
+ // tslint:disable-next-line:custom-no-magic-numbers
+ typeName = typeDocType === TypeDocTypes.Intrinsic ? typeName.slice(0, -2) : typeName;
+ type = {
+ elementType: { name: typeName, typeDocType },
+ typeDocType: TypeDocTypes.Array,
+ name: '',
+ };
+ } else {
+ type = { name: typeName, typeDocType };
+ }
+ return type;
+ }
+ private _overwriteStructNames(customTypes: CustomType[], customTypeHashToName?: ObjectMap<string>): CustomType[] {
+ if (_.isUndefined(customTypeHashToName)) {
+ return customTypes;
+ }
+ const localCustomTypes = _.cloneDeep(customTypes);
+ _.each(localCustomTypes, customType => {
+ const hash = SolDoc._generateCustomTypeHash(customType);
+ if (!_.isUndefined(this._customTypeHashToName) && !_.isUndefined(this._customTypeHashToName[hash])) {
+ customType.name = this._customTypeHashToName[hash];
+ }
+ });
+ return localCustomTypes;
+ }
+ private _extractStructs(compiledContract: StandardContractOutput): CustomType[] {
+ let customTypes: CustomType[] = [];
+ for (const abiDefinition of compiledContract.abi) {
+ let types: CustomType[] = [];
+ switch (abiDefinition.type) {
+ case 'constructor': {
+ types = this._getStructsAsCustomTypes(abiDefinition);
+ break;
+ }
+ case 'function': {
+ types = this._getStructsAsCustomTypes(abiDefinition);
+ break;
+ }
+ case 'event':
+ case 'fallback':
+ // No types exist
+ break;
+ default:
+ throw new Error(
+ `unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`,
+ );
+ }
+ customTypes = [...customTypes, ...types];
+ }
+ return customTypes;
+ }
+ private _genDocSection(compiledContract: StandardContractOutput, contractName: string): DocSection {
+ const docSection: DocSection = {
+ comment: _.isUndefined(compiledContract.devdoc) ? '' : compiledContract.devdoc.title,
+ constructors: [],
+ methods: [],
+ properties: [],
+ types: [],
+ functions: [],
+ events: [],
+ };
+
+ for (const abiDefinition of compiledContract.abi) {
+ switch (abiDefinition.type) {
+ case 'constructor':
+ docSection.constructors.push(
+ this._genConstructorDoc(contractName, abiDefinition, compiledContract.devdoc),
+ );
+ break;
+ case 'event':
+ (docSection.events as Event[]).push(SolDoc._genEventDoc(abiDefinition));
+ // note that we're not sending devdoc to this._genEventDoc().
+ // that's because the type of the events array doesn't have any fields for documentation!
+ break;
+ case 'function':
+ docSection.methods.push(this._genMethodDoc(abiDefinition, compiledContract.devdoc));
+ break;
+ case 'fallback':
+ docSection.methods.push(SolDoc._genFallbackDoc(abiDefinition, compiledContract.devdoc));
+ break;
+ default:
+ throw new Error(
+ `unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`,
+ );
+ }
+ }
+
+ return docSection;
+ }
+ private _genConstructorDoc(
+ contractName: string,
+ abiDefinition: ConstructorAbi,
+ devdocIfExists: DevdocOutput | undefined,
+ ): SolidityMethod {
+ const { parameters, methodSignature } = this._genMethodParamsDoc('', abiDefinition.inputs, devdocIfExists);
+
+ const comment = SolDoc._devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
+
+ const constructorDoc: SolidityMethod = {
+ isConstructor: true,
+ name: contractName,
+ callPath: '',
+ parameters,
+ returnType: { name: contractName, typeDocType: TypeDocTypes.Reference }, // sad we have to specify this
+ isConstant: false,
+ isPayable: abiDefinition.payable,
+ comment,
+ };
+
+ return constructorDoc;
+ }
+ private _genMethodDoc(abiDefinition: MethodAbi, devdocIfExists: DevdocOutput | undefined): SolidityMethod {
+ const name = abiDefinition.name;
+ const { parameters, methodSignature } = this._genMethodParamsDoc(name, abiDefinition.inputs, devdocIfExists);
+ const devDocComment = SolDoc._devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
+ const returnType = this._genMethodReturnTypeDoc(abiDefinition.outputs);
+ const returnComment =
+ _.isUndefined(devdocIfExists) || _.isUndefined(devdocIfExists.methods[methodSignature])
+ ? undefined
+ : devdocIfExists.methods[methodSignature].return;
+
+ const hasNoNamedParameters = _.isUndefined(_.find(parameters, p => !_.isEmpty(p.name)));
+ const isGeneratedGetter = hasNoNamedParameters;
+ const comment =
+ _.isEmpty(devDocComment) && isGeneratedGetter
+ ? `This is an auto-generated accessor method of the '${name}' contract instance variable.`
+ : devDocComment;
+ const methodDoc: SolidityMethod = {
+ isConstructor: false,
+ name,
+ callPath: '',
+ parameters,
+ returnType,
+ returnComment,
+ isConstant: abiDefinition.constant,
+ isPayable: abiDefinition.payable,
+ comment,
+ };
+ return methodDoc;
+ }
+ /**
+ * Extract documentation for each method parameter from @param params.
+ */
+ private _genMethodParamsDoc(
+ name: string,
+ abiParams: DataItem[],
+ devdocIfExists: DevdocOutput | undefined,
+ ): { parameters: Parameter[]; methodSignature: string } {
+ const parameters: Parameter[] = [];
+ for (const abiParam of abiParams) {
+ const type = this._getTypeFromDataItem(abiParam);
+
+ const parameter: Parameter = {
+ name: abiParam.name,
+ comment: '<No comment>',
+ isOptional: false, // Unsupported in Solidity, until resolution of https://github.com/ethereum/solidity/issues/232
+ type,
+ };
+ parameters.push(parameter);
+ }
+
+ const methodSignature = `${name}(${abiParams
+ .map(abiParam => {
+ if (!_.startsWith(abiParam.type, 'tuple')) {
+ return abiParam.type;
+ } else {
+ // Need to expand tuples:
+ // E.g: fillOrder(tuple,uint256,bytes) -> fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)
+ const isArray = _.endsWith(abiParam.type, '[]');
+ const expandedTypes = _.map(abiParam.components, c => c.type);
+ const type = `(${expandedTypes.join(',')})${isArray ? '[]' : ''}`;
+ return type;
+ }
+ })
+ .join(',')})`;
+
+ if (!_.isUndefined(devdocIfExists)) {
+ const devdocMethodIfExists = devdocIfExists.methods[methodSignature];
+ if (!_.isUndefined(devdocMethodIfExists)) {
+ const devdocParamsIfExist = devdocMethodIfExists.params;
+ if (!_.isUndefined(devdocParamsIfExist)) {
+ for (const parameter of parameters) {
+ parameter.comment = devdocParamsIfExist[parameter.name];
+ }
+ }
+ }
+ }
+
+ return { parameters, methodSignature };
+ }
+ private _genMethodReturnTypeDoc(outputs: DataItem[]): Type {
+ let type: Type;
+ if (outputs.length > 1) {
+ type = {
+ name: '',
+ typeDocType: TypeDocTypes.Tuple,
+ tupleElements: [],
+ };
+ for (const output of outputs) {
+ const tupleType = this._getTypeFromDataItem(output);
+ (type.tupleElements as Type[]).push(tupleType);
+ }
+ return type;
+ } else if (outputs.length === 1) {
+ const output = outputs[0];
+ type = this._getTypeFromDataItem(output);
+ } else {
+ type = {
+ name: 'void',
+ typeDocType: TypeDocTypes.Intrinsic,
+ };
+ }
+ return type;
+ }
+ private _getStructsAsCustomTypes(abiDefinition: AbiDefinition): CustomType[] {
+ const customTypes: CustomType[] = [];
+ // We cast to `any` here because we do not know yet if this type of abiDefinition contains
+ // an `input` key
+ if (!_.isUndefined((abiDefinition as any).inputs)) {
+ const methodOrConstructorAbi = abiDefinition as MethodAbi | ConstructorAbi;
+ _.each(methodOrConstructorAbi.inputs, input => {
+ if (!_.isUndefined(input.components)) {
+ const customType = this._getCustomTypeFromDataItem(input);
+ customTypes.push(customType);
+ }
+ });
+ }
+ if (!_.isUndefined((abiDefinition as any).outputs)) {
+ const methodAbi = abiDefinition as MethodAbi;
+ _.each(methodAbi.outputs, output => {
+ if (!_.isUndefined(output.components)) {
+ const customType = this._getCustomTypeFromDataItem(output);
+ customTypes.push(customType);
+ }
+ });
+ }
+ return customTypes;
+ }
+}
+// tslint:disable:max-file-line-count
diff --git a/packages/sol-doc/src/solidity_doc_generator.ts b/packages/sol-doc/src/solidity_doc_generator.ts
deleted file mode 100644
index 5ddf001a6..000000000
--- a/packages/sol-doc/src/solidity_doc_generator.ts
+++ /dev/null
@@ -1,306 +0,0 @@
-import * as path from 'path';
-
-import * as _ from 'lodash';
-
-import {
- AbiDefinition,
- ConstructorAbi,
- DataItem,
- DevdocOutput,
- EventAbi,
- EventParameter,
- FallbackAbi,
- MethodAbi,
- StandardContractOutput,
-} from 'ethereum-types';
-
-import { Compiler, CompilerOptions } from '@0xproject/sol-compiler';
-import {
- DocAgnosticFormat,
- DocSection,
- Event,
- EventArg,
- Parameter,
- SolidityMethod,
- Type,
- TypeDocTypes,
-} from '@0xproject/types';
-
-/**
- * Invoke the Solidity compiler and transform its ABI and devdoc outputs into a
- * JSON format easily consumed by documentation rendering tools.
- * @param contractsToDocument list of contracts for which to generate doc objects
- * @param contractsDir the directory in which to find the `contractsToCompile` as well as their dependencies.
- * @return doc object for use with documentation generation tools.
- */
-export async function generateSolDocAsync(
- contractsDir: string,
- contractsToDocument?: string[],
-): Promise<DocAgnosticFormat> {
- const docWithDependencies: DocAgnosticFormat = {};
- const compilerOptions = _makeCompilerOptions(contractsDir, contractsToDocument);
- const compiler = new Compiler(compilerOptions);
- const compilerOutputs = await compiler.getCompilerOutputsAsync();
- for (const compilerOutput of compilerOutputs) {
- const contractFileNames = _.keys(compilerOutput.contracts);
- for (const contractFileName of contractFileNames) {
- const contractNameToOutput = compilerOutput.contracts[contractFileName];
-
- const contractNames = _.keys(contractNameToOutput);
- for (const contractName of contractNames) {
- const compiledContract = contractNameToOutput[contractName];
- if (_.isUndefined(compiledContract.abi)) {
- throw new Error('compiled contract did not contain ABI output');
- }
- docWithDependencies[contractName] = _genDocSection(compiledContract, contractName);
- }
- }
- }
-
- let doc: DocAgnosticFormat = {};
- if (_.isUndefined(contractsToDocument) || contractsToDocument.length === 0) {
- doc = docWithDependencies;
- } else {
- for (const contractToDocument of contractsToDocument) {
- const contractBasename = path.basename(contractToDocument);
- const contractName =
- contractBasename.lastIndexOf('.sol') === -1
- ? contractBasename
- : contractBasename.substring(0, contractBasename.lastIndexOf('.sol'));
- doc[contractName] = docWithDependencies[contractName];
- }
- }
-
- return doc;
-}
-
-function _makeCompilerOptions(contractsDir: string, contractsToCompile?: string[]): CompilerOptions {
- const compilerOptions: CompilerOptions = {
- contractsDir,
- contracts: '*',
- compilerSettings: {
- outputSelection: {
- ['*']: {
- ['*']: ['abi', 'devdoc'],
- },
- },
- },
- };
-
- const shouldOverrideCatchAllContractsConfig = !_.isUndefined(contractsToCompile) && contractsToCompile.length > 0;
- if (shouldOverrideCatchAllContractsConfig) {
- compilerOptions.contracts = contractsToCompile;
- }
-
- return compilerOptions;
-}
-
-function _genDocSection(compiledContract: StandardContractOutput, contractName: string): DocSection {
- const docSection: DocSection = {
- comment: _.isUndefined(compiledContract.devdoc) ? '' : compiledContract.devdoc.title,
- constructors: [],
- methods: [],
- properties: [],
- types: [],
- functions: [],
- events: [],
- };
-
- for (const abiDefinition of compiledContract.abi) {
- switch (abiDefinition.type) {
- case 'constructor':
- docSection.constructors.push(_genConstructorDoc(contractName, abiDefinition, compiledContract.devdoc));
- break;
- case 'event':
- (docSection.events as Event[]).push(_genEventDoc(abiDefinition));
- // note that we're not sending devdoc to _genEventDoc().
- // that's because the type of the events array doesn't have any fields for documentation!
- break;
- case 'function':
- case 'fallback':
- docSection.methods.push(_genMethodDoc(abiDefinition, compiledContract.devdoc));
- break;
- default:
- throw new Error(
- `unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`,
- );
- }
- }
-
- return docSection;
-}
-
-function _genConstructorDoc(
- contractName: string,
- abiDefinition: ConstructorAbi,
- devdocIfExists: DevdocOutput | undefined,
-): SolidityMethod {
- const { parameters, methodSignature } = _genMethodParamsDoc('', abiDefinition.inputs, devdocIfExists);
-
- const comment = _devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
-
- const constructorDoc: SolidityMethod = {
- isConstructor: true,
- name: contractName,
- callPath: '',
- parameters,
- returnType: { name: contractName, typeDocType: TypeDocTypes.Reference }, // sad we have to specify this
- isConstant: false,
- isPayable: abiDefinition.payable,
- comment,
- };
-
- return constructorDoc;
-}
-
-function _devdocMethodDetailsIfExist(
- methodSignature: string,
- devdocIfExists: DevdocOutput | undefined,
-): string | undefined {
- let details;
- if (!_.isUndefined(devdocIfExists)) {
- const devdocMethodsIfExist = devdocIfExists.methods;
- if (!_.isUndefined(devdocMethodsIfExist)) {
- const devdocMethodIfExists = devdocMethodsIfExist[methodSignature];
- if (!_.isUndefined(devdocMethodIfExists)) {
- const devdocMethodDetailsIfExist = devdocMethodIfExists.details;
- if (!_.isUndefined(devdocMethodDetailsIfExist)) {
- details = devdocMethodDetailsIfExist;
- }
- }
- }
- }
- return details;
-}
-
-function _genMethodDoc(
- abiDefinition: MethodAbi | FallbackAbi,
- devdocIfExists: DevdocOutput | undefined,
-): SolidityMethod {
- const name = abiDefinition.type === 'fallback' ? '' : abiDefinition.name;
-
- const { parameters, methodSignature } =
- abiDefinition.type === 'fallback'
- ? { parameters: [], methodSignature: `${name}()` }
- : _genMethodParamsDoc(name, abiDefinition.inputs, devdocIfExists);
-
- const comment = _devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
-
- const returnType =
- abiDefinition.type === 'fallback'
- ? { name: '', typeDocType: TypeDocTypes.Intrinsic }
- : _genMethodReturnTypeDoc(abiDefinition.outputs, methodSignature, devdocIfExists);
-
- const returnComment =
- _.isUndefined(devdocIfExists) || _.isUndefined(devdocIfExists.methods[methodSignature])
- ? undefined
- : devdocIfExists.methods[methodSignature].return;
-
- const isConstant = abiDefinition.type === 'fallback' ? true : abiDefinition.constant;
-
- const methodDoc: SolidityMethod = {
- isConstructor: false,
- name,
- callPath: '',
- parameters,
- returnType,
- returnComment,
- isConstant,
- isPayable: abiDefinition.payable,
- comment,
- };
- return methodDoc;
-}
-
-function _genEventDoc(abiDefinition: EventAbi): Event {
- const eventDoc: Event = {
- name: abiDefinition.name,
- eventArgs: _genEventArgsDoc(abiDefinition.inputs, undefined),
- };
- return eventDoc;
-}
-
-function _genEventArgsDoc(args: EventParameter[], devdocIfExists: DevdocOutput | undefined): EventArg[] {
- const eventArgsDoc: EventArg[] = [];
-
- for (const arg of args) {
- const name = arg.name;
-
- const type: Type = {
- name: arg.type,
- typeDocType: TypeDocTypes.Intrinsic,
- };
-
- const eventArgDoc: EventArg = {
- isIndexed: arg.indexed,
- name,
- type,
- };
-
- eventArgsDoc.push(eventArgDoc);
- }
- return eventArgsDoc;
-}
-
-/**
- * Extract documentation for each method parameter from @param params.
- */
-function _genMethodParamsDoc(
- name: string,
- abiParams: DataItem[],
- devdocIfExists: DevdocOutput | undefined,
-): { parameters: Parameter[]; methodSignature: string } {
- const parameters: Parameter[] = [];
- for (const abiParam of abiParams) {
- const parameter: Parameter = {
- name: abiParam.name,
- comment: '',
- isOptional: false, // Unsupported in Solidity, until resolution of https://github.com/ethereum/solidity/issues/232
- type: { name: abiParam.type, typeDocType: TypeDocTypes.Intrinsic },
- };
- parameters.push(parameter);
- }
-
- const methodSignature = `${name}(${abiParams
- .map(abiParam => {
- return abiParam.type;
- })
- .join(',')})`;
-
- if (!_.isUndefined(devdocIfExists)) {
- const devdocMethodIfExists = devdocIfExists.methods[methodSignature];
- if (!_.isUndefined(devdocMethodIfExists)) {
- const devdocParamsIfExist = devdocMethodIfExists.params;
- if (!_.isUndefined(devdocParamsIfExist)) {
- for (const parameter of parameters) {
- parameter.comment = devdocParamsIfExist[parameter.name];
- }
- }
- }
- }
-
- return { parameters, methodSignature };
-}
-
-function _genMethodReturnTypeDoc(
- outputs: DataItem[],
- methodSignature: string,
- devdocIfExists: DevdocOutput | undefined,
-): Type {
- const methodReturnTypeDoc: Type = {
- name: '',
- typeDocType: TypeDocTypes.Intrinsic,
- tupleElements: undefined,
- };
- if (outputs.length > 1) {
- methodReturnTypeDoc.typeDocType = TypeDocTypes.Tuple;
- methodReturnTypeDoc.tupleElements = [];
- for (const output of outputs) {
- methodReturnTypeDoc.tupleElements.push({ name: output.type, typeDocType: TypeDocTypes.Intrinsic });
- }
- } else if (outputs.length === 1) {
- methodReturnTypeDoc.typeDocType = TypeDocTypes.Intrinsic;
- methodReturnTypeDoc.name = outputs[0].type;
- }
- return methodReturnTypeDoc;
-}
diff --git a/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol b/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol
new file mode 100644
index 000000000..b9a7ccdbc
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol
@@ -0,0 +1,18 @@
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+
+contract StructParamAndReturn {
+
+ struct Stuff {
+ address anAddress;
+ uint256 aNumber;
+ }
+
+ /// @dev DEV_COMMENT
+ /// @param stuff STUFF_COMMENT
+ /// @return RETURN_COMMENT
+ function methodWithStructParamAndReturn(Stuff stuff) public pure returns(Stuff) {
+ return stuff;
+ }
+}
diff --git a/packages/sol-doc/test/solidity_doc_generator_test.ts b/packages/sol-doc/test/solidity_doc_generator_test.ts
index c780d3d31..f166fb143 100644
--- a/packages/sol-doc/test/solidity_doc_generator_test.ts
+++ b/packages/sol-doc/test/solidity_doc_generator_test.ts
@@ -5,16 +5,17 @@ import 'mocha';
import { DocAgnosticFormat, Event, SolidityMethod } from '@0xproject/types';
-import { generateSolDocAsync } from '../src/solidity_doc_generator';
+import { SolDoc } from '../src/sol_doc';
import { chaiSetup } from './util/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
+const solDoc = new SolDoc();
describe('#SolidityDocGenerator', () => {
it('should generate a doc object that matches the devdoc-free TokenTransferProxy fixture', async () => {
- const doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
'TokenTransferProxyNoDevdoc',
]);
expect(doc).to.not.be.undefined();
@@ -22,8 +23,8 @@ describe('#SolidityDocGenerator', () => {
verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxyNoDevdoc');
});
const docPromises: Array<Promise<DocAgnosticFormat>> = [
- generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`),
- generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, []),
+ solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`),
+ solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, []),
];
docPromises.forEach(docPromise => {
it('should generate a doc object that matches the TokenTransferProxy fixture with its dependencies', async () => {
@@ -48,7 +49,7 @@ describe('#SolidityDocGenerator', () => {
});
});
it('should generate a doc object that matches the TokenTransferProxy fixture', async () => {
- const doc: DocAgnosticFormat = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ const doc: DocAgnosticFormat = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
'TokenTransferProxy',
]);
verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxy');
@@ -56,7 +57,7 @@ describe('#SolidityDocGenerator', () => {
describe('when processing all the permutations of devdoc stuff that we use in our contracts', () => {
let doc: DocAgnosticFormat;
before(async () => {
- doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['NatspecEverything']);
+ doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['NatspecEverything']);
expect(doc).to.not.be.undefined();
expect(doc.NatspecEverything).to.not.be.undefined();
});
@@ -159,7 +160,9 @@ describe('#SolidityDocGenerator', () => {
});
});
it('should document a method that returns multiple values', async () => {
- const doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['MultipleReturnValues']);
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'MultipleReturnValues',
+ ]);
expect(doc.MultipleReturnValues).to.not.be.undefined();
expect(doc.MultipleReturnValues.methods).to.not.be.undefined();
let methodWithMultipleReturnValues: SolidityMethod | undefined;
@@ -178,6 +181,39 @@ describe('#SolidityDocGenerator', () => {
}
expect(returnType.tupleElements.length).to.equal(2);
});
+ it('should document a method that has a struct param and return value', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'StructParamAndReturn',
+ ]);
+ expect(doc.StructParamAndReturn).to.not.be.undefined();
+ expect(doc.StructParamAndReturn.methods).to.not.be.undefined();
+ let methodWithStructParamAndReturn: SolidityMethod | undefined;
+ for (const method of doc.StructParamAndReturn.methods) {
+ if (method.name === 'methodWithStructParamAndReturn') {
+ methodWithStructParamAndReturn = method;
+ }
+ }
+ if (_.isUndefined(methodWithStructParamAndReturn)) {
+ throw new Error('method should not be undefined');
+ }
+ /**
+ * Solc maps devDoc comments to methods using a method signature. If we incorrectly
+ * generate the methodSignatures, the devDoc comments won't be correctly associated
+ * with their methods and they won't show up in the output. By checking that the comments
+ * are included for a method with structs as params/returns, we are sure that the methodSignature
+ * generation is correct for this case.
+ */
+ expect(methodWithStructParamAndReturn.comment).to.be.equal('DEV_COMMENT');
+ expect(methodWithStructParamAndReturn.returnComment).to.be.equal('RETURN_COMMENT');
+ expect(methodWithStructParamAndReturn.parameters[0].comment).to.be.equal('STUFF_COMMENT');
+ });
+ it('should document the structs included in a contract', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'StructParamAndReturn',
+ ]);
+ expect(doc.structs).to.not.be.undefined();
+ expect(doc.structs.types.length).to.be.equal(1);
+ });
});
function verifyTokenTransferProxyABIIsDocumented(doc: DocAgnosticFormat, contractName: string): void {
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 5ef8b54a4..3ae0536d5 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -519,6 +519,7 @@ export interface TypescriptFunction extends BaseFunction {
export interface SolidityMethod extends BaseMethod {
isConstant?: boolean;
isPayable?: boolean;
+ isFallback?: boolean;
}
export interface Source {
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index bb61e4fb9..7cf3c6ecb 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -136,7 +136,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
<Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none">
<MenuItem
style={{ fontSize: styles.menuItem.fontSize }}
- primaryText={this.props.translate.get(Key.SmartContract, Deco.CapWords)}
+ primaryText={this.props.translate.get(Key.SmartContracts, Deco.CapWords)}
/>
</Link>,
<Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none">
diff --git a/packages/website/ts/containers/smart_contracts_documentation.ts b/packages/website/ts/containers/smart_contracts_documentation.ts
index 8d69afe71..05b2a50c3 100644
--- a/packages/website/ts/containers/smart_contracts_documentation.ts
+++ b/packages/website/ts/containers/smart_contracts_documentation.ts
@@ -37,28 +37,56 @@ const docsInfoConfig: DocsInfoConfig = {
contractsByVersionByNetworkId: {
'1.0.0': {
[Networks.Mainnet]: {
- [Sections.Exchange]: '0x12459c951127e0c374ff9105dda097662a027093',
- [Sections.TokenTransferProxy]: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
- [Sections.ZRXToken]: '0xe41d2489571d322189246dafa5ebde1f4699f498',
- [Sections.TokenRegistry]: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
+ Exchange_v1: '0x12459c951127e0c374ff9105dda097662a027093',
+ TokenTransferProxy_v1: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
+ TokenRegistry: '0x926a74c5c36adf004c87399e65f75628b0f98d2c',
},
[Networks.Ropsten]: {
- [Sections.Exchange]: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
- [Sections.TokenTransferProxy]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
- [Sections.ZRXToken]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
- [Sections.TokenRegistry]: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
+ Exchange_v1: '0x479cc461fecd078f766ecc58533d6f69580cf3ac',
+ TokenTransferProxy_v1: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
+ TokenRegistry: '0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed',
},
[Networks.Kovan]: {
- [Sections.Exchange]: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
- [Sections.TokenTransferProxy]: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
- [Sections.ZRXToken]: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
- [Sections.TokenRegistry]: '0xf18e504561f4347bea557f3d4558f559dddbae7f',
+ Exchange_v1: '0x90fe2af704b34e0224bf2299c838e04d4dcf1364',
+ TokenTransferProxy_v1: '0x087Eed4Bc1ee3DE49BeFbd66C662B434B15d49d4',
+ TokenRegistry: '0xf18e504561f4347bea557f3d4558f559dddbae7f',
},
[Networks.Rinkeby]: {
- [Sections.Exchange]: '0x1d16ef40fac01cec8adac2ac49427b9384192c05',
- [Sections.TokenTransferProxy]: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
- [Sections.ZRXToken]: '0x00f58d6d585f84b2d7267940cede30ce2fe6eae8',
- [Sections.TokenRegistry]: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
+ Exchange_v1: '0x1d16ef40fac01cec8adac2ac49427b9384192c05',
+ TokenTransferProxy_v1: '0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d',
+ TokenRegistry: '0x4e9aad8184de8833365fea970cd9149372fdf1e6',
+ },
+ },
+ '2.0.0': {
+ [Networks.Mainnet]: {
+ AssetProxyOwner: '0x17992e4ffb22730138e4b62aaa6367fa9d3699a6',
+ ERC20Proxy: '0x2240dab907db71e64d3e0dba4800c83b5c502d4e',
+ ERC721Proxy: '0x208e41fb445f1bb1b6780d58356e81405f3e6127',
+ Exchange: '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
+ Forwarder: '0x7afc2d5107af94c462a194d2c21b5bdd238709d6',
+ OrderValidator: '0x9463e518dea6810309563c81d5266c1b1d149138',
+ WETH9: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
+ ZRXToken: '0xe41d2489571d322189246dafa5ebde1f4699f498',
+ },
+ [Networks.Ropsten]: {
+ AssetProxyOwner: '0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b',
+ ERC20Proxy: '0xb1408f4c245a23c31b98d2c626777d4c0d766caa',
+ ERC721Proxy: '0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4',
+ Exchange: '0x4530c0483a1633c7a1c97d2c53721caff2caaaaf',
+ Forwarder: '0x3983e204b12b3c02fb0638caf2cd406a62e0ead3',
+ OrderValidator: '0x90431a90516ab49af23a0530e04e8c7836e7122f',
+ WETH9: '0xc778417e063141139fce010982780140aa0cd5ab',
+ ZRXToken: '0xff67881f8d12f372d91baae9752eb3631ff0ed00',
+ },
+ [Networks.Kovan]: {
+ AssetProxyOwner: '0x2c824d2882baa668e0d5202b1e7f2922278703f8',
+ ERC20Proxy: '0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e',
+ ERC721Proxy: '0x2a9127c745688a165106c11cd4d647d2220af821',
+ Exchange: '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
+ Forwarder: '0xd85e2fa7e7e252b27b01bf0d65c946959d2f45b8',
+ OrderValidator: '0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d',
+ WETH9: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
+ ZRXToken: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa',
},
},
},
diff --git a/packages/website/ts/pages/documentation/doc_page.tsx b/packages/website/ts/pages/documentation/doc_page.tsx
index 6f029b6a2..87a806b2b 100644
--- a/packages/website/ts/pages/documentation/doc_page.tsx
+++ b/packages/website/ts/pages/documentation/doc_page.tsx
@@ -146,9 +146,9 @@ export class DocPage extends React.Component<DocPageProps, DocPageState> {
docAgnosticFormat = versionDocObj as DocAgnosticFormat;
// HACK: need to modify docsInfo like convertToDocAgnosticFormat() would do
this.props.docsInfo.menu.Contracts = [];
- _.each(docAgnosticFormat, (docObj, contractName) => {
- this.props.docsInfo.sections[contractName] = contractName;
- this.props.docsInfo.menu.Contracts.push(contractName);
+ _.each(docAgnosticFormat, (_docObj, sectionName) => {
+ this.props.docsInfo.sections[sectionName] = sectionName;
+ this.props.docsInfo.menu.Contracts.push(sectionName);
});
}