From 5a06e560f492e2e3ca198295e0d129d74f966eb3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 12:32:05 -0700 Subject: [PATCH 01/76] Add sol-meta project --- packages/sol-meta/README.md | 64 +++++++++++++++++++ packages/sol-meta/package.json | 84 +++++++++++++++++++++++++ packages/sol-meta/tsconfig.json | 10 +++ packages/sol-meta/tslint.json | 3 + packages/sol-meta/typedoc-tsconfig.json | 8 +++ 5 files changed, 169 insertions(+) create mode 100644 packages/sol-meta/README.md create mode 100644 packages/sol-meta/package.json create mode 100644 packages/sol-meta/tsconfig.json create mode 100644 packages/sol-meta/tslint.json create mode 100644 packages/sol-meta/typedoc-tsconfig.json diff --git a/packages/sol-meta/README.md b/packages/sol-meta/README.md new file mode 100644 index 0000000000..05030798de --- /dev/null +++ b/packages/sol-meta/README.md @@ -0,0 +1,64 @@ +## @0xproject/sol-meta + +Sol-meta is a Solidity to Solidity compiler to automatically generate testing contracts. It has two modes: + +**Mock generation.** Given an interface contract (either a public interface or a mixin), it can generate a mock implementation. + +**Exposing.** Given an implemented contract it generates a contract that exposes all internal functions with public wrappers. + +## Architecture + +Source --Parser--> AST --transform--> AST --unparser--> Source + + +## Contributing + +We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0xproject/sol-compiler yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0xproject/sol-compiler yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` diff --git a/packages/sol-meta/package.json b/packages/sol-meta/package.json new file mode 100644 index 0000000000..d22819401b --- /dev/null +++ b/packages/sol-meta/package.json @@ -0,0 +1,84 @@ +{ + "name": "@0xproject/sol-meta", + "version": "1.1.7", + "engines": { + "node": ">=6.12" + }, + "description": "Solidity to Solidty compiler", + "main": "lib/src/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "yarn pre_build && tsc", + "build:ci": "yarn build", + "build:watch": "tsc -w", + "pre_build": "run-s update_contract_fixtures", + "update_contract_fixtures": "copyfiles 'test/fixtures/contracts/**/*' ./lib", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/index.js --exit", + "test:coverage": "nyc npm run test --all && yarn coverage:report:lcov", + "coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info", + "clean": "shx rm -rf lib generated_docs", + "migrate": "npm run build; node lib/src/cli.js migrate", + "lint": "tslint --project .", + "test:circleci": "yarn test:coverage", + "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" + }, + "config": { + "postpublish": { + "assets": [] + } + }, + "bin": { + "sol-meta": "bin/sol-meta.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-meta/README.md", + "devDependencies": { + "@0xproject/dev-utils": "^1.0.12", + "@0xproject/tslint-config": "^1.0.8", + "@types/mkdirp": "^0.5.2", + "@types/require-from-string": "^1.2.0", + "@types/semver": "^5.5.0", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^2.0.2", + "copyfiles": "^2.0.0", + "dirty-chai": "^2.0.1", + "glob": "^7.1.3", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "nyc": "^11.0.1", + "shx": "^0.2.2", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "dependencies": { + "@0xproject/assert": "^1.0.13", + "@0xproject/json-schemas": "^1.0.7", + "@0xproject/sol-resolver": "^1.0.14", + "@0xproject/types": "^1.1.4", + "@0xproject/typescript-typings": "^3.0.2", + "@0xproject/utils": "^2.0.2", + "@types/yargs": "^11.0.0", + "chalk": "^2.3.0", + "lodash": "^4.17.5", + "mkdirp": "^0.5.1", + "require-from-string": "^2.0.1", + "semver": "5.5.0", + "solidity-parser-antlr": "^0.3.2", + "source-map-support": "^0.5.0", + "yargs": "^10.0.3" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/sol-meta/tsconfig.json b/packages/sol-meta/tsconfig.json new file mode 100644 index 0000000000..100b063255 --- /dev/null +++ b/packages/sol-meta/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": ".", + "strictFunctionTypes": false, + "noImplicitAny": false + }, + "include": ["./src/**/*", "./test/**/*"] +} diff --git a/packages/sol-meta/tslint.json b/packages/sol-meta/tslint.json new file mode 100644 index 0000000000..ffaefe83a6 --- /dev/null +++ b/packages/sol-meta/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["@0xproject/tslint-config"] +} diff --git a/packages/sol-meta/typedoc-tsconfig.json b/packages/sol-meta/typedoc-tsconfig.json new file mode 100644 index 0000000000..22897c1315 --- /dev/null +++ b/packages/sol-meta/typedoc-tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../typedoc-tsconfig", + "compilerOptions": { + "outDir": "lib", + "strictFunctionTypes": false + }, + "include": ["./src/**/*", "./test/**/*"] +} From f156ebf4ac00d883dc23963190d25b6ba53ebaa6 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 12:33:13 -0700 Subject: [PATCH 02/76] Improved typings for solidity-parser-antlr --- .../types/solidity-parser-antlr/index.d.ts | 584 ++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 packages/typescript-typings/types/solidity-parser-antlr/index.d.ts diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts new file mode 100644 index 0000000000..7df3afa4db --- /dev/null +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -0,0 +1,584 @@ +// Type definitions for solidity-parser-antlr 0.3.2 +// Project: https://github.com/federicobond/solidity-parser-antlr +// Definitions by: Leonid Logvinov +// Alex Browne +// Remco Bloemen +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +export interface LineColumn { + line: number; + column: number; +} +export interface Location { + start: LineColumn; + end: LineColumn; +} + +export enum NodeType { + SourceUnit = 'SourceUnit', + PragmaDirective = 'PragmaDirective', + PragmaName = 'PragmaName', + PragmaValue = 'PragmaValue', + Version = 'Version', + VersionOperator = 'VersionOperator', + VersionConstraint = 'VersionConstraint', + ImportDeclaration = 'ImportDeclaration', + ImportDirective = 'ImportDirective', + ContractDefinition = 'ContractDefinition', + InheritanceSpecifier = 'InheritanceSpecifier', + ContractPart = 'ContractPart', + StateVariableDeclaration = 'StateVariableDeclaration', + UsingForDeclaration = 'UsingForDeclaration', + StructDefinition = 'StructDefinition', + ModifierDefinition = 'ModifierDefinition', + ModifierInvocation = 'ModifierInvocation', + FunctionDefinition = 'FunctionDefinition', + ReturnParameters = 'ReturnParameters', + ModifierList = 'ModifierList', + EventDefinition = 'EventDefinition', + EnumValue = 'EnumValue', + EnumDefinition = 'EnumDefinition', + ParameterList = 'ParameterList', + Parameter = 'Parameter', + EventParameterList = 'EventParameterList', + EventParameter = 'EventParameter', + FunctionTypeParameterList = 'FunctionTypeParameterList', + FunctionTypeParameter = 'FunctionTypeParameter', + VariableDeclaration = 'VariableDeclaration', + TypeName = 'TypeName', + UserDefinedTypeName = 'UserDefinedTypeName', + Mapping = 'Mapping', + FunctionTypeName = 'FunctionTypeName', + StorageLocation = 'StorageLocation', + StateMutability = 'StateMutability', + Block = 'Block', + Statement = 'Statement', + ExpressionStatement = 'ExpressionStatement', + IfStatement = 'IfStatement', + WhileStatement = 'WhileStatement', + SimpleStatement = 'SimpleStatement', + ForStatement = 'ForStatement', + InlineAssemblyStatement = 'InlineAssemblyStatement', + DoWhileStatement = 'DoWhileStatement', + ContinueStatement = 'ContinueStatement', + BreakStatement = 'BreakStatement', + ReturnStatement = 'ReturnStatement', + ThrowStatement = 'ThrowStatement', + VariableDeclarationStatement = 'VariableDeclarationStatement', + IdentifierList = 'IdentifierList', + ElementaryTypeName = 'ElementaryTypeName', + Expression = 'Expression', + PrimaryExpression = 'PrimaryExpression', + ExpressionList = 'ExpressionList', + NameValueList = 'NameValueList', + NameValue = 'NameValue', + FunctionCallArguments = 'FunctionCallArguments', + AssemblyBlock = 'AssemblyBlock', + AssemblyItem = 'AssemblyItem', + AssemblyExpression = 'AssemblyExpression', + AssemblyCall = 'AssemblyCall', + AssemblyLocalDefinition = 'AssemblyLocalDefinition', + AssemblyAssignment = 'AssemblyAssignment', + AssemblyIdentifierOrList = 'AssemblyIdentifierOrList', + AssemblyIdentifierList = 'AssemblyIdentifierList', + AssemblyStackAssignment = 'AssemblyStackAssignment', + LabelDefinition = 'LabelDefinition', + AssemblySwitch = 'AssemblySwitch', + AssemblyCase = 'AssemblyCase', + AssemblyFunctionDefinition = 'AssemblyFunctionDefinition', + AssemblyFunctionReturns = 'AssemblyFunctionReturns', + AssemblyFor = 'AssemblyFor', + AssemblyIf = 'AssemblyIf', + AssemblyLiteral = 'AssemblyLiteral', + SubAssembly = 'SubAssembly', + TupleExpression = 'TupleExpression', + ElementaryTypeNameExpression = 'ElementaryTypeNameExpression', + NumberLiteral = 'NumberLiteral', + Identifier = 'Identifier', + BinaryOperation = 'BinaryOperation', + Conditional = 'Conditional', +} + +export interface BaseASTNode { + type: NodeType; + range?: [number, number]; + loc?: Location; +} +export interface SourceUnit extends BaseASTNode { + type: NodeType.SourceUnit; + children: [ASTNode]; +} +export interface PragmaDirective extends BaseASTNode { + type: NodeType.PragmaDirective; + name: string; + value: string; +} +export interface PragmaName extends BaseASTNode { + type: NodeType.PragmaName; +} +export interface PragmaValue extends BaseASTNode { + type: NodeType.PragmaValue; +} +export interface Version extends BaseASTNode { + type: NodeType.Version; +} +export interface VersionOperator extends BaseASTNode { + type: NodeType.VersionOperator; +} +export interface VersionConstraint extends BaseASTNode { + type: NodeType.VersionConstraint; +} +export interface ImportDeclaration extends BaseASTNode { + type: NodeType.ImportDeclaration; +} +export interface ImportDirective extends BaseASTNode { + type: NodeType.ImportDirective; + path: string; +} +export interface ContractDefinition extends BaseASTNode { + type: NodeType.ContractDefinition; + name: string; + kind: string; + baseContracts: [string]; + subNodes: [string]; +} +export interface InheritanceSpecifier extends BaseASTNode { + type: NodeType.InheritanceSpecifier; +} +export interface ContractPart extends BaseASTNode { + type: NodeType.ContractPart; +} +export interface StateVariableDeclaration extends BaseASTNode { + type: NodeType.StateVariableDeclaration; + variables: VariableDeclaration[]; +} +export interface UsingForDeclaration extends BaseASTNode { + type: NodeType.UsingForDeclaration; +} +export interface StructDefinition extends BaseASTNode { + type: NodeType.StructDefinition; +} +export interface ModifierDefinition extends BaseASTNode { + type: NodeType.ModifierDefinition; + name: string; +} +export interface ModifierInvocation extends BaseASTNode { + type: NodeType.ModifierInvocation; + name: string; +} +export interface FunctionDefinition extends BaseASTNode { + type: NodeType.FunctionDefinition; + name: string; +} +export interface ReturnParameters extends BaseASTNode { + type: NodeType.ReturnParameters; +} +export interface ModifierList extends BaseASTNode { + type: NodeType.ModifierList; +} +export interface EventDefinition extends BaseASTNode { + type: NodeType.EventDefinition; +} +export interface EnumValue extends BaseASTNode { + type: NodeType.EnumValue; + name: string; +} +export interface EnumDefinition extends BaseASTNode { + type: NodeType.EnumDefinition; + name: string; + members: [ASTNode]; +} +export interface ParameterList extends BaseASTNode { + type: NodeType.ParameterList; +} +export interface Parameter extends BaseASTNode { + type: NodeType.Parameter; +} +export interface EventParameterList extends BaseASTNode { + type: NodeType.EventParameterList; +} +export interface EventParameter extends BaseASTNode { + type: NodeType.EventParameter; +} +export interface FunctionTypeParameterList extends BaseASTNode { + type: NodeType.FunctionTypeParameterList; +} +export interface FunctionTypeParameter extends BaseASTNode { + type: NodeType.FunctionTypeParameter; +} +export interface VariableDeclaration extends BaseASTNode { + type: NodeType.VariableDeclaration; + visibility: 'public' | 'private'; + isStateVar: boolean; +} +export interface TypeName extends BaseASTNode { + type: NodeType.TypeName; +} +export interface UserDefinedTypeName extends BaseASTNode { + type: NodeType.UserDefinedTypeName; +} +export interface Mapping extends BaseASTNode { + type: NodeType.Mapping; +} +export interface FunctionTypeName extends BaseASTNode { + type: NodeType.FunctionTypeName; +} +export interface StorageLocation extends BaseASTNode { + type: NodeType.StorageLocation; +} +export interface StateMutability extends BaseASTNode { + type: NodeType.StateMutability; +} +export interface Block extends BaseASTNode { + type: NodeType.Block; +} +export interface Statement extends BaseASTNode { + type: NodeType.Statement; +} +export interface ExpressionStatement extends BaseASTNode { + type: NodeType.ExpressionStatement; + expression: ASTNode; +} +export interface IfStatement extends BaseASTNode { + type: NodeType.IfStatement; + trueBody: ASTNode; + falseBody: ASTNode; +} +export interface WhileStatement extends BaseASTNode { + type: NodeType.WhileStatement; +} +export interface SimpleStatement extends BaseASTNode { + type: NodeType.SimpleStatement; +} +export interface ForStatement extends BaseASTNode { + type: NodeType.ForStatement; +} +export interface InlineAssemblyStatement extends BaseASTNode { + type: NodeType.InlineAssemblyStatement; +} +export interface DoWhileStatement extends BaseASTNode { + type: NodeType.DoWhileStatement; +} +export interface ContinueStatement extends BaseASTNode { + type: NodeType.ContinueStatement; +} +export interface BreakStatement extends BaseASTNode { + type: NodeType.BreakStatement; +} +export interface ReturnStatement extends BaseASTNode { + type: NodeType.ReturnStatement; +} +export interface ThrowStatement extends BaseASTNode { + type: NodeType.ThrowStatement; +} +export interface VariableDeclarationStatement extends BaseASTNode { + type: NodeType.VariableDeclarationStatement; +} +export interface IdentifierList extends BaseASTNode { + type: NodeType.IdentifierList; +} +export interface ElementaryTypeName extends BaseASTNode { + type: NodeType.ElementaryTypeName; +} +export interface Expression extends BaseASTNode { + type: NodeType.Expression; +} +export interface PrimaryExpression extends BaseASTNode { + type: NodeType.PrimaryExpression; +} +export interface ExpressionList extends BaseASTNode { + type: NodeType.ExpressionList; +} +export interface NameValueList extends BaseASTNode { + type: NodeType.NameValueList; +} +export interface NameValue extends BaseASTNode { + type: NodeType.NameValue; +} +export interface FunctionCallArguments extends BaseASTNode { + type: NodeType.FunctionCallArguments; +} +export interface AssemblyBlock extends BaseASTNode { + type: NodeType.AssemblyBlock; +} +export interface AssemblyItem extends BaseASTNode { + type: NodeType.AssemblyItem; +} +export interface AssemblyExpression extends BaseASTNode { + type: NodeType.AssemblyExpression; +} +export interface AssemblyCall extends BaseASTNode { + type: NodeType.AssemblyCall; +} +export interface AssemblyLocalDefinition extends BaseASTNode { + type: NodeType.AssemblyLocalDefinition; +} +export interface AssemblyAssignment extends BaseASTNode { + type: NodeType.AssemblyAssignment; +} +export interface AssemblyIdentifierOrList extends BaseASTNode { + type: NodeType.AssemblyIdentifierOrList; +} +export interface AssemblyIdentifierList extends BaseASTNode { + type: NodeType.AssemblyIdentifierList; +} +export interface AssemblyStackAssignment extends BaseASTNode { + type: NodeType.AssemblyStackAssignment; +} +export interface LabelDefinition extends BaseASTNode { + type: NodeType.LabelDefinition; +} +export interface AssemblySwitch extends BaseASTNode { + type: NodeType.AssemblySwitch; +} +export interface AssemblyCase extends BaseASTNode { + type: NodeType.AssemblyCase; +} +export interface AssemblyFunctionDefinition extends BaseASTNode { + type: NodeType.AssemblyFunctionDefinition; +} +export interface AssemblyFunctionReturns extends BaseASTNode { + type: NodeType.AssemblyFunctionReturns; +} +export interface AssemblyFor extends BaseASTNode { + type: NodeType.AssemblyFor; +} +export interface AssemblyIf extends BaseASTNode { + type: NodeType.AssemblyIf; +} +export interface AssemblyLiteral extends BaseASTNode { + type: NodeType.AssemblyLiteral; +} +export interface SubAssembly extends BaseASTNode { + type: NodeType.SubAssembly; +} +export interface TupleExpression extends BaseASTNode { + type: NodeType.TupleExpression; +} +export interface ElementaryTypeNameExpression extends BaseASTNode { + type: NodeType.ElementaryTypeNameExpression; +} +export interface NumberLiteral extends BaseASTNode { + type: NodeType.NumberLiteral; +} +export interface Identifier extends BaseASTNode { + type: NodeType.Identifier; +} +export type BinOp = + | '+' + | '-' + | '*' + | '/' + | '**' + | '%' + | '<<' + | '>>' + | '&&' + | '||' + | '&' + | '|' + | '^' + | '<' + | '>' + | '<=' + | '>=' + | '==' + | '!=' + | '=' + | '|=' + | '^=' + | '&=' + | '<<=' + | '>>=' + | '+=' + | '-=' + | '*=' + | '/=' + | '%='; +export interface BinaryOperation extends BaseASTNode { + type: NodeType.BinaryOperation; + left: ASTNode; + right: ASTNode; + operator: BinOp; +} +export interface Conditional extends BaseASTNode { + type: NodeType.Conditional; + trueExpression: ASTNode; + falseExpression: ASTNode; +} + +export type ASTNode = + | SourceUnit + | PragmaDirective + | PragmaName + | PragmaValue + | Version + | VersionOperator + | VersionConstraint + | ImportDeclaration + | ImportDirective + | ContractDefinition + | InheritanceSpecifier + | ContractPart + | StateVariableDeclaration + | UsingForDeclaration + | StructDefinition + | ModifierDefinition + | ModifierInvocation + | FunctionDefinition + | ReturnParameters + | ModifierList + | EventDefinition + | EnumValue + | EnumDefinition + | ParameterList + | Parameter + | EventParameterList + | EventParameter + | FunctionTypeParameterList + | FunctionTypeParameter + | VariableDeclaration + | TypeName + | UserDefinedTypeName + | Mapping + | FunctionTypeName + | StorageLocation + | StateMutability + | Block + | Statement + | ExpressionStatement + | IfStatement + | WhileStatement + | SimpleStatement + | ForStatement + | InlineAssemblyStatement + | DoWhileStatement + | ContinueStatement + | BreakStatement + | ReturnStatement + | ThrowStatement + | VariableDeclarationStatement + | IdentifierList + | ElementaryTypeName + | Expression + | PrimaryExpression + | ExpressionList + | NameValueList + | NameValue + | FunctionCallArguments + | AssemblyBlock + | AssemblyItem + | AssemblyExpression + | AssemblyCall + | AssemblyLocalDefinition + | AssemblyAssignment + | AssemblyIdentifierOrList + | AssemblyIdentifierList + | AssemblyStackAssignment + | LabelDefinition + | AssemblySwitch + | AssemblyCase + | AssemblyFunctionDefinition + | AssemblyFunctionReturns + | AssemblyFor + | AssemblyIf + | AssemblyLiteral + | SubAssembly + | TupleExpression + | ElementaryTypeNameExpression + | NumberLiteral + | Identifier + | BinaryOperation + | Conditional; +export interface Visitor { + SourceUnit?: (node: SourceUnit) => void; + PragmaDirective?: (node: PragmaDirective) => void; + PragmaName?: (node: PragmaName) => void; + PragmaValue?: (node: PragmaValue) => void; + Version?: (node: Version) => void; + VersionOperator?: (node: VersionOperator) => void; + VersionConstraint?: (node: VersionConstraint) => void; + ImportDeclaration?: (node: ImportDeclaration) => void; + ImportDirective?: (node: ImportDirective) => void; + ContractDefinition?: (node: ContractDefinition) => void; + InheritanceSpecifier?: (node: InheritanceSpecifier) => void; + ContractPart?: (node: ContractPart) => void; + StateVariableDeclaration?: (node: StateVariableDeclaration) => void; + UsingForDeclaration?: (node: UsingForDeclaration) => void; + StructDefinition?: (node: StructDefinition) => void; + ModifierDefinition?: (node: ModifierDefinition) => void; + ModifierInvocation?: (node: ModifierInvocation) => void; + FunctionDefinition?: (node: FunctionDefinition) => void; + ReturnParameters?: (node: ReturnParameters) => void; + ModifierList?: (node: ModifierList) => void; + EventDefinition?: (node: EventDefinition) => void; + EnumValue?: (node: EnumValue) => void; + EnumDefinition?: (node: EnumDefinition) => void; + ParameterList?: (node: ParameterList) => void; + Parameter?: (node: Parameter) => void; + EventParameterList?: (node: EventParameterList) => void; + EventParameter?: (node: EventParameter) => void; + FunctionTypeParameterList?: (node: FunctionTypeParameterList) => void; + FunctionTypeParameter?: (node: FunctionTypeParameter) => void; + VariableDeclaration?: (node: VariableDeclaration) => void; + TypeName?: (node: TypeName) => void; + UserDefinedTypeName?: (node: UserDefinedTypeName) => void; + Mapping?: (node: Mapping) => void; + FunctionTypeName?: (node: FunctionTypeName) => void; + StorageLocation?: (node: StorageLocation) => void; + StateMutability?: (node: StateMutability) => void; + Block?: (node: Block) => void; + Statement?: (node: Statement) => void; + ExpressionStatement?: (node: ExpressionStatement) => void; + IfStatement?: (node: IfStatement) => void; + WhileStatement?: (node: WhileStatement) => void; + SimpleStatement?: (node: SimpleStatement) => void; + ForStatement?: (node: ForStatement) => void; + InlineAssemblyStatement?: (node: InlineAssemblyStatement) => void; + DoWhileStatement?: (node: DoWhileStatement) => void; + ContinueStatement?: (node: ContinueStatement) => void; + BreakStatement?: (node: BreakStatement) => void; + ReturnStatement?: (node: ReturnStatement) => void; + ThrowStatement?: (node: ThrowStatement) => void; + VariableDeclarationStatement?: (node: VariableDeclarationStatement) => void; + IdentifierList?: (node: IdentifierList) => void; + ElementaryTypeName?: (node: ElementaryTypeName) => void; + Expression?: (node: Expression) => void; + PrimaryExpression?: (node: PrimaryExpression) => void; + ExpressionList?: (node: ExpressionList) => void; + NameValueList?: (node: NameValueList) => void; + NameValue?: (node: NameValue) => void; + FunctionCallArguments?: (node: FunctionCallArguments) => void; + AssemblyBlock?: (node: AssemblyBlock) => void; + AssemblyItem?: (node: AssemblyItem) => void; + AssemblyExpression?: (node: AssemblyExpression) => void; + AssemblyCall?: (node: AssemblyCall) => void; + AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => void; + AssemblyAssignment?: (node: AssemblyAssignment) => void; + AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => void; + AssemblyIdentifierList?: (node: AssemblyIdentifierList) => void; + AssemblyStackAssignment?: (node: AssemblyStackAssignment) => void; + LabelDefinition?: (node: LabelDefinition) => void; + AssemblySwitch?: (node: AssemblySwitch) => void; + AssemblyCase?: (node: AssemblyCase) => void; + AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => void; + AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => void; + AssemblyFor?: (node: AssemblyFor) => void; + AssemblyIf?: (node: AssemblyIf) => void; + AssemblyLiteral?: (node: AssemblyLiteral) => void; + SubAssembly?: (node: SubAssembly) => void; + TupleExpression?: (node: TupleExpression) => void; + ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => void; + NumberLiteral?: (node: NumberLiteral) => void; + Identifier?: (node: Identifier) => void; + BinaryOperation?: (node: BinaryOperation) => void; + Conditional?: (node: Conditional) => void; +} +export interface ParserOpts { + tolerant?: boolean; + range?: boolean; + loc?: boolean; +} +export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode; +export function visit(ast: ASTNode, visitor: Visitor): void; From 02382c17599604099d390179a485e77d8fcb140c Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 12:34:09 -0700 Subject: [PATCH 03/76] Parser and Unparser --- packages/sol-meta/bin/sol-meta.js | 2 + packages/sol-meta/src/astToMock.ts | 6 + packages/sol-meta/src/cli.ts | 40 ++++++ packages/sol-meta/src/compiler.ts | 32 +++++ packages/sol-meta/src/index.ts | 0 packages/sol-meta/src/parser.ts | 3 + packages/sol-meta/src/transform.ts | 13 ++ packages/sol-meta/src/unparser.ts | 213 +++++++++++++++++++++++++++++ packages/sol-meta/test/dump | 0 packages/sol-meta/test/index.ts | 48 +++++++ 10 files changed, 357 insertions(+) create mode 100755 packages/sol-meta/bin/sol-meta.js create mode 100644 packages/sol-meta/src/astToMock.ts create mode 100644 packages/sol-meta/src/cli.ts create mode 100644 packages/sol-meta/src/compiler.ts create mode 100644 packages/sol-meta/src/index.ts create mode 100644 packages/sol-meta/src/parser.ts create mode 100644 packages/sol-meta/src/transform.ts create mode 100644 packages/sol-meta/src/unparser.ts create mode 100644 packages/sol-meta/test/dump create mode 100644 packages/sol-meta/test/index.ts diff --git a/packages/sol-meta/bin/sol-meta.js b/packages/sol-meta/bin/sol-meta.js new file mode 100755 index 0000000000..0e5b69af0e --- /dev/null +++ b/packages/sol-meta/bin/sol-meta.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../lib/src/cli.js') diff --git a/packages/sol-meta/src/astToMock.ts b/packages/sol-meta/src/astToMock.ts new file mode 100644 index 0000000000..5d3d23ddad --- /dev/null +++ b/packages/sol-meta/src/astToMock.ts @@ -0,0 +1,6 @@ + +import { ASTNode } from 'solidity-parser-antlr'; + +export function astToMock(ast: ASTNode): ASTNode { + return ast; +} diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts new file mode 100644 index 0000000000..0e670a6069 --- /dev/null +++ b/packages/sol-meta/src/cli.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env node +// We need the above pragma since this script will be run as a command-line tool. + +import { logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; +import 'source-map-support/register'; +import * as yargs from 'yargs'; + +import { Compiler } from './compiler'; + +const DEFAULT_CONTRACTS_LIST = '*'; +const SEPARATOR = ','; + +(async () => { + const argv = yargs + .option('contracts-dir', { + type: 'string', + description: 'path of contracts directory to compile', + }) + .option('contracts', { + type: 'string', + description: 'comma separated list of contracts to compile', + }) + .help().argv; + const contracts = _.isUndefined(argv.contracts) + ? undefined + : argv.contracts === DEFAULT_CONTRACTS_LIST + ? DEFAULT_CONTRACTS_LIST + : argv.contracts.split(SEPARATOR); + const opts = { + contractsDir: argv.contractsDir, + artifactsDir: argv.artifactsDir, + contracts, + }; + const compiler = new Compiler(opts); + await compiler.compileAsync(); +})().catch(err => { + logUtils.log(err); + process.exit(1); +}); diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts new file mode 100644 index 0000000000..defe6d8f69 --- /dev/null +++ b/packages/sol-meta/src/compiler.ts @@ -0,0 +1,32 @@ +import fs = require('fs'); +import { parse } from './parser'; +import { unparse } from './unparser'; + +export interface CompilerOptions { + contractsDir: string; + contracts: string; +} + +export class Compiler { + + private readonly opts: CompilerOptions; + + constructor(opts?: CompilerOptions) { + this.opts = opts || { + contractsDir: '', + contracts: '', + } ; + } + + public async compileAsync() { + console.log(this.opts.contracts[0]); + const source = fs.readFileSync(this.opts.contracts[0], 'utf8'); + try { + const ast = parse(source); + console.log(unparse(ast)); + } catch (e) { + console.log(e); + } + console.log('Compiling'); + } +} diff --git a/packages/sol-meta/src/index.ts b/packages/sol-meta/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sol-meta/src/parser.ts b/packages/sol-meta/src/parser.ts new file mode 100644 index 0000000000..312a5c0616 --- /dev/null +++ b/packages/sol-meta/src/parser.ts @@ -0,0 +1,3 @@ +import * as parser from 'solidity-parser-antlr'; + +export const parse = (source) => parser.parse(source, {}); diff --git a/packages/sol-meta/src/transform.ts b/packages/sol-meta/src/transform.ts new file mode 100644 index 0000000000..82d722376e --- /dev/null +++ b/packages/sol-meta/src/transform.ts @@ -0,0 +1,13 @@ +import { ASTNode } from 'solidity-parser-antlr'; + +const visitor = { + + // If a function is not public, add a wrapper to make it public + FunctionDefinition: func => + func.visbility, + +} + +export function transform(ast: ASTNode): ASTNode { + return (visitor[ast.type] || (a => a))(ast); +} diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts new file mode 100644 index 0000000000..9631e0f7f3 --- /dev/null +++ b/packages/sol-meta/src/unparser.ts @@ -0,0 +1,213 @@ +import { ASTNode } from 'solidity-parser-antlr'; + +const stresc = (s: string) => `\"${s}\"`; +const indent = (s: string) => `\t${s.replace(/\n/g, '\n\t')}`; +const block = (s: string) => `{\n${indent(s)}\n}`; +const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1'); + +const visitor = { + // Source level + + SourceUnit: ({children}) => + children.map(unparse).join('\n'), + + PragmaDirective: ({ name, value }) => + `pragma ${name} ${value};`, + + ImportDirective: ({ path, symbolAliases }) => + `import `+ + (symbolAliases ? `{${symbolAliases.map(([from, to]) => + from + (to ? ` as ${to}` : '') + ).join(', ')}} from `: '') + + `${stresc(path)};`, + + ContractDefinition: ({name, kind, baseContracts, subNodes}) => + `${kind} ${name} ${(baseContracts.length > 0 ? 'is ' : '')}` + + baseContracts.map(unparse).join(', ') + + block('\n' + subNodes.map(unparse).join('\n\n')), + + InheritanceSpecifier: ({baseName: {namePath}}) => + namePath, + + // Contract level + + UsingForDeclaration: ({typeName, libraryName}) => + `using ${libraryName} for ${unparse(typeName)};`, + + StateVariableDeclaration: ({variables}) => + variables.map(unparse).join(', ') + ';', + + StructDefinition: ({name, members}) => + `struct ${name} ${block(members.map(unparse).join(';\n') + ';')}`, + + EnumDefinition: ({name, members}) => + `enum ${name} ${block(members.map(unparse).join(',\n'))}`, + + EnumValue: ({name}) => + name, + + EventDefinition: ({ name, parameters }) => + `event ${name}${unparse(parameters)};`, + + ModifierDefinition: ({name, parameters, body}) => + `modifier ${name}${Array.isArray(parameters) ? '' : unparse(parameters)} ${unparse(body)}`, + // Note: when there is no parameter block, instead of an ASTNode there is a [] + + FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability}) => + (isConstructor ? 'constructor' : `function ${name}`) + + unparse(parameters) + ' ' + + (visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + '\n' + + indent(modifiers.map(unparse).join('\n')) + '\n' + + (body ? unparse(body) : ';'), + + ParameterList: ({parameters}) => + `(${parameters.map(unparse).join(', ')})`, + + Parameter: ({typeName, name, storageLocation}) => + `${unparse(typeName)} ${storageLocation || ''} ${name || ''}`, + + ModifierInvocation: ({name, arguments: args}) => + `${name}(${args.map(unparse).join(', ')})`, + + // Statements + + Block: ({statements}) => + block(statements.map(unparse).join('\n')), + + VariableDeclarationStatement: ({variables, initialValue}) => + variables.map(unparse) + + (initialValue ? ` = ${unparse(initialValue)};` : ';'), + + ExpressionStatement: ({expression}) => + `${unparse(expression)};`, + + EmitStatement: ({eventCall}) => + `emit ${unparen(unparse(eventCall))};`, + + ReturnStatement: ({expression}) => + `return ${expression ? unparse(expression) : ''};`, + + BreakStatement: ({}) => + `break;`, + + ContinueStatement: ({}) => + `continue;`, + + ThrowStatement: ({}) => + `throw;`, + + IfStatement: ({condition, trueBody, falseBody}) => + `if (${unparse(condition)})\n${unparse(trueBody)}` + + (falseBody ? `else\n${unparse(falseBody)}` : ''), + + ForStatement: ({initExpression: i, conditionExpression: c, loopExpression: l, body}) => + `for (${unparse(i).replace(';','')}; ${unparse(c)}; ${unparse(l).replace(';','')}) ${unparse(body)}`, + + InlineAssemblyStatement: ({language, body}) => // TODO language + `assembly ${unparse(body)}`, + + // Types + + ElementaryTypeName: ({name}) => + name, + + UserDefinedTypeName: ({namePath}) => + namePath, + + ArrayTypeName: ({baseTypeName, length}) => + `${unparse(baseTypeName)}[${length ? unparse(length) : ''}]`, + + Mapping: ({keyType, valueType}) => + `mapping (${unparse(keyType)} => ${unparse(valueType)})`, + + // Expressions + + Identifier: ({ name }) => + name, + + BooleanLiteral: ({ value }) => + value ? 'true' : 'false', + + NumberLiteral: ({number, subdenomination}) => // TODO subdenomination + number, + + StringLiteral: ({value}) => + stresc(value), + + FunctionCall: ({expression, arguments: args, names}) => // TODO: names + `(${unparse(expression)}(${args.map(unparse).join(', ')}))`, + + Conditional: ({condition, trueExpression, falseExpression}) => + `(${unparse(condition)} ? ${unparse(trueExpression)} : ${unparse(falseExpression)})`, + + UnaryOperation: ({operator, subExpression, isPrefix}) => + `(${isPrefix ? operator : ''}${unparse(subExpression)}${isPrefix ? '' : operator})`, + + BinaryOperation: ({operator, left, right}) => + `(${unparse(left)} ${operator} ${unparse(right)})`, + + MemberAccess: ({expression, memberName}) => + `(${unparse(expression)}.${memberName})`, + + IndexAccess: ({base, index}) => + `(${unparse(base)}[${unparse(index)}])`, + + ElementaryTypeNameExpression: ({typeName}) => + `(${unparse(typeName)})`, + + VariableDeclaration: ({typeName, name, visibility, isDeclaredConst, isIndexed, expression}) => + `${unparse(typeName)} ` + + (isIndexed ? 'indexed ' : '') + + (visibility && visibility != 'default' ? visibility + ' ' : '') + + (isDeclaredConst ? 'constant ' : '') + + `${name}` + + (expression ? ` = ${unparse(expression)}` : ''), + + NewExpression: ({typeName}) => + `(new ${unparse(typeName)})`, + + TupleExpression: ({components}) => + `[${components.map(unparse).join(', ')}]`, + + // Assembly + + AssemblyBlock: ({operations}) => + block(operations.map(unparse).join('\n')), + + AssemblyAssignment: ({names, expression}) => + `${names.map(unparse).join(', ')} := ${unparse(expression)}`, + + AssemblyLocalDefinition: ({names, expression}) => + `let ${names.map(unparse).join(', ')} := ${unparse(expression)}`, + + AssemblyCall: ({functionName, arguments: args}) => + args.length == 0 ? + functionName : + `${functionName}(${args.map(unparse).join(', ')})`, + + AssemblyIf: ({condition, body}) => + `if ${unparse(condition)} ${unparse(body)}`, + + AssemblyFor: ({pre, condition, post, body}) => + `for ${[pre, condition, post, body].map(unparse).join(' ')}`, + + AssemblySwitch: ({expression, cases}) => + `switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`, + + AssemblyCase: ({value, block}) => + `case ${unparse(value)} ${unparse(block)}` + + DecimalNumber: ({ value }) => + value, + + HexNumber: ({ value }) => + value, +} + +export function unparse(ast: ASTNode): string { + return (visitor[ast.type] || (a => { + console.log(a); + console.trace(); + return `<${a.type}>`; + }))(ast); +} diff --git a/packages/sol-meta/test/dump b/packages/sol-meta/test/dump new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sol-meta/test/index.ts b/packages/sol-meta/test/index.ts new file mode 100644 index 0000000000..88181e05eb --- /dev/null +++ b/packages/sol-meta/test/index.ts @@ -0,0 +1,48 @@ +import * as chai from 'chai'; +import 'mocha'; +import * as glob from 'glob'; +import * as fs from 'fs'; +import * as path from 'path'; +import { parse } from '../src/parser'; +import { unparse } from '../src/unparser'; + +const expect = chai.expect; + +const promisify = (func) => (...args) => + new Promise((resolve, reject) => + func(...args, (error, result) => + error ? reject(error) : resolve(result))); + +const findContracts = searchPath => + glob.sync(searchPath).map(file => ({ + name: path.basename(file, '.sol') + ` (${file})`, + source: fs.readFileSync(file, 'utf8') + })); + +const contracts = findContracts('../contracts/src/**/*.sol'); + +describe('Parser', () => { + + it('should have test contracts', () => { + expect(contracts).to.have.lengthOf.above(10); + }); + + contracts.forEach(({name, source}) => + it(`should parse ${name}`, () => { + parse(source); + }) + ); + +}); + +describe.only('Unparser', () => { + + contracts.forEach(({name, source}) => + it(`should unparse ${name}`, () => { + const ast = parse(source); + const src = unparse(ast) ; + const ast2 = parse(src); + //expect(ast2).to.deep.equal(ast); + }) + ); +}) From 3667fddb79cdfcf400d5449aa9a72c524d3f8814 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 12:38:16 -0700 Subject: [PATCH 04/76] Don't use calldata as a variable name --- .../Exchange/MixinSignatureValidator.sol | 12 ++++---- .../ReentrantERC20Token.sol | 30 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol index 176e283511..bd5b011b68 100644 --- a/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/2.0.0/protocol/Exchange/MixinSignatureValidator.sol @@ -239,18 +239,18 @@ contract MixinSignatureValidator is view returns (bool isValid) { - bytes memory calldata = abi.encodeWithSelector( + bytes memory cdata = abi.encodeWithSelector( IWallet(walletAddress).isValidSignature.selector, hash, signature ); assembly { - let cdStart := add(calldata, 32) + let cdStart := add(cdata, 32) let success := staticcall( gas, // forward all gas walletAddress, // address of Wallet contract cdStart, // pointer to start of input - mload(calldata), // length of input + mload(cdata), // length of input cdStart, // write output over input 32 // output size is 32 bytes ) @@ -288,19 +288,19 @@ contract MixinSignatureValidator is view returns (bool isValid) { - bytes memory calldata = abi.encodeWithSelector( + bytes memory cdata = abi.encodeWithSelector( IValidator(signerAddress).isValidSignature.selector, hash, signerAddress, signature ); assembly { - let cdStart := add(calldata, 32) + let cdStart := add(cdata, 32) let success := staticcall( gas, // forward all gas validatorAddress, // address of Validator contract cdStart, // pointer to start of input - mload(calldata), // length of input + mload(cdata), // length of input cdStart, // write output over input 32 // output size is 32 bytes ) diff --git a/packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol b/packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol index 99dd47a785..86561f83c3 100644 --- a/packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol +++ b/packages/contracts/src/2.0.0/test/ReentrantERC20Token/ReentrantERC20Token.sol @@ -92,53 +92,53 @@ contract ReentrantERC20Token is LibOrder.Order[] memory orders; uint256[] memory takerAssetFillAmounts; bytes[] memory signatures; - bytes memory calldata; + bytes memory cdata; - // Create calldata for function that corresponds to currentFunctionId + // Create cdata for function that corresponds to currentFunctionId if (currentFunctionId == uint8(ExchangeFunction.FILL_ORDER)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.fillOrder.selector, order, 0, signature ); } else if (currentFunctionId == uint8(ExchangeFunction.FILL_OR_KILL_ORDER)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.fillOrKillOrder.selector, order, 0, signature ); } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.batchFillOrders.selector, orders, takerAssetFillAmounts, signatures ); } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_FILL_OR_KILL_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.batchFillOrKillOrders.selector, orders, takerAssetFillAmounts, signatures ); } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_BUY_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.marketBuyOrders.selector, orders, 0, signatures ); } else if (currentFunctionId == uint8(ExchangeFunction.MARKET_SELL_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.marketSellOrders.selector, orders, 0, signatures ); } else if (currentFunctionId == uint8(ExchangeFunction.MATCH_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.matchOrders.selector, order, order, @@ -146,22 +146,22 @@ contract ReentrantERC20Token is signature ); } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDER)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.cancelOrder.selector, order ); } else if (currentFunctionId == uint8(ExchangeFunction.BATCH_CANCEL_ORDERS)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.batchCancelOrders.selector, orders ); } else if (currentFunctionId == uint8(ExchangeFunction.CANCEL_ORDERS_UP_TO)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.cancelOrdersUpTo.selector, 0 ); } else if (currentFunctionId == uint8(ExchangeFunction.SET_SIGNATURE_VALIDATOR_APPROVAL)) { - calldata = abi.encodeWithSelector( + cdata = abi.encodeWithSelector( EXCHANGE.setSignatureValidatorApproval.selector, address(0), false @@ -169,7 +169,7 @@ contract ReentrantERC20Token is } // Call Exchange function, swallow error - address(EXCHANGE).call(calldata); + address(EXCHANGE).call(cdata); // Revert reason is 100 bytes bytes memory returnData = new bytes(100); @@ -185,4 +185,4 @@ contract ReentrantERC20Token is // Transfer will return true if function failed for any other reason return true; } -} \ No newline at end of file +} From 9787b3602bd4be4ded16b195fb44fb7d9ee22515 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 17:46:46 -0700 Subject: [PATCH 05/76] Add exposer transform --- packages/sol-meta/src/exposer.ts | 267 +++++++++++++++++++++++++++++ packages/sol-meta/src/transform.ts | 13 -- 2 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 packages/sol-meta/src/exposer.ts delete mode 100644 packages/sol-meta/src/transform.ts diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts new file mode 100644 index 0000000000..ffc3f5484e --- /dev/null +++ b/packages/sol-meta/src/exposer.ts @@ -0,0 +1,267 @@ +import * as S from 'solidity-parser-antlr'; + +// Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap + +const flatMap = (a, f) => [].concat(...a.map(f)); + +const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => + (ast as any).children.filter(({type}) => type == 'PragmaDirective'); + +const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => + (ast as any).children.filter(({type}) => type == 'ContractDefinition'); + +const identifier = (name: string): S.ASTNode => + ( { + type: 'Identifier', + name, + }) + +// Creates a public getter for a state variable +const getter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { + const [{name, typeName }] = (stateVar as any).variables; + return { + type: 'FunctionDefinition', + name: `get_${name}`, + visibility: 'public', + isConstructor: false, + stateMutability: 'view', + parameters: { + type: 'ParameterList', + parameters: [] + }, + returnParameters: { + type: 'ParameterList', + parameters: [ + { + type: 'Parameter', + typeName, + name: null + } + ] + }, + modifiers: [], + body: { + type: 'Block', + statements: [ { + type: 'ReturnStatement', + expression: identifier(name) + }] + }, + }; +} + +// Creates a public getter for a state variable +const setter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { + const [{name, typeName }] = (stateVar as any).variables; + return { + type: 'FunctionDefinition', + name: `set_${name}`, + visibility: 'public', + isConstructor: false, + stateMutability: '', + parameters: { + type: 'ParameterList', + parameters: [ + { + type: 'Parameter', + typeName, + name: 'setterNewValue' + }, + ], + }, + returnParameters: null, + modifiers: [], + body: { + type: 'Block', + statements: [ { + type: 'ExpressionStatement', + expression: { + type: 'BinaryOperation', + operator: '=', + left: identifier(name), + right: identifier('setterNewValue') + } + }] + }, + }; +} + +// Creates a public wrapper for a function +const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { + const call = /**/ { + type: 'FunctionCall', + expression: identifier(func.name), + arguments: (func as any).parameters.parameters.map( + ({name}) => identifier(name) + ) + }; + return { + ...func, + name: `public_${func.name}`, + visibility: 'public', + modifiers: [], + body: { + type: 'Block', + statements: [ + (func as any).returnParameters ? + { + type: 'ReturnStatement', + expression: call + } : + { + type: 'ExpressionStatement', + expression: call + } + ] + } + }; +} + +// Creates a public function that triggers a log event +const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { + const {name, parameters} = event as any; + return { + type: 'FunctionDefinition', + name: `emit_${name}`, + visibility: 'public', + isConstructor: false, + stateMutability: '', + parameters: { + type: 'ParameterList', + parameters: parameters.parameters.map(param => ({ + ...param, + isIndexed: false + })) + }, + returnParameters: null, + modifiers: [], + body: { + type: 'Block', + statements: [/* */ { + type: 'EmitStatement', + eventCall: /**/ { + type: 'FunctionCall', + expression: identifier(name), + arguments: parameters.parameters.map( + ({name}) => identifier(name) + ) + } + }] + }, + }; +} + +// Creates a public function that has modifier +const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { + const {name, parameters} = modifier as any; + return { + type: 'FunctionDefinition', + name: `modifier_${name}`, + visibility: 'public', + isConstructor: false, + stateMutability: '', + parameters: Array.isArray(parameters) ? + { + type: 'ParameterList', + parameters: [] + } : + parameters, + returnParameters: { + type: 'ParameterList', + parameters: [ { + type: 'Parameter', + typeName: { + type: 'ElementaryTypeName', + name: 'bool' + }, + name: 'executed' + }] + }, + modifiers: [ { + type: 'ModifierInvocation', + name, + arguments: Array.isArray(parameters) ? + [] : + parameters.parameters.map( + ({name}) => identifier(name) + ) + }], + body: { + type: 'Block', + statements: [ + { + type: 'ReturnStatement', + expression: /**/ { + type: 'BooleanLiteral', + value: true, + } + } + ] + }, + }; +} + +const exposeNode = (ast: S.ASTNode): S.ASTNode[] => { + switch (ast.type) { + default: { + return []; + } + case 'StateVariableDeclaration': { + const [vardecl] = (ast as any).variables; + if (vardecl.visibility !== 'internal') { + return []; + } + return [ + getter(ast as S.StateVariableDeclaration), + setter(ast as S.StateVariableDeclaration) + ]; + // TODO: handle mappings: The keys become additional + // function arguments to the getter and setter. + } + case 'EventDefinition': { + return [emitEvent(ast)]; + } + case 'ModifierDefinition': { + return [testModifier(ast as S.ModifierDefinition)]; + } + case 'FunctionDefinition': { + const func = ast as any; + if (func.visibility !== 'internal') { + return []; + } + return [wrapFunction(func)]; + } + } +} + +export function expose(filePath: string, ast: S.SourceUnit): S.SourceUnit { + + // TODO: gp down inheritance hierarchy and expose those events. etc as well + // we probably want a separate `flattenInheritance` function or something + // that traces the imports and does a fairly simple concatenation. + + return { + type: 'SourceUnit', + children: [ + ...pragmaNodes(ast), + { + type: 'ImportDirective', + path: filePath, + unitAliases: null, + symbolAliases: null + }, + ...contracts(ast).map((ctr: any) => ( { + type: 'ContractDefinition', + kind: 'contract', + name: `${ctr.name}Exposed`, + baseContracts: [ { + type: 'InheritanceSpecifier', + baseName: { + namePath: ctr.name, + } + }], + subNodes: flatMap(ctr.subNodes, exposeNode), + })) + ] + } +} diff --git a/packages/sol-meta/src/transform.ts b/packages/sol-meta/src/transform.ts deleted file mode 100644 index 82d722376e..0000000000 --- a/packages/sol-meta/src/transform.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ASTNode } from 'solidity-parser-antlr'; - -const visitor = { - - // If a function is not public, add a wrapper to make it public - FunctionDefinition: func => - func.visbility, - -} - -export function transform(ast: ASTNode): ASTNode { - return (visitor[ast.type] || (a => a))(ast); -} From 62c069965a8ed3a794aebe561246a6473d833665 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 17:47:14 -0700 Subject: [PATCH 06/76] Test exposer --- packages/sol-meta/src/compiler.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts index defe6d8f69..b0b74566e0 100644 --- a/packages/sol-meta/src/compiler.ts +++ b/packages/sol-meta/src/compiler.ts @@ -1,6 +1,9 @@ import fs = require('fs'); import { parse } from './parser'; import { unparse } from './unparser'; +import { expose } from './exposer'; + +import * as util from 'util'; // DEBUG export interface CompilerOptions { contractsDir: string; @@ -19,14 +22,17 @@ export class Compiler { } public async compileAsync() { - console.log(this.opts.contracts[0]); - const source = fs.readFileSync(this.opts.contracts[0], 'utf8'); + const filePath = this.opts.contracts[0]; + const source = fs.readFileSync(filePath, 'utf8'); try { const ast = parse(source); - console.log(unparse(ast)); + //console.log(util.inspect(ast, {depth: 4})); + //console.log(unparse(ast)); + const astp = expose(filePath, ast); + //console.log(util.inspect(astp, {depth: 4})); + console.log(unparse(astp)); } catch (e) { console.log(e); } - console.log('Compiling'); } } From fd9d44bcdcd66f966ec3054cf777fd1a7fdd04dc Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 10 Oct 2018 17:47:54 -0700 Subject: [PATCH 07/76] Add returns to function declaration --- packages/sol-meta/src/unparser.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 9631e0f7f3..b4f6f81767 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -53,11 +53,14 @@ const visitor = { `modifier ${name}${Array.isArray(parameters) ? '' : unparse(parameters)} ${unparse(body)}`, // Note: when there is no parameter block, instead of an ASTNode there is a [] - FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability}) => + FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability, returnParameters}) => // TODO Return type (isConstructor ? 'constructor' : `function ${name}`) + - unparse(parameters) + ' ' + - (visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + '\n' + - indent(modifiers.map(unparse).join('\n')) + '\n' + + unparse(parameters) + '\n' + + indent( + (visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + + modifiers.map(unparse).join('\n') + + (returnParameters ? `\nreturns ${unparse(returnParameters)}` : '') + ) + '\n' + (body ? unparse(body) : ';'), ParameterList: ({parameters}) => @@ -79,7 +82,7 @@ const visitor = { (initialValue ? ` = ${unparse(initialValue)};` : ';'), ExpressionStatement: ({expression}) => - `${unparse(expression)};`, + `${unparen(unparse(expression))};`, EmitStatement: ({eventCall}) => `emit ${unparen(unparse(eventCall))};`, @@ -195,7 +198,7 @@ const visitor = { `switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`, AssemblyCase: ({value, block}) => - `case ${unparse(value)} ${unparse(block)}` + `case ${unparse(value)} ${unparse(block)}`, DecimalNumber: ({ value }) => value, From d3a60949baf2f96dd5a95c2a942c9ba838f4fdf3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:36:28 -0700 Subject: [PATCH 08/76] children is an array not a tuple --- .../typescript-typings/types/solidity-parser-antlr/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 7df3afa4db..00f6d7329f 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -107,7 +107,7 @@ export interface BaseASTNode { } export interface SourceUnit extends BaseASTNode { type: NodeType.SourceUnit; - children: [ASTNode]; + children: ASTNode[]; } export interface PragmaDirective extends BaseASTNode { type: NodeType.PragmaDirective; From 6ccb9061df55873dbdfc42148378ff95af47a7f2 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:36:58 -0700 Subject: [PATCH 09/76] variables can be internal or have default visibility --- .../typescript-typings/types/solidity-parser-antlr/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 00f6d7329f..b5da66c27d 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -209,7 +209,7 @@ export interface FunctionTypeParameter extends BaseASTNode { } export interface VariableDeclaration extends BaseASTNode { type: NodeType.VariableDeclaration; - visibility: 'public' | 'private'; + visibility: 'public' | 'private' | 'default' | 'internal'; isStateVar: boolean; } export interface TypeName extends BaseASTNode { From 7e3592fc3fdda32259901082ebc74c941b18d916 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:37:35 -0700 Subject: [PATCH 10/76] Add FunctionCall node type, remove FunctionCallArguments --- .../types/solidity-parser-antlr/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index b5da66c27d..43c42731fb 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -73,7 +73,7 @@ export enum NodeType { ExpressionList = 'ExpressionList', NameValueList = 'NameValueList', NameValue = 'NameValue', - FunctionCallArguments = 'FunctionCallArguments', + FunctionCall = 'FunctionCall', AssemblyBlock = 'AssemblyBlock', AssemblyItem = 'AssemblyItem', AssemblyExpression = 'AssemblyExpression', @@ -296,8 +296,8 @@ export interface NameValueList extends BaseASTNode { export interface NameValue extends BaseASTNode { type: NodeType.NameValue; } -export interface FunctionCallArguments extends BaseASTNode { - type: NodeType.FunctionCallArguments; +export interface FunctionCall extends BaseASTNode { + type: NodeType.FunctionCall; } export interface AssemblyBlock extends BaseASTNode { type: NodeType.AssemblyBlock; @@ -466,7 +466,7 @@ export type ASTNode = | ExpressionList | NameValueList | NameValue - | FunctionCallArguments + | FunctionCall | AssemblyBlock | AssemblyItem | AssemblyExpression From 2cee9c2bb079d3f8c59738129b6f176a2b41e06f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:37:57 -0700 Subject: [PATCH 11/76] add EmitStatement node type --- .../types/solidity-parser-antlr/index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 43c42731fb..1f6c396939 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -65,6 +65,7 @@ export enum NodeType { BreakStatement = 'BreakStatement', ReturnStatement = 'ReturnStatement', ThrowStatement = 'ThrowStatement', + EmitStatement = 'EmitStatement', VariableDeclarationStatement = 'VariableDeclarationStatement', IdentifierList = 'IdentifierList', ElementaryTypeName = 'ElementaryTypeName', @@ -272,6 +273,9 @@ export interface ReturnStatement extends BaseASTNode { export interface ThrowStatement extends BaseASTNode { type: NodeType.ThrowStatement; } +export interface EmitStatement extends BaseASTNode { + type: NodeType.EmitStatement; +} export interface VariableDeclarationStatement extends BaseASTNode { type: NodeType.VariableDeclarationStatement; } @@ -458,6 +462,7 @@ export type ASTNode = | BreakStatement | ReturnStatement | ThrowStatement + | EmitStatement | VariableDeclarationStatement | IdentifierList | ElementaryTypeName From 048155b5d6ecdfe0622b6d786ba323d91acc370b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:38:25 -0700 Subject: [PATCH 12/76] add BooleanLiteral node type --- .../types/solidity-parser-antlr/index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 1f6c396939..65a66b0b63 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -95,6 +95,7 @@ export enum NodeType { SubAssembly = 'SubAssembly', TupleExpression = 'TupleExpression', ElementaryTypeNameExpression = 'ElementaryTypeNameExpression', + BooleanLiteral = 'BooleanLiteral', NumberLiteral = 'NumberLiteral', Identifier = 'Identifier', BinaryOperation = 'BinaryOperation', @@ -363,6 +364,9 @@ export interface TupleExpression extends BaseASTNode { export interface ElementaryTypeNameExpression extends BaseASTNode { type: NodeType.ElementaryTypeNameExpression; } +export interface BooleanLiteral extends BaseASTNode { + type: NodeType.BooleanLiteral; +} export interface NumberLiteral extends BaseASTNode { type: NodeType.NumberLiteral; } @@ -492,6 +496,7 @@ export type ASTNode = | SubAssembly | TupleExpression | ElementaryTypeNameExpression + | BooleanLiteral | NumberLiteral | Identifier | BinaryOperation From 17d002e79bf0859b609e8e544610c470f1548224 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:39:36 -0700 Subject: [PATCH 13/76] make Visitor generic --- .../types/solidity-parser-antlr/index.d.ts | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 65a66b0b63..446401ea30 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -501,89 +501,90 @@ export type ASTNode = | Identifier | BinaryOperation | Conditional; -export interface Visitor { - SourceUnit?: (node: SourceUnit) => void; - PragmaDirective?: (node: PragmaDirective) => void; - PragmaName?: (node: PragmaName) => void; - PragmaValue?: (node: PragmaValue) => void; - Version?: (node: Version) => void; - VersionOperator?: (node: VersionOperator) => void; - VersionConstraint?: (node: VersionConstraint) => void; - ImportDeclaration?: (node: ImportDeclaration) => void; - ImportDirective?: (node: ImportDirective) => void; - ContractDefinition?: (node: ContractDefinition) => void; - InheritanceSpecifier?: (node: InheritanceSpecifier) => void; - ContractPart?: (node: ContractPart) => void; - StateVariableDeclaration?: (node: StateVariableDeclaration) => void; - UsingForDeclaration?: (node: UsingForDeclaration) => void; - StructDefinition?: (node: StructDefinition) => void; - ModifierDefinition?: (node: ModifierDefinition) => void; - ModifierInvocation?: (node: ModifierInvocation) => void; - FunctionDefinition?: (node: FunctionDefinition) => void; - ReturnParameters?: (node: ReturnParameters) => void; - ModifierList?: (node: ModifierList) => void; - EventDefinition?: (node: EventDefinition) => void; - EnumValue?: (node: EnumValue) => void; - EnumDefinition?: (node: EnumDefinition) => void; - ParameterList?: (node: ParameterList) => void; - Parameter?: (node: Parameter) => void; - EventParameterList?: (node: EventParameterList) => void; - EventParameter?: (node: EventParameter) => void; - FunctionTypeParameterList?: (node: FunctionTypeParameterList) => void; - FunctionTypeParameter?: (node: FunctionTypeParameter) => void; - VariableDeclaration?: (node: VariableDeclaration) => void; - TypeName?: (node: TypeName) => void; - UserDefinedTypeName?: (node: UserDefinedTypeName) => void; - Mapping?: (node: Mapping) => void; - FunctionTypeName?: (node: FunctionTypeName) => void; - StorageLocation?: (node: StorageLocation) => void; - StateMutability?: (node: StateMutability) => void; - Block?: (node: Block) => void; - Statement?: (node: Statement) => void; - ExpressionStatement?: (node: ExpressionStatement) => void; - IfStatement?: (node: IfStatement) => void; - WhileStatement?: (node: WhileStatement) => void; - SimpleStatement?: (node: SimpleStatement) => void; - ForStatement?: (node: ForStatement) => void; - InlineAssemblyStatement?: (node: InlineAssemblyStatement) => void; - DoWhileStatement?: (node: DoWhileStatement) => void; - ContinueStatement?: (node: ContinueStatement) => void; - BreakStatement?: (node: BreakStatement) => void; - ReturnStatement?: (node: ReturnStatement) => void; - ThrowStatement?: (node: ThrowStatement) => void; - VariableDeclarationStatement?: (node: VariableDeclarationStatement) => void; - IdentifierList?: (node: IdentifierList) => void; - ElementaryTypeName?: (node: ElementaryTypeName) => void; - Expression?: (node: Expression) => void; - PrimaryExpression?: (node: PrimaryExpression) => void; - ExpressionList?: (node: ExpressionList) => void; - NameValueList?: (node: NameValueList) => void; - NameValue?: (node: NameValue) => void; - FunctionCallArguments?: (node: FunctionCallArguments) => void; - AssemblyBlock?: (node: AssemblyBlock) => void; - AssemblyItem?: (node: AssemblyItem) => void; - AssemblyExpression?: (node: AssemblyExpression) => void; - AssemblyCall?: (node: AssemblyCall) => void; - AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => void; - AssemblyAssignment?: (node: AssemblyAssignment) => void; - AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => void; - AssemblyIdentifierList?: (node: AssemblyIdentifierList) => void; - AssemblyStackAssignment?: (node: AssemblyStackAssignment) => void; - LabelDefinition?: (node: LabelDefinition) => void; - AssemblySwitch?: (node: AssemblySwitch) => void; - AssemblyCase?: (node: AssemblyCase) => void; - AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => void; - AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => void; - AssemblyFor?: (node: AssemblyFor) => void; - AssemblyIf?: (node: AssemblyIf) => void; - AssemblyLiteral?: (node: AssemblyLiteral) => void; - SubAssembly?: (node: SubAssembly) => void; - TupleExpression?: (node: TupleExpression) => void; - ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => void; - NumberLiteral?: (node: NumberLiteral) => void; - Identifier?: (node: Identifier) => void; - BinaryOperation?: (node: BinaryOperation) => void; - Conditional?: (node: Conditional) => void; +export interface Visitor { + SourceUnit?: (node: SourceUnit) => T; + PragmaDirective?: (node: PragmaDirective) => T; + PragmaName?: (node: PragmaName) => T; + PragmaValue?: (node: PragmaValue) => T; + Version?: (node: Version) => T; + VersionOperator?: (node: VersionOperator) => T; + VersionConstraint?: (node: VersionConstraint) => T; + ImportDeclaration?: (node: ImportDeclaration) => T; + ImportDirective?: (node: ImportDirective) => T; + ContractDefinition?: (node: ContractDefinition) => T; + InheritanceSpecifier?: (node: InheritanceSpecifier) => T; + ContractPart?: (node: ContractPart) => T; + StateVariableDeclaration?: (node: StateVariableDeclaration) => T; + UsingForDeclaration?: (node: UsingForDeclaration) => T; + StructDefinition?: (node: StructDefinition) => T; + ModifierDefinition?: (node: ModifierDefinition) => T; + ModifierInvocation?: (node: ModifierInvocation) => T; + FunctionDefinition?: (node: FunctionDefinition) => T; + ReturnParameters?: (node: ReturnParameters) => T; + ModifierList?: (node: ModifierList) => T; + EventDefinition?: (node: EventDefinition) => T; + EnumValue?: (node: EnumValue) => T; + EnumDefinition?: (node: EnumDefinition) => T; + ParameterList?: (node: ParameterList) => T; + Parameter?: (node: Parameter) => T; + EventParameterList?: (node: EventParameterList) => T; + EventParameter?: (node: EventParameter) => T; + FunctionTypeParameterList?: (node: FunctionTypeParameterList) => T; + FunctionTypeParameter?: (node: FunctionTypeParameter) => T; + VariableDeclaration?: (node: VariableDeclaration) => T; + TypeName?: (node: TypeName) => T; + UserDefinedTypeName?: (node: UserDefinedTypeName) => T; + Mapping?: (node: Mapping) => T; + FunctionTypeName?: (node: FunctionTypeName) => T; + StorageLocation?: (node: StorageLocation) => T; + StateMutability?: (node: StateMutability) => T; + Block?: (node: Block) => T; + Statement?: (node: Statement) => T; + ExpressionStatement?: (node: ExpressionStatement) => T; + IfStatement?: (node: IfStatement) => T; + WhileStatement?: (node: WhileStatement) => T; + SimpleStatement?: (node: SimpleStatement) => T; + ForStatement?: (node: ForStatement) => T; + InlineAssemblyStatement?: (node: InlineAssemblyStatement) => T; + DoWhileStatement?: (node: DoWhileStatement) => T; + ContinueStatement?: (node: ContinueStatement) => T; + BreakStatement?: (node: BreakStatement) => T; + ReturnStatement?: (node: ReturnStatement) => T; + ThrowStatement?: (node: ThrowStatement) => T; + VariableDeclarationStatement?: (node: VariableDeclarationStatement) => T; + IdentifierList?: (node: IdentifierList) => T; + ElementaryTypeName?: (node: ElementaryTypeName) => T; + Expression?: (node: Expression) => T; + PrimaryExpression?: (node: PrimaryExpression) => T; + ExpressionList?: (node: ExpressionList) => T; + NameValueList?: (node: NameValueList) => T; + NameValue?: (node: NameValue) => T; + FunctionCall?: (node: FunctionCall) => T; + AssemblyBlock?: (node: AssemblyBlock) => T; + AssemblyItem?: (node: AssemblyItem) => T; + AssemblyExpression?: (node: AssemblyExpression) => T; + AssemblyCall?: (node: AssemblyCall) => T; + AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => T; + AssemblyAssignment?: (node: AssemblyAssignment) => T; + AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => T; + AssemblyIdentifierList?: (node: AssemblyIdentifierList) => T; + AssemblyStackAssignment?: (node: AssemblyStackAssignment) => T; + LabelDefinition?: (node: LabelDefinition) => T; + AssemblySwitch?: (node: AssemblySwitch) => T; + AssemblyCase?: (node: AssemblyCase) => T; + AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => T; + AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => T; + AssemblyFor?: (node: AssemblyFor) => T; + AssemblyIf?: (node: AssemblyIf) => T; + AssemblyLiteral?: (node: AssemblyLiteral) => T; + SubAssembly?: (node: SubAssembly) => T; + TupleExpression?: (node: TupleExpression) => T; + ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => T; + BooleanLiteral?: (node: BooleanLiteral) => T; + NumberLiteral?: (node: NumberLiteral) => T; + Identifier?: (node: Identifier) => T; + BinaryOperation?: (node: BinaryOperation) => T; + Conditional?: (node: Conditional) => T; } export interface ParserOpts { tolerant?: boolean; @@ -591,4 +592,4 @@ export interface ParserOpts { loc?: boolean; } export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode; -export function visit(ast: ASTNode, visitor: Visitor): void; +export function visit(ast: ASTNode, visitor: Visitor): void; From 120d872556367af9092802bd356510c19ca74cb2 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 11 Oct 2018 17:40:03 -0700 Subject: [PATCH 14/76] FunctionDeclaration has body --- .../typescript-typings/types/solidity-parser-antlr/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 446401ea30..19a4d9a5d3 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -172,6 +172,7 @@ export interface ModifierInvocation extends BaseASTNode { export interface FunctionDefinition extends BaseASTNode { type: NodeType.FunctionDefinition; name: string; + body: Block | null; } export interface ReturnParameters extends BaseASTNode { type: NodeType.ReturnParameters; From 36bdbec0219ca4878e76acd6b149dee836acaa56 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 12:53:17 -0700 Subject: [PATCH 15/76] typeify some grammar --- .../types/solidity-parser-antlr/index.d.ts | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 19a4d9a5d3..47a16ba1a9 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -14,7 +14,6 @@ export interface Location { start: LineColumn; end: LineColumn; } - export enum NodeType { SourceUnit = 'SourceUnit', PragmaDirective = 'PragmaDirective', @@ -53,7 +52,6 @@ export enum NodeType { StorageLocation = 'StorageLocation', StateMutability = 'StateMutability', Block = 'Block', - Statement = 'Statement', ExpressionStatement = 'ExpressionStatement', IfStatement = 'IfStatement', WhileStatement = 'WhileStatement', @@ -69,7 +67,6 @@ export enum NodeType { VariableDeclarationStatement = 'VariableDeclarationStatement', IdentifierList = 'IdentifierList', ElementaryTypeName = 'ElementaryTypeName', - Expression = 'Expression', PrimaryExpression = 'PrimaryExpression', ExpressionList = 'ExpressionList', NameValueList = 'NameValueList', @@ -109,7 +106,7 @@ export interface BaseASTNode { } export interface SourceUnit extends BaseASTNode { type: NodeType.SourceUnit; - children: ASTNode[]; + children: SourceMembers[]; } export interface PragmaDirective extends BaseASTNode { type: NodeType.PragmaDirective; @@ -142,8 +139,8 @@ export interface ContractDefinition extends BaseASTNode { type: NodeType.ContractDefinition; name: string; kind: string; - baseContracts: [string]; - subNodes: [string]; + baseContracts: InheritanceSpecifier[]; + subNodes: ContractMembers[] } export interface InheritanceSpecifier extends BaseASTNode { type: NodeType.InheritanceSpecifier; @@ -190,7 +187,7 @@ export interface EnumValue extends BaseASTNode { export interface EnumDefinition extends BaseASTNode { type: NodeType.EnumDefinition; name: string; - members: [ASTNode]; + members: ASTNode[]; } export interface ParameterList extends BaseASTNode { type: NodeType.ParameterList; @@ -236,9 +233,6 @@ export interface StateMutability extends BaseASTNode { export interface Block extends BaseASTNode { type: NodeType.Block; } -export interface Statement extends BaseASTNode { - type: NodeType.Statement; -} export interface ExpressionStatement extends BaseASTNode { type: NodeType.ExpressionStatement; expression: ASTNode; @@ -416,7 +410,30 @@ export interface Conditional extends BaseASTNode { trueExpression: ASTNode; falseExpression: ASTNode; } - +export type SourceMembers = + | PragmaDirective + | ImportDirective + | ContractDefinition; +export type ContractMembers = + | UsingForDeclaration + | StateVariableDeclaration + | StructDefinition + | EnumDefinition + | EventDefinition + | ModifierDefinition + | FunctionDefinition; +export type Statement = + | Block + | VariableDeclarationStatement + | ExpressionStatement + | EmitStatement + | ReturnStatement + | BreakStatement + | ContinueStatement + | ThrowStatement + | IfStatement + | ForStatement + | InlineAssemblyStatement export type ASTNode = | SourceUnit | PragmaDirective @@ -540,7 +557,6 @@ export interface Visitor { StorageLocation?: (node: StorageLocation) => T; StateMutability?: (node: StateMutability) => T; Block?: (node: Block) => T; - Statement?: (node: Statement) => T; ExpressionStatement?: (node: ExpressionStatement) => T; IfStatement?: (node: IfStatement) => T; WhileStatement?: (node: WhileStatement) => T; From 38430199c5dc79f8cea89c49f009ed405cad31a4 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 12:54:23 -0700 Subject: [PATCH 16/76] Add StringLiteral and UnaryOperation --- .../types/solidity-parser-antlr/index.d.ts | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 47a16ba1a9..01e1956911 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -94,10 +94,49 @@ export enum NodeType { ElementaryTypeNameExpression = 'ElementaryTypeNameExpression', BooleanLiteral = 'BooleanLiteral', NumberLiteral = 'NumberLiteral', + StringLiteral = 'StringLiteral', Identifier = 'Identifier', + UnaryOperation = 'UnaryOperation', BinaryOperation = 'BinaryOperation', Conditional = 'Conditional', } +export type UnOp = + | '++' + | '--' + | '-' + | '+' + | '!'; +export type BinOp = + | '+' + | '-' + | '*' + | '/' + | '**' + | '%' + | '<<' + | '>>' + | '&&' + | '||' + | '&' + | '|' + | '^' + | '<' + | '>' + | '<=' + | '>=' + | '==' + | '!=' + | '=' + | '|=' + | '^=' + | '&=' + | '<<=' + | '>>=' + | '+=' + | '-=' + | '*=' + | '/=' + | '%='; export interface BaseASTNode { type: NodeType; @@ -281,9 +320,6 @@ export interface IdentifierList extends BaseASTNode { export interface ElementaryTypeName extends BaseASTNode { type: NodeType.ElementaryTypeName; } -export interface Expression extends BaseASTNode { - type: NodeType.Expression; -} export interface PrimaryExpression extends BaseASTNode { type: NodeType.PrimaryExpression; } @@ -365,40 +401,17 @@ export interface BooleanLiteral extends BaseASTNode { export interface NumberLiteral extends BaseASTNode { type: NodeType.NumberLiteral; } +export interface StringLiteral extends BaseASTNode { + type: NodeType.StringLiteral; +} export interface Identifier extends BaseASTNode { type: NodeType.Identifier; } -export type BinOp = - | '+' - | '-' - | '*' - | '/' - | '**' - | '%' - | '<<' - | '>>' - | '&&' - | '||' - | '&' - | '|' - | '^' - | '<' - | '>' - | '<=' - | '>=' - | '==' - | '!=' - | '=' - | '|=' - | '^=' - | '&=' - | '<<=' - | '>>=' - | '+=' - | '-=' - | '*=' - | '/=' - | '%='; +export interface UnaryOperation extends BaseASTNode { + type: NodeType.BinaryOperation; + subExrpession: Expression; + operator: UnOp; +} export interface BinaryOperation extends BaseASTNode { type: NodeType.BinaryOperation; left: ASTNode; @@ -599,7 +612,9 @@ export interface Visitor { ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => T; BooleanLiteral?: (node: BooleanLiteral) => T; NumberLiteral?: (node: NumberLiteral) => T; + StringLiteral?: (node: StringLiteral) => T; Identifier?: (node: Identifier) => T; + UnaryOperation?: (node: UnaryOperation) => T; BinaryOperation?: (node: BinaryOperation) => T; Conditional?: (node: Conditional) => T; } From 557f05931e92cd2d0e7f6d2ddcb6f6c8a2b9990d Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 15:29:15 -0700 Subject: [PATCH 17/76] stricter types --- .../types/solidity-parser-antlr/index.d.ts | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 01e1956911..a60115735d 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -14,7 +14,7 @@ export interface Location { start: LineColumn; end: LineColumn; } -export enum NodeType { +export const enum NodeType { SourceUnit = 'SourceUnit', PragmaDirective = 'PragmaDirective', PragmaName = 'PragmaName', @@ -100,6 +100,17 @@ export enum NodeType { BinaryOperation = 'BinaryOperation', Conditional = 'Conditional', } +export const enum Visibility { + Default = 'default', + Public = 'public', + Internal = 'internal', + Private = 'private', +} +export const enum StateMutability { + Default = null, + View = 'view', + Pure = 'pure' +} export type UnOp = | '++' | '--' @@ -179,10 +190,11 @@ export interface ContractDefinition extends BaseASTNode { name: string; kind: string; baseContracts: InheritanceSpecifier[]; - subNodes: ContractMembers[] + subNodes: ContractMember[] } export interface InheritanceSpecifier extends BaseASTNode { type: NodeType.InheritanceSpecifier; + baseName: UserDefinedTypeName; } export interface ContractPart extends BaseASTNode { type: NodeType.ContractPart; @@ -190,6 +202,7 @@ export interface ContractPart extends BaseASTNode { export interface StateVariableDeclaration extends BaseASTNode { type: NodeType.StateVariableDeclaration; variables: VariableDeclaration[]; + initialValue: Expression | null; } export interface UsingForDeclaration extends BaseASTNode { type: NodeType.UsingForDeclaration; @@ -208,7 +221,14 @@ export interface ModifierInvocation extends BaseASTNode { export interface FunctionDefinition extends BaseASTNode { type: NodeType.FunctionDefinition; name: string; + parameters: ParameterList; + returnParameters: ParameterList | null; body: Block | null; + visibility: Visibility; + modifiers: ModifierInvocation[]; + isConstructor: boolean; + stateMutability: StateMutability; + } export interface ReturnParameters extends BaseASTNode { type: NodeType.ReturnParameters; @@ -218,6 +238,9 @@ export interface ModifierList extends BaseASTNode { } export interface EventDefinition extends BaseASTNode { type: NodeType.EventDefinition; + name: string; + parameters: ParameterList; + isAnonymous: boolean; } export interface EnumValue extends BaseASTNode { type: NodeType.EnumValue; @@ -248,14 +271,20 @@ export interface FunctionTypeParameter extends BaseASTNode { } export interface VariableDeclaration extends BaseASTNode { type: NodeType.VariableDeclaration; - visibility: 'public' | 'private' | 'default' | 'internal'; + name: string; + visibility: Visibility; + typeName: Type; + expression: Expression; isStateVar: boolean; + isDeclaredConst: boolean; + isIndexed: boolean; } export interface TypeName extends BaseASTNode { type: NodeType.TypeName; } export interface UserDefinedTypeName extends BaseASTNode { type: NodeType.UserDefinedTypeName; + namePath: string; } export interface Mapping extends BaseASTNode { type: NodeType.Mapping; @@ -271,15 +300,17 @@ export interface StateMutability extends BaseASTNode { } export interface Block extends BaseASTNode { type: NodeType.Block; + statements: Statement[]; } export interface ExpressionStatement extends BaseASTNode { type: NodeType.ExpressionStatement; - expression: ASTNode; + expression: Expression; } export interface IfStatement extends BaseASTNode { type: NodeType.IfStatement; - trueBody: ASTNode; - falseBody: ASTNode; + condition: Expression; + trueBody: Statement; + falseBody: Statement; } export interface WhileStatement extends BaseASTNode { type: NodeType.WhileStatement; @@ -304,12 +335,14 @@ export interface BreakStatement extends BaseASTNode { } export interface ReturnStatement extends BaseASTNode { type: NodeType.ReturnStatement; + expression: Expression | null; } export interface ThrowStatement extends BaseASTNode { type: NodeType.ThrowStatement; } export interface EmitStatement extends BaseASTNode { type: NodeType.EmitStatement; + eventCall: FunctionCall; } export interface VariableDeclarationStatement extends BaseASTNode { type: NodeType.VariableDeclarationStatement; @@ -319,6 +352,7 @@ export interface IdentifierList extends BaseASTNode { } export interface ElementaryTypeName extends BaseASTNode { type: NodeType.ElementaryTypeName; + name: string; } export interface PrimaryExpression extends BaseASTNode { type: NodeType.PrimaryExpression; @@ -334,6 +368,9 @@ export interface NameValue extends BaseASTNode { } export interface FunctionCall extends BaseASTNode { type: NodeType.FunctionCall; + expression: Expression; + arguments: Expression[]; + names: []; } export interface AssemblyBlock extends BaseASTNode { type: NodeType.AssemblyBlock; @@ -391,21 +428,27 @@ export interface SubAssembly extends BaseASTNode { } export interface TupleExpression extends BaseASTNode { type: NodeType.TupleExpression; + components: Expression[]; } export interface ElementaryTypeNameExpression extends BaseASTNode { type: NodeType.ElementaryTypeNameExpression; + typeName: Type; } export interface BooleanLiteral extends BaseASTNode { type: NodeType.BooleanLiteral; + value: boolean; } export interface NumberLiteral extends BaseASTNode { type: NodeType.NumberLiteral; + number: string; } export interface StringLiteral extends BaseASTNode { type: NodeType.StringLiteral; + value: string; } export interface Identifier extends BaseASTNode { type: NodeType.Identifier; + name: string; } export interface UnaryOperation extends BaseASTNode { type: NodeType.BinaryOperation; @@ -414,20 +457,20 @@ export interface UnaryOperation extends BaseASTNode { } export interface BinaryOperation extends BaseASTNode { type: NodeType.BinaryOperation; - left: ASTNode; - right: ASTNode; + left: Expression; + right: Expression; operator: BinOp; } export interface Conditional extends BaseASTNode { type: NodeType.Conditional; - trueExpression: ASTNode; - falseExpression: ASTNode; + trueExpression: Expression; + falseExpression: Expression; } -export type SourceMembers = +export type SourceMember = | PragmaDirective | ImportDirective | ContractDefinition; -export type ContractMembers = +export type ContractMember = | UsingForDeclaration | StateVariableDeclaration | StructDefinition @@ -447,6 +490,23 @@ export type Statement = | IfStatement | ForStatement | InlineAssemblyStatement +export type Expression = + | BooleanLiteral + | NumberLiteral + | StringLiteral + | Identifier + | FunctionCall + | Conditional + | UnaryOperation + | BinaryOperation + | MemberAccess + | IndexAccess + | ElementaryTypeNameExpression + | VariableDeclaration // TODO: Is this really an expression? + | NewExpression + | TupleExpression +export type Type = + | ElementaryType export type ASTNode = | SourceUnit | PragmaDirective @@ -584,7 +644,6 @@ export interface Visitor { VariableDeclarationStatement?: (node: VariableDeclarationStatement) => T; IdentifierList?: (node: IdentifierList) => T; ElementaryTypeName?: (node: ElementaryTypeName) => T; - Expression?: (node: Expression) => T; PrimaryExpression?: (node: PrimaryExpression) => T; ExpressionList?: (node: ExpressionList) => T; NameValueList?: (node: NameValueList) => T; From fa2925af766c5d3236881178e513a88cedc5f942 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 15:30:51 -0700 Subject: [PATCH 18/76] move utils to separate file --- packages/sol-meta/src/exposer.ts | 18 ++---------------- packages/sol-meta/src/utils.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 packages/sol-meta/src/utils.ts diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index ffc3f5484e..bf0dbeaec6 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -1,20 +1,6 @@ import * as S from 'solidity-parser-antlr'; - -// Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap - -const flatMap = (a, f) => [].concat(...a.map(f)); - -const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - (ast as any).children.filter(({type}) => type == 'PragmaDirective'); - -const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => - (ast as any).children.filter(({type}) => type == 'ContractDefinition'); - -const identifier = (name: string): S.ASTNode => - ( { - type: 'Identifier', - name, - }) +import * as utils from './utils'; +import {identifier} from './utils'; // Creates a public getter for a state variable const getter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts new file mode 100644 index 0000000000..15a8c45740 --- /dev/null +++ b/packages/sol-meta/src/utils.ts @@ -0,0 +1,19 @@ +import * as S from 'solidity-parser-antlr'; + +// TODO: Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap +export const flatMap = (a, f) => [].concat(...a.map(f)); + +export const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => + (ast as any).children.filter(({type}) => type == 'PragmaDirective'); + +export const importNodes = (ast: S.SourceUnit): S.PragmaDirective[] => + (ast as any).children.filter(({type}) => type == 'ImportDirective'); + +export const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => + (ast as any).children.filter(({type}) => type == 'ContractDefinition'); + +export const identifier = (name: string): S.ASTNode => + ( { + type: 'Identifier', + name, + }) From fb0b1a86e4f4e48a92a9e8da3d2ea7de8f2c544a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 17:48:30 -0700 Subject: [PATCH 19/76] typesafe storage location, contract kind and more --- .../types/solidity-parser-antlr/index.d.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index a60115735d..be7e50d4cf 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -100,6 +100,11 @@ export const enum NodeType { BinaryOperation = 'BinaryOperation', Conditional = 'Conditional', } +export const enum ContractKind { + Contract = 'contract', + Interface = 'interface', + Library = 'library' +} export const enum Visibility { Default = 'default', Public = 'public', @@ -111,6 +116,12 @@ export const enum StateMutability { View = 'view', Pure = 'pure' } +export const enum StorageLocation { + Default = null, + Memory = 'memory', + Storage = 'storage', + Calldata = 'calldata' +} export type UnOp = | '++' | '--' @@ -188,7 +199,7 @@ export interface ImportDirective extends BaseASTNode { export interface ContractDefinition extends BaseASTNode { type: NodeType.ContractDefinition; name: string; - kind: string; + kind: ContractKind; baseContracts: InheritanceSpecifier[]; subNodes: ContractMember[] } @@ -217,6 +228,7 @@ export interface ModifierDefinition extends BaseASTNode { export interface ModifierInvocation extends BaseASTNode { type: NodeType.ModifierInvocation; name: string; + arguments: Expression[]; } export interface FunctionDefinition extends BaseASTNode { type: NodeType.FunctionDefinition; @@ -253,9 +265,13 @@ export interface EnumDefinition extends BaseASTNode { } export interface ParameterList extends BaseASTNode { type: NodeType.ParameterList; + parameters: Parameter[]; } export interface Parameter extends BaseASTNode { type: NodeType.Parameter; + name: string | null; + typeName: Type; + storageLocation: StorageLocation | null; } export interface EventParameterList extends BaseASTNode { type: NodeType.EventParameterList; From d4b889847e90c2dcd6dc7b2e5c4a1be85b938fe5 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 17:49:20 -0700 Subject: [PATCH 20/76] stricter types --- packages/sol-meta/src/exposer.ts | 191 +++++++++++++++---------------- packages/sol-meta/src/parser.ts | 7 +- packages/sol-meta/src/utils.ts | 15 ++- 3 files changed, 108 insertions(+), 105 deletions(-) diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index bf0dbeaec6..f01cf9f7f7 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -3,33 +3,32 @@ import * as utils from './utils'; import {identifier} from './utils'; // Creates a public getter for a state variable -const getter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { +const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { const [{name, typeName }] = (stateVar as any).variables; - return { - type: 'FunctionDefinition', + return { + type: S.NodeType.FunctionDefinition, name: `get_${name}`, - visibility: 'public', + visibility: S.Visibility.Public, isConstructor: false, - stateMutability: 'view', - parameters: { - type: 'ParameterList', + stateMutability: S.StateMutability.View, + parameters: { + type: S.NodeType.ParameterList, parameters: [] }, - returnParameters: { - type: 'ParameterList', - parameters: [ - { - type: 'Parameter', - typeName, - name: null - } - ] + returnParameters: { + type: S.NodeType.ParameterList, + parameters: [{ + type: S.NodeType.Parameter, + typeName, + name: null, + storageLocation: S.StorageLocation.Default, + }] }, modifiers: [], - body: { - type: 'Block', - statements: [ { - type: 'ReturnStatement', + body: { + type: S.NodeType.Block, + statements: [{ + type: S.NodeType.ReturnStatement, expression: identifier(name) }] }, @@ -37,32 +36,31 @@ const getter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { } // Creates a public getter for a state variable -const setter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { +const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { const [{name, typeName }] = (stateVar as any).variables; - return { - type: 'FunctionDefinition', + return { + type: S.NodeType.FunctionDefinition, name: `set_${name}`, - visibility: 'public', + visibility: S.Visibility.Public, isConstructor: false, - stateMutability: '', - parameters: { - type: 'ParameterList', - parameters: [ - { - type: 'Parameter', - typeName, - name: 'setterNewValue' - }, - ], + stateMutability: S.StateMutability.Default, + parameters: { + type: S.NodeType.ParameterList, + parameters: [{ + type: S.NodeType.Parameter, + typeName, + name: 'setterNewValue', + storageLocation: S.StorageLocation.Default, + }], }, returnParameters: null, modifiers: [], - body: { - type: 'Block', - statements: [ { - type: 'ExpressionStatement', - expression: { - type: 'BinaryOperation', + body: { + type: S.NodeType.Block, + statements: [{ + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.BinaryOperation, operator: '=', left: identifier(name), right: identifier('setterNewValue') @@ -74,28 +72,28 @@ const setter = (stateVar: S.StateVariableDeclaration): S.ASTNode => { // Creates a public wrapper for a function const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { - const call = /**/ { - type: 'FunctionCall', + const call = { + type: S.NodeType.FunctionCall, expression: identifier(func.name), arguments: (func as any).parameters.parameters.map( ({name}) => identifier(name) ) }; - return { + return { ...func, name: `public_${func.name}`, - visibility: 'public', + visibility: S.Visibility.Public, modifiers: [], - body: { - type: 'Block', + body: { + type: S.NodeType.Block, statements: [ (func as any).returnParameters ? - { - type: 'ReturnStatement', + { + type: S.NodeType.ReturnStatement, expression: call } : - { - type: 'ExpressionStatement', + { + type: S.NodeType.ExpressionStatement, expression: call } ] @@ -106,14 +104,14 @@ const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { // Creates a public function that triggers a log event const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { const {name, parameters} = event as any; - return { - type: 'FunctionDefinition', + return { + type: S.NodeType.FunctionDefinition, name: `emit_${name}`, - visibility: 'public', + visibility: S.Visibility.Public, isConstructor: false, - stateMutability: '', - parameters: { - type: 'ParameterList', + stateMutability: S.StateMutability.Default, + parameters: { + type: S.NodeType.ParameterList, parameters: parameters.parameters.map(param => ({ ...param, isIndexed: false @@ -121,16 +119,17 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { }, returnParameters: null, modifiers: [], - body: { - type: 'Block', - statements: [/* */ { - type: 'EmitStatement', - eventCall: /**/ { - type: 'FunctionCall', + body: { + type: S.NodeType.Block, + statements: [/* */ { + type: S.NodeType.EmitStatement, + eventCall: /**/ { + type: S.NodeType.FunctionCall, expression: identifier(name), arguments: parameters.parameters.map( ({name}) => identifier(name) - ) + ), + names: [] } }] }, @@ -140,31 +139,32 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { // Creates a public function that has modifier const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { const {name, parameters} = modifier as any; - return { - type: 'FunctionDefinition', + return { + type: S.NodeType.FunctionDefinition, name: `modifier_${name}`, - visibility: 'public', + visibility: S.Visibility.Public, isConstructor: false, - stateMutability: '', + stateMutability: S.StateMutability.Default, parameters: Array.isArray(parameters) ? - { - type: 'ParameterList', + { + type: S.NodeType.ParameterList, parameters: [] } : parameters, - returnParameters: { - type: 'ParameterList', - parameters: [ { - type: 'Parameter', - typeName: { - type: 'ElementaryTypeName', + returnParameters: { + type: S.NodeType.ParameterList, + parameters: [{ + type: S.NodeType.Parameter, + typeName: { + type: S.NodeType.ElementaryTypeName, name: 'bool' }, - name: 'executed' + name: 'executed', + storageLocation: S.StorageLocation.Default }] }, - modifiers: [ { - type: 'ModifierInvocation', + modifiers: [{ + type: S.NodeType.ModifierInvocation, name, arguments: Array.isArray(parameters) ? [] : @@ -172,13 +172,13 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { ({name}) => identifier(name) ) }], - body: { - type: 'Block', + body: { + type: S.NodeType.Block, statements: [ - { - type: 'ReturnStatement', - expression: /**/ { - type: 'BooleanLiteral', + { + type: S.NodeType.ReturnStatement, + expression: { + type: S.NodeType.BooleanLiteral, value: true, } } @@ -187,7 +187,7 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { }; } -const exposeNode = (ast: S.ASTNode): S.ASTNode[] => { +const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { switch (ast.type) { default: { return []; @@ -226,27 +226,26 @@ export function expose(filePath: string, ast: S.SourceUnit): S.SourceUnit { // we probably want a separate `flattenInheritance` function or something // that traces the imports and does a fairly simple concatenation. - return { - type: 'SourceUnit', - children: [ - ...pragmaNodes(ast), - { - type: 'ImportDirective', + return { + type: S.NodeType.SourceUnit, + children: [ + ...utils.pragmaNodes(ast), { + type: S.NodeType.ImportDirective, path: filePath, unitAliases: null, symbolAliases: null }, - ...contracts(ast).map((ctr: any) => ( { - type: 'ContractDefinition', + ...utils.contracts(ast).map((ctr: any) => ({ + type: S.NodeType.ContractDefinition, kind: 'contract', name: `${ctr.name}Exposed`, - baseContracts: [ { - type: 'InheritanceSpecifier', + baseContracts: [{ + type: S.NodeType.InheritanceSpecifier, baseName: { namePath: ctr.name, } }], - subNodes: flatMap(ctr.subNodes, exposeNode), + subNodes: utils.flatMap(ctr.subNodes, exposeNode), })) ] } diff --git a/packages/sol-meta/src/parser.ts b/packages/sol-meta/src/parser.ts index 312a5c0616..e53d17467f 100644 --- a/packages/sol-meta/src/parser.ts +++ b/packages/sol-meta/src/parser.ts @@ -1,3 +1,8 @@ import * as parser from 'solidity-parser-antlr'; -export const parse = (source) => parser.parse(source, {}); +export const parse = (source: string): parser.SourceUnit => + parser.parse(source, {}); + +// TODO: Replace [] with empty parameter list on modifiers +// TODO: +// TODO: Push these fixes to upstream and remove them here. diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 15a8c45740..4657dd057c 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -4,16 +4,15 @@ import * as S from 'solidity-parser-antlr'; export const flatMap = (a, f) => [].concat(...a.map(f)); export const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - (ast as any).children.filter(({type}) => type == 'PragmaDirective'); + ast.children.filter(({type}) => type == S.NodeType.PragmaDirective); export const importNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - (ast as any).children.filter(({type}) => type == 'ImportDirective'); + ast.children.filter(({type}) => type == S.NodeType.ImportDirective); export const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => - (ast as any).children.filter(({type}) => type == 'ContractDefinition'); + ast.children.filter(({type}) => type == S.NodeType.ContractDefinition); -export const identifier = (name: string): S.ASTNode => - ( { - type: 'Identifier', - name, - }) +export const identifier = (name: string): S.Identifier => ({ + type: S.NodeType.Identifier, + name, +}) From 2805a5ccfbd3dc081fcec4307879f6e61e2a3821 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 17:49:56 -0700 Subject: [PATCH 21/76] add mocker --- packages/sol-meta/src/compiler.ts | 7 +- packages/sol-meta/src/mocker.ts | 119 ++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 packages/sol-meta/src/mocker.ts diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts index b0b74566e0..1b716d1b27 100644 --- a/packages/sol-meta/src/compiler.ts +++ b/packages/sol-meta/src/compiler.ts @@ -2,6 +2,7 @@ import fs = require('fs'); import { parse } from './parser'; import { unparse } from './unparser'; import { expose } from './exposer'; +import { mock } from './mocker'; import * as util from 'util'; // DEBUG @@ -26,10 +27,10 @@ export class Compiler { const source = fs.readFileSync(filePath, 'utf8'); try { const ast = parse(source); - //console.log(util.inspect(ast, {depth: 4})); + //console.log(util.inspect(ast, {depth: 40})); //console.log(unparse(ast)); - const astp = expose(filePath, ast); - //console.log(util.inspect(astp, {depth: 4})); + const astp = mock(ast); + console.log(util.inspect(astp, {depth: 4})); console.log(unparse(astp)); } catch (e) { console.log(e); diff --git a/packages/sol-meta/src/mocker.ts b/packages/sol-meta/src/mocker.ts new file mode 100644 index 0000000000..3810579099 --- /dev/null +++ b/packages/sol-meta/src/mocker.ts @@ -0,0 +1,119 @@ +import * as S from 'solidity-parser-antlr'; +import * as utils from './utils'; +import {identifier} from './utils'; + +const uint256: S.ElementaryTypeName = ({ + type: S.NodeType.ElementaryTypeName, + name: 'uint256' +}); + +const zero: S.NumberLiteral = ({ + type: S.NodeType.NumberLiteral, + number: '0', + // TODO subdenomination: null +}); + +const id = (name:string): S.Identifier => ({ + type: S.NodeType.Identifier, + name +}); + +const call = (func: S.Expression, ...args: S.Expression[]): S.FunctionCall => ({ + type: S.NodeType.FunctionCall, + expression: func, + arguments: args, + names: [] +}); + +const emit = (name: string, ...args: S.Expression[]): S.EmitStatement => ({ + type: S.NodeType.EmitStatement, + eventCall: call(id(name), ...args) +}); + +const makeCounter = (name: string): S.StateVariableDeclaration => ({ + type: S.NodeType.StateVariableDeclaration, + variables: [{ + type: S.NodeType.VariableDeclaration, + typeName: uint256, + name, + expression: zero, + visibility: S.Visibility.Internal, + isStateVar: true, + isDeclaredConst: false, + isIndexed: false + }], + initialValue: zero, +}); + +const makeEvent = (name: string, parameters: S.ParameterList): S.EventDefinition => ({ + type: S.NodeType.EventDefinition, + name, + parameters, + isAnonymous: false +}); + +const makeAction = (name: string, parameters: S.ParameterList): S.FunctionDefinition => ({ + type: S.NodeType.FunctionDefinition, + name, + parameters, + returnParameters: null, + body: { + type: S.NodeType.Block, + statements: [ + emit( + name, + id('dispatchTransferFrom_counter'), + ...(parameters as any).parameters.map(({name}) => id(name)) + ), + { + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.UnaryOperation, + operator: '++', + subExpression: id('dispatchTransferFrom_counter'), + isPrefix: false + } + } + ] + }, + visibility: S.Visibility.Internal, + modifiers: [], + isConstructor: false, + stateMutability: S.StateMutability.Default, +}); + +const isDeclaration = (func: S.FunctionDefinition) => func.body == null; + +const visitor = { + + default: (node) => node, +} + +export function mock(ast: S.SourceUnit): S.SourceUnit { + + // TODO: gp down inheritance hierarchy and expose those events. etc as well + // we probably want a separate `flattenInheritance` function or something + // that traces the imports and does a fairly simple concatenation. + return { + type: S.NodeType.SourceUnit, + children: [ + ...utils.pragmaNodes(ast), + ...utils.importNodes(ast), + ...utils.contracts(ast).map((ctr): S.ContractDefinition => ({ + type: S.NodeType.ContractDefinition, + kind: S.ContractKind.Contract, + name: `${ctr.name}Exposed`, + baseContracts: [{ + type: S.NodeType.InheritanceSpecifier, + baseName: { + type: S.NodeType.UserDefinedTypeName, + namePath: ctr.name, + } + }], + subNodes: utils.flatMap(ctr.subNodes, node => + (visitor[node.type] || visitor.default)(node) + ), + })) + ] + } +} From b7a5e18b09a3d0e9f9ee25d6ee2dfd04e4878625 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 12 Oct 2018 17:50:36 -0700 Subject: [PATCH 22/76] cleanup --- packages/sol-meta/README.md | 6 +++++- packages/sol-meta/src/astToMock.ts | 6 ------ packages/sol-meta/src/unparser.ts | 5 ++++- 3 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 packages/sol-meta/src/astToMock.ts diff --git a/packages/sol-meta/README.md b/packages/sol-meta/README.md index 05030798de..303e16fcdd 100644 --- a/packages/sol-meta/README.md +++ b/packages/sol-meta/README.md @@ -2,10 +2,14 @@ Sol-meta is a Solidity to Solidity compiler to automatically generate testing contracts. It has two modes: -**Mock generation.** Given an interface contract (either a public interface or a mixin), it can generate a mock implementation. +**Mocking.** Given an interface contract (either a public interface or a mixin), it can generate a mock implementation. **Exposing.** Given an implemented contract it generates a contract that exposes all internal functions with public wrappers. +**Flattening.** Given a contract it will pull in all inherited contracts and flatten them into a single contract without inheritance. + +**Combined.** Flatten a contract and then generate a wrapper that adds mocks for all unimplemented functions and exposes all internal members. + ## Architecture Source --Parser--> AST --transform--> AST --unparser--> Source diff --git a/packages/sol-meta/src/astToMock.ts b/packages/sol-meta/src/astToMock.ts deleted file mode 100644 index 5d3d23ddad..0000000000 --- a/packages/sol-meta/src/astToMock.ts +++ /dev/null @@ -1,6 +0,0 @@ - -import { ASTNode } from 'solidity-parser-antlr'; - -export function astToMock(ast: ASTNode): ASTNode { - return ast; -} diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index b4f6f81767..36ab9b720f 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -1,3 +1,5 @@ +// TODO: instead use https://github.com/prettier-solidity/prettier-plugin-solidity/blob/master/src/printer.js + import { ASTNode } from 'solidity-parser-antlr'; const stresc = (s: string) => `\"${s}\"`; @@ -6,6 +8,7 @@ const block = (s: string) => `{\n${indent(s)}\n}`; const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1'); const visitor = { + // Source level SourceUnit: ({children}) => @@ -53,7 +56,7 @@ const visitor = { `modifier ${name}${Array.isArray(parameters) ? '' : unparse(parameters)} ${unparse(body)}`, // Note: when there is no parameter block, instead of an ASTNode there is a [] - FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability, returnParameters}) => // TODO Return type + FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability, returnParameters}) => (isConstructor ? 'constructor' : `function ${name}`) + unparse(parameters) + '\n' + indent( From de0e443a3fa78ac6182898997267867879d598d5 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 13 Oct 2018 00:14:26 -0700 Subject: [PATCH 23/76] add missing typing for unparser --- .../types/solidity-parser-antlr/index.d.ts | 289 ++++++++++++------ 1 file changed, 200 insertions(+), 89 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index be7e50d4cf..2a5e79cdd7 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -6,6 +6,8 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.1 +// See: https://solidity.readthedocs.io/en/develop/miscellaneous.html#language-grammar + export interface LineColumn { line: number; column: number; @@ -48,6 +50,7 @@ export const enum NodeType { TypeName = 'TypeName', UserDefinedTypeName = 'UserDefinedTypeName', Mapping = 'Mapping', + ArrayTypeName = 'ArrayTypeName', FunctionTypeName = 'FunctionTypeName', StorageLocation = 'StorageLocation', StateMutability = 'StateMutability', @@ -99,6 +102,11 @@ export const enum NodeType { UnaryOperation = 'UnaryOperation', BinaryOperation = 'BinaryOperation', Conditional = 'Conditional', + IndexAccess = 'IndexAccess', + MemberAccess = 'MemberAccess', + NewExpression = 'NewExpression', + DecimalNumber = 'DecimalNumber', + HexNumber = 'HexNumber', } export const enum ContractKind { Contract = 'contract', @@ -174,6 +182,7 @@ export interface PragmaDirective extends BaseASTNode { name: string; value: string; } +/* export interface PragmaName extends BaseASTNode { type: NodeType.PragmaName; } @@ -192,9 +201,11 @@ export interface VersionConstraint extends BaseASTNode { export interface ImportDeclaration extends BaseASTNode { type: NodeType.ImportDeclaration; } +*/ export interface ImportDirective extends BaseASTNode { type: NodeType.ImportDirective; path: string; + symbolAliases: [string, string][]; } export interface ContractDefinition extends BaseASTNode { type: NodeType.ContractDefinition; @@ -207,28 +218,46 @@ export interface InheritanceSpecifier extends BaseASTNode { type: NodeType.InheritanceSpecifier; baseName: UserDefinedTypeName; } +/* export interface ContractPart extends BaseASTNode { type: NodeType.ContractPart; } +*/ +export interface UsingForDeclaration extends BaseASTNode { + type: NodeType.UsingForDeclaration; + typeName: Type; + libraryName: string; +} export interface StateVariableDeclaration extends BaseASTNode { type: NodeType.StateVariableDeclaration; variables: VariableDeclaration[]; - initialValue: Expression | null; -} -export interface UsingForDeclaration extends BaseASTNode { - type: NodeType.UsingForDeclaration; + initialValue: Expression | null; // TODO check if exists } export interface StructDefinition extends BaseASTNode { type: NodeType.StructDefinition; + name: string; + members: VariableDeclaration[]; } -export interface ModifierDefinition extends BaseASTNode { - type: NodeType.ModifierDefinition; +export interface EnumDefinition extends BaseASTNode { + type: NodeType.EnumDefinition; name: string; + members: EnumValue[]; } -export interface ModifierInvocation extends BaseASTNode { - type: NodeType.ModifierInvocation; +export interface EnumValue extends BaseASTNode { + type: NodeType.EnumValue; name: string; - arguments: Expression[]; +} +export interface EventDefinition extends BaseASTNode { + type: NodeType.EventDefinition; + name: string; + parameters: ParameterList; + isAnonymous: boolean; +} +export interface ModifierDefinition extends BaseASTNode { + type: NodeType.ModifierDefinition; + name: string; + parameters: ParameterList; + body: Block; } export interface FunctionDefinition extends BaseASTNode { type: NodeType.FunctionDefinition; @@ -240,28 +269,11 @@ export interface FunctionDefinition extends BaseASTNode { modifiers: ModifierInvocation[]; isConstructor: boolean; stateMutability: StateMutability; - -} -export interface ReturnParameters extends BaseASTNode { - type: NodeType.ReturnParameters; -} -export interface ModifierList extends BaseASTNode { - type: NodeType.ModifierList; } -export interface EventDefinition extends BaseASTNode { - type: NodeType.EventDefinition; - name: string; - parameters: ParameterList; - isAnonymous: boolean; -} -export interface EnumValue extends BaseASTNode { - type: NodeType.EnumValue; - name: string; -} -export interface EnumDefinition extends BaseASTNode { - type: NodeType.EnumDefinition; +export interface ModifierInvocation extends BaseASTNode { + type: NodeType.ModifierInvocation; name: string; - members: ASTNode[]; + arguments: Expression[]; } export interface ParameterList extends BaseASTNode { type: NodeType.ParameterList; @@ -273,6 +285,7 @@ export interface Parameter extends BaseASTNode { typeName: Type; storageLocation: StorageLocation | null; } +/* export interface EventParameterList extends BaseASTNode { type: NodeType.EventParameterList; } @@ -285,6 +298,11 @@ export interface FunctionTypeParameterList extends BaseASTNode { export interface FunctionTypeParameter extends BaseASTNode { type: NodeType.FunctionTypeParameter; } +*/ +export interface Block extends BaseASTNode { + type: NodeType.Block; + statements: Statement[]; +} export interface VariableDeclaration extends BaseASTNode { type: NodeType.VariableDeclaration; name: string; @@ -295,29 +313,17 @@ export interface VariableDeclaration extends BaseASTNode { isDeclaredConst: boolean; isIndexed: boolean; } +/* export interface TypeName extends BaseASTNode { type: NodeType.TypeName; } -export interface UserDefinedTypeName extends BaseASTNode { - type: NodeType.UserDefinedTypeName; - namePath: string; -} -export interface Mapping extends BaseASTNode { - type: NodeType.Mapping; -} -export interface FunctionTypeName extends BaseASTNode { - type: NodeType.FunctionTypeName; -} export interface StorageLocation extends BaseASTNode { type: NodeType.StorageLocation; } export interface StateMutability extends BaseASTNode { type: NodeType.StateMutability; } -export interface Block extends BaseASTNode { - type: NodeType.Block; - statements: Statement[]; -} +*/ export interface ExpressionStatement extends BaseASTNode { type: NodeType.ExpressionStatement; expression: Expression; @@ -330,18 +336,23 @@ export interface IfStatement extends BaseASTNode { } export interface WhileStatement extends BaseASTNode { type: NodeType.WhileStatement; -} -export interface SimpleStatement extends BaseASTNode { - type: NodeType.SimpleStatement; + // TODO } export interface ForStatement extends BaseASTNode { type: NodeType.ForStatement; + initExpression: Expression; + conditionExpression: Expression; + loopExpression: Expression; + body: Statement; } export interface InlineAssemblyStatement extends BaseASTNode { type: NodeType.InlineAssemblyStatement; + language: string; + body: AssemblyBlock; } export interface DoWhileStatement extends BaseASTNode { type: NodeType.DoWhileStatement; + // TODO } export interface ContinueStatement extends BaseASTNode { type: NodeType.ContinueStatement; @@ -362,25 +373,71 @@ export interface EmitStatement extends BaseASTNode { } export interface VariableDeclarationStatement extends BaseASTNode { type: NodeType.VariableDeclarationStatement; + variables: VariableDeclaration[]; + initialValue: Expression; } -export interface IdentifierList extends BaseASTNode { - type: NodeType.IdentifierList; +export interface NewExpression extends BaseASTNode { + type: NodeType.NewExpression; + typeName: Type; } + +// Types export interface ElementaryTypeName extends BaseASTNode { type: NodeType.ElementaryTypeName; name: string; } +export interface UserDefinedTypeName extends BaseASTNode { + type: NodeType.UserDefinedTypeName; + namePath: string; +} +export interface Mapping extends BaseASTNode { + type: NodeType.Mapping; + keyType: Type; + valueType: Type; +} +export interface ArrayTypeName extends BaseASTNode { + type: NodeType.Mapping; + baseTypeName: Type; + length: Expression | null; +} +export interface FunctionTypeName extends BaseASTNode { + type: NodeType.FunctionTypeName; + // TODO +} +/* export interface PrimaryExpression extends BaseASTNode { type: NodeType.PrimaryExpression; } export interface ExpressionList extends BaseASTNode { type: NodeType.ExpressionList; } +*/ export interface NameValueList extends BaseASTNode { type: NodeType.NameValueList; + // TODO } export interface NameValue extends BaseASTNode { type: NodeType.NameValue; + // TODO +} + +// Expressions +export interface Identifier extends BaseASTNode { + type: NodeType.Identifier; + name: string; +} +export interface BooleanLiteral extends BaseASTNode { + type: NodeType.BooleanLiteral; + value: boolean; +} +export interface NumberLiteral extends BaseASTNode { + type: NodeType.NumberLiteral; + number: string; + subdenomination: any; // TODO +} +export interface StringLiteral extends BaseASTNode { + type: NodeType.StringLiteral; + value: string; } export interface FunctionCall extends BaseASTNode { type: NodeType.FunctionCall; @@ -388,24 +445,72 @@ export interface FunctionCall extends BaseASTNode { arguments: Expression[]; names: []; } +export interface TupleExpression extends BaseASTNode { + type: NodeType.TupleExpression; + components: Expression[]; +} +export interface ElementaryTypeNameExpression extends BaseASTNode { + type: NodeType.ElementaryTypeNameExpression; + typeName: Type; +} +export interface UnaryOperation extends BaseASTNode { + type: NodeType.UnaryOperation; + operator: UnOp; + isPrefix: boolean; + subExpression: Expression; +} +export interface BinaryOperation extends BaseASTNode { + type: NodeType.BinaryOperation; + operator: BinOp; + left: Expression; + right: Expression; +} +export interface Conditional extends BaseASTNode { + type: NodeType.Conditional; + condition: Expression; + trueExpression: Expression; + falseExpression: Expression; +} +export interface IndexAccess extends BaseASTNode { + type: NodeType.IndexAccess; + base: Expression; + index: Expression; +} +export interface MemberAccess extends BaseASTNode { + type: NodeType.MemberAccess; + expression: Expression; + memberName: string; +} + + export interface AssemblyBlock extends BaseASTNode { type: NodeType.AssemblyBlock; + operations: AssemblyStatement[]; } +/* export interface AssemblyItem extends BaseASTNode { type: NodeType.AssemblyItem; } export interface AssemblyExpression extends BaseASTNode { type: NodeType.AssemblyExpression; } +*/ export interface AssemblyCall extends BaseASTNode { type: NodeType.AssemblyCall; + functionName: string; + arguments: AssemblyExpression[]; } export interface AssemblyLocalDefinition extends BaseASTNode { type: NodeType.AssemblyLocalDefinition; + names: Identifier[]; + expression: AssemblyExpression; } export interface AssemblyAssignment extends BaseASTNode { type: NodeType.AssemblyAssignment; + names: Identifier[]; + expression: AssemblyExpression; } +/* export interface AssemblyIdentifierOrList extends BaseASTNode { type: NodeType.AssemblyIdentifierOrList; } @@ -415,73 +520,59 @@ export interface AssemblyIdentifierList extends BaseASTNode { export interface AssemblyStackAssignment extends BaseASTNode { type: NodeType.AssemblyStackAssignment; } +*/ export interface LabelDefinition extends BaseASTNode { type: NodeType.LabelDefinition; + // TODO } export interface AssemblySwitch extends BaseASTNode { type: NodeType.AssemblySwitch; + expression: Expression; + cases: AssemblyCase[]; } export interface AssemblyCase extends BaseASTNode { type: NodeType.AssemblyCase; + value: Expression; + block: AssemblyBlock; } export interface AssemblyFunctionDefinition extends BaseASTNode { type: NodeType.AssemblyFunctionDefinition; + // TODO } export interface AssemblyFunctionReturns extends BaseASTNode { type: NodeType.AssemblyFunctionReturns; + // TODO } export interface AssemblyFor extends BaseASTNode { type: NodeType.AssemblyFor; + pre: AssemblyBlock | AssemblyExpression; + condition: AssemblyExpression; + post: AssemblyBlock | AssemblyExpression; + body: AssemblyBlock; } export interface AssemblyIf extends BaseASTNode { type: NodeType.AssemblyIf; + condition: AssemblyExpression; + body: AssemblyBlock; } +export interface DecimalNumber extends BaseASTNode { + type: NodeType.AssemblyIf; + value: string; +} +export interface HexNumber extends BaseASTNode { + type: NodeType.AssemblyIf; + value: string; +} + + +/* export interface AssemblyLiteral extends BaseASTNode { type: NodeType.AssemblyLiteral; } export interface SubAssembly extends BaseASTNode { type: NodeType.SubAssembly; } -export interface TupleExpression extends BaseASTNode { - type: NodeType.TupleExpression; - components: Expression[]; -} -export interface ElementaryTypeNameExpression extends BaseASTNode { - type: NodeType.ElementaryTypeNameExpression; - typeName: Type; -} -export interface BooleanLiteral extends BaseASTNode { - type: NodeType.BooleanLiteral; - value: boolean; -} -export interface NumberLiteral extends BaseASTNode { - type: NodeType.NumberLiteral; - number: string; -} -export interface StringLiteral extends BaseASTNode { - type: NodeType.StringLiteral; - value: string; -} -export interface Identifier extends BaseASTNode { - type: NodeType.Identifier; - name: string; -} -export interface UnaryOperation extends BaseASTNode { - type: NodeType.BinaryOperation; - subExrpession: Expression; - operator: UnOp; -} -export interface BinaryOperation extends BaseASTNode { - type: NodeType.BinaryOperation; - left: Expression; - right: Expression; - operator: BinOp; -} -export interface Conditional extends BaseASTNode { - type: NodeType.Conditional; - trueExpression: Expression; - falseExpression: Expression; -} +*/ export type SourceMember = | PragmaDirective | ImportDirective @@ -521,8 +612,22 @@ export type Expression = | VariableDeclaration // TODO: Is this really an expression? | NewExpression | TupleExpression + | IndexAccess + | MemberAccess export type Type = | ElementaryType +export type AssemblyStatement = + | AssemblyCall + | AssemblyAssignment + | AssemblyLocalDefinition + | AssemblyIf + | AssemblyFor + | AssemblySwitch + | AssemblyCase +export type AssemblyExpression = + | AssemblyCall + | DecimalNumber + | HexNumber export type ASTNode = | SourceUnit | PragmaDirective @@ -585,7 +690,6 @@ export type ASTNode = | FunctionCall | AssemblyBlock | AssemblyItem - | AssemblyExpression | AssemblyCall | AssemblyLocalDefinition | AssemblyAssignment @@ -642,6 +746,7 @@ export interface Visitor { TypeName?: (node: TypeName) => T; UserDefinedTypeName?: (node: UserDefinedTypeName) => T; Mapping?: (node: Mapping) => T; + ArrayTypeName?: (node: ArrayTypeName) => T; FunctionTypeName?: (node: FunctionTypeName) => T; StorageLocation?: (node: StorageLocation) => T; StateMutability?: (node: StateMutability) => T; @@ -657,6 +762,7 @@ export interface Visitor { BreakStatement?: (node: BreakStatement) => T; ReturnStatement?: (node: ReturnStatement) => T; ThrowStatement?: (node: ThrowStatement) => T; + EmitStatement?: (node: EmitStatement) => T; VariableDeclarationStatement?: (node: VariableDeclarationStatement) => T; IdentifierList?: (node: IdentifierList) => T; ElementaryTypeName?: (node: ElementaryTypeName) => T; @@ -692,6 +798,11 @@ export interface Visitor { UnaryOperation?: (node: UnaryOperation) => T; BinaryOperation?: (node: BinaryOperation) => T; Conditional?: (node: Conditional) => T; + MemberAccess?: (node: MemberAccess) => T; + IndexAccess?: (node: IndexAccess) => T; + NewExpression?: (node: NewExpression) => T; + DecimalNumber?: (node: DecimalNumber) => T; + HexNumber?: (node: HexNumber) => T; } export interface ParserOpts { tolerant?: boolean; From 6b2ce887b0f33e63cadc7860ca5ddc6c7fe2d668 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:52:21 -0700 Subject: [PATCH 24/76] start with source mapper --- packages/sol-meta/src/cli.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts index 0e670a6069..1fd1c03e99 100644 --- a/packages/sol-meta/src/cli.ts +++ b/packages/sol-meta/src/cli.ts @@ -1,9 +1,6 @@ -#!/usr/bin/env node -// We need the above pragma since this script will be run as a command-line tool. - +import 'source-map-support/register'; import { logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; -import 'source-map-support/register'; import * as yargs from 'yargs'; import { Compiler } from './compiler'; From d9996420b642c9bdbeba6dcd56ff23994d4037e8 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:52:56 -0700 Subject: [PATCH 25/76] utils for parameter lists --- packages/sol-meta/src/utils.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 4657dd057c..4ea8f7c61f 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -16,3 +16,21 @@ export const identifier = (name: string): S.Identifier => ({ type: S.NodeType.Identifier, name, }) + +export const nameParameters = (params: S.ParameterList, prefix:string = '_arg'): S.ParameterList => ({ + ...params, + parameters: params.parameters.map((param, i) => ({ + ...param, + name: param.name || `${prefix}${i}` + })) +}) + +export const argumentExpressions = (params: S.ParameterList): S.Expression[] => + params.parameters.map(({name}) => { + // TODO: rewrite using throw expressions or do notation + if (name !== null) { + return identifier(name) + } else { + throw new Error("Anonymous parameter"); + } + }); From 88e2ed5e834589fb6e1a25c179c89394fae6aa3f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:53:35 -0700 Subject: [PATCH 26/76] typesafety in unparser --- packages/sol-meta/src/unparser.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 36ab9b720f..d9db664664 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -1,13 +1,13 @@ // TODO: instead use https://github.com/prettier-solidity/prettier-plugin-solidity/blob/master/src/printer.js -import { ASTNode } from 'solidity-parser-antlr'; +import * as S from 'solidity-parser-antlr'; const stresc = (s: string) => `\"${s}\"`; const indent = (s: string) => `\t${s.replace(/\n/g, '\n\t')}`; const block = (s: string) => `{\n${indent(s)}\n}`; const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1'); -const visitor = { +const visitor: S.Visitor = { // Source level @@ -185,7 +185,7 @@ const visitor = { AssemblyLocalDefinition: ({names, expression}) => `let ${names.map(unparse).join(', ')} := ${unparse(expression)}`, - + AssemblyCall: ({functionName, arguments: args}) => args.length == 0 ? functionName : @@ -210,7 +210,7 @@ const visitor = { value, } -export function unparse(ast: ASTNode): string { +export function unparse(ast: S.ASTNode): string { return (visitor[ast.type] || (a => { console.log(a); console.trace(); From 75696e3044fa55de2941f9673be41f999bc3a767 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:53:47 -0700 Subject: [PATCH 27/76] storage location in variable declaration --- packages/sol-meta/src/unparser.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index d9db664664..4f1010658b 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -161,9 +161,10 @@ const visitor: S.Visitor = { ElementaryTypeNameExpression: ({typeName}) => `(${unparse(typeName)})`, - VariableDeclaration: ({typeName, name, visibility, isDeclaredConst, isIndexed, expression}) => + VariableDeclaration: ({typeName, name, visibility, isDeclaredConst, isIndexed, expression, storageLocation}) => `${unparse(typeName)} ` + (isIndexed ? 'indexed ' : '') + + (storageLocation ? storageLocation + ' ' : '') + (visibility && visibility != 'default' ? visibility + ' ' : '') + (isDeclaredConst ? 'constant ' : '') + `${name}` + From 74efc84af92d8a65acc920bce50627d23ed24f6f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:54:06 -0700 Subject: [PATCH 28/76] support parenthesis based tuples --- packages/sol-meta/src/unparser.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 4f1010658b..0ab29e1984 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -173,8 +173,10 @@ const visitor: S.Visitor = { NewExpression: ({typeName}) => `(new ${unparse(typeName)})`, - TupleExpression: ({components}) => - `[${components.map(unparse).join(', ')}]`, + TupleExpression: ({isArray, components}) => + isArray + ? `[${components.map(unparse).join(', ')}]` + : `(${components.map(unparse).join(', ')})`, // Assembly From 5eca80d27f95cd9d66f80db33b87fb396a577c57 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:54:31 -0700 Subject: [PATCH 29/76] more typeings --- .../types/solidity-parser-antlr/index.d.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 2a5e79cdd7..79ab893858 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -8,6 +8,8 @@ // See: https://solidity.readthedocs.io/en/develop/miscellaneous.html#language-grammar +// TODO: Parameter and VariableDeclaration are the same node + export interface LineColumn { line: number; column: number; @@ -205,7 +207,7 @@ export interface ImportDeclaration extends BaseASTNode { export interface ImportDirective extends BaseASTNode { type: NodeType.ImportDirective; path: string; - symbolAliases: [string, string][]; + symbolAliases: [string, string][] | null; } export interface ContractDefinition extends BaseASTNode { type: NodeType.ContractDefinition; @@ -283,7 +285,12 @@ export interface Parameter extends BaseASTNode { type: NodeType.Parameter; name: string | null; typeName: Type; - storageLocation: StorageLocation | null; + visibility?: Visibility; + storageLocation?: StorageLocation | null; + expression?: Expression; + isStateVar?: boolean; + isDeclaredConst?: boolean; + isIndexed?: boolean; } /* export interface EventParameterList extends BaseASTNode { @@ -306,11 +313,12 @@ export interface Block extends BaseASTNode { export interface VariableDeclaration extends BaseASTNode { type: NodeType.VariableDeclaration; name: string; - visibility: Visibility; + visibility?: Visibility; + storageLocation?: StorageLocation; typeName: Type; - expression: Expression; + expression?: Expression; isStateVar: boolean; - isDeclaredConst: boolean; + isDeclaredConst?: boolean; isIndexed: boolean; } /* @@ -448,6 +456,7 @@ export interface FunctionCall extends BaseASTNode { export interface TupleExpression extends BaseASTNode { type: NodeType.TupleExpression; components: Expression[]; + isArray: boolean } export interface ElementaryTypeNameExpression extends BaseASTNode { type: NodeType.ElementaryTypeNameExpression; @@ -616,6 +625,10 @@ export type Expression = | MemberAccess export type Type = | ElementaryType + | UserDefinedTypeName + | Mapping + | ArrayTypeName + | FunctionTypeName export type AssemblyStatement = | AssemblyCall | AssemblyAssignment From 8d02db9ae33a54bd84cc9c4f0f68ce7f07b7a2b0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:58:12 -0700 Subject: [PATCH 30/76] more typesafety in exposer --- packages/sol-meta/src/exposer.ts | 57 +++++++++++++++----------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index f01cf9f7f7..2a03a2bf60 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -1,10 +1,10 @@ import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; -import {identifier} from './utils'; +import {identifier, nameParameters, argumentExpressions} from './utils'; // Creates a public getter for a state variable const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { - const [{name, typeName }] = (stateVar as any).variables; + const [{name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, name: `get_${name}`, @@ -37,7 +37,7 @@ const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { // Creates a public getter for a state variable const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { - const [{name, typeName }] = (stateVar as any).variables; + const [{name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, name: `set_${name}`, @@ -72,27 +72,26 @@ const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { // Creates a public wrapper for a function const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { - const call = { + const params = nameParameters(func.parameters); + const call: S.FunctionCall = { type: S.NodeType.FunctionCall, expression: identifier(func.name), - arguments: (func as any).parameters.parameters.map( - ({name}) => identifier(name) - ) + arguments: argumentExpressions(params), + names: [] }; return { ...func, name: `public_${func.name}`, visibility: S.Visibility.Public, + parameters: params, modifiers: [], body: { type: S.NodeType.Block, statements: [ - (func as any).returnParameters ? - { + func.returnParameters ? { type: S.NodeType.ReturnStatement, expression: call - } : - { + } : { type: S.NodeType.ExpressionStatement, expression: call } @@ -103,7 +102,7 @@ const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { // Creates a public function that triggers a log event const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { - const {name, parameters} = event as any; + const {name, parameters} = event; return { type: S.NodeType.FunctionDefinition, name: `emit_${name}`, @@ -121,14 +120,12 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { modifiers: [], body: { type: S.NodeType.Block, - statements: [/* */ { + statements: [{ type: S.NodeType.EmitStatement, - eventCall: /**/ { + eventCall: { type: S.NodeType.FunctionCall, expression: identifier(name), - arguments: parameters.parameters.map( - ({name}) => identifier(name) - ), + arguments: argumentExpressions(parameters), names: [] } }] @@ -138,19 +135,20 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { // Creates a public function that has modifier const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { - const {name, parameters} = modifier as any; + const {name, parameters} = modifier; return { type: S.NodeType.FunctionDefinition, name: `modifier_${name}`, visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.Default, - parameters: Array.isArray(parameters) ? - { + parameters: + parameters === null + ? { type: S.NodeType.ParameterList, parameters: [] - } : - parameters, + } + : parameters, returnParameters: { type: S.NodeType.ParameterList, parameters: [{ @@ -166,11 +164,10 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { modifiers: [{ type: S.NodeType.ModifierInvocation, name, - arguments: Array.isArray(parameters) ? - [] : - parameters.parameters.map( - ({name}) => identifier(name) - ) + arguments: + Array.isArray(parameters) + ? argumentExpressions(parameters) + : [] }], body: { type: S.NodeType.Block, @@ -193,7 +190,7 @@ const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { return []; } case 'StateVariableDeclaration': { - const [vardecl] = (ast as any).variables; + const [vardecl] = ast.variables; if (vardecl.visibility !== 'internal') { return []; } @@ -211,7 +208,7 @@ const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { return [testModifier(ast as S.ModifierDefinition)]; } case 'FunctionDefinition': { - const func = ast as any; + const func = ast; if (func.visibility !== 'internal') { return []; } @@ -235,7 +232,7 @@ export function expose(filePath: string, ast: S.SourceUnit): S.SourceUnit { unitAliases: null, symbolAliases: null }, - ...utils.contracts(ast).map((ctr: any) => ({ + ...utils.contracts(ast).map((ctr) => ({ type: S.NodeType.ContractDefinition, kind: 'contract', name: `${ctr.name}Exposed`, From ef64e8ca6f2669744aa46757f6577c95bfceedd0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 16:58:32 -0700 Subject: [PATCH 31/76] mock actions and functions --- packages/sol-meta/src/mocker.ts | 280 ++++++++++++++++++++++++++++---- 1 file changed, 251 insertions(+), 29 deletions(-) diff --git a/packages/sol-meta/src/mocker.ts b/packages/sol-meta/src/mocker.ts index 3810579099..87e2c6d4d4 100644 --- a/packages/sol-meta/src/mocker.ts +++ b/packages/sol-meta/src/mocker.ts @@ -1,6 +1,8 @@ import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; -import {identifier} from './utils'; +import {identifier, nameParameters, argumentExpressions} from './utils'; + +// TODO: both Actions and Functions can throw in addition to returning const uint256: S.ElementaryTypeName = ({ type: S.NodeType.ElementaryTypeName, @@ -10,12 +12,7 @@ const uint256: S.ElementaryTypeName = ({ const zero: S.NumberLiteral = ({ type: S.NodeType.NumberLiteral, number: '0', - // TODO subdenomination: null -}); - -const id = (name:string): S.Identifier => ({ - type: S.NodeType.Identifier, - name + subdenomination: null // TODO }); const call = (func: S.Expression, ...args: S.Expression[]): S.FunctionCall => ({ @@ -27,7 +24,7 @@ const call = (func: S.Expression, ...args: S.Expression[]): S.FunctionCall => ({ const emit = (name: string, ...args: S.Expression[]): S.EmitStatement => ({ type: S.NodeType.EmitStatement, - eventCall: call(id(name), ...args) + eventCall: call(identifier(name), ...args) }); const makeCounter = (name: string): S.StateVariableDeclaration => ({ @@ -52,7 +49,39 @@ const makeEvent = (name: string, parameters: S.ParameterList): S.EventDefinition isAnonymous: false }); -const makeAction = (name: string, parameters: S.ParameterList): S.FunctionDefinition => ({ +const makeCountedEvent = (name: string, parameters: S.ParameterList) => + makeEvent(name, { + ...parameters, + parameters: [ + { + type: S.NodeType.Parameter, + name: 'counter', + typeName: uint256, + storageLocation: S.StorageLocation.Default + }, + ...parameters.parameters.map(param => ({ + ...param, + storageLocation: S.StorageLocation.Default + }))] + }); + +const makeIncrement = (name: string): S.ExpressionStatement => ({ + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.UnaryOperation, + operator: '++', + subExpression: identifier(name), + isPrefix: false + } +}) + +const makeAction = ( + name: string, + visibility: S.Visibility, + counterName: string, + eventName: string, + parameters: S.ParameterList +): S.FunctionDefinition => ({ type: S.NodeType.FunctionDefinition, name, parameters, @@ -60,35 +89,227 @@ const makeAction = (name: string, parameters: S.ParameterList): S.FunctionDefini body: { type: S.NodeType.Block, statements: [ - emit( - name, - id('dispatchTransferFrom_counter'), - ...(parameters as any).parameters.map(({name}) => id(name)) - ), - { - type: S.NodeType.ExpressionStatement, - expression: { - type: S.NodeType.UnaryOperation, - operator: '++', - subExpression: id('dispatchTransferFrom_counter'), - isPrefix: false - } - } + emit(eventName, identifier(counterName), + ...argumentExpressions(parameters)), + makeIncrement(counterName) ] }, - visibility: S.Visibility.Internal, + visibility: visibility, modifiers: [], isConstructor: false, stateMutability: S.StateMutability.Default, }); -const isDeclaration = (func: S.FunctionDefinition) => func.body == null; +const variableDeclaration = ( + name: string, + typeName: S.Type +): S.VariableDeclaration => ({ + type: S.NodeType.VariableDeclaration, + name, + typeName, + storageLocation: S.StorageLocation.Default, + isStateVar: false, + isIndexed: false +}) + +const makeResultType = (name: string, fields: S.ParameterList): S.StructDefinition => ({ + type: S.NodeType.StructDefinition, + name, + members: fields.parameters.map(({name, typeName}) => + variableDeclaration(name as string, typeName) + ) +}); + +const userType = (name: string): S.UserDefinedTypeName => ({ + type: S.NodeType.UserDefinedTypeName, + namePath: name +}) + +const mapping = (keyType: S.Type, valueType: S.Type): S.Type => ({ + type: S.NodeType.Mapping, + keyType, + valueType, +}); + +const makeStorageVariable = ( + name: string, + type: S.Type +): S.StateVariableDeclaration => ({ + type: S.NodeType.StateVariableDeclaration, + variables: [variableDeclaration(name, type)], + initialValue: null +}) + +const makeSetter = ( + name: string, + resultTypeName: string, + resultMapName: string +): S.FunctionDefinition => ({ + type: S.NodeType.FunctionDefinition, + name, + parameters: { + type: S.NodeType.ParameterList, + parameters: [{ + type: S.NodeType.Parameter, + name: '_counter', + typeName: uint256, + storageLocation: S.StorageLocation.Default, + isStateVar: false, + isIndexed: false + }, { + type: S.NodeType.Parameter, + name: '_value', + typeName: userType(resultTypeName), + storageLocation: S.StorageLocation.Default, + isStateVar: false, + isIndexed: false + }] + }, + returnParameters: null, + visibility: S.Visibility.Public, + modifiers: [], + isConstructor: false, + stateMutability: S.StateMutability.Default, + body: { + type: S.NodeType.Block, + statements: [{ + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.BinaryOperation, + operator: '=', + left: { + type: S.NodeType.IndexAccess, + base: identifier(resultMapName), + index: identifier('_counter') + }, + right: identifier('_value') + } + }] + } +}) + +const makeFunction = ( + name: string, + visibility: S.Visibility, + counterName: string, + eventName: string, + resultTypeName: string, + resultMapName: string, + parameters: S.ParameterList, + returnParameters: S.ParameterList +): S.FunctionDefinition => ({ + type: S.NodeType.FunctionDefinition, + name, + parameters, + returnParameters, + visibility: visibility, + modifiers: [], + isConstructor: false, + stateMutability: S.StateMutability.Default, + body: { + type: S.NodeType.Block, + statements: [ + emit(eventName, identifier(counterName), + ...argumentExpressions(parameters)), + { + type: S.NodeType.VariableDeclarationStatement, + variables: [{ + ...variableDeclaration('result', userType(resultTypeName)), + storageLocation: S.StorageLocation.Storage, + }], + initialValue: { + type: S.NodeType.IndexAccess, + base: identifier(resultMapName), + index: identifier(counterName) + } + }, + makeIncrement(counterName), + { + type: S.NodeType.ReturnStatement, + expression: { + type: S.NodeType.TupleExpression, + isArray: false, + components: returnParameters.parameters.map( + ({name}): S.MemberAccess => ({ + type: S.NodeType.MemberAccess, + expression: identifier('result'), + memberName: name as string + }) + ) + } + } + ] + } +}); + +const mockAction = (func: S.FunctionDefinition): S.ContractMember[] => { + const counterName = `_${func.name}_counter`; + const eventName = `_${func.name}_log`; + const params = nameParameters(func.parameters); + return [ + makeCounter(counterName), + makeCountedEvent(eventName, params), + makeAction(func.name, func.visibility, counterName, eventName, params) + ]; +}; + +const mockFunction = (func: S.FunctionDefinition): S.ContractMember[] => { + const counterName = `_${func.name}_counter`; + const resultTypeName = `_${func.name}_Result`; + const resultMapName = `_${func.name}_results`; + const eventName = `_${func.name}_log`; + const setterName = `_${func.name}_set`; + const params = nameParameters(func.parameters); + const returns = nameParameters(func.returnParameters as S.ParameterList, + '_ret'); + return [ + makeCounter(counterName), + makeCountedEvent(eventName, params), + makeResultType(resultTypeName, returns), + makeStorageVariable(resultMapName, + mapping(uint256, userType(resultTypeName))), + makeSetter(setterName, resultTypeName, resultMapName), + makeFunction(func.name, func.visibility, counterName, eventName, + resultTypeName, resultMapName, params, returns) + ]; +} + +const isDeclaration = (func: S.FunctionDefinition) => func.body === null; + +const hasReturns = (func: S.FunctionDefinition) => func.returnParameters !== null; const visitor = { + FunctionDefinition: (func) => + isDeclaration(func) + ? ( + hasReturns(func) + ? mockFunction(func) + : mockAction(func) + ) + : [], + default: (node) => node, } +const pragmaSolVersion: S.PragmaDirective = { + type: S.NodeType.PragmaDirective, + name: 'solidity', + value: '^0.4.24' +}; + +const pragmaAbiV2: S.PragmaDirective = { + type: S.NodeType.PragmaDirective, + name: 'experimental', + value: 'ABIEncoderV2' +}; + +const importDirective = (path: string): S.ImportDirective => ({ + type: S.NodeType.ImportDirective, + path, + symbolAliases: null +}) + export function mock(ast: S.SourceUnit): S.SourceUnit { // TODO: gp down inheritance hierarchy and expose those events. etc as well @@ -97,12 +318,13 @@ export function mock(ast: S.SourceUnit): S.SourceUnit { return { type: S.NodeType.SourceUnit, children: [ - ...utils.pragmaNodes(ast), - ...utils.importNodes(ast), - ...utils.contracts(ast).map((ctr): S.ContractDefinition => ({ + pragmaSolVersion, + pragmaAbiV2, + importDirective('interface.sol'), + ...utils.contracts(ast).map((ctr) => ({ type: S.NodeType.ContractDefinition, kind: S.ContractKind.Contract, - name: `${ctr.name}Exposed`, + name: `${ctr.name}Mock`, baseContracts: [{ type: S.NodeType.InheritanceSpecifier, baseName: { From fe4cc6eafb64e06430b246d2a3b1864d862f38bb Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 15 Oct 2018 22:22:29 -0700 Subject: [PATCH 32/76] Add C3 Linearization function --- packages/sol-meta/src/linearization.ts | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/sol-meta/src/linearization.ts diff --git a/packages/sol-meta/src/linearization.ts b/packages/sol-meta/src/linearization.ts new file mode 100644 index 0000000000..b54c3ed9eb --- /dev/null +++ b/packages/sol-meta/src/linearization.ts @@ -0,0 +1,49 @@ +// Implements C3 Linearization as used in Solidity +// see: https://www.python.org/download/releases/2.3/mro/ + +// Produce an array such that each input array is a subarray of the result. +// In other words, if the inputs specify lists sorted according to some partial +// order produce a sorted list of all elements compatible. +// +// NOTE: merge(a.reverse(), b.reversed(), ...) == merge(a, b, ...).reversed() +// (or at least equal up to the implied partial order) +export function merge(...ilists: T[][]): T[] { + + // Only consider non-empty lists + const lists = ilists.filter(x => x.length > 0); + if (lists.length === 0) { + return []; + } + + // The first item of each list are heads, the remainders are tails + const heads = lists.map(([head, ...tail]) => head); + const tails = lists.map(([head, ...tail]) => tail); + + // A good head is one that does not occur in any tail + const goodHead = heads.find(head => + tails.every(tail => !tail.includes(head))); + + // If there is no valid head the hierarchy can not be linearized. + if (goodHead === undefined) { + throw new Error("Hierarchy can not be linearized."); + } + + // Remove the good head from the lists + const newLists = lists.map(list => list.filter(elem => elem !== goodHead)); + + // Prepend head to the linearization and repeat + return [goodHead, ...merge(...newLists)]; +} + +// Given a final element and an parents function, compute the C3 linearization +// of all ancestors of the final element. +// NOTE: Solidity has its ancestors in reverse order (base to most derived) +// compared to the Python C3 Linearization algorithm (most derived to +// base). This version uses the Solidity convention. +export function linearize(final: T, parents: (_:T) => T[]) : T[] { + // TODO: Memoization + // TODO: Throw on cycles (instead of stack overflowing) + const p = parents(final); + const recurse = p.map(a => linearize(a, parents)); + return [...merge(p, ...recurse), final]; +} From 2c91d803e3cd6f92528d2a10491b643ecccd9cad Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 8 Nov 2018 17:03:22 +0100 Subject: [PATCH 33/76] [WIP] Flattener --- packages/sol-meta/src/compiler.ts | 10 +-- packages/sol-meta/src/flattener.ts | 91 ++++++++++++++++++++++++++ packages/sol-meta/src/linearization.ts | 19 +++++- packages/sol-meta/src/mocker.ts | 32 +++++++-- 4 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 packages/sol-meta/src/flattener.ts diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts index 1b716d1b27..3b69df3cfa 100644 --- a/packages/sol-meta/src/compiler.ts +++ b/packages/sol-meta/src/compiler.ts @@ -3,6 +3,8 @@ import { parse } from './parser'; import { unparse } from './unparser'; import { expose } from './exposer'; import { mock } from './mocker'; +import { linearize } from './linearization'; +import { flattenSource } from './flattener'; import * as util from 'util'; // DEBUG @@ -19,18 +21,18 @@ export class Compiler { this.opts = opts || { contractsDir: '', contracts: '', - } ; + }; } public async compileAsync() { + const filePath = this.opts.contracts[0]; const source = fs.readFileSync(filePath, 'utf8'); try { const ast = parse(source); - //console.log(util.inspect(ast, {depth: 40})); //console.log(unparse(ast)); - const astp = mock(ast); - console.log(util.inspect(astp, {depth: 4})); + const astp = flattenSource(ast, 'MAssetProxyDispatcher'); + //console.log(util.inspect(astp, {depth: 4})); console.log(unparse(astp)); } catch (e) { console.log(e); diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts new file mode 100644 index 0000000000..022eedcffe --- /dev/null +++ b/packages/sol-meta/src/flattener.ts @@ -0,0 +1,91 @@ +import * as S from 'solidity-parser-antlr'; +import { linearize, linearizeAsync } from './linearization' +import { flatMap } from './utils' + +// See: https://solidity.readthedocs.io/en/v0.4.25/contracts.html#inheritance + +// Merge a base contract in a derived contract +function merge( + base: S.ContractMember[], + derived: S.ContractMember[] +): S.ContractMember[] { + // Solidity method lookup is as if all functions are virtual. The most + // derived version is always called. We can implement this by overriding + // the base implementation. + const functions: S.FunctionDefinition[] = Object.values( + derived + .filter(({type}) => type === S.NodeType.FunctionDefinition) + .reduce( + (a, func: S.FunctionDefinition) => ({...a, [func.name]: func}), + base + .filter(({type}) => type === S.NodeType.FunctionDefinition) + .reduce( + (a, func: S.FunctionDefinition) => ({...a, [func.name]: func}), + {}))); + + // TODO: Merge constructors + // TODO: Implement rules that enforce type signature and visibility e.d. + // to be preserbed. + // TODO: Check other objects than functions. + // TODO: Sort members by type + return [ + ...base.filter(({type}) => type !== S.NodeType.FunctionDefinition), + ...derived.filter(({type}) => type !== S.NodeType.FunctionDefinition), + ...functions + ] +} + +// Inline all inheritance for a contract +function flattenContract( + contract: S.ContractDefinition, + resolve: (name: string) => S.ContractDefinition +): S.ContractDefinition { + + // Close over resolve to create a parents function + const parents = (contract: S.ContractDefinition) => + contract.baseContracts.map(({baseName: {namePath}}) => + resolve(namePath)); + + // Linearize the contract inheritance tree from least to most derived + const linear = linearize(contract, parents); + + // TODO: Implement `super()` and `SomeBase.functionName(...)`. + + // Concatenate contract bodies + return { + ...contract, + baseContracts: [], + subNodes: linear.reduce((a, {subNodes}) => merge(a, subNodes), []) + } +} + +function sourceResolver( + source: S.SourceUnit +): (string) => S.ContractDefinition { + return function (name) { + const result = source.children.find(x => + x.type === S.NodeType.ContractDefinition && + x.name === name); + if (result === undefined) { + throw new Error(`Could not resolve ${name}`); + } + return result; + } +} + +export function flattenSource( + source: S.SourceUnit, + contractName: string +): S.ContractDefinition { + const resolver = sourceResolver(source); + return flattenContract(resolver(contractName), resolver); +} + +// Chase down import statements and produce a single source unit. +/*function flattenFile( + contract: S.SourceUnit, + resolver: (string) => S.SourceUnit +): S.SourceUnit { + // TODO: symbolAliases +} +*/ diff --git a/packages/sol-meta/src/linearization.ts b/packages/sol-meta/src/linearization.ts index b54c3ed9eb..1b579f2d86 100644 --- a/packages/sol-meta/src/linearization.ts +++ b/packages/sol-meta/src/linearization.ts @@ -5,7 +5,7 @@ // In other words, if the inputs specify lists sorted according to some partial // order produce a sorted list of all elements compatible. // -// NOTE: merge(a.reverse(), b.reversed(), ...) == merge(a, b, ...).reversed() +// NOTE: merge(a.reverse(), b.reversed(), ...) = merge(a, b, ...).reversed() // (or at least equal up to the implied partial order) export function merge(...ilists: T[][]): T[] { @@ -19,7 +19,7 @@ export function merge(...ilists: T[][]): T[] { const heads = lists.map(([head, ...tail]) => head); const tails = lists.map(([head, ...tail]) => tail); - // A good head is one that does not occur in any tail + // A good head is one that does not occur in any tail. const goodHead = heads.find(head => tails.every(tail => !tail.includes(head))); @@ -37,9 +37,15 @@ export function merge(...ilists: T[][]): T[] { // Given a final element and an parents function, compute the C3 linearization // of all ancestors of the final element. +// // NOTE: Solidity has its ancestors in reverse order (base to most derived) // compared to the Python C3 Linearization algorithm (most derived to // base). This version uses the Solidity convention. +// +// NOTE: The nature of the algorithm makes it so that some cases where a // linearization does exists, it is not found. The way merge picks +// an arbitrary solution adds additional constraints wich can lead +// to conflicts later on. It would be better if instead of recursion, +// a single large merge was done with all the constraints. export function linearize(final: T, parents: (_:T) => T[]) : T[] { // TODO: Memoization // TODO: Throw on cycles (instead of stack overflowing) @@ -47,3 +53,12 @@ export function linearize(final: T, parents: (_:T) => T[]) : T[] { const recurse = p.map(a => linearize(a, parents)); return [...merge(p, ...recurse), final]; } + +export async function linearizeAsync( + final: T, parents: (_:T) => Promise +): Promise { + const p = await parents(final); + const recurse = await Promise.all( + p.map(a => linearizeAsync(a, parents))); + return [...merge(p, ...recurse), final]; +} diff --git a/packages/sol-meta/src/mocker.ts b/packages/sol-meta/src/mocker.ts index 87e2c6d4d4..20b76f4c6f 100644 --- a/packages/sol-meta/src/mocker.ts +++ b/packages/sol-meta/src/mocker.ts @@ -4,6 +4,11 @@ import {identifier, nameParameters, argumentExpressions} from './utils'; // TODO: both Actions and Functions can throw in addition to returning +const bool: S.ElementaryTypeName = ({ + type: S.NodeType.ElementaryTypeName, + name: 'bool' +}); + const uint256: S.ElementaryTypeName = ({ type: S.NodeType.ElementaryTypeName, name: 'uint256' @@ -115,9 +120,11 @@ const variableDeclaration = ( const makeResultType = (name: string, fields: S.ParameterList): S.StructDefinition => ({ type: S.NodeType.StructDefinition, name, - members: fields.parameters.map(({name, typeName}) => - variableDeclaration(name as string, typeName) - ) + members: [ + variableDeclaration('reverts', bool), + ...fields.parameters.map(({name, typeName}) => + variableDeclaration(name as string, typeName)) + ] }); const userType = (name: string): S.UserDefinedTypeName => ({ @@ -224,6 +231,19 @@ const makeFunction = ( } }, makeIncrement(counterName), + { + type: S.NodeType.ExpressionStatement, + expression: call(identifier('require'), { + type: S.NodeType.UnaryOperation, + operator: '!', + isPrefix: true, + subExpression: { + type: S.NodeType.MemberAccess, + expression: identifier('result'), + memberName: 'reverts' + } + }) + }, { type: S.NodeType.ReturnStatement, expression: { @@ -312,9 +332,9 @@ const importDirective = (path: string): S.ImportDirective => ({ export function mock(ast: S.SourceUnit): S.SourceUnit { - // TODO: gp down inheritance hierarchy and expose those events. etc as well - // we probably want a separate `flattenInheritance` function or something - // that traces the imports and does a fairly simple concatenation. + // TODO: go down inheritance hierarchy and expose those events. etc as well. + // We probably want a separate `flattenInheritance` function or something + // that traces the imports and does a C3 linearization. return { type: S.NodeType.SourceUnit, children: [ From 612fe7eee110ae060d4b1b3ec89924e932a30a3f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 8 Nov 2018 17:03:46 +0100 Subject: [PATCH 34/76] [WIP] Test files --- packages/sol-meta/test.iface.out.sol | 42 +++ packages/sol-meta/test.sol | 33 ++ packages/sol-meta/test/interface.expected.ast | 339 ++++++++++++++++++ .../sol-meta/test/interface.expected.ast.js | 289 +++++++++++++++ packages/sol-meta/test/interface.expected.sol | 60 ++++ packages/sol-meta/test/interface.sol | 54 +++ packages/sol-meta/test/out.sol | 43 +++ packages/sol-meta/test/test.out.sol | 57 +++ packages/sol-meta/test/test.sol | 48 +++ 9 files changed, 965 insertions(+) create mode 100644 packages/sol-meta/test.iface.out.sol create mode 100644 packages/sol-meta/test.sol create mode 100644 packages/sol-meta/test/interface.expected.ast create mode 100644 packages/sol-meta/test/interface.expected.ast.js create mode 100644 packages/sol-meta/test/interface.expected.sol create mode 100644 packages/sol-meta/test/interface.sol create mode 100644 packages/sol-meta/test/out.sol create mode 100644 packages/sol-meta/test/test.out.sol create mode 100644 packages/sol-meta/test/test.sol diff --git a/packages/sol-meta/test.iface.out.sol b/packages/sol-meta/test.iface.out.sol new file mode 100644 index 0000000000..f1cf9e000f --- /dev/null +++ b/packages/sol-meta/test.iface.out.sol @@ -0,0 +1,42 @@ +pragma experimental ABIEncoderV2; + +contract MAssetProxyDispatcherMock { + + uint256 internal _dispatchTransferFrom_counter = 0; + + event _dispatchTransferFrom_log(uint256 counter, bytes assetData, address from, address to, uint256 amount); + + function dispatchTransferFrom(bytes memory assetData, address from, address to, uint256 amount) + internal + { + emit _dispatchTransferFrom_log(_dispatchTransferFrom_counter, assetData, from, to, amount); + _dispatchTransferFrom_counter++; + } + + uint256 internal _someFunction_counter = 0; + + event _someFunction_log(uint256 counter, uint256 a, uint256 b); + + struct _someFunction_Result { + uint256 _ret0; + bytes _ret1; + } + + mapping (uint256 => _someFunction_Result) _someFunction_results; + + function _someFunction_set(uint256 _counter, _someFunction_Result _value) + public + { + (_someFunction_results[_counter]) = _value; + } + + function someFunction(uint256 a, uint256 b) + public + returns (uint256 _ret0, bytes _ret1) + { + emit _someFunction_log(_someFunction_counter, a, b); + _someFunction_Result storage result = (_someFunction_results[_someFunction_counter]); + _someFunction_counter++; + return ((result._ret0), (result._ret1)); + } +} diff --git a/packages/sol-meta/test.sol b/packages/sol-meta/test.sol new file mode 100644 index 0000000000..80add9bbaa --- /dev/null +++ b/packages/sol-meta/test.sol @@ -0,0 +1,33 @@ +contract MAssetProxyDispatcher { + + address public owner; + + modifier onlyOwner() { + require(((msg.sender) == owner), "ONLY_CONTRACT_OWNER"); + _; + } + + function transferOwnership(address newOwner) + public onlyOwner() + { + if ((newOwner != ((address)(0)))) + { + owner = newOwner; + } + } + + constructor() + public + { + owner = (msg.sender); + } + + function dispatchTransferFrom(bytes memory assetData, address from, address to, uint256 amount) + internal + ; + + function someFunction(uint256 a, uint256 b) + internal + returns (uint256 , bytes ) + ; +} diff --git a/packages/sol-meta/test/interface.expected.ast b/packages/sol-meta/test/interface.expected.ast new file mode 100644 index 0000000000..f20f293afb --- /dev/null +++ b/packages/sol-meta/test/interface.expected.ast @@ -0,0 +1,339 @@ +{ type: 'SourceUnit', + children: + [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, + { type: 'PragmaDirective', + name: 'experimental', + value: 'ABIEncoderV2' }, + { type: 'ContractDefinition', + name: 'MAssetProxyDispatcher', + baseContracts: [], + subNodes: + [ { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'dispatchTransferFrom_counter', + expression: + { type: 'NumberLiteral', number: '0', subdenomination: null }, + visibility: 'default', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: + { type: 'NumberLiteral', number: '0', subdenomination: null } }, + { type: 'EventDefinition', + name: 'dispatchTransferFromCalled', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: 'assetData', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'from', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'to', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'amount', + isStateVar: false, + isIndexed: false } ] }, + isAnonymous: false }, + { type: 'FunctionDefinition', + name: 'dispatchTransferFrom', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: 'assetData', + storageLocation: 'memory', + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'from', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'to', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'amount', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: null, + body: + { type: 'Block', + statements: + [ { type: 'EmitStatement', + eventCall: + { type: 'FunctionCall', + expression: { type: 'Identifier', name: 'dispatchTransferFromCalled' }, + arguments: + [ { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, + { type: 'Identifier', name: 'assetData' }, + { type: 'Identifier', name: 'from' }, + { type: 'Identifier', name: 'to' }, + { type: 'Identifier', name: 'amount' } ], + names: [] } }, + { type: 'ExpressionStatement', + expression: + { type: 'UnaryOperation', + operator: '++', + subExpression: { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, + isPrefix: false } } ] }, + visibility: 'internal', + modifiers: [], + isConstructor: false, + stateMutability: null }, + { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'someFunction_counter', + expression: + { type: 'NumberLiteral', number: '0', subdenomination: null }, + visibility: 'internal', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: + { type: 'NumberLiteral', number: '0', subdenomination: null } }, + { type: 'StructDefinition', + name: 'someFunction_ReturnType', + members: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: '_arg_0', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: '_arg_1', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: + { type: 'Mapping', + keyType: { type: 'ElementaryTypeName', name: 'uint256' }, + valueType: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' } }, + name: 'someFunction_returns', + expression: null, + visibility: 'default', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: null }, + { type: 'EventDefinition', + name: 'someFunctionCalled', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'a', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'b', + isStateVar: false, + isIndexed: false } ] }, + isAnonymous: false }, + { type: 'FunctionDefinition', + name: 'someFunctionSet', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' }, + name: 'values', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: null, + body: + { type: 'Block', + statements: + [ { type: 'ExpressionStatement', + expression: + { type: 'BinaryOperation', + operator: '=', + left: + { type: 'IndexAccess', + base: { type: 'Identifier', name: 'someFunction_returns' }, + index: { type: 'Identifier', name: 'counter' } }, + right: { type: 'Identifier', name: 'values' } } } ] }, + visibility: 'public', + modifiers: [], + isConstructor: false, + stateMutability: null }, + { type: 'FunctionDefinition', + name: 'someFunction', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'a', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'b', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: null, + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: null, + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + body: + { type: 'Block', + statements: + [ { type: 'EmitStatement', + eventCall: + { type: 'FunctionCall', + expression: { type: 'Identifier', name: 'someFunctionCalled' }, + arguments: + [ { type: 'Identifier', name: 'someFunction_counter' }, + { type: 'Identifier', name: 'a' }, + { type: 'Identifier', name: 'b' } ], + names: [] } }, + { type: 'VariableDeclarationStatement', + variables: + [ { type: 'VariableDeclaration', + typeName: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' }, + name: 'returnValues', + storageLocation: 'storage', + isStateVar: false, + isIndexed: false } ], + initialValue: + { type: 'IndexAccess', + base: { type: 'Identifier', name: 'someFunction_returns' }, + index: { type: 'Identifier', name: 'someFunction_counter' } } }, + { type: 'ExpressionStatement', + expression: + { type: 'UnaryOperation', + operator: '++', + subExpression: { type: 'Identifier', name: 'someFunction_counter' }, + isPrefix: false } }, + { type: 'ReturnStatement', + expression: + { type: 'TupleExpression', + components: + [ { type: 'MemberAccess', + expression: { type: 'Identifier', name: 'returnValues' }, + memberName: '_arg_0' }, + { type: 'MemberAccess', + expression: { type: 'Identifier', name: 'returnValues' }, + memberName: '_arg_1' } ], + isArray: false } } ] }, + visibility: 'internal', + modifiers: [], + isConstructor: false, + stateMutability: null } ], + kind: 'contract' } ] } +{ type: 'SourceUnit', + children: + [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, + { type: 'PragmaDirective', + name: 'experimental', + value: 'ABIEncoderV2' }, + { type: 'ContractDefinition', + kind: 'contract', + name: 'MAssetProxyDispatcherMock', + baseContracts: [ { type: 'InheritanceSpecifier', baseName: [Object] } ], + subNodes: + [ { type: 'StateVariableDeclaration', + variables: [Array], + initialValue: [Object] }, + { type: 'EventDefinition', + name: 'dispatchTransferFromCalled', + parameters: [Object], + isAnonymous: false }, + { type: 'StateVariableDeclaration', + variables: [Array], + initialValue: [Object] }, + { type: 'StructDefinition', + name: 'someFunction_ReturnType', + members: [Array] }, + { type: 'StateVariableDeclaration', + variables: [Array], + initialValue: null }, + { type: 'EventDefinition', + name: 'someFunctionCalled', + parameters: [Object], + isAnonymous: false } ] } ] } +pragma solidity ^0.4.23; +pragma experimental ABIEncoderV2; +contract MAssetProxyDispatcherMock is MAssetProxyDispatcher{ + + uint256 dispatchTransferFrom_counter = 0; + + event dispatchTransferFromCalled(uint256 counter, bytes assetData, address from, address to, uint256 amount); + + uint256 internal someFunction_counter = 0; + + struct someFunction_ReturnType { + uint256 _arg_0; + bytes _arg_1; + } + + mapping (uint256 => someFunction_ReturnType) someFunction_returns; + + event someFunctionCalled(uint256 counter, uint256 a, uint256 b); +} diff --git a/packages/sol-meta/test/interface.expected.ast.js b/packages/sol-meta/test/interface.expected.ast.js new file mode 100644 index 0000000000..0c1ac3d272 --- /dev/null +++ b/packages/sol-meta/test/interface.expected.ast.js @@ -0,0 +1,289 @@ +x={ type: 'SourceUnit', + children: + [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, + { type: 'PragmaDirective', + name: 'experimental', + value: 'ABIEncoderV2' }, + { type: 'ContractDefinition', + name: 'MAssetProxyDispatcher', + baseContracts: [], + subNodes: + [ { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'dispatchTransferFrom_counter', + expression: + { type: 'NumberLiteral', number: '0', subdenomination: null }, + visibility: 'default', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: + { type: 'NumberLiteral', number: '0', subdenomination: null } }, + { type: 'EventDefinition', + name: 'dispatchTransferFromCalled', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: 'assetData', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'from', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'to', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'amount', + isStateVar: false, + isIndexed: false } ] }, + isAnonymous: false }, + { type: 'FunctionDefinition', + name: 'dispatchTransferFrom', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: 'assetData', + storageLocation: 'memory', + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'from', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'address' }, + name: 'to', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'amount', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: null, + body: + { type: 'Block', + statements: + [ { type: 'EmitStatement', + eventCall: + { type: 'FunctionCall', + expression: { type: 'Identifier', name: 'dispatchTransferFromCalled' }, + arguments: + [ { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, + { type: 'Identifier', name: 'assetData' }, + { type: 'Identifier', name: 'from' }, + { type: 'Identifier', name: 'to' }, + { type: 'Identifier', name: 'amount' } ], + names: [] } }, + { type: 'ExpressionStatement', + expression: + { type: 'UnaryOperation', + operator: '++', + subExpression: { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, + isPrefix: false } } ] }, + visibility: 'internal', + modifiers: [], + isConstructor: false, + stateMutability: null }, + { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'someFunction_counter', + expression: + { type: 'NumberLiteral', number: '0', subdenomination: null }, + visibility: 'internal', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: + { type: 'NumberLiteral', number: '0', subdenomination: null } }, + { type: 'StructDefinition', + name: 'someFunction_ReturnType', + members: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: '_arg_0', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: '_arg_1', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + { type: 'StateVariableDeclaration', + variables: + [ { type: 'VariableDeclaration', + typeName: + { type: 'Mapping', + keyType: { type: 'ElementaryTypeName', name: 'uint256' }, + valueType: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' } }, + name: 'someFunction_returns', + expression: null, + visibility: 'default', + isStateVar: true, + isDeclaredConst: false, + isIndexed: false } ], + initialValue: null }, + { type: 'EventDefinition', + name: 'someFunctionCalled', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'a', + isStateVar: false, + isIndexed: false }, + { type: 'VariableDeclaration', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'b', + isStateVar: false, + isIndexed: false } ] }, + isAnonymous: false }, + { type: 'FunctionDefinition', + name: 'someFunctionSet', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'counter', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' }, + name: 'values', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: null, + body: + { type: 'Block', + statements: + [ { type: 'ExpressionStatement', + expression: + { type: 'BinaryOperation', + operator: '=', + left: + { type: 'IndexAccess', + base: { type: 'Identifier', name: 'someFunction_returns' }, + index: { type: 'Identifier', name: 'counter' } }, + right: { type: 'Identifier', name: 'values' } } } ] }, + visibility: 'public', + modifiers: [], + isConstructor: false, + stateMutability: null }, + { type: 'FunctionDefinition', + name: 'someFunction', + parameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'a', + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: 'b', + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + returnParameters: + { type: 'ParameterList', + parameters: + [ { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'uint256' }, + name: null, + storageLocation: null, + isStateVar: false, + isIndexed: false }, + { type: 'Parameter', + typeName: { type: 'ElementaryTypeName', name: 'bytes' }, + name: null, + storageLocation: null, + isStateVar: false, + isIndexed: false } ] }, + body: + { type: 'Block', + statements: + [ { type: 'EmitStatement', + eventCall: + { type: 'FunctionCall', + expression: { type: 'Identifier', name: 'someFunctionCalled' }, + arguments: + [ { type: 'Identifier', name: 'someFunction_counter' }, + { type: 'Identifier', name: 'a' }, + { type: 'Identifier', name: 'b' } ], + names: [] } }, + { type: 'VariableDeclarationStatement', + variables: + [ { type: 'VariableDeclaration', + typeName: + { type: 'UserDefinedTypeName', + namePath: 'someFunction_ReturnType' }, + name: 'returnValues', + storageLocation: 'storage', + isStateVar: false, + isIndexed: false } ], + initialValue: + { type: 'IndexAccess', + base: { type: 'Identifier', name: 'someFunction_returns' }, + index: { type: 'Identifier', name: 'someFunction_counter' } } }, + { type: 'ExpressionStatement', + expression: + { type: 'UnaryOperation', + operator: '++', + subExpression: { type: 'Identifier', name: 'someFunction_counter' }, + isPrefix: false } }, + { type: 'ReturnStatement', + expression: + { type: 'TupleExpression', + components: + [ { type: 'MemberAccess', + expression: { type: 'Identifier', name: 'returnValues' }, + memberName: '_arg_0' }, + { type: 'MemberAccess', + expression: { type: 'Identifier', name: 'returnValues' }, + memberName: '_arg_1' } ], + isArray: false } } ] }, + visibility: 'internal', + modifiers: [], + isConstructor: false, + stateMutability: null } ], + kind: 'contract' } ] } diff --git a/packages/sol-meta/test/interface.expected.sol b/packages/sol-meta/test/interface.expected.sol new file mode 100644 index 0000000000..b364cc060c --- /dev/null +++ b/packages/sol-meta/test/interface.expected.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.4.23; +pragma experimental ABIEncoderV2; + +contract MAssetProxyDispatcher { + + uint256 dispatchTransferFrom_counter = 0; + + event dispatchTransferFromCalled(uint256 counter, + bytes assetData, + address from, + address to, + uint256 amount); + + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + emit dispatchTransferFromCalled( + dispatchTransferFrom_counter, + assetData, + from, + to, + amount + ); + dispatchTransferFrom_counter++; + } + + uint256 internal someFunction_counter = 0; + + struct someFunction_ReturnType { + bool reverts; + uint256 _arg_0; + bytes _arg_1; + } + + mapping (uint256 => someFunction_ReturnType) someFunction_returns; + + event someFunctionCalled(uint256 counter, uint256 a, uint256 b); + + function someFunctionSet(uint256 counter, someFunction_ReturnType values) + public + { + (someFunction_returns[counter]) = values; + } + + function someFunction(uint256 a, uint256 b) + internal + returns (uint256, bytes) + { + emit someFunctionCalled(someFunction_counter, a, b); + (someFunction_ReturnType storage returnValues) = (someFunction_returns[someFunction_counter]); + someFunction_counter++; + require(!returnValues.reverts); + return ((returnValues._arg_0), (returnValues._arg_1)); + } +} diff --git a/packages/sol-meta/test/interface.sol b/packages/sol-meta/test/interface.sol new file mode 100644 index 0000000000..9015089566 --- /dev/null +++ b/packages/sol-meta/test/interface.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.4.24; + + +contract IOwnable { + + function transferOwnership(address newOwner) + public; +} + +contract Ownable is + IOwnable +{ + address public owner; + + constructor () + public + { + owner = msg.sender; + } + + modifier onlyOwner() { + require( + msg.sender == owner, + "ONLY_CONTRACT_OWNER" + ); + _; + } + + function transferOwnership(address newOwner) + public + onlyOwner + { + if (newOwner != address(0)) { + owner = newOwner; + } + } +} + + +contract MAssetProxyDispatcher is Ownable { + + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal; + + + function someFunction(uint256 a, uint256 b) + internal + returns (uint256, bytes); +} diff --git a/packages/sol-meta/test/out.sol b/packages/sol-meta/test/out.sol new file mode 100644 index 0000000000..55de391fc0 --- /dev/null +++ b/packages/sol-meta/test/out.sol @@ -0,0 +1,43 @@ +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; +import "interface.sol"; +contract MAssetProxyDispatcherMock is MAssetProxyDispatcher{ + + uint256 internal _dispatchTransferFrom_counter = 0; + + event _dispatchTransferFrom_log(uint256 counter, bytes assetData, address from, address to, uint256 amount); + + function dispatchTransferFrom(bytes memory assetData, address from, address to, uint256 amount) + internal + { + emit _dispatchTransferFrom_log(_dispatchTransferFrom_counter, assetData, from, to, amount); + _dispatchTransferFrom_counter++; + } + + uint256 internal _someFunction_counter = 0; + + event _someFunction_log(uint256 counter, uint256 a, uint256 b); + + struct _someFunction_Result { + uint256 _ret0; + bytes _ret1; + } + + mapping (uint256 => _someFunction_Result) _someFunction_results; + + function _someFunction_set(uint256 _counter, _someFunction_Result _value) + public + { + (_someFunction_results[_counter]) = _value; + } + + function someFunction(uint256 a, uint256 b) + internal + returns (uint256 _ret0, bytes _ret1) + { + emit _someFunction_log(_someFunction_counter, a, b); + _someFunction_Result storage result = (_someFunction_results[_someFunction_counter]); + _someFunction_counter++; + return ((result._ret0), (result._ret1)); + } +} diff --git a/packages/sol-meta/test/test.out.sol b/packages/sol-meta/test/test.out.sol new file mode 100644 index 0000000000..0f9b72d17d --- /dev/null +++ b/packages/sol-meta/test/test.out.sol @@ -0,0 +1,57 @@ +pragma solidity ^0.4.0; +import "./test.sol"; +contract TestExposed is Test{ + + function get_hiddenState() + public view + returns (uint256 ) + { + return hiddenState; + } + + function set_hiddenState(uint256 setterNewValue) + public + { + hiddenState = setterNewValue; + } + + function emit_SomeLogEvent(uint256 withValues, address canBeIndexed) + public + { + emit SomeLogEvent(withValues, canBeIndexed); + } + + function modifier_modWithoutParameters() + public modWithoutParameters() + returns (bool executed) + { + return true; + } + + function modifier_modWithParameters(bytes32 hash) + public modWithParameters(hash) + returns (bool executed) + { + return true; + } + + function public_someInternalAction(uint256[4] withParameters) + public + { + someInternalAction(withParameters); + } + + function public_someInternalFunction(uint256 x) + public pure + returns (uint256 y) + { + return (someInternalFunction(x)); + } + + function public_someMultiFunction(uint256 a) + public view + returns (uint256 x, uint256 y) + { + return (someMultiFunction(a)); + } +} diff --git a/packages/sol-meta/test/test.sol b/packages/sol-meta/test/test.sol new file mode 100644 index 0000000000..067ef11919 --- /dev/null +++ b/packages/sol-meta/test/test.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.4.0; + +contract Test { + + uint256 internal hiddenState; + + event SomeLogEvent(uint256 withValues, address indexed canBeIndexed); + + constructor () public { + hiddenState = 42; + } + + modifier modWithoutParameters { + if(msg.sender == 0x3) { + _; + } + } + + modifier modWithParameters(bytes32 hash) { + if (keccak256(abi.encodePacked(msg.sender)) == hash) { + revert(); + } + _; + } + + function someInternalAction(uint256[4] withParameters) + internal + modWithoutParameters + { + hiddenState = withParameters[3]; + } + + function someInternalFunction(uint256 x) + internal pure + returns (uint256 y) + { + y = x * x + 5; + } + + function someMultiFunction(uint256 a) + internal view + returns (uint256 x, uint256 y) + { + x = hiddenState + a; + y = x * x + 5; + } + +} From e4527badc7b1cb247db3efcf65c9cffab133b620 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 8 Nov 2018 17:04:19 +0100 Subject: [PATCH 35/76] [WIP] tsconfig --- packages/sol-meta/tsconfig.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/sol-meta/tsconfig.json b/packages/sol-meta/tsconfig.json index 100b063255..c630ddb933 100644 --- a/packages/sol-meta/tsconfig.json +++ b/packages/sol-meta/tsconfig.json @@ -4,7 +4,15 @@ "outDir": "lib", "rootDir": ".", "strictFunctionTypes": false, - "noImplicitAny": false + "noImplicitAny": false, + "typeRoots": ["node_modules/@types"], }, - "include": ["./src/**/*", "./test/**/*"] + "include": [ + "node_modules/@types", + "node_modules/@0xproject/typescript-typings/types", + "../../node_modules/@types", + "../../node_modules/@0xproject/typescript-typings/types", + "./src/**/*", + "./test/**/*" + ] } From 081e1045bee09074d7ca6de34e70d7219adf00b3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 9 Nov 2018 14:57:43 +0100 Subject: [PATCH 36/76] Remove ast typings from sol-cov --- packages/sol-cov/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index 34e7a85592..db85765b7e 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -48,7 +48,6 @@ "@0x/typescript-typings": "^3.0.4", "@0x/utils": "^2.0.4", "@0x/web3-wrapper": "^3.1.1", - "@types/solidity-parser-antlr": "^0.2.0", "ethereum-types": "^1.1.2", "ethereumjs-util": "^5.1.1", "glob": "^7.1.2", From b1aac19014fb691d9fdfdd6cfb524f79d7deb3b0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 9 Nov 2018 14:59:15 +0100 Subject: [PATCH 37/76] declare local typings as module --- .../types/solidity-parser-antlr/index.d.ts | 1596 ++++++++--------- 1 file changed, 789 insertions(+), 807 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index 79ab893858..d6a20f5f5f 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -10,817 +10,799 @@ // TODO: Parameter and VariableDeclaration are the same node -export interface LineColumn { - line: number; - column: number; -} -export interface Location { - start: LineColumn; - end: LineColumn; -} -export const enum NodeType { - SourceUnit = 'SourceUnit', - PragmaDirective = 'PragmaDirective', - PragmaName = 'PragmaName', - PragmaValue = 'PragmaValue', - Version = 'Version', - VersionOperator = 'VersionOperator', - VersionConstraint = 'VersionConstraint', - ImportDeclaration = 'ImportDeclaration', - ImportDirective = 'ImportDirective', - ContractDefinition = 'ContractDefinition', - InheritanceSpecifier = 'InheritanceSpecifier', - ContractPart = 'ContractPart', - StateVariableDeclaration = 'StateVariableDeclaration', - UsingForDeclaration = 'UsingForDeclaration', - StructDefinition = 'StructDefinition', - ModifierDefinition = 'ModifierDefinition', - ModifierInvocation = 'ModifierInvocation', - FunctionDefinition = 'FunctionDefinition', - ReturnParameters = 'ReturnParameters', - ModifierList = 'ModifierList', - EventDefinition = 'EventDefinition', - EnumValue = 'EnumValue', - EnumDefinition = 'EnumDefinition', - ParameterList = 'ParameterList', - Parameter = 'Parameter', - EventParameterList = 'EventParameterList', - EventParameter = 'EventParameter', - FunctionTypeParameterList = 'FunctionTypeParameterList', - FunctionTypeParameter = 'FunctionTypeParameter', - VariableDeclaration = 'VariableDeclaration', - TypeName = 'TypeName', - UserDefinedTypeName = 'UserDefinedTypeName', - Mapping = 'Mapping', - ArrayTypeName = 'ArrayTypeName', - FunctionTypeName = 'FunctionTypeName', - StorageLocation = 'StorageLocation', - StateMutability = 'StateMutability', - Block = 'Block', - ExpressionStatement = 'ExpressionStatement', - IfStatement = 'IfStatement', - WhileStatement = 'WhileStatement', - SimpleStatement = 'SimpleStatement', - ForStatement = 'ForStatement', - InlineAssemblyStatement = 'InlineAssemblyStatement', - DoWhileStatement = 'DoWhileStatement', - ContinueStatement = 'ContinueStatement', - BreakStatement = 'BreakStatement', - ReturnStatement = 'ReturnStatement', - ThrowStatement = 'ThrowStatement', - EmitStatement = 'EmitStatement', - VariableDeclarationStatement = 'VariableDeclarationStatement', - IdentifierList = 'IdentifierList', - ElementaryTypeName = 'ElementaryTypeName', - PrimaryExpression = 'PrimaryExpression', - ExpressionList = 'ExpressionList', - NameValueList = 'NameValueList', - NameValue = 'NameValue', - FunctionCall = 'FunctionCall', - AssemblyBlock = 'AssemblyBlock', - AssemblyItem = 'AssemblyItem', - AssemblyExpression = 'AssemblyExpression', - AssemblyCall = 'AssemblyCall', - AssemblyLocalDefinition = 'AssemblyLocalDefinition', - AssemblyAssignment = 'AssemblyAssignment', - AssemblyIdentifierOrList = 'AssemblyIdentifierOrList', - AssemblyIdentifierList = 'AssemblyIdentifierList', - AssemblyStackAssignment = 'AssemblyStackAssignment', - LabelDefinition = 'LabelDefinition', - AssemblySwitch = 'AssemblySwitch', - AssemblyCase = 'AssemblyCase', - AssemblyFunctionDefinition = 'AssemblyFunctionDefinition', - AssemblyFunctionReturns = 'AssemblyFunctionReturns', - AssemblyFor = 'AssemblyFor', - AssemblyIf = 'AssemblyIf', - AssemblyLiteral = 'AssemblyLiteral', - SubAssembly = 'SubAssembly', - TupleExpression = 'TupleExpression', - ElementaryTypeNameExpression = 'ElementaryTypeNameExpression', - BooleanLiteral = 'BooleanLiteral', - NumberLiteral = 'NumberLiteral', - StringLiteral = 'StringLiteral', - Identifier = 'Identifier', - UnaryOperation = 'UnaryOperation', - BinaryOperation = 'BinaryOperation', - Conditional = 'Conditional', - IndexAccess = 'IndexAccess', - MemberAccess = 'MemberAccess', - NewExpression = 'NewExpression', - DecimalNumber = 'DecimalNumber', - HexNumber = 'HexNumber', -} -export const enum ContractKind { - Contract = 'contract', - Interface = 'interface', - Library = 'library' -} -export const enum Visibility { - Default = 'default', - Public = 'public', - Internal = 'internal', - Private = 'private', -} -export const enum StateMutability { - Default = null, - View = 'view', - Pure = 'pure' -} -export const enum StorageLocation { - Default = null, - Memory = 'memory', - Storage = 'storage', - Calldata = 'calldata' -} -export type UnOp = - | '++' - | '--' - | '-' - | '+' - | '!'; -export type BinOp = - | '+' - | '-' - | '*' - | '/' - | '**' - | '%' - | '<<' - | '>>' - | '&&' - | '||' - | '&' - | '|' - | '^' - | '<' - | '>' - | '<=' - | '>=' - | '==' - | '!=' - | '=' - | '|=' - | '^=' - | '&=' - | '<<=' - | '>>=' - | '+=' - | '-=' - | '*=' - | '/=' - | '%='; - -export interface BaseASTNode { - type: NodeType; - range?: [number, number]; - loc?: Location; -} -export interface SourceUnit extends BaseASTNode { - type: NodeType.SourceUnit; - children: SourceMembers[]; -} -export interface PragmaDirective extends BaseASTNode { - type: NodeType.PragmaDirective; - name: string; - value: string; -} -/* -export interface PragmaName extends BaseASTNode { - type: NodeType.PragmaName; -} -export interface PragmaValue extends BaseASTNode { - type: NodeType.PragmaValue; -} -export interface Version extends BaseASTNode { - type: NodeType.Version; -} -export interface VersionOperator extends BaseASTNode { - type: NodeType.VersionOperator; -} -export interface VersionConstraint extends BaseASTNode { - type: NodeType.VersionConstraint; -} -export interface ImportDeclaration extends BaseASTNode { - type: NodeType.ImportDeclaration; -} -*/ -export interface ImportDirective extends BaseASTNode { - type: NodeType.ImportDirective; - path: string; - symbolAliases: [string, string][] | null; -} -export interface ContractDefinition extends BaseASTNode { - type: NodeType.ContractDefinition; - name: string; - kind: ContractKind; - baseContracts: InheritanceSpecifier[]; - subNodes: ContractMember[] -} -export interface InheritanceSpecifier extends BaseASTNode { - type: NodeType.InheritanceSpecifier; - baseName: UserDefinedTypeName; -} -/* -export interface ContractPart extends BaseASTNode { - type: NodeType.ContractPart; -} -*/ -export interface UsingForDeclaration extends BaseASTNode { - type: NodeType.UsingForDeclaration; - typeName: Type; - libraryName: string; -} -export interface StateVariableDeclaration extends BaseASTNode { - type: NodeType.StateVariableDeclaration; - variables: VariableDeclaration[]; - initialValue: Expression | null; // TODO check if exists -} -export interface StructDefinition extends BaseASTNode { - type: NodeType.StructDefinition; - name: string; - members: VariableDeclaration[]; -} -export interface EnumDefinition extends BaseASTNode { - type: NodeType.EnumDefinition; - name: string; - members: EnumValue[]; -} -export interface EnumValue extends BaseASTNode { - type: NodeType.EnumValue; - name: string; -} -export interface EventDefinition extends BaseASTNode { - type: NodeType.EventDefinition; - name: string; - parameters: ParameterList; - isAnonymous: boolean; -} -export interface ModifierDefinition extends BaseASTNode { - type: NodeType.ModifierDefinition; - name: string; - parameters: ParameterList; - body: Block; -} -export interface FunctionDefinition extends BaseASTNode { - type: NodeType.FunctionDefinition; - name: string; - parameters: ParameterList; - returnParameters: ParameterList | null; - body: Block | null; - visibility: Visibility; - modifiers: ModifierInvocation[]; - isConstructor: boolean; - stateMutability: StateMutability; -} -export interface ModifierInvocation extends BaseASTNode { - type: NodeType.ModifierInvocation; - name: string; - arguments: Expression[]; -} -export interface ParameterList extends BaseASTNode { - type: NodeType.ParameterList; - parameters: Parameter[]; -} -export interface Parameter extends BaseASTNode { - type: NodeType.Parameter; - name: string | null; - typeName: Type; - visibility?: Visibility; - storageLocation?: StorageLocation | null; - expression?: Expression; - isStateVar?: boolean; - isDeclaredConst?: boolean; - isIndexed?: boolean; -} -/* -export interface EventParameterList extends BaseASTNode { - type: NodeType.EventParameterList; -} -export interface EventParameter extends BaseASTNode { - type: NodeType.EventParameter; -} -export interface FunctionTypeParameterList extends BaseASTNode { - type: NodeType.FunctionTypeParameterList; -} -export interface FunctionTypeParameter extends BaseASTNode { - type: NodeType.FunctionTypeParameter; -} -*/ -export interface Block extends BaseASTNode { - type: NodeType.Block; - statements: Statement[]; -} -export interface VariableDeclaration extends BaseASTNode { - type: NodeType.VariableDeclaration; - name: string; - visibility?: Visibility; - storageLocation?: StorageLocation; - typeName: Type; - expression?: Expression; - isStateVar: boolean; - isDeclaredConst?: boolean; - isIndexed: boolean; -} -/* -export interface TypeName extends BaseASTNode { - type: NodeType.TypeName; -} -export interface StorageLocation extends BaseASTNode { - type: NodeType.StorageLocation; -} -export interface StateMutability extends BaseASTNode { - type: NodeType.StateMutability; -} -*/ -export interface ExpressionStatement extends BaseASTNode { - type: NodeType.ExpressionStatement; - expression: Expression; -} -export interface IfStatement extends BaseASTNode { - type: NodeType.IfStatement; - condition: Expression; - trueBody: Statement; - falseBody: Statement; -} -export interface WhileStatement extends BaseASTNode { - type: NodeType.WhileStatement; - // TODO -} -export interface ForStatement extends BaseASTNode { - type: NodeType.ForStatement; - initExpression: Expression; - conditionExpression: Expression; - loopExpression: Expression; - body: Statement; -} -export interface InlineAssemblyStatement extends BaseASTNode { - type: NodeType.InlineAssemblyStatement; - language: string; - body: AssemblyBlock; -} -export interface DoWhileStatement extends BaseASTNode { - type: NodeType.DoWhileStatement; - // TODO -} -export interface ContinueStatement extends BaseASTNode { - type: NodeType.ContinueStatement; -} -export interface BreakStatement extends BaseASTNode { - type: NodeType.BreakStatement; -} -export interface ReturnStatement extends BaseASTNode { - type: NodeType.ReturnStatement; - expression: Expression | null; -} -export interface ThrowStatement extends BaseASTNode { - type: NodeType.ThrowStatement; -} -export interface EmitStatement extends BaseASTNode { - type: NodeType.EmitStatement; - eventCall: FunctionCall; -} -export interface VariableDeclarationStatement extends BaseASTNode { - type: NodeType.VariableDeclarationStatement; - variables: VariableDeclaration[]; - initialValue: Expression; -} -export interface NewExpression extends BaseASTNode { - type: NodeType.NewExpression; - typeName: Type; -} +declare module 'solidity-parser-antlr' { + export interface LineColumn { + line: number; + column: number; + } + export interface Location { + start: LineColumn; + end: LineColumn; + } + export const enum NodeType { + SourceUnit = 'SourceUnit', + PragmaDirective = 'PragmaDirective', + PragmaName = 'PragmaName', + PragmaValue = 'PragmaValue', + Version = 'Version', + VersionOperator = 'VersionOperator', + VersionConstraint = 'VersionConstraint', + ImportDeclaration = 'ImportDeclaration', + ImportDirective = 'ImportDirective', + ContractDefinition = 'ContractDefinition', + InheritanceSpecifier = 'InheritanceSpecifier', + ContractPart = 'ContractPart', + StateVariableDeclaration = 'StateVariableDeclaration', + UsingForDeclaration = 'UsingForDeclaration', + StructDefinition = 'StructDefinition', + ModifierDefinition = 'ModifierDefinition', + ModifierInvocation = 'ModifierInvocation', + FunctionDefinition = 'FunctionDefinition', + ReturnParameters = 'ReturnParameters', + ModifierList = 'ModifierList', + EventDefinition = 'EventDefinition', + EnumValue = 'EnumValue', + EnumDefinition = 'EnumDefinition', + ParameterList = 'ParameterList', + Parameter = 'Parameter', + EventParameterList = 'EventParameterList', + EventParameter = 'EventParameter', + FunctionTypeParameterList = 'FunctionTypeParameterList', + FunctionTypeParameter = 'FunctionTypeParameter', + VariableDeclaration = 'VariableDeclaration', + TypeName = 'TypeName', + UserDefinedTypeName = 'UserDefinedTypeName', + Mapping = 'Mapping', + ArrayTypeName = 'ArrayTypeName', + FunctionTypeName = 'FunctionTypeName', + StorageLocation = 'StorageLocation', + StateMutability = 'StateMutability', + Block = 'Block', + ExpressionStatement = 'ExpressionStatement', + IfStatement = 'IfStatement', + WhileStatement = 'WhileStatement', + ForStatement = 'ForStatement', + InlineAssemblyStatement = 'InlineAssemblyStatement', + DoWhileStatement = 'DoWhileStatement', + ContinueStatement = 'ContinueStatement', + BreakStatement = 'BreakStatement', + ReturnStatement = 'ReturnStatement', + ThrowStatement = 'ThrowStatement', + EmitStatement = 'EmitStatement', + VariableDeclarationStatement = 'VariableDeclarationStatement', + IdentifierList = 'IdentifierList', + ElementaryTypeName = 'ElementaryTypeName', + PrimaryExpression = 'PrimaryExpression', + ExpressionList = 'ExpressionList', + NameValueList = 'NameValueList', + NameValue = 'NameValue', + FunctionCall = 'FunctionCall', + AssemblyBlock = 'AssemblyBlock', + AssemblyItem = 'AssemblyItem', + AssemblyExpression = 'AssemblyExpression', + AssemblyCall = 'AssemblyCall', + AssemblyLocalDefinition = 'AssemblyLocalDefinition', + AssemblyAssignment = 'AssemblyAssignment', + AssemblyIdentifierOrList = 'AssemblyIdentifierOrList', + AssemblyIdentifierList = 'AssemblyIdentifierList', + AssemblyStackAssignment = 'AssemblyStackAssignment', + LabelDefinition = 'LabelDefinition', + AssemblySwitch = 'AssemblySwitch', + AssemblyCase = 'AssemblyCase', + AssemblyFunctionDefinition = 'AssemblyFunctionDefinition', + AssemblyFunctionReturns = 'AssemblyFunctionReturns', + AssemblyFor = 'AssemblyFor', + AssemblyIf = 'AssemblyIf', + AssemblyLiteral = 'AssemblyLiteral', + SubAssembly = 'SubAssembly', + TupleExpression = 'TupleExpression', + ElementaryTypeNameExpression = 'ElementaryTypeNameExpression', + BooleanLiteral = 'BooleanLiteral', + NumberLiteral = 'NumberLiteral', + StringLiteral = 'StringLiteral', + Identifier = 'Identifier', + UnaryOperation = 'UnaryOperation', + BinaryOperation = 'BinaryOperation', + Conditional = 'Conditional', + IndexAccess = 'IndexAccess', + MemberAccess = 'MemberAccess', + NewExpression = 'NewExpression', + DecimalNumber = 'DecimalNumber', + HexNumber = 'HexNumber', + } + export const enum ContractKind { + Contract = 'contract', + Interface = 'interface', + Library = 'library', + } + export const enum Visibility { + Default = 'default', + Public = 'public', + Internal = 'internal', + Private = 'private', + } + export const enum StateMutability { + Default = null, + View = 'view', + Pure = 'pure', + } + export const enum StorageLocation { + Default = null, + Memory = 'memory', + Storage = 'storage', + Calldata = 'calldata', + } + export type UnOp = '++' | '--' | '-' | '+' | '!'; + export type BinOp = + | '+' + | '-' + | '*' + | '/' + | '**' + | '%' + | '<<' + | '>>' + | '&&' + | '||' + | '&' + | '|' + | '^' + | '<' + | '>' + | '<=' + | '>=' + | '==' + | '!=' + | '=' + | '|=' + | '^=' + | '&=' + | '<<=' + | '>>=' + | '+=' + | '-=' + | '*=' + | '/=' + | '%='; -// Types -export interface ElementaryTypeName extends BaseASTNode { - type: NodeType.ElementaryTypeName; - name: string; -} -export interface UserDefinedTypeName extends BaseASTNode { - type: NodeType.UserDefinedTypeName; - namePath: string; -} -export interface Mapping extends BaseASTNode { - type: NodeType.Mapping; - keyType: Type; - valueType: Type; -} -export interface ArrayTypeName extends BaseASTNode { - type: NodeType.Mapping; - baseTypeName: Type; - length: Expression | null; -} -export interface FunctionTypeName extends BaseASTNode { - type: NodeType.FunctionTypeName; - // TODO -} -/* -export interface PrimaryExpression extends BaseASTNode { - type: NodeType.PrimaryExpression; -} -export interface ExpressionList extends BaseASTNode { - type: NodeType.ExpressionList; -} -*/ -export interface NameValueList extends BaseASTNode { - type: NodeType.NameValueList; - // TODO -} -export interface NameValue extends BaseASTNode { - type: NodeType.NameValue; - // TODO -} + export interface BaseASTNode { + type: NodeType; + range?: [number, number]; + loc?: Location; + } -// Expressions -export interface Identifier extends BaseASTNode { - type: NodeType.Identifier; - name: string; -} -export interface BooleanLiteral extends BaseASTNode { - type: NodeType.BooleanLiteral; - value: boolean; -} -export interface NumberLiteral extends BaseASTNode { - type: NodeType.NumberLiteral; - number: string; - subdenomination: any; // TODO -} -export interface StringLiteral extends BaseASTNode { - type: NodeType.StringLiteral; - value: string; -} -export interface FunctionCall extends BaseASTNode { - type: NodeType.FunctionCall; - expression: Expression; - arguments: Expression[]; - names: []; -} -export interface TupleExpression extends BaseASTNode { - type: NodeType.TupleExpression; - components: Expression[]; - isArray: boolean -} -export interface ElementaryTypeNameExpression extends BaseASTNode { - type: NodeType.ElementaryTypeNameExpression; - typeName: Type; -} -export interface UnaryOperation extends BaseASTNode { - type: NodeType.UnaryOperation; - operator: UnOp; - isPrefix: boolean; - subExpression: Expression; -} -export interface BinaryOperation extends BaseASTNode { - type: NodeType.BinaryOperation; - operator: BinOp; - left: Expression; - right: Expression; -} -export interface Conditional extends BaseASTNode { - type: NodeType.Conditional; - condition: Expression; - trueExpression: Expression; - falseExpression: Expression; -} -export interface IndexAccess extends BaseASTNode { - type: NodeType.IndexAccess; - base: Expression; - index: Expression; -} -export interface MemberAccess extends BaseASTNode { - type: NodeType.MemberAccess; - expression: Expression; - memberName: string; -} + export interface SourceUnit extends BaseASTNode { + type: NodeType.SourceUnit; + children: SourceMembers[]; + } + export interface PragmaDirective extends BaseASTNode { + type: NodeType.PragmaDirective; + name: string; + value: string; + } + /* + export interface PragmaName extends BaseASTNode { + type: NodeType.PragmaName; + } + export interface PragmaValue extends BaseASTNode { + type: NodeType.PragmaValue; + } + export interface Version extends BaseASTNode { + type: NodeType.Version; + } + export interface VersionOperator extends BaseASTNode { + type: NodeType.VersionOperator; + } + export interface VersionConstraint extends BaseASTNode { + type: NodeType.VersionConstraint; + } + export interface ImportDeclaration extends BaseASTNode { + type: NodeType.ImportDeclaration; + } + */ + export interface ImportDirective extends BaseASTNode { + type: NodeType.ImportDirective; + path: string; + symbolAliases: [string, string][] | null; + } + export interface ContractDefinition extends BaseASTNode { + type: NodeType.ContractDefinition; + name: string; + kind: ContractKind; + baseContracts: InheritanceSpecifier[]; + subNodes: ContractMember[]; + } + export interface InheritanceSpecifier extends BaseASTNode { + type: NodeType.InheritanceSpecifier; + baseName: UserDefinedTypeName; + } + /* + export interface ContractPart extends BaseASTNode { + type: NodeType.ContractPart; + } + */ + export interface UsingForDeclaration extends BaseASTNode { + type: NodeType.UsingForDeclaration; + typeName: Type; + libraryName: string; + } + export interface StateVariableDeclaration extends BaseASTNode { + type: NodeType.StateVariableDeclaration; + variables: VariableDeclaration[]; + initialValue: Expression | null; // TODO check if exists + } + export interface StructDefinition extends BaseASTNode { + type: NodeType.StructDefinition; + name: string; + members: VariableDeclaration[]; + } + export interface EnumDefinition extends BaseASTNode { + type: NodeType.EnumDefinition; + name: string; + members: EnumValue[]; + } + export interface EnumValue extends BaseASTNode { + type: NodeType.EnumValue; + name: string; + } + export interface EventDefinition extends BaseASTNode { + type: NodeType.EventDefinition; + name: string; + parameters: ParameterList; + isAnonymous: boolean; + } + export interface ModifierDefinition extends BaseASTNode { + type: NodeType.ModifierDefinition; + name: string; + parameters: ParameterList; + body: Block; + } + export interface FunctionDefinition extends BaseASTNode { + type: NodeType.FunctionDefinition; + name: string; + parameters: ParameterList; + returnParameters: ParameterList | null; + body: Block | null; + visibility: Visibility; + modifiers: ModifierInvocation[]; + isConstructor: boolean; + stateMutability: StateMutability; + } + export interface ModifierInvocation extends BaseASTNode { + type: NodeType.ModifierInvocation; + name: string; + arguments: Expression[]; + } + export interface ParameterList extends BaseASTNode { + type: NodeType.ParameterList; + parameters: Parameter[]; + } + export interface Parameter extends BaseASTNode { + type: NodeType.Parameter; + name: string | null; + typeName: Type; + visibility?: Visibility; + storageLocation?: StorageLocation | null; + expression?: Expression; + isStateVar?: boolean; + isDeclaredConst?: boolean; + isIndexed?: boolean; + } + /* + export interface EventParameterList extends BaseASTNode { + type: NodeType.EventParameterList; + } + export interface EventParameter extends BaseASTNode { + type: NodeType.EventParameter; + } + export interface FunctionTypeParameterList extends BaseASTNode { + type: NodeType.FunctionTypeParameterList; + } + export interface FunctionTypeParameter extends BaseASTNode { + type: NodeType.FunctionTypeParameter; + } + */ + export interface Block extends BaseASTNode { + type: NodeType.Block; + statements: Statement[]; + } + export interface VariableDeclaration extends BaseASTNode { + type: NodeType.VariableDeclaration; + name: string; + visibility?: Visibility; + storageLocation?: StorageLocation; + typeName: Type; + expression?: Expression; + isStateVar: boolean; + isDeclaredConst?: boolean; + isIndexed: boolean; + } + /* + export interface TypeName extends BaseASTNode { + type: NodeType.TypeName; + } + export interface StorageLocation extends BaseASTNode { + type: NodeType.StorageLocation; + } + export interface StateMutability extends BaseASTNode { + type: NodeType.StateMutability; + } + */ + export interface ExpressionStatement extends BaseASTNode { + type: NodeType.ExpressionStatement; + expression: Expression; + } + export interface IfStatement extends BaseASTNode { + type: NodeType.IfStatement; + condition: Expression; + trueBody: Statement; + falseBody: Statement; + } + export interface WhileStatement extends BaseASTNode { + type: NodeType.WhileStatement; + // TODO + } + export interface ForStatement extends BaseASTNode { + type: NodeType.ForStatement; + initExpression: Expression; + conditionExpression: Expression; + loopExpression: Expression; + body: Statement; + } + export interface InlineAssemblyStatement extends BaseASTNode { + type: NodeType.InlineAssemblyStatement; + language: string; + body: AssemblyBlock; + } + export interface DoWhileStatement extends BaseASTNode { + type: NodeType.DoWhileStatement; + // TODO + } + export interface ContinueStatement extends BaseASTNode { + type: NodeType.ContinueStatement; + } + export interface BreakStatement extends BaseASTNode { + type: NodeType.BreakStatement; + } + export interface ReturnStatement extends BaseASTNode { + type: NodeType.ReturnStatement; + expression: Expression | null; + } + export interface ThrowStatement extends BaseASTNode { + type: NodeType.ThrowStatement; + } + export interface EmitStatement extends BaseASTNode { + type: NodeType.EmitStatement; + eventCall: FunctionCall; + } + export interface VariableDeclarationStatement extends BaseASTNode { + type: NodeType.VariableDeclarationStatement; + variables: VariableDeclaration[]; + initialValue: Expression; + } + export interface NewExpression extends BaseASTNode { + type: NodeType.NewExpression; + typeName: Type; + } + // Types + export interface ElementaryTypeName extends BaseASTNode { + type: NodeType.ElementaryTypeName; + name: string; + } + export interface UserDefinedTypeName extends BaseASTNode { + type: NodeType.UserDefinedTypeName; + namePath: string; + } + export interface Mapping extends BaseASTNode { + type: NodeType.Mapping; + keyType: Type; + valueType: Type; + } + export interface ArrayTypeName extends BaseASTNode { + type: NodeType.Mapping; + baseTypeName: Type; + length: Expression | null; + } + export interface FunctionTypeName extends BaseASTNode { + type: NodeType.FunctionTypeName; + // TODO + } + /* + export interface PrimaryExpression extends BaseASTNode { + type: NodeType.PrimaryExpression; + } + export interface ExpressionList extends BaseASTNode { + type: NodeType.ExpressionList; + } + */ + export interface NameValueList extends BaseASTNode { + type: NodeType.NameValueList; + // TODO + } + export interface NameValue extends BaseASTNode { + type: NodeType.NameValue; + // TODO + } -export interface AssemblyBlock extends BaseASTNode { - type: NodeType.AssemblyBlock; - operations: AssemblyStatement[]; -} -/* -export interface AssemblyItem extends BaseASTNode { - type: NodeType.AssemblyItem; -} -export interface AssemblyExpression extends BaseASTNode { - type: NodeType.AssemblyExpression; -} -*/ -export interface AssemblyCall extends BaseASTNode { - type: NodeType.AssemblyCall; - functionName: string; - arguments: AssemblyExpression[]; -} -export interface AssemblyLocalDefinition extends BaseASTNode { - type: NodeType.AssemblyLocalDefinition; - names: Identifier[]; - expression: AssemblyExpression; -} -export interface AssemblyAssignment extends BaseASTNode { - type: NodeType.AssemblyAssignment; - names: Identifier[]; - expression: AssemblyExpression; -} -/* -export interface AssemblyIdentifierOrList extends BaseASTNode { - type: NodeType.AssemblyIdentifierOrList; -} -export interface AssemblyIdentifierList extends BaseASTNode { - type: NodeType.AssemblyIdentifierList; -} -export interface AssemblyStackAssignment extends BaseASTNode { - type: NodeType.AssemblyStackAssignment; -} -*/ -export interface LabelDefinition extends BaseASTNode { - type: NodeType.LabelDefinition; - // TODO -} -export interface AssemblySwitch extends BaseASTNode { - type: NodeType.AssemblySwitch; - expression: Expression; - cases: AssemblyCase[]; -} -export interface AssemblyCase extends BaseASTNode { - type: NodeType.AssemblyCase; - value: Expression; - block: AssemblyBlock; -} -export interface AssemblyFunctionDefinition extends BaseASTNode { - type: NodeType.AssemblyFunctionDefinition; - // TODO -} -export interface AssemblyFunctionReturns extends BaseASTNode { - type: NodeType.AssemblyFunctionReturns; - // TODO -} -export interface AssemblyFor extends BaseASTNode { - type: NodeType.AssemblyFor; - pre: AssemblyBlock | AssemblyExpression; - condition: AssemblyExpression; - post: AssemblyBlock | AssemblyExpression; - body: AssemblyBlock; -} -export interface AssemblyIf extends BaseASTNode { - type: NodeType.AssemblyIf; - condition: AssemblyExpression; - body: AssemblyBlock; -} -export interface DecimalNumber extends BaseASTNode { - type: NodeType.AssemblyIf; - value: string; -} -export interface HexNumber extends BaseASTNode { - type: NodeType.AssemblyIf; - value: string; -} + // Expressions + export interface Identifier extends BaseASTNode { + type: NodeType.Identifier; + name: string; + } + export interface BooleanLiteral extends BaseASTNode { + type: NodeType.BooleanLiteral; + value: boolean; + } + export interface NumberLiteral extends BaseASTNode { + type: NodeType.NumberLiteral; + number: string; + subdenomination: any; // TODO + } + export interface StringLiteral extends BaseASTNode { + type: NodeType.StringLiteral; + value: string; + } + export interface FunctionCall extends BaseASTNode { + type: NodeType.FunctionCall; + expression: Expression; + arguments: Expression[]; + names: []; + } + export interface TupleExpression extends BaseASTNode { + type: NodeType.TupleExpression; + components: Expression[]; + isArray: boolean; + } + export interface ElementaryTypeNameExpression extends BaseASTNode { + type: NodeType.ElementaryTypeNameExpression; + typeName: Type; + } + export interface UnaryOperation extends BaseASTNode { + type: NodeType.UnaryOperation; + operator: UnOp; + isPrefix: boolean; + subExpression: Expression; + } + export interface BinaryOperation extends BaseASTNode { + type: NodeType.BinaryOperation; + operator: BinOp; + left: Expression; + right: Expression; + } + export interface Conditional extends BaseASTNode { + type: NodeType.Conditional; + condition: Expression; + trueExpression: Expression; + falseExpression: Expression; + } + export interface IndexAccess extends BaseASTNode { + type: NodeType.IndexAccess; + base: Expression; + index: Expression; + } + export interface MemberAccess extends BaseASTNode { + type: NodeType.MemberAccess; + expression: Expression; + memberName: string; + } + export interface AssemblyBlock extends BaseASTNode { + type: NodeType.AssemblyBlock; + operations: AssemblyStatement[]; + } + /* + export interface AssemblyItem extends BaseASTNode { + type: NodeType.AssemblyItem; + } + export interface AssemblyExpression extends BaseASTNode { + type: NodeType.AssemblyExpression; + } + */ + export interface AssemblyCall extends BaseASTNode { + type: NodeType.AssemblyCall; + functionName: string; + arguments: AssemblyExpression[]; + } + export interface AssemblyLocalDefinition extends BaseASTNode { + type: NodeType.AssemblyLocalDefinition; + names: Identifier[]; + expression: AssemblyExpression; + } + export interface AssemblyAssignment extends BaseASTNode { + type: NodeType.AssemblyAssignment; + names: Identifier[]; + expression: AssemblyExpression; + } + /* + export interface AssemblyIdentifierOrList extends BaseASTNode { + type: NodeType.AssemblyIdentifierOrList; + } + export interface AssemblyIdentifierList extends BaseASTNode { + type: NodeType.AssemblyIdentifierList; + } + export interface AssemblyStackAssignment extends BaseASTNode { + type: NodeType.AssemblyStackAssignment; + } + */ + export interface LabelDefinition extends BaseASTNode { + type: NodeType.LabelDefinition; + // TODO + } + export interface AssemblySwitch extends BaseASTNode { + type: NodeType.AssemblySwitch; + expression: Expression; + cases: AssemblyCase[]; + } + export interface AssemblyCase extends BaseASTNode { + type: NodeType.AssemblyCase; + value: Expression; + block: AssemblyBlock; + } + export interface AssemblyFunctionDefinition extends BaseASTNode { + type: NodeType.AssemblyFunctionDefinition; + // TODO + } + export interface AssemblyFunctionReturns extends BaseASTNode { + type: NodeType.AssemblyFunctionReturns; + // TODO + } + export interface AssemblyFor extends BaseASTNode { + type: NodeType.AssemblyFor; + pre: AssemblyBlock | AssemblyExpression; + condition: AssemblyExpression; + post: AssemblyBlock | AssemblyExpression; + body: AssemblyBlock; + } + export interface AssemblyIf extends BaseASTNode { + type: NodeType.AssemblyIf; + condition: AssemblyExpression; + body: AssemblyBlock; + } + export interface DecimalNumber extends BaseASTNode { + type: NodeType.AssemblyIf; + value: string; + } + export interface HexNumber extends BaseASTNode { + type: NodeType.AssemblyIf; + value: string; + } -/* -export interface AssemblyLiteral extends BaseASTNode { - type: NodeType.AssemblyLiteral; -} -export interface SubAssembly extends BaseASTNode { - type: NodeType.SubAssembly; -} -*/ -export type SourceMember = - | PragmaDirective - | ImportDirective - | ContractDefinition; -export type ContractMember = - | UsingForDeclaration - | StateVariableDeclaration - | StructDefinition - | EnumDefinition - | EventDefinition - | ModifierDefinition - | FunctionDefinition; -export type Statement = - | Block - | VariableDeclarationStatement - | ExpressionStatement - | EmitStatement - | ReturnStatement - | BreakStatement - | ContinueStatement - | ThrowStatement - | IfStatement - | ForStatement - | InlineAssemblyStatement -export type Expression = - | BooleanLiteral - | NumberLiteral - | StringLiteral - | Identifier - | FunctionCall - | Conditional - | UnaryOperation - | BinaryOperation - | MemberAccess - | IndexAccess - | ElementaryTypeNameExpression - | VariableDeclaration // TODO: Is this really an expression? - | NewExpression - | TupleExpression - | IndexAccess - | MemberAccess -export type Type = - | ElementaryType - | UserDefinedTypeName - | Mapping - | ArrayTypeName - | FunctionTypeName -export type AssemblyStatement = - | AssemblyCall - | AssemblyAssignment - | AssemblyLocalDefinition - | AssemblyIf - | AssemblyFor - | AssemblySwitch - | AssemblyCase -export type AssemblyExpression = - | AssemblyCall - | DecimalNumber - | HexNumber -export type ASTNode = - | SourceUnit - | PragmaDirective - | PragmaName - | PragmaValue - | Version - | VersionOperator - | VersionConstraint - | ImportDeclaration - | ImportDirective - | ContractDefinition - | InheritanceSpecifier - | ContractPart - | StateVariableDeclaration - | UsingForDeclaration - | StructDefinition - | ModifierDefinition - | ModifierInvocation - | FunctionDefinition - | ReturnParameters - | ModifierList - | EventDefinition - | EnumValue - | EnumDefinition - | ParameterList - | Parameter - | EventParameterList - | EventParameter - | FunctionTypeParameterList - | FunctionTypeParameter - | VariableDeclaration - | TypeName - | UserDefinedTypeName - | Mapping - | FunctionTypeName - | StorageLocation - | StateMutability - | Block - | Statement - | ExpressionStatement - | IfStatement - | WhileStatement - | SimpleStatement - | ForStatement - | InlineAssemblyStatement - | DoWhileStatement - | ContinueStatement - | BreakStatement - | ReturnStatement - | ThrowStatement - | EmitStatement - | VariableDeclarationStatement - | IdentifierList - | ElementaryTypeName - | Expression - | PrimaryExpression - | ExpressionList - | NameValueList - | NameValue - | FunctionCall - | AssemblyBlock - | AssemblyItem - | AssemblyCall - | AssemblyLocalDefinition - | AssemblyAssignment - | AssemblyIdentifierOrList - | AssemblyIdentifierList - | AssemblyStackAssignment - | LabelDefinition - | AssemblySwitch - | AssemblyCase - | AssemblyFunctionDefinition - | AssemblyFunctionReturns - | AssemblyFor - | AssemblyIf - | AssemblyLiteral - | SubAssembly - | TupleExpression - | ElementaryTypeNameExpression - | BooleanLiteral - | NumberLiteral - | Identifier - | BinaryOperation - | Conditional; -export interface Visitor { - SourceUnit?: (node: SourceUnit) => T; - PragmaDirective?: (node: PragmaDirective) => T; - PragmaName?: (node: PragmaName) => T; - PragmaValue?: (node: PragmaValue) => T; - Version?: (node: Version) => T; - VersionOperator?: (node: VersionOperator) => T; - VersionConstraint?: (node: VersionConstraint) => T; - ImportDeclaration?: (node: ImportDeclaration) => T; - ImportDirective?: (node: ImportDirective) => T; - ContractDefinition?: (node: ContractDefinition) => T; - InheritanceSpecifier?: (node: InheritanceSpecifier) => T; - ContractPart?: (node: ContractPart) => T; - StateVariableDeclaration?: (node: StateVariableDeclaration) => T; - UsingForDeclaration?: (node: UsingForDeclaration) => T; - StructDefinition?: (node: StructDefinition) => T; - ModifierDefinition?: (node: ModifierDefinition) => T; - ModifierInvocation?: (node: ModifierInvocation) => T; - FunctionDefinition?: (node: FunctionDefinition) => T; - ReturnParameters?: (node: ReturnParameters) => T; - ModifierList?: (node: ModifierList) => T; - EventDefinition?: (node: EventDefinition) => T; - EnumValue?: (node: EnumValue) => T; - EnumDefinition?: (node: EnumDefinition) => T; - ParameterList?: (node: ParameterList) => T; - Parameter?: (node: Parameter) => T; - EventParameterList?: (node: EventParameterList) => T; - EventParameter?: (node: EventParameter) => T; - FunctionTypeParameterList?: (node: FunctionTypeParameterList) => T; - FunctionTypeParameter?: (node: FunctionTypeParameter) => T; - VariableDeclaration?: (node: VariableDeclaration) => T; - TypeName?: (node: TypeName) => T; - UserDefinedTypeName?: (node: UserDefinedTypeName) => T; - Mapping?: (node: Mapping) => T; - ArrayTypeName?: (node: ArrayTypeName) => T; - FunctionTypeName?: (node: FunctionTypeName) => T; - StorageLocation?: (node: StorageLocation) => T; - StateMutability?: (node: StateMutability) => T; - Block?: (node: Block) => T; - ExpressionStatement?: (node: ExpressionStatement) => T; - IfStatement?: (node: IfStatement) => T; - WhileStatement?: (node: WhileStatement) => T; - SimpleStatement?: (node: SimpleStatement) => T; - ForStatement?: (node: ForStatement) => T; - InlineAssemblyStatement?: (node: InlineAssemblyStatement) => T; - DoWhileStatement?: (node: DoWhileStatement) => T; - ContinueStatement?: (node: ContinueStatement) => T; - BreakStatement?: (node: BreakStatement) => T; - ReturnStatement?: (node: ReturnStatement) => T; - ThrowStatement?: (node: ThrowStatement) => T; - EmitStatement?: (node: EmitStatement) => T; - VariableDeclarationStatement?: (node: VariableDeclarationStatement) => T; - IdentifierList?: (node: IdentifierList) => T; - ElementaryTypeName?: (node: ElementaryTypeName) => T; - PrimaryExpression?: (node: PrimaryExpression) => T; - ExpressionList?: (node: ExpressionList) => T; - NameValueList?: (node: NameValueList) => T; - NameValue?: (node: NameValue) => T; - FunctionCall?: (node: FunctionCall) => T; - AssemblyBlock?: (node: AssemblyBlock) => T; - AssemblyItem?: (node: AssemblyItem) => T; - AssemblyExpression?: (node: AssemblyExpression) => T; - AssemblyCall?: (node: AssemblyCall) => T; - AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => T; - AssemblyAssignment?: (node: AssemblyAssignment) => T; - AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => T; - AssemblyIdentifierList?: (node: AssemblyIdentifierList) => T; - AssemblyStackAssignment?: (node: AssemblyStackAssignment) => T; - LabelDefinition?: (node: LabelDefinition) => T; - AssemblySwitch?: (node: AssemblySwitch) => T; - AssemblyCase?: (node: AssemblyCase) => T; - AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => T; - AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => T; - AssemblyFor?: (node: AssemblyFor) => T; - AssemblyIf?: (node: AssemblyIf) => T; - AssemblyLiteral?: (node: AssemblyLiteral) => T; - SubAssembly?: (node: SubAssembly) => T; - TupleExpression?: (node: TupleExpression) => T; - ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => T; - BooleanLiteral?: (node: BooleanLiteral) => T; - NumberLiteral?: (node: NumberLiteral) => T; - StringLiteral?: (node: StringLiteral) => T; - Identifier?: (node: Identifier) => T; - UnaryOperation?: (node: UnaryOperation) => T; - BinaryOperation?: (node: BinaryOperation) => T; - Conditional?: (node: Conditional) => T; - MemberAccess?: (node: MemberAccess) => T; - IndexAccess?: (node: IndexAccess) => T; - NewExpression?: (node: NewExpression) => T; - DecimalNumber?: (node: DecimalNumber) => T; - HexNumber?: (node: HexNumber) => T; -} -export interface ParserOpts { - tolerant?: boolean; - range?: boolean; - loc?: boolean; + /* + export interface AssemblyLiteral extends BaseASTNode { + type: NodeType.AssemblyLiteral; + } + export interface SubAssembly extends BaseASTNode { + type: NodeType.SubAssembly; + } + */ + export type SourceMember = PragmaDirective | ImportDirective | ContractDefinition; + export type ContractMember = + | UsingForDeclaration + | StateVariableDeclaration + | StructDefinition + | EnumDefinition + | EventDefinition + | ModifierDefinition + | FunctionDefinition; + export type Statement = + | Block + | VariableDeclarationStatement + | ExpressionStatement + | EmitStatement + | ReturnStatement + | BreakStatement + | ContinueStatement + | ThrowStatement + | IfStatement + | ForStatement + | InlineAssemblyStatement; + export type Expression = + | BooleanLiteral + | NumberLiteral + | StringLiteral + | Identifier + | FunctionCall + | Conditional + | UnaryOperation + | BinaryOperation + | MemberAccess + | IndexAccess + | ElementaryTypeNameExpression + | VariableDeclaration // TODO: Is this really an expression? + | NewExpression + | TupleExpression + | IndexAccess + | MemberAccess; + export type Type = ElementaryTypeName | UserDefinedTypeName | Mapping | ArrayTypeName | FunctionTypeName; + export type AssemblyStatement = + | AssemblyCall + | AssemblyAssignment + | AssemblyLocalDefinition + | AssemblyIf + | AssemblyFor + | AssemblySwitch + | AssemblyCase; + export type AssemblyExpression = AssemblyCall | DecimalNumber | HexNumber; + export type ASTNode = + | SourceUnit + | PragmaDirective + | PragmaName + | PragmaValue + | Version + | VersionOperator + | VersionConstraint + | ImportDeclaration + | ImportDirective + | ContractDefinition + | InheritanceSpecifier + | ContractPart + | StateVariableDeclaration + | UsingForDeclaration + | StructDefinition + | ModifierDefinition + | ModifierInvocation + | FunctionDefinition + | ReturnParameters + | ModifierList + | EventDefinition + | EnumValue + | EnumDefinition + | ParameterList + | Parameter + | EventParameterList + | EventParameter + | FunctionTypeParameterList + | FunctionTypeParameter + | VariableDeclaration + | TypeName + | UserDefinedTypeName + | Mapping + | FunctionTypeName + | StorageLocation + | StateMutability + | Block + | Statement + | ExpressionStatement + | IfStatement + | WhileStatement + | ForStatement + | InlineAssemblyStatement + | DoWhileStatement + | ContinueStatement + | BreakStatement + | ReturnStatement + | ThrowStatement + | EmitStatement + | VariableDeclarationStatement + | IdentifierList + | ElementaryTypeName + | Expression + | PrimaryExpression + | ExpressionList + | NameValueList + | NameValue + | FunctionCall + | AssemblyBlock + | AssemblyItem + | AssemblyCall + | AssemblyLocalDefinition + | AssemblyAssignment + | AssemblyIdentifierOrList + | AssemblyIdentifierList + | AssemblyStackAssignment + | LabelDefinition + | AssemblySwitch + | AssemblyCase + | AssemblyFunctionDefinition + | AssemblyFunctionReturns + | AssemblyFor + | AssemblyIf + | AssemblyLiteral + | SubAssembly + | TupleExpression + | ElementaryTypeNameExpression + | BooleanLiteral + | NumberLiteral + | Identifier + | BinaryOperation + | Conditional; + export interface Visitor { + SourceUnit?: (node: SourceUnit) => T; + PragmaDirective?: (node: PragmaDirective) => T; + PragmaName?: (node: PragmaName) => T; + PragmaValue?: (node: PragmaValue) => T; + Version?: (node: Version) => T; + VersionOperator?: (node: VersionOperator) => T; + VersionConstraint?: (node: VersionConstraint) => T; + ImportDeclaration?: (node: ImportDeclaration) => T; + ImportDirective?: (node: ImportDirective) => T; + ContractDefinition?: (node: ContractDefinition) => T; + InheritanceSpecifier?: (node: InheritanceSpecifier) => T; + ContractPart?: (node: ContractPart) => T; + StateVariableDeclaration?: (node: StateVariableDeclaration) => T; + UsingForDeclaration?: (node: UsingForDeclaration) => T; + StructDefinition?: (node: StructDefinition) => T; + ModifierDefinition?: (node: ModifierDefinition) => T; + ModifierInvocation?: (node: ModifierInvocation) => T; + FunctionDefinition?: (node: FunctionDefinition) => T; + ReturnParameters?: (node: ReturnParameters) => T; + ModifierList?: (node: ModifierList) => T; + EventDefinition?: (node: EventDefinition) => T; + EnumValue?: (node: EnumValue) => T; + EnumDefinition?: (node: EnumDefinition) => T; + ParameterList?: (node: ParameterList) => T; + Parameter?: (node: Parameter) => T; + EventParameterList?: (node: EventParameterList) => T; + EventParameter?: (node: EventParameter) => T; + FunctionTypeParameterList?: (node: FunctionTypeParameterList) => T; + FunctionTypeParameter?: (node: FunctionTypeParameter) => T; + VariableDeclaration?: (node: VariableDeclaration) => T; + TypeName?: (node: TypeName) => T; + UserDefinedTypeName?: (node: UserDefinedTypeName) => T; + Mapping?: (node: Mapping) => T; + ArrayTypeName?: (node: ArrayTypeName) => T; + FunctionTypeName?: (node: FunctionTypeName) => T; + StorageLocation?: (node: StorageLocation) => T; + StateMutability?: (node: StateMutability) => T; + Block?: (node: Block) => T; + ExpressionStatement?: (node: ExpressionStatement) => T; + IfStatement?: (node: IfStatement) => T; + WhileStatement?: (node: WhileStatement) => T; + ForStatement?: (node: ForStatement) => T; + InlineAssemblyStatement?: (node: InlineAssemblyStatement) => T; + DoWhileStatement?: (node: DoWhileStatement) => T; + ContinueStatement?: (node: ContinueStatement) => T; + BreakStatement?: (node: BreakStatement) => T; + ReturnStatement?: (node: ReturnStatement) => T; + ThrowStatement?: (node: ThrowStatement) => T; + EmitStatement?: (node: EmitStatement) => T; + VariableDeclarationStatement?: (node: VariableDeclarationStatement) => T; + IdentifierList?: (node: IdentifierList) => T; + ElementaryTypeName?: (node: ElementaryTypeName) => T; + PrimaryExpression?: (node: PrimaryExpression) => T; + ExpressionList?: (node: ExpressionList) => T; + NameValueList?: (node: NameValueList) => T; + NameValue?: (node: NameValue) => T; + FunctionCall?: (node: FunctionCall) => T; + AssemblyBlock?: (node: AssemblyBlock) => T; + AssemblyItem?: (node: AssemblyItem) => T; + AssemblyExpression?: (node: AssemblyExpression) => T; + AssemblyCall?: (node: AssemblyCall) => T; + AssemblyLocalDefinition?: (node: AssemblyLocalDefinition) => T; + AssemblyAssignment?: (node: AssemblyAssignment) => T; + AssemblyIdentifierOrList?: (node: AssemblyIdentifierOrList) => T; + AssemblyIdentifierList?: (node: AssemblyIdentifierList) => T; + AssemblyStackAssignment?: (node: AssemblyStackAssignment) => T; + LabelDefinition?: (node: LabelDefinition) => T; + AssemblySwitch?: (node: AssemblySwitch) => T; + AssemblyCase?: (node: AssemblyCase) => T; + AssemblyFunctionDefinition?: (node: AssemblyFunctionDefinition) => T; + AssemblyFunctionReturns?: (node: AssemblyFunctionReturns) => T; + AssemblyFor?: (node: AssemblyFor) => T; + AssemblyIf?: (node: AssemblyIf) => T; + AssemblyLiteral?: (node: AssemblyLiteral) => T; + SubAssembly?: (node: SubAssembly) => T; + TupleExpression?: (node: TupleExpression) => T; + ElementaryTypeNameExpression?: (node: ElementaryTypeNameExpression) => T; + BooleanLiteral?: (node: BooleanLiteral) => T; + NumberLiteral?: (node: NumberLiteral) => T; + StringLiteral?: (node: StringLiteral) => T; + Identifier?: (node: Identifier) => T; + UnaryOperation?: (node: UnaryOperation) => T; + BinaryOperation?: (node: BinaryOperation) => T; + Conditional?: (node: Conditional) => T; + MemberAccess?: (node: MemberAccess) => T; + IndexAccess?: (node: IndexAccess) => T; + NewExpression?: (node: NewExpression) => T; + DecimalNumber?: (node: DecimalNumber) => T; + HexNumber?: (node: HexNumber) => T; + } + export interface ParserOpts { + tolerant?: boolean; + range?: boolean; + loc?: boolean; + } + export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode; + export function visit(ast: ASTNode, visitor: Visitor): void; } -export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode; -export function visit(ast: ASTNode, visitor: Visitor): void; From 41ccf5340cca0f2625dd2aad85c26d65ad85decc Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 9 Nov 2018 15:00:11 +0100 Subject: [PATCH 38/76] update sol-cov to new grammar --- packages/sol-cov/src/ast_visitor.ts | 3 --- packages/sol-cov/src/get_source_range_snippet.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/packages/sol-cov/src/ast_visitor.ts b/packages/sol-cov/src/ast_visitor.ts index e55cdf6ec9..6293cab2ab 100644 --- a/packages/sol-cov/src/ast_visitor.ts +++ b/packages/sol-cov/src/ast_visitor.ts @@ -79,9 +79,6 @@ export class ASTVisitor { public WhileStatement(ast: Parser.WhileStatement): void { this._visitStatement(ast); } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } public ThrowStatement(ast: Parser.ThrowStatement): void { this._visitStatement(ast); } diff --git a/packages/sol-cov/src/get_source_range_snippet.ts b/packages/sol-cov/src/get_source_range_snippet.ts index bea17beae9..b1529e45d5 100644 --- a/packages/sol-cov/src/get_source_range_snippet.ts +++ b/packages/sol-cov/src/get_source_range_snippet.ts @@ -105,9 +105,6 @@ class ASTInfoVisitor { public WhileStatement(ast: Parser.WhileStatement): void { this._visitStatement(ast); } - public SimpleStatement(ast: Parser.SimpleStatement): void { - this._visitStatement(ast); - } public ThrowStatement(ast: Parser.ThrowStatement): void { this._visitStatement(ast); } From 69b26b77f9873ac36201890e071909dccdabe333 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 9 Nov 2018 15:00:27 +0100 Subject: [PATCH 39/76] clean up ts config --- packages/sol-meta/tsconfig.json | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/sol-meta/tsconfig.json b/packages/sol-meta/tsconfig.json index c630ddb933..7968153719 100644 --- a/packages/sol-meta/tsconfig.json +++ b/packages/sol-meta/tsconfig.json @@ -2,17 +2,11 @@ "extends": "../../tsconfig", "compilerOptions": { "outDir": "lib", - "rootDir": ".", - "strictFunctionTypes": false, - "noImplicitAny": false, - "typeRoots": ["node_modules/@types"], + "rootDir": "." }, "include": [ - "node_modules/@types", - "node_modules/@0xproject/typescript-typings/types", - "../../node_modules/@types", - "../../node_modules/@0xproject/typescript-typings/types", - "./src/**/*", - "./test/**/*" + "./src/**/*", + "./test/**/*", + "../typescript-typings/types/solidity-parser-antlr/*" ] -} +} \ No newline at end of file From 1bab108f2e22beaa857d49a143579c20bf2d22de Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 9 Nov 2018 15:01:37 +0100 Subject: [PATCH 40/76] type correctness --- packages/sol-meta/src/exposer.ts | 216 ++++++++++---------- packages/sol-meta/src/flattener.ts | 86 ++++---- packages/sol-meta/src/mocker.ts | 311 ++++++++++++++--------------- packages/sol-meta/src/unparser.ts | 299 +++++++++++++-------------- packages/sol-meta/src/utils.ts | 30 +-- packages/sol-meta/src/visitor.ts | 124 ++++++++++++ packages/sol-meta/test/index.ts | 34 ++-- 7 files changed, 604 insertions(+), 496 deletions(-) create mode 100644 packages/sol-meta/src/visitor.ts diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index 2a03a2bf60..944f53f948 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -1,10 +1,10 @@ import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; -import {identifier, nameParameters, argumentExpressions} from './utils'; +import { identifier, nameParameters, argumentExpressions } from './utils'; // Creates a public getter for a state variable const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { - const [{name, typeName }] = stateVar.variables; + const [{ name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, name: `get_${name}`, @@ -13,31 +13,35 @@ const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { stateMutability: S.StateMutability.View, parameters: { type: S.NodeType.ParameterList, - parameters: [] + parameters: [], }, returnParameters: { type: S.NodeType.ParameterList, - parameters: [{ - type: S.NodeType.Parameter, - typeName, - name: null, - storageLocation: S.StorageLocation.Default, - }] + parameters: [ + { + type: S.NodeType.Parameter, + typeName, + name: null, + storageLocation: S.StorageLocation.Default, + }, + ], }, modifiers: [], body: { type: S.NodeType.Block, - statements: [{ - type: S.NodeType.ReturnStatement, - expression: identifier(name) - }] + statements: [ + { + type: S.NodeType.ReturnStatement, + expression: identifier(name), + }, + ], }, }; -} +}; // Creates a public getter for a state variable const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { - const [{name, typeName }] = stateVar.variables; + const [{ name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, name: `set_${name}`, @@ -46,29 +50,33 @@ const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { stateMutability: S.StateMutability.Default, parameters: { type: S.NodeType.ParameterList, - parameters: [{ - type: S.NodeType.Parameter, - typeName, - name: 'setterNewValue', - storageLocation: S.StorageLocation.Default, - }], + parameters: [ + { + type: S.NodeType.Parameter, + typeName, + name: 'setterNewValue', + storageLocation: S.StorageLocation.Default, + }, + ], }, returnParameters: null, modifiers: [], body: { type: S.NodeType.Block, - statements: [{ - type: S.NodeType.ExpressionStatement, - expression: { - type: S.NodeType.BinaryOperation, - operator: '=', - left: identifier(name), - right: identifier('setterNewValue') - } - }] + statements: [ + { + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.BinaryOperation, + operator: '=', + left: identifier(name), + right: identifier('setterNewValue'), + }, + }, + ], }, }; -} +}; // Creates a public wrapper for a function const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { @@ -77,7 +85,7 @@ const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { type: S.NodeType.FunctionCall, expression: identifier(func.name), arguments: argumentExpressions(params), - names: [] + names: [], }; return { ...func, @@ -88,21 +96,23 @@ const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { body: { type: S.NodeType.Block, statements: [ - func.returnParameters ? { - type: S.NodeType.ReturnStatement, - expression: call - } : { - type: S.NodeType.ExpressionStatement, - expression: call - } - ] - } + func.returnParameters + ? { + type: S.NodeType.ReturnStatement, + expression: call, + } + : { + type: S.NodeType.ExpressionStatement, + expression: call, + }, + ], + }, }; -} +}; // Creates a public function that triggers a log event const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { - const {name, parameters} = event; + const { name, parameters } = event; return { type: S.NodeType.FunctionDefinition, name: `emit_${name}`, @@ -113,62 +123,65 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { type: S.NodeType.ParameterList, parameters: parameters.parameters.map(param => ({ ...param, - isIndexed: false - })) + isIndexed: false, + })), }, returnParameters: null, modifiers: [], body: { type: S.NodeType.Block, - statements: [{ - type: S.NodeType.EmitStatement, - eventCall: { - type: S.NodeType.FunctionCall, - expression: identifier(name), - arguments: argumentExpressions(parameters), - names: [] - } - }] + statements: [ + { + type: S.NodeType.EmitStatement, + eventCall: { + type: S.NodeType.FunctionCall, + expression: identifier(name), + arguments: argumentExpressions(parameters), + names: [], + }, + }, + ], }, }; -} +}; // Creates a public function that has modifier const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { - const {name, parameters} = modifier; + const { name, parameters } = modifier; return { type: S.NodeType.FunctionDefinition, name: `modifier_${name}`, visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.Default, - parameters: + parameters: parameters === null - ? { - type: S.NodeType.ParameterList, - parameters: [] - } - : parameters, + ? { + type: S.NodeType.ParameterList, + parameters: [], + } + : parameters, returnParameters: { type: S.NodeType.ParameterList, - parameters: [{ - type: S.NodeType.Parameter, - typeName: { - type: S.NodeType.ElementaryTypeName, - name: 'bool' + parameters: [ + { + type: S.NodeType.Parameter, + typeName: { + type: S.NodeType.ElementaryTypeName, + name: 'bool', + }, + name: 'executed', + storageLocation: S.StorageLocation.Default, }, - name: 'executed', - storageLocation: S.StorageLocation.Default - }] + ], }, - modifiers: [{ - type: S.NodeType.ModifierInvocation, - name, - arguments: - Array.isArray(parameters) - ? argumentExpressions(parameters) - : [] - }], + modifiers: [ + { + type: S.NodeType.ModifierInvocation, + name, + arguments: Array.isArray(parameters) ? argumentExpressions(parameters) : [], + }, + ], body: { type: S.NodeType.Block, statements: [ @@ -177,12 +190,12 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { expression: { type: S.NodeType.BooleanLiteral, value: true, - } - } - ] + }, + }, + ], }, }; -} +}; const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { switch (ast.type) { @@ -194,10 +207,7 @@ const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { if (vardecl.visibility !== 'internal') { return []; } - return [ - getter(ast as S.StateVariableDeclaration), - setter(ast as S.StateVariableDeclaration) - ]; + return [getter(ast as S.StateVariableDeclaration), setter(ast as S.StateVariableDeclaration)]; // TODO: handle mappings: The keys become additional // function arguments to the getter and setter. } @@ -215,35 +225,37 @@ const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { return [wrapFunction(func)]; } } -} +}; export function expose(filePath: string, ast: S.SourceUnit): S.SourceUnit { - // TODO: gp down inheritance hierarchy and expose those events. etc as well // we probably want a separate `flattenInheritance` function or something // that traces the imports and does a fairly simple concatenation. - + return { type: S.NodeType.SourceUnit, children: [ - ...utils.pragmaNodes(ast), { + ...utils.pragmaNodes(ast), + { type: S.NodeType.ImportDirective, - path: filePath, + path: filePath, unitAliases: null, - symbolAliases: null + symbolAliases: null, }, - ...utils.contracts(ast).map((ctr) => ({ + ...utils.contracts(ast).map(ctr => ({ type: S.NodeType.ContractDefinition, kind: 'contract', name: `${ctr.name}Exposed`, - baseContracts: [{ - type: S.NodeType.InheritanceSpecifier, - baseName: { - namePath: ctr.name, - } - }], + baseContracts: [ + { + type: S.NodeType.InheritanceSpecifier, + baseName: { + namePath: ctr.name, + }, + }, + ], subNodes: utils.flatMap(ctr.subNodes, exposeNode), - })) - ] - } + })), + ], + }; } diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts index 022eedcffe..f9b99ec5b4 100644 --- a/packages/sol-meta/src/flattener.ts +++ b/packages/sol-meta/src/flattener.ts @@ -1,68 +1,74 @@ import * as S from 'solidity-parser-antlr'; -import { linearize, linearizeAsync } from './linearization' -import { flatMap } from './utils' + +import { linearize, linearizeAsync } from './linearization'; +import { flatMap } from './utils'; // See: https://solidity.readthedocs.io/en/v0.4.25/contracts.html#inheritance -// Merge a base contract in a derived contract +// Merge a base contract into a derived contract function merge( base: S.ContractMember[], - derived: S.ContractMember[] + derived: S.ContractMember[], ): S.ContractMember[] { + // Extracts functions by name from contract members + const functions = (contract: S.ContractMember[]): { [name: string]: S.FunctionDefinition } => + (contract + .filter(({ type }) => type === S.NodeType.FunctionDefinition) as S.FunctionDefinition[]) + .reduce((a, func: S.FunctionDefinition) => ({ ...a, [func.name]: func }), {}); + // Solidity method lookup is as if all functions are virtual. The most // derived version is always called. We can implement this by overriding // the base implementation. - const functions: S.FunctionDefinition[] = Object.values( - derived - .filter(({type}) => type === S.NodeType.FunctionDefinition) - .reduce( - (a, func: S.FunctionDefinition) => ({...a, [func.name]: func}), - base - .filter(({type}) => type === S.NodeType.FunctionDefinition) - .reduce( - (a, func: S.FunctionDefinition) => ({...a, [func.name]: func}), - {}))); - + const mergedFunctions = { + ...functions(base), + ...functions(derived), + }; + // TODO: Merge constructors - // TODO: Implement rules that enforce type signature and visibility e.d. - // to be preserbed. + // TODO: Implement rules that enforce type signature and visibility etc. + // to be preserved when overriding. // TODO: Check other objects than functions. // TODO: Sort members by type + // TODO: Less derived functions remain available through `super` + // and `SomeBase.functionName(...)`. return [ - ...base.filter(({type}) => type !== S.NodeType.FunctionDefinition), - ...derived.filter(({type}) => type !== S.NodeType.FunctionDefinition), - ...functions - ] + ...base.filter(({ type }) => type !== S.NodeType.FunctionDefinition), + ...derived.filter(({ type }) => type !== S.NodeType.FunctionDefinition), + ...Object.values(mergedFunctions), + ]; } // Inline all inheritance for a contract function flattenContract( contract: S.ContractDefinition, - resolve: (name: string) => S.ContractDefinition + resolve: (name: string) => S.ContractDefinition, ): S.ContractDefinition { - + // Close over resolve to create a parents function - const parents = (contract: S.ContractDefinition) => - contract.baseContracts.map(({baseName: {namePath}}) => + const parents = (child: S.ContractDefinition) => + child.baseContracts.map(({ baseName: { namePath } }) => resolve(namePath)); - + // Linearize the contract inheritance tree from least to most derived const linear = linearize(contract, parents); - - // TODO: Implement `super()` and `SomeBase.functionName(...)`. - + + // Merge contract members according to linearization + const members: S.ContractMember[] = linear.reduce( + (a: S.ContractMember[], { subNodes }) => merge(a, subNodes), []); + // Concatenate contract bodies return { ...contract, baseContracts: [], - subNodes: linear.reduce((a, {subNodes}) => merge(a, subNodes), []) - } + + subNodes: members, + }; } function sourceResolver( - source: S.SourceUnit -): (string) => S.ContractDefinition { - return function (name) { + source: S.SourceUnit, +): ((name: string) => S.ContractDefinition) { + return (name: string): S.ContractDefinition => { const result = source.children.find(x => x.type === S.NodeType.ContractDefinition && x.name === name); @@ -70,22 +76,24 @@ function sourceResolver( throw new Error(`Could not resolve ${name}`); } return result; - } + }; } export function flattenSource( source: S.SourceUnit, - contractName: string + contractName: string, ): S.ContractDefinition { const resolver = sourceResolver(source); return flattenContract(resolver(contractName), resolver); } // Chase down import statements and produce a single source unit. -/*function flattenFile( +// NOTE: Does not support `import as` statements +function flattenFile( contract: S.SourceUnit, - resolver: (string) => S.SourceUnit + resolver: (name: string) => S.SourceUnit, ): S.SourceUnit { // TODO: symbolAliases + + return contract; // TODO } -*/ diff --git a/packages/sol-meta/src/mocker.ts b/packages/sol-meta/src/mocker.ts index 20b76f4c6f..052ea797ed 100644 --- a/packages/sol-meta/src/mocker.ts +++ b/packages/sol-meta/src/mocker.ts @@ -1,49 +1,53 @@ import * as S from 'solidity-parser-antlr'; + import * as utils from './utils'; -import {identifier, nameParameters, argumentExpressions} from './utils'; +import { identifier, nameParameters, argumentExpressions } from './utils'; +import { visit, Visitor } from './visitor'; // TODO: both Actions and Functions can throw in addition to returning -const bool: S.ElementaryTypeName = ({ +const bool: S.ElementaryTypeName = { type: S.NodeType.ElementaryTypeName, - name: 'bool' -}); + name: 'bool', +}; -const uint256: S.ElementaryTypeName = ({ +const uint256: S.ElementaryTypeName = { type: S.NodeType.ElementaryTypeName, - name: 'uint256' -}); + name: 'uint256', +}; -const zero: S.NumberLiteral = ({ +const zero: S.NumberLiteral = { type: S.NodeType.NumberLiteral, number: '0', - subdenomination: null // TODO -}); + subdenomination: null, // TODO +}; const call = (func: S.Expression, ...args: S.Expression[]): S.FunctionCall => ({ type: S.NodeType.FunctionCall, expression: func, arguments: args, - names: [] + names: [], }); const emit = (name: string, ...args: S.Expression[]): S.EmitStatement => ({ type: S.NodeType.EmitStatement, - eventCall: call(identifier(name), ...args) + eventCall: call(identifier(name), ...args), }); const makeCounter = (name: string): S.StateVariableDeclaration => ({ type: S.NodeType.StateVariableDeclaration, - variables: [{ - type: S.NodeType.VariableDeclaration, - typeName: uint256, - name, - expression: zero, - visibility: S.Visibility.Internal, - isStateVar: true, - isDeclaredConst: false, - isIndexed: false - }], + variables: [ + { + type: S.NodeType.VariableDeclaration, + typeName: uint256, + name, + expression: zero, + visibility: S.Visibility.Internal, + isStateVar: true, + isDeclaredConst: false, + isIndexed: false, + }, + ], initialValue: zero, }); @@ -51,23 +55,24 @@ const makeEvent = (name: string, parameters: S.ParameterList): S.EventDefinition type: S.NodeType.EventDefinition, name, parameters, - isAnonymous: false + isAnonymous: false, }); const makeCountedEvent = (name: string, parameters: S.ParameterList) => makeEvent(name, { ...parameters, parameters: [ - { - type: S.NodeType.Parameter, - name: 'counter', - typeName: uint256, - storageLocation: S.StorageLocation.Default - }, - ...parameters.parameters.map(param => ({ - ...param, - storageLocation: S.StorageLocation.Default - }))] + { + type: S.NodeType.Parameter, + name: 'counter', + typeName: uint256, + storageLocation: S.StorageLocation.Default, + }, + ...parameters.parameters.map(param => ({ + ...param, + storageLocation: S.StorageLocation.Default, + })), + ], }); const makeIncrement = (name: string): S.ExpressionStatement => ({ @@ -76,16 +81,16 @@ const makeIncrement = (name: string): S.ExpressionStatement => ({ type: S.NodeType.UnaryOperation, operator: '++', subExpression: identifier(name), - isPrefix: false - } -}) + isPrefix: false, + }, +}); const makeAction = ( name: string, visibility: S.Visibility, counterName: string, eventName: string, - parameters: S.ParameterList + parameters: S.ParameterList, ): S.FunctionDefinition => ({ type: S.NodeType.FunctionDefinition, name, @@ -94,43 +99,38 @@ const makeAction = ( body: { type: S.NodeType.Block, statements: [ - emit(eventName, identifier(counterName), - ...argumentExpressions(parameters)), - makeIncrement(counterName) - ] + emit(eventName, identifier(counterName), ...argumentExpressions(parameters)), + makeIncrement(counterName), + ], }, - visibility: visibility, + visibility, modifiers: [], isConstructor: false, - stateMutability: S.StateMutability.Default, + stateMutability: S.StateMutability.Default, }); -const variableDeclaration = ( - name: string, - typeName: S.Type -): S.VariableDeclaration => ({ +const variableDeclaration = (name: string, typeName: S.Type): S.VariableDeclaration => ({ type: S.NodeType.VariableDeclaration, name, typeName, storageLocation: S.StorageLocation.Default, isStateVar: false, - isIndexed: false -}) + isIndexed: false, +}); const makeResultType = (name: string, fields: S.ParameterList): S.StructDefinition => ({ type: S.NodeType.StructDefinition, name, members: [ variableDeclaration('reverts', bool), - ...fields.parameters.map(({name, typeName}) => - variableDeclaration(name as string, typeName)) - ] + ...fields.parameters.map(({ name, typeName }) => variableDeclaration(name as string, typeName)), + ], }); const userType = (name: string): S.UserDefinedTypeName => ({ type: S.NodeType.UserDefinedTypeName, - namePath: name -}) + namePath: name, +}); const mapping = (keyType: S.Type, valueType: S.Type): S.Type => ({ type: S.NodeType.Mapping, @@ -138,39 +138,35 @@ const mapping = (keyType: S.Type, valueType: S.Type): S.Type => ({ valueType, }); -const makeStorageVariable = ( - name: string, - type: S.Type -): S.StateVariableDeclaration => ({ +const makeStorageVariable = (name: string, type: S.Type): S.StateVariableDeclaration => ({ type: S.NodeType.StateVariableDeclaration, variables: [variableDeclaration(name, type)], - initialValue: null -}) + initialValue: null, +}); -const makeSetter = ( - name: string, - resultTypeName: string, - resultMapName: string -): S.FunctionDefinition => ({ +const makeSetter = (name: string, resultTypeName: string, resultMapName: string): S.FunctionDefinition => ({ type: S.NodeType.FunctionDefinition, name, parameters: { type: S.NodeType.ParameterList, - parameters: [{ - type: S.NodeType.Parameter, - name: '_counter', - typeName: uint256, - storageLocation: S.StorageLocation.Default, - isStateVar: false, - isIndexed: false - }, { - type: S.NodeType.Parameter, - name: '_value', - typeName: userType(resultTypeName), - storageLocation: S.StorageLocation.Default, - isStateVar: false, - isIndexed: false - }] + parameters: [ + { + type: S.NodeType.Parameter, + name: '_counter', + typeName: uint256, + storageLocation: S.StorageLocation.Default, + isStateVar: false, + isIndexed: false, + }, + { + type: S.NodeType.Parameter, + name: '_value', + typeName: userType(resultTypeName), + storageLocation: S.StorageLocation.Default, + isStateVar: false, + isIndexed: false, + }, + ], }, returnParameters: null, visibility: S.Visibility.Public, @@ -179,21 +175,23 @@ const makeSetter = ( stateMutability: S.StateMutability.Default, body: { type: S.NodeType.Block, - statements: [{ - type: S.NodeType.ExpressionStatement, - expression: { - type: S.NodeType.BinaryOperation, - operator: '=', - left: { - type: S.NodeType.IndexAccess, - base: identifier(resultMapName), - index: identifier('_counter') + statements: [ + { + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.BinaryOperation, + operator: '=', + left: { + type: S.NodeType.IndexAccess, + base: identifier(resultMapName), + index: identifier('_counter'), + }, + right: identifier('_value'), }, - right: identifier('_value') - } - }] - } -}) + }, + ], + }, +}); const makeFunction = ( name: string, @@ -203,32 +201,33 @@ const makeFunction = ( resultTypeName: string, resultMapName: string, parameters: S.ParameterList, - returnParameters: S.ParameterList + returnParameters: S.ParameterList, ): S.FunctionDefinition => ({ type: S.NodeType.FunctionDefinition, name, parameters, returnParameters, - visibility: visibility, + visibility, modifiers: [], isConstructor: false, stateMutability: S.StateMutability.Default, body: { type: S.NodeType.Block, statements: [ - emit(eventName, identifier(counterName), - ...argumentExpressions(parameters)), + emit(eventName, identifier(counterName), ...argumentExpressions(parameters)), { type: S.NodeType.VariableDeclarationStatement, - variables: [{ - ...variableDeclaration('result', userType(resultTypeName)), - storageLocation: S.StorageLocation.Storage, - }], + variables: [ + { + ...variableDeclaration('result', userType(resultTypeName)), + storageLocation: S.StorageLocation.Storage, + }, + ], initialValue: { type: S.NodeType.IndexAccess, base: identifier(resultMapName), - index: identifier(counterName) - } + index: identifier(counterName), + }, }, makeIncrement(counterName), { @@ -240,26 +239,24 @@ const makeFunction = ( subExpression: { type: S.NodeType.MemberAccess, expression: identifier('result'), - memberName: 'reverts' - } - }) + memberName: 'reverts', + }, + }), }, { type: S.NodeType.ReturnStatement, expression: { type: S.NodeType.TupleExpression, isArray: false, - components: returnParameters.parameters.map( - ({name}): S.MemberAccess => ({ - type: S.NodeType.MemberAccess, - expression: identifier('result'), - memberName: name as string - }) - ) - } - } - ] - } + components: returnParameters.parameters.map(({ name: memberName }): S.MemberAccess => ({ + type: S.NodeType.MemberAccess, + expression: identifier('result'), + memberName: memberName as string, + })), + }, + }, + ], + }, }); const mockAction = (func: S.FunctionDefinition): S.ContractMember[] => { @@ -269,7 +266,7 @@ const mockAction = (func: S.FunctionDefinition): S.ContractMember[] => { return [ makeCounter(counterName), makeCountedEvent(eventName, params), - makeAction(func.name, func.visibility, counterName, eventName, params) + makeAction(func.name, func.visibility, counterName, eventName, params), ]; }; @@ -280,58 +277,56 @@ const mockFunction = (func: S.FunctionDefinition): S.ContractMember[] => { const eventName = `_${func.name}_log`; const setterName = `_${func.name}_set`; const params = nameParameters(func.parameters); - const returns = nameParameters(func.returnParameters as S.ParameterList, - '_ret'); + const returns = nameParameters(func.returnParameters as S.ParameterList, '_ret'); return [ makeCounter(counterName), makeCountedEvent(eventName, params), makeResultType(resultTypeName, returns), - makeStorageVariable(resultMapName, - mapping(uint256, userType(resultTypeName))), + makeStorageVariable(resultMapName, mapping(uint256, userType(resultTypeName))), makeSetter(setterName, resultTypeName, resultMapName), - makeFunction(func.name, func.visibility, counterName, eventName, - resultTypeName, resultMapName, params, returns) + makeFunction( + func.name, + func.visibility, + counterName, + eventName, + resultTypeName, + resultMapName, + params, + returns, + ), ]; -} +}; const isDeclaration = (func: S.FunctionDefinition) => func.body === null; const hasReturns = (func: S.FunctionDefinition) => func.returnParameters !== null; -const visitor = { - - FunctionDefinition: (func) => - isDeclaration(func) - ? ( - hasReturns(func) - ? mockFunction(func) - : mockAction(func) - ) - : [], - - default: (node) => node, -} +const visitor: Visitor = { + FunctionDefinition: (func: S.FunctionDefinition) => + isDeclaration(func) ? (hasReturns(func) ? mockFunction(func) : mockAction(func)) : [], + + ContractMember: (node: S.ContractMember) => [node], +}; const pragmaSolVersion: S.PragmaDirective = { type: S.NodeType.PragmaDirective, name: 'solidity', - value: '^0.4.24' + value: '^0.4.24', }; const pragmaAbiV2: S.PragmaDirective = { type: S.NodeType.PragmaDirective, name: 'experimental', - value: 'ABIEncoderV2' + value: 'ABIEncoderV2', }; const importDirective = (path: string): S.ImportDirective => ({ type: S.NodeType.ImportDirective, path, - symbolAliases: null -}) + symbolAliases: null, +}); export function mock(ast: S.SourceUnit): S.SourceUnit { - // TODO: go down inheritance hierarchy and expose those events. etc as well. // We probably want a separate `flattenInheritance` function or something // that traces the imports and does a C3 linearization. @@ -341,21 +336,21 @@ export function mock(ast: S.SourceUnit): S.SourceUnit { pragmaSolVersion, pragmaAbiV2, importDirective('interface.sol'), - ...utils.contracts(ast).map((ctr) => ({ + ...utils.contracts(ast).map(ctr => ({ type: S.NodeType.ContractDefinition, kind: S.ContractKind.Contract, name: `${ctr.name}Mock`, - baseContracts: [{ - type: S.NodeType.InheritanceSpecifier, - baseName: { - type: S.NodeType.UserDefinedTypeName, - namePath: ctr.name, - } - }], - subNodes: utils.flatMap(ctr.subNodes, node => - (visitor[node.type] || visitor.default)(node) - ), - })) - ] - } + baseContracts: [ + { + type: S.NodeType.InheritanceSpecifier, + baseName: { + type: S.NodeType.UserDefinedTypeName, + namePath: ctr.name, + }, + }, + ], + subNodes: utils.flatMap(ctr.subNodes, (node: S.ContractMember) => visit(node, visitor)), + })), + ], + }; } diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 0ab29e1984..2f74f1f5d1 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -2,166 +2,150 @@ import * as S from 'solidity-parser-antlr'; +import { visit, Visitor } from './visitor'; + const stresc = (s: string) => `\"${s}\"`; const indent = (s: string) => `\t${s.replace(/\n/g, '\n\t')}`; const block = (s: string) => `{\n${indent(s)}\n}`; const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1'); -const visitor: S.Visitor = { - +const visitor: Visitor = { // Source level - - SourceUnit: ({children}) => - children.map(unparse).join('\n'), - PragmaDirective: ({ name, value }) => - `pragma ${name} ${value};`, - + SourceUnit: ({ children }) => children.map(unparse).join('\n'), + + PragmaDirective: ({ name, value }) => `pragma ${name} ${value};`, + ImportDirective: ({ path, symbolAliases }) => - `import `+ - (symbolAliases ? `{${symbolAliases.map(([from, to]) => - from + (to ? ` as ${to}` : '') - ).join(', ')}} from `: '') + + `import ` + + (symbolAliases + ? `{${symbolAliases.map(([from, to]) => from + (to ? ` as ${to}` : '')).join(', ')}} from ` + : '') + `${stresc(path)};`, - ContractDefinition: ({name, kind, baseContracts, subNodes}) => - `${kind} ${name} ${(baseContracts.length > 0 ? 'is ' : '')}` + + ContractDefinition: ({ name, kind, baseContracts, subNodes }) => + `${kind} ${name} ${baseContracts.length > 0 ? 'is ' : ''}` + baseContracts.map(unparse).join(', ') + block('\n' + subNodes.map(unparse).join('\n\n')), - InheritanceSpecifier: ({baseName: {namePath}}) => - namePath, - + InheritanceSpecifier: ({ baseName: { namePath } }) => namePath, + // Contract level - - UsingForDeclaration: ({typeName, libraryName}) => - `using ${libraryName} for ${unparse(typeName)};`, - - StateVariableDeclaration: ({variables}) => - variables.map(unparse).join(', ') + ';', - - StructDefinition: ({name, members}) => - `struct ${name} ${block(members.map(unparse).join(';\n') + ';')}`, - - EnumDefinition: ({name, members}) => - `enum ${name} ${block(members.map(unparse).join(',\n'))}`, - - EnumValue: ({name}) => - name, - EventDefinition: ({ name, parameters }) => - `event ${name}${unparse(parameters)};`, + UsingForDeclaration: ({ typeName, libraryName }) => `using ${libraryName} for ${unparse(typeName)};`, + + StateVariableDeclaration: ({ variables }) => variables.map(unparse).join(', ') + ';', - ModifierDefinition: ({name, parameters, body}) => + StructDefinition: ({ name, members }) => `struct ${name} ${block(members.map(unparse).join(';\n') + ';')}`, + + EnumDefinition: ({ name, members }) => `enum ${name} ${block(members.map(unparse).join(',\n'))}`, + + EnumValue: ({ name }) => name, + + EventDefinition: ({ name, parameters }) => `event ${name}${unparse(parameters)};`, + + ModifierDefinition: ({ name, parameters, body }) => `modifier ${name}${Array.isArray(parameters) ? '' : unparse(parameters)} ${unparse(body)}`, - // Note: when there is no parameter block, instead of an ASTNode there is a [] + // Note: when there is no parameter block, instead of an ASTNode there is a [] - FunctionDefinition: ({visibility, name, parameters, body, modifiers, isConstructor, stateMutability, returnParameters}) => + FunctionDefinition: ({ + visibility, + name, + parameters, + body, + modifiers, + isConstructor, + stateMutability, + returnParameters, + }) => (isConstructor ? 'constructor' : `function ${name}`) + - unparse(parameters) + '\n' + + unparse(parameters) + + '\n' + indent( - (visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + - modifiers.map(unparse).join('\n') + - (returnParameters ? `\nreturns ${unparse(returnParameters)}` : '') - ) + '\n' + + (visibility && visibility != 'default' ? visibility + ' ' : '') + + (stateMutability || '') + + modifiers.map(unparse).join('\n') + + (returnParameters ? `\nreturns ${unparse(returnParameters)}` : ''), + ) + + '\n' + (body ? unparse(body) : ';'), - ParameterList: ({parameters}) => - `(${parameters.map(unparse).join(', ')})`, + ParameterList: ({ parameters }) => `(${parameters.map(unparse).join(', ')})`, - Parameter: ({typeName, name, storageLocation}) => - `${unparse(typeName)} ${storageLocation || ''} ${name || ''}`, + Parameter: ({ typeName, name, storageLocation }) => `${unparse(typeName)} ${storageLocation || ''} ${name || ''}`, - ModifierInvocation: ({name, arguments: args}) => - `${name}(${args.map(unparse).join(', ')})`, + ModifierInvocation: ({ name, arguments: args }) => `${name}(${args.map(unparse).join(', ')})`, // Statements - - Block: ({statements}) => - block(statements.map(unparse).join('\n')), - - VariableDeclarationStatement: ({variables, initialValue}) => - variables.map(unparse) + - (initialValue ? ` = ${unparse(initialValue)};` : ';'), - - ExpressionStatement: ({expression}) => - `${unparen(unparse(expression))};`, - - EmitStatement: ({eventCall}) => - `emit ${unparen(unparse(eventCall))};`, - - ReturnStatement: ({expression}) => - `return ${expression ? unparse(expression) : ''};`, - - BreakStatement: ({}) => - `break;`, - - ContinueStatement: ({}) => - `continue;`, - - ThrowStatement: ({}) => - `throw;`, - - IfStatement: ({condition, trueBody, falseBody}) => - `if (${unparse(condition)})\n${unparse(trueBody)}` + - (falseBody ? `else\n${unparse(falseBody)}` : ''), - - ForStatement: ({initExpression: i, conditionExpression: c, loopExpression: l, body}) => - `for (${unparse(i).replace(';','')}; ${unparse(c)}; ${unparse(l).replace(';','')}) ${unparse(body)}`, - - InlineAssemblyStatement: ({language, body}) => // TODO language - `assembly ${unparse(body)}`, + + Block: ({ statements }) => block(statements.map(unparse).join('\n')), + + VariableDeclarationStatement: ({ variables, initialValue }) => + variables.map(unparse) + (initialValue ? ` = ${unparse(initialValue)};` : ';'), + + ExpressionStatement: ({ expression }) => `${unparen(unparse(expression))};`, + + EmitStatement: ({ eventCall }) => `emit ${unparen(unparse(eventCall))};`, + + ReturnStatement: ({ expression }) => `return ${expression ? unparse(expression) : ''};`, + + BreakStatement: ({}) => `break;`, + + ContinueStatement: ({}) => `continue;`, + + ThrowStatement: ({}) => `throw;`, + + IfStatement: ({ condition, trueBody, falseBody }) => + `if (${unparse(condition)})\n${unparse(trueBody)}` + (falseBody ? `else\n${unparse(falseBody)}` : ''), + + ForStatement: ({ initExpression: i, conditionExpression: c, loopExpression: l, body }) => + `for (${unparse(i).replace(';', '')}; ${unparse(c)}; ${unparse(l).replace(';', '')}) ${unparse(body)}`, + + InlineAssemblyStatement: ( + { language, body }, // TODO language + ) => `assembly ${unparse(body)}`, // Types - ElementaryTypeName: ({name}) => - name, + ElementaryTypeName: ({ name }) => name, + + UserDefinedTypeName: ({ namePath }) => namePath, - UserDefinedTypeName: ({namePath}) => - namePath, - - ArrayTypeName: ({baseTypeName, length}) => - `${unparse(baseTypeName)}[${length ? unparse(length) : ''}]`, + ArrayTypeName: ({ baseTypeName, length }) => `${unparse(baseTypeName)}[${length ? unparse(length) : ''}]`, - Mapping: ({keyType, valueType}) => - `mapping (${unparse(keyType)} => ${unparse(valueType)})`, + Mapping: ({ keyType, valueType }) => `mapping (${unparse(keyType)} => ${unparse(valueType)})`, // Expressions - Identifier: ({ name }) => - name, - - BooleanLiteral: ({ value }) => - value ? 'true' : 'false', + Identifier: ({ name }) => name, - NumberLiteral: ({number, subdenomination}) => // TODO subdenomination - number, - - StringLiteral: ({value}) => - stresc(value), + BooleanLiteral: ({ value }) => (value ? 'true' : 'false'), - FunctionCall: ({expression, arguments: args, names}) => // TODO: names - `(${unparse(expression)}(${args.map(unparse).join(', ')}))`, + NumberLiteral: ( + { number: value, subdenomination }, // TODO subdenomination + ) => value, - Conditional: ({condition, trueExpression, falseExpression}) => + StringLiteral: ({ value }) => stresc(value), + + FunctionCall: ( + { expression, arguments: args, names }, // TODO: names + ) => `(${unparse(expression)}(${args.map(unparse).join(', ')}))`, + + Conditional: ({ condition, trueExpression, falseExpression }) => `(${unparse(condition)} ? ${unparse(trueExpression)} : ${unparse(falseExpression)})`, - - UnaryOperation: ({operator, subExpression, isPrefix}) => + + UnaryOperation: ({ operator, subExpression, isPrefix }) => `(${isPrefix ? operator : ''}${unparse(subExpression)}${isPrefix ? '' : operator})`, - BinaryOperation: ({operator, left, right}) => - `(${unparse(left)} ${operator} ${unparse(right)})`, + BinaryOperation: ({ operator, left, right }) => `(${unparse(left)} ${operator} ${unparse(right)})`, - MemberAccess: ({expression, memberName}) => - `(${unparse(expression)}.${memberName})`, + MemberAccess: ({ expression, memberName }) => `(${unparse(expression)}.${memberName})`, - IndexAccess: ({base, index}) => - `(${unparse(base)}[${unparse(index)}])`, + IndexAccess: ({ base, index }) => `(${unparse(base)}[${unparse(index)}])`, - ElementaryTypeNameExpression: ({typeName}) => - `(${unparse(typeName)})`, + ElementaryTypeNameExpression: ({ typeName }) => `(${unparse(typeName)})`, - VariableDeclaration: ({typeName, name, visibility, isDeclaredConst, isIndexed, expression, storageLocation}) => + VariableDeclaration: ({ typeName, name, visibility, isDeclaredConst, isIndexed, expression, storageLocation }) => `${unparse(typeName)} ` + (isIndexed ? 'indexed ' : '') + (storageLocation ? storageLocation + ' ' : '') + @@ -169,54 +153,41 @@ const visitor: S.Visitor = { (isDeclaredConst ? 'constant ' : '') + `${name}` + (expression ? ` = ${unparse(expression)}` : ''), - - NewExpression: ({typeName}) => - `(new ${unparse(typeName)})`, - - TupleExpression: ({isArray, components}) => - isArray - ? `[${components.map(unparse).join(', ')}]` - : `(${components.map(unparse).join(', ')})`, - + + NewExpression: ({ typeName }) => `(new ${unparse(typeName)})`, + + TupleExpression: ({ isArray, components }) => + isArray ? `[${components.map(unparse).join(', ')}]` : `(${components.map(unparse).join(', ')})`, + // Assembly - - AssemblyBlock: ({operations}) => - block(operations.map(unparse).join('\n')), - - AssemblyAssignment: ({names, expression}) => - `${names.map(unparse).join(', ')} := ${unparse(expression)}`, - - AssemblyLocalDefinition: ({names, expression}) => + + AssemblyBlock: ({ operations }) => block(operations.map(unparse).join('\n')), + + AssemblyAssignment: ({ names, expression }) => `${names.map(unparse).join(', ')} := ${unparse(expression)}`, + + AssemblyLocalDefinition: ({ names, expression }) => `let ${names.map(unparse).join(', ')} := ${unparse(expression)}`, - - AssemblyCall: ({functionName, arguments: args}) => - args.length == 0 ? - functionName : - `${functionName}(${args.map(unparse).join(', ')})`, - - AssemblyIf: ({condition, body}) => - `if ${unparse(condition)} ${unparse(body)}`, - - AssemblyFor: ({pre, condition, post, body}) => - `for ${[pre, condition, post, body].map(unparse).join(' ')}`, - - AssemblySwitch: ({expression, cases}) => - `switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`, - - AssemblyCase: ({value, block}) => - `case ${unparse(value)} ${unparse(block)}`, - - DecimalNumber: ({ value }) => - value, - - HexNumber: ({ value }) => - value, -} - -export function unparse(ast: S.ASTNode): string { - return (visitor[ast.type] || (a => { - console.log(a); + + AssemblyCall: ({ functionName, arguments: args }) => + args.length == 0 ? functionName : `${functionName}(${args.map(unparse).join(', ')})`, + + AssemblyIf: ({ condition, body }) => `if ${unparse(condition)} ${unparse(body)}`, + + AssemblyFor: ({ pre, condition, post, body }) => `for ${[pre, condition, post, body].map(unparse).join(' ')}`, + + AssemblySwitch: ({ expression, cases }) => `switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`, + + AssemblyCase: ({ value, block }) => `case ${unparse(value)} ${unparse(block)}`, + + DecimalNumber: ({ value }) => value, + + HexNumber: ({ value }) => value, + + ASTNode: node => { + console.log(node); console.trace(); - return `<${a.type}>`; - }))(ast); -} + return `<${node.type}>`; + }, +}; + +export const unparse = (ast: S.ASTNode) => visit(ast, visitor); diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 4ea8f7c61f..e45f1e18d3 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -1,36 +1,36 @@ import * as S from 'solidity-parser-antlr'; // TODO: Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap -export const flatMap = (a, f) => [].concat(...a.map(f)); +export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).concat(...a.map(f)); export const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - ast.children.filter(({type}) => type == S.NodeType.PragmaDirective); - -export const importNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - ast.children.filter(({type}) => type == S.NodeType.ImportDirective); + ast.children.filter(({ type }) => type === S.NodeType.PragmaDirective); + +export const importNodes = (ast: S.SourceUnit): S.ImportDirective[] => + ast.children.filter(({ type }) => type === S.NodeType.ImportDirective); export const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => - ast.children.filter(({type}) => type == S.NodeType.ContractDefinition); + ast.children.filter(({ type }) => type === S.NodeType.ContractDefinition); export const identifier = (name: string): S.Identifier => ({ type: S.NodeType.Identifier, name, -}) +}); -export const nameParameters = (params: S.ParameterList, prefix:string = '_arg'): S.ParameterList => ({ +export const nameParameters = (params: S.ParameterList, prefix: string = '_arg'): S.ParameterList => ({ ...params, parameters: params.parameters.map((param, i) => ({ ...param, - name: param.name || `${prefix}${i}` - })) -}) + name: param.name || `${prefix}${i}`, + })), +}); -export const argumentExpressions = (params: S.ParameterList): S.Expression[] => - params.parameters.map(({name}) => { +export const argumentExpressions = (params: S.ParameterList): S.Expression[] => + params.parameters.map(({ name }) => { // TODO: rewrite using throw expressions or do notation if (name !== null) { - return identifier(name) + return identifier(name); } else { - throw new Error("Anonymous parameter"); + throw new Error('Anonymous parameter'); } }); diff --git a/packages/sol-meta/src/visitor.ts b/packages/sol-meta/src/visitor.ts new file mode 100644 index 0000000000..440ea3a27b --- /dev/null +++ b/packages/sol-meta/src/visitor.ts @@ -0,0 +1,124 @@ +import * as S from 'solidity-parser-antlr'; + +export interface Visitor extends S.Visitor { + SourceMember?: (node: S.SourceMember) => T; + ContractMember?: (node: S.ContractMember) => T; + Statement?: (node: S.Statement) => T; + Expression?: (node: S.Expression) => T; + Type?: (node: S.Type) => T; + AssemblyStatement?: (node: S.AssemblyStatement) => T; + AssemblyExpression?: (node: S.AssemblyExpression) => T; + ASTNode?: (node: S.ASTNode) => T; +} + +export const isSourceMember = (node: S.ASTNode): node is S.SourceMember => + [S.NodeType.PragmaDirective, S.NodeType.ImportDirective, S.NodeType.ContractDefinition].includes(node.type); + +export const isContractMember = (node: S.ASTNode): node is S.ContractMember => + [ + S.NodeType.UsingForDeclaration, + S.NodeType.StateVariableDeclaration, + S.NodeType.StructDefinition, + S.NodeType.EnumDefinition, + S.NodeType.EventDefinition, + S.NodeType.ModifierDefinition, + S.NodeType.FunctionDefinition, + ].includes(node.type); + +export const isStatement = (node: S.ASTNode): node is S.Statement => + [ + S.NodeType.Block, + S.NodeType.VariableDeclarationStatement, + S.NodeType.ExpressionStatement, + S.NodeType.EmitStatement, + S.NodeType.ReturnStatement, + S.NodeType.BreakStatement, + S.NodeType.ContinueStatement, + S.NodeType.ThrowStatement, + S.NodeType.IfStatement, + S.NodeType.ForStatement, + S.NodeType.InlineAssemblyStatement, + ].includes(node.type); + +export const isExpression = (node: S.ASTNode): node is S.Expression => + [ + S.NodeType.BooleanLiteral, + S.NodeType.NumberLiteral, + S.NodeType.StringLiteral, + S.NodeType.Identifier, + S.NodeType.FunctionCall, + S.NodeType.Conditional, + S.NodeType.UnaryOperation, + S.NodeType.BinaryOperation, + S.NodeType.MemberAccess, + S.NodeType.IndexAccess, + S.NodeType.ElementaryTypeNameExpression, + S.NodeType.VariableDeclaration, + S.NodeType.NewExpression, + S.NodeType.TupleExpression, + S.NodeType.IndexAccess, + S.NodeType.MemberAccess, + ].includes(node); + +export const isType = (node: S.ASTNode): node is S.Type => + [ + S.NodeType.ElementaryTypeName, + S.NodeType.UserDefinedTypeName, + S.NodeType.Mapping, + S.NodeType.ArrayTypeName, + S.NodeType.FunctionTypeName, + ].includes(node); + +export const isAssemblyStatement = (node: S.ASTNode): node is S.AssemblyStatement => + [ + S.NodeType.AssemblyCall, // Note: also an expression! + S.NodeType.AssemblyAssignment, + S.NodeType.AssemblyLocalDefinition, + S.NodeType.AssemblyIf, + S.NodeType.AssemblyFor, + S.NodeType.AssemblySwitch, + S.NodeType.AssemblyCase, + ].includes(node); + +export const isAssemblyExpression = (node: S.ASTNode): node is S.AssemblyExpression => + [ + S.NodeType.AssemblyCall, // Note: also a statement! + S.NodeType.DecimalNumber, + S.NodeType.HexNumber, + ].includes(node); + +// Dispatches based on node type +export function visit(node: S.ASTNode, visitor: Visitor): T { + // Try to dispatch on the exact type + const indexed = visitor as { [type: string]: (node: S.ASTNode) => T }; + if (indexed[node.type]) { + return indexed[node.type](node); + } + + // Try to dispatch on classes of nodes, picking the first match + if (isSourceMember(node) && visitor.SourceMember) { + return visitor.SourceMember(node); + } + if (isContractMember(node) && visitor.ContractMember) { + return visitor.ContractMember(node); + } + if (isStatement(node) && visitor.Statement) { + return visitor.Statement(node); + } + if (isExpression(node) && visitor.Expression) { + return visitor.Expression(node); + } + if (isType(node) && visitor.Type) { + return visitor.Type(node); + } + if (isAssemblyStatement(node) && visitor.AssemblyStatement) { + return visitor.AssemblyStatement(node); + } + if (isAssemblyExpression(node) && visitor.AssemblyExpression) { + return visitor.AssemblyExpression(node); + } + if (visitor.ASTNode) { + return visitor.ASTNode(node); + } + throw new Error(`No matching visitor found for ${node.type}.`); +} diff --git a/packages/sol-meta/test/index.ts b/packages/sol-meta/test/index.ts index 88181e05eb..bdf12b1102 100644 --- a/packages/sol-meta/test/index.ts +++ b/packages/sol-meta/test/index.ts @@ -1,48 +1,46 @@ import * as chai from 'chai'; -import 'mocha'; -import * as glob from 'glob'; import * as fs from 'fs'; +import * as glob from 'glob'; +import 'mocha'; import * as path from 'path'; + import { parse } from '../src/parser'; import { unparse } from '../src/unparser'; const expect = chai.expect; -const promisify = (func) => (...args) => - new Promise((resolve, reject) => - func(...args, (error, result) => - error ? reject(error) : resolve(result))); +const promisify = (func: ((...args: any[]) => void)) => (...args: any[]) => + new Promise((resolve, reject) => + func(...args, (error: any, result: T) => (error ? reject(error) : resolve(result))), + ); -const findContracts = searchPath => +const findContracts = (searchPath: string) => glob.sync(searchPath).map(file => ({ name: path.basename(file, '.sol') + ` (${file})`, - source: fs.readFileSync(file, 'utf8') + source: fs.readFileSync(file, 'utf8'), })); const contracts = findContracts('../contracts/src/**/*.sol'); describe('Parser', () => { - it('should have test contracts', () => { expect(contracts).to.have.lengthOf.above(10); }); - - contracts.forEach(({name, source}) => + + contracts.forEach(({ name, source }) => it(`should parse ${name}`, () => { parse(source); - }) + }), ); - }); describe.only('Unparser', () => { - - contracts.forEach(({name, source}) => + contracts.forEach(({ name, source }) => it(`should unparse ${name}`, () => { const ast = parse(source); - const src = unparse(ast) ; + const src = unparse(ast); const ast2 = parse(src); //expect(ast2).to.deep.equal(ast); - }) + }), ); -}) +}); From 135d9e5ed4fb0266e53ed23cff1707a2e5152e83 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 12 Nov 2018 12:56:17 +0100 Subject: [PATCH 41/76] Resolve imports and compile --- packages/sol-meta/src/cli.ts | 51 ++++--- packages/sol-meta/src/compiler.ts | 215 ++++++++++++++++++++++++++---- 2 files changed, 222 insertions(+), 44 deletions(-) diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts index 1fd1c03e99..2c1551be7c 100644 --- a/packages/sol-meta/src/cli.ts +++ b/packages/sol-meta/src/cli.ts @@ -1,37 +1,56 @@ import 'source-map-support/register'; + import { logUtils } from '@0xproject/utils'; import * as _ from 'lodash'; import * as yargs from 'yargs'; import { Compiler } from './compiler'; -const DEFAULT_CONTRACTS_LIST = '*'; const SEPARATOR = ','; (async () => { + // Parse command line arguments and handle help const argv = yargs - .option('contracts-dir', { + .option('remapping', { + type: 'string', + description: 'search path remappings for contracts', + }) + .option('includes', { + type: 'string', + description: 'search path for contracts', + }) + .option('sources', { type: 'string', - description: 'path of contracts directory to compile', + description: 'comma separated list of Solidity files to process', }) - .option('contracts', { + .option('output', { type: 'string', - description: 'comma separated list of contracts to compile', + description: 'directory to output too', }) .help().argv; - const contracts = _.isUndefined(argv.contracts) - ? undefined - : argv.contracts === DEFAULT_CONTRACTS_LIST - ? DEFAULT_CONTRACTS_LIST - : argv.contracts.split(SEPARATOR); - const opts = { - contractsDir: argv.contractsDir, - artifactsDir: argv.artifactsDir, - contracts, - }; - const compiler = new Compiler(opts); + + // Handle command line arguments + const options = Compiler.defaultOptions; + if (argv.paths) { + options.includes = argv.paths.split(SEPARATOR) as string[]; + } + if (argv.sources) { + options.sources = argv.sources.split(SEPARATOR) as string[]; + } + if (argv.output) { + options.output = argv.output as string; + } + + // Instantiate and run compiler + console.time('Compilation'); + const compiler = new Compiler(options); await compiler.compileAsync(); + console.timeEnd('Compilation'); + + // Exit successfully + process.exit(0); })().catch(err => { + // Catch and log errorsm exit with failure logUtils.log(err); process.exit(1); }); diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts index 3b69df3cfa..85eccfde49 100644 --- a/packages/sol-meta/src/compiler.ts +++ b/packages/sol-meta/src/compiler.ts @@ -1,41 +1,200 @@ -import fs = require('fs'); -import { parse } from './parser'; -import { unparse } from './unparser'; -import { expose } from './exposer'; -import { mock } from './mocker'; -import { linearize } from './linearization'; -import { flattenSource } from './flattener'; +import * as fs from 'fs'; +import * as pathUtils from 'path'; +import * as process from 'process'; +import * as S from 'solidity-parser-antlr'; -import * as util from 'util'; // DEBUG +import { flatMap } from './utils'; +import { flattenContract } from './flattener'; +import { unparse } from './unparser'; +import { mockContract } from './mocker'; +import { Children } from 'react'; export interface CompilerOptions { - contractsDir: string; - contracts: string; + remapping: { [prefix: string]: string }; + includes: string[]; + sources: string[]; + output: string; } +const existsAsync = (path: string): Promise => + new Promise((resolve, reject) => + fs.access(path, fs.constants.R_OK, error => (error ? resolve(false) : resolve(true))), + ); + +const readFileAsync = (path: string): Promise => + new Promise((resolve, reject) => + fs.readFile(path, 'utf-8', (error, contents) => (error ? reject(error) : resolve(contents))), + ); + +const writeFileAsync = (path: string, contents: string): Promise => + new Promise((resolve, reject) => + fs.writeFile(path, contents, 'utf-8', error => (error ? reject(error) : resolve())), + ); + export class Compiler { + public static readonly defaultOptions: CompilerOptions = { + remapping: {}, + includes: [], + sources: [], + output: './out.sol', + }; + + private readonly _opts: CompilerOptions; + + // ASTs for all source files + private _sources: { [path: string]: S.SourceUnit }; + + // For a given source file, contracts by name (including imports) + private _contracts: { [path: string]: { [name: string]: S.ContractDefinition } }; + + constructor(opts: CompilerOptions) { + this._opts = opts; + this._sources = {}; + this._contracts = {}; + + const cwd = process.cwd(); + const makeAbsolute = (path: string) => (pathUtils.isAbsolute(path) ? path : pathUtils.join(cwd, path)); - private readonly opts: CompilerOptions; + // Make remappings absolute + for (const prefix in this._opts.remapping) { + if (this._opts.remapping.hasOwnProperty(prefix)) { + this._opts.remapping[prefix] = makeAbsolute(this._opts.remapping[prefix]); + } + } + + // Make include dirs absolute + this._opts.includes = this._opts.includes.map(makeAbsolute); + + // Make sources absolute + this._opts.sources = this._opts.sources.map(makeAbsolute); + } + + // Takes an import path and returns the absolute file path + public async resolveAsync(sourcePath: string, importPath: string): Promise { + // Try relative path + if (importPath.startsWith('./') || importPath.startsWith('../')) { + const abs = pathUtils.join(pathUtils.dirname(sourcePath), importPath); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } else { + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); + } + } + + // Try remappings + for (const prefix in this._opts.remapping) { + if (this._opts.remapping.hasOwnProperty(prefix)) { + if (importPath.startsWith(prefix)) { + const replacement = this._opts.remapping[prefix]; + const abs = pathUtils.normalize(importPath.replace(prefix, replacement)); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } else { + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); + } + } + } + } - constructor(opts?: CompilerOptions) { - this.opts = opts || { - contractsDir: '', - contracts: '', - }; + // Try global include directories + for (const include of this._opts.includes) { + const abs = pathUtils.join(include, importPath); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } + } + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); } - public async compileAsync() { - - const filePath = this.opts.contracts[0]; - const source = fs.readFileSync(filePath, 'utf8'); - try { - const ast = parse(source); - //console.log(unparse(ast)); - const astp = flattenSource(ast, 'MAssetProxyDispatcher'); - //console.log(util.inspect(astp, {depth: 4})); - console.log(unparse(astp)); - } catch (e) { - console.log(e); + public async readSourceAsync(absolutePath: string): Promise { + // Try cache + if (absolutePath in this._sources) { + return Promise.resolve(this._sources[absolutePath]); + } + + // Set a placeholder to act as an improvised mutex. + this._sources[absolutePath] = { type: S.NodeType.SourceUnit, children: [] }; + + // Read and save in cache + const contents = await readFileAsync(absolutePath); + const source = S.parse(contents, {}); + this._sources[absolutePath] = source; + + // List contracts + const contracts = source.children.filter( + ({ type }) => type === S.NodeType.ContractDefinition, + ) as S.ContractDefinition[]; + contracts.forEach(({ name }) => console.log(`Read ${name}`)); + + // Resolve import statments + const imports = source.children.filter( + ({ type }) => type === S.NodeType.ImportDirective, + ) as S.ImportDirective[]; + const importPaths = await Promise.all(imports.map(({ path }) => this.resolveAsync(absolutePath, path))); + + // Recursively parse imported sources + await Promise.all(importPaths.map(path => this.readSourceAsync(path))); + + // Compute global scope include imports + // TODO: `SomeContract as SomeAlias` in import directives. + let definitions: { [name: string]: S.ContractDefinition } = {}; + importPaths.forEach(path => { + definitions = { ...definitions, ...this._contracts[path] }; + }); + contracts.forEach(contract => { + definitions[contract.name] = contract; + }); + this._contracts[absolutePath] = definitions; + + // Return parsed source code + return Promise.resolve(source); + } + + public async compileAsync(): Promise { + if (this._opts.sources.length === 0) { + console.log('No sources to process.'); + } + + // Read all contract sources and imports + await Promise.all(this._opts.sources.map(path => this.readSourceAsync(path))); + + const contracts = (source: S.SourceUnit) => + source.children.filter(({ type }) => type === S.NodeType.ContractDefinition) as S.ContractDefinition[]; + + // Target sources + for (const path of this._opts.sources) { + const source = this._sources[path]; + const contractsByName = this._contracts[path]; + + for (const contract of contracts(source)) { + console.log('Analyzing ', contract.name); + console.log(Object.keys(contractsByName)); + + // Flatten contract + const flat = flattenContract(contract, name => contractsByName[name]); + + // Mock contract + const mocked = mockContract(flat); + + // Construct output source unit + const output = { + type: S.NodeType.SourceUnit, + children: [ + ...source.children.filter(({ type }) => type === S.NodeType.PragmaDirective), + { + type: S.NodeType.ImportDirective, + path, + }, + mocked, + ], + }; + + if (await existsAsync(this._opts.output)) { + throw new Error(`Ouput file ${this._opts.output} already exists`); + } + + await writeFileAsync(this._opts.output, unparse(output)); + } } } } From 5d9ab19a20648c3d1cef2466e84a3a2851d6cee0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 14 Nov 2018 15:52:54 +0100 Subject: [PATCH 42/76] Refactor to SourceReader --- packages/sol-meta/src/compiler.ts | 200 ------------------------- packages/sol-meta/src/source_reader.ts | 160 ++++++++++++++++++++ 2 files changed, 160 insertions(+), 200 deletions(-) delete mode 100644 packages/sol-meta/src/compiler.ts create mode 100644 packages/sol-meta/src/source_reader.ts diff --git a/packages/sol-meta/src/compiler.ts b/packages/sol-meta/src/compiler.ts deleted file mode 100644 index 85eccfde49..0000000000 --- a/packages/sol-meta/src/compiler.ts +++ /dev/null @@ -1,200 +0,0 @@ -import * as fs from 'fs'; -import * as pathUtils from 'path'; -import * as process from 'process'; -import * as S from 'solidity-parser-antlr'; - -import { flatMap } from './utils'; -import { flattenContract } from './flattener'; -import { unparse } from './unparser'; -import { mockContract } from './mocker'; -import { Children } from 'react'; - -export interface CompilerOptions { - remapping: { [prefix: string]: string }; - includes: string[]; - sources: string[]; - output: string; -} - -const existsAsync = (path: string): Promise => - new Promise((resolve, reject) => - fs.access(path, fs.constants.R_OK, error => (error ? resolve(false) : resolve(true))), - ); - -const readFileAsync = (path: string): Promise => - new Promise((resolve, reject) => - fs.readFile(path, 'utf-8', (error, contents) => (error ? reject(error) : resolve(contents))), - ); - -const writeFileAsync = (path: string, contents: string): Promise => - new Promise((resolve, reject) => - fs.writeFile(path, contents, 'utf-8', error => (error ? reject(error) : resolve())), - ); - -export class Compiler { - public static readonly defaultOptions: CompilerOptions = { - remapping: {}, - includes: [], - sources: [], - output: './out.sol', - }; - - private readonly _opts: CompilerOptions; - - // ASTs for all source files - private _sources: { [path: string]: S.SourceUnit }; - - // For a given source file, contracts by name (including imports) - private _contracts: { [path: string]: { [name: string]: S.ContractDefinition } }; - - constructor(opts: CompilerOptions) { - this._opts = opts; - this._sources = {}; - this._contracts = {}; - - const cwd = process.cwd(); - const makeAbsolute = (path: string) => (pathUtils.isAbsolute(path) ? path : pathUtils.join(cwd, path)); - - // Make remappings absolute - for (const prefix in this._opts.remapping) { - if (this._opts.remapping.hasOwnProperty(prefix)) { - this._opts.remapping[prefix] = makeAbsolute(this._opts.remapping[prefix]); - } - } - - // Make include dirs absolute - this._opts.includes = this._opts.includes.map(makeAbsolute); - - // Make sources absolute - this._opts.sources = this._opts.sources.map(makeAbsolute); - } - - // Takes an import path and returns the absolute file path - public async resolveAsync(sourcePath: string, importPath: string): Promise { - // Try relative path - if (importPath.startsWith('./') || importPath.startsWith('../')) { - const abs = pathUtils.join(pathUtils.dirname(sourcePath), importPath); - if (await existsAsync(abs)) { - return Promise.resolve(abs); - } else { - throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); - } - } - - // Try remappings - for (const prefix in this._opts.remapping) { - if (this._opts.remapping.hasOwnProperty(prefix)) { - if (importPath.startsWith(prefix)) { - const replacement = this._opts.remapping[prefix]; - const abs = pathUtils.normalize(importPath.replace(prefix, replacement)); - if (await existsAsync(abs)) { - return Promise.resolve(abs); - } else { - throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); - } - } - } - } - - // Try global include directories - for (const include of this._opts.includes) { - const abs = pathUtils.join(include, importPath); - if (await existsAsync(abs)) { - return Promise.resolve(abs); - } - } - throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); - } - - public async readSourceAsync(absolutePath: string): Promise { - // Try cache - if (absolutePath in this._sources) { - return Promise.resolve(this._sources[absolutePath]); - } - - // Set a placeholder to act as an improvised mutex. - this._sources[absolutePath] = { type: S.NodeType.SourceUnit, children: [] }; - - // Read and save in cache - const contents = await readFileAsync(absolutePath); - const source = S.parse(contents, {}); - this._sources[absolutePath] = source; - - // List contracts - const contracts = source.children.filter( - ({ type }) => type === S.NodeType.ContractDefinition, - ) as S.ContractDefinition[]; - contracts.forEach(({ name }) => console.log(`Read ${name}`)); - - // Resolve import statments - const imports = source.children.filter( - ({ type }) => type === S.NodeType.ImportDirective, - ) as S.ImportDirective[]; - const importPaths = await Promise.all(imports.map(({ path }) => this.resolveAsync(absolutePath, path))); - - // Recursively parse imported sources - await Promise.all(importPaths.map(path => this.readSourceAsync(path))); - - // Compute global scope include imports - // TODO: `SomeContract as SomeAlias` in import directives. - let definitions: { [name: string]: S.ContractDefinition } = {}; - importPaths.forEach(path => { - definitions = { ...definitions, ...this._contracts[path] }; - }); - contracts.forEach(contract => { - definitions[contract.name] = contract; - }); - this._contracts[absolutePath] = definitions; - - // Return parsed source code - return Promise.resolve(source); - } - - public async compileAsync(): Promise { - if (this._opts.sources.length === 0) { - console.log('No sources to process.'); - } - - // Read all contract sources and imports - await Promise.all(this._opts.sources.map(path => this.readSourceAsync(path))); - - const contracts = (source: S.SourceUnit) => - source.children.filter(({ type }) => type === S.NodeType.ContractDefinition) as S.ContractDefinition[]; - - // Target sources - for (const path of this._opts.sources) { - const source = this._sources[path]; - const contractsByName = this._contracts[path]; - - for (const contract of contracts(source)) { - console.log('Analyzing ', contract.name); - console.log(Object.keys(contractsByName)); - - // Flatten contract - const flat = flattenContract(contract, name => contractsByName[name]); - - // Mock contract - const mocked = mockContract(flat); - - // Construct output source unit - const output = { - type: S.NodeType.SourceUnit, - children: [ - ...source.children.filter(({ type }) => type === S.NodeType.PragmaDirective), - { - type: S.NodeType.ImportDirective, - path, - }, - mocked, - ], - }; - - if (await existsAsync(this._opts.output)) { - throw new Error(`Ouput file ${this._opts.output} already exists`); - } - - await writeFileAsync(this._opts.output, unparse(output)); - } - } - } -} diff --git a/packages/sol-meta/src/source_reader.ts b/packages/sol-meta/src/source_reader.ts new file mode 100644 index 0000000000..d673cc63f1 --- /dev/null +++ b/packages/sol-meta/src/source_reader.ts @@ -0,0 +1,160 @@ +import * as pathUtils from 'path'; +import * as S from 'solidity-parser-antlr'; + +import { Deferred, existsAsync, objectPromise, readFileAsync } from './utils'; + +export interface SourceReaderOptions { + remapping: { [prefix: string]: string }; + includes: string[]; + sources: string[]; +} + +export interface SourceInfo { + source: string; + parsed: S.SourceUnit; + contracts: { + [name: string]: S.ContractDefinition; + }; +} + +export interface SourceCollection { + [path: string]: SourceInfo; +} + +// Helper class to read contract files. You can use the function `readContracts` instead. +export class ContractReader { + public static readonly defaultOptions: SourceReaderOptions = { + remapping: {}, + includes: [], + sources: [], + }; + + private readonly _opts: SourceReaderOptions; + + private _result: { + [path: string]: Promise; + }; + + constructor(opts: Partial) { + this._opts = { ...ContractReader.defaultOptions, ...opts }; + this._result = {}; + + // Utility to create absolute paths + const cwd = process.cwd(); + const makeAbsolute = (path: string) => (pathUtils.isAbsolute(path) ? path : pathUtils.join(cwd, path)); + + // Make remappings absolute + for (const prefix in this._opts.remapping) { + if (this._opts.remapping.hasOwnProperty(prefix)) { + this._opts.remapping[prefix] = makeAbsolute(this._opts.remapping[prefix]); + } + } + + // Make include dirs absolute + this._opts.includes = this._opts.includes.map(makeAbsolute); + + // Make sources absolute + this._opts.sources = this._opts.sources.map(makeAbsolute); + } + + public async processSourcesAsync(): Promise { + // Read all contract sources and imports + await Promise.all(this._opts.sources.map(path => this._readSourceAsync(path))); + + // Resolve the result + return objectPromise(this._result); + } + + // Takes an import path and returns the absolute file path + private async _resolveAsync(sourcePath: string, importPath: string): Promise { + // Try relative path + if (importPath.startsWith('./') || importPath.startsWith('../')) { + const abs = pathUtils.join(pathUtils.dirname(sourcePath), importPath); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } else { + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); + } + } + + // Try remappings + for (const prefix in this._opts.remapping) { + if (this._opts.remapping.hasOwnProperty(prefix)) { + if (importPath.startsWith(prefix)) { + const replacement = this._opts.remapping[prefix]; + const abs = pathUtils.normalize(importPath.replace(prefix, replacement)); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } else { + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); + } + } + } + } + + // Try global include directories + for (const include of this._opts.includes) { + const abs = pathUtils.join(include, importPath); + if (await existsAsync(abs)) { + return Promise.resolve(abs); + } + } + throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); + } + + private async _readSourceAsync(absolutePath: string): Promise { + // Try existing promises + if (absolutePath in this._result) { + return this._result[absolutePath]; + } + + // Create promise here so it will act as a mutex. + // When we recursively re-enter this function below, the deferred + // promise will already be there and we will not repeat the work. + const deffered = new Deferred(); + this._result[absolutePath] = deffered.promise; + + // Read and save in cache + const source = await readFileAsync(absolutePath); + const parsed = S.parse(source, {}); + + // Resolve import statments paths + const imports = parsed.children.filter( + ({ type }) => type === S.NodeType.ImportDirective, + ) as S.ImportDirective[]; + const importPaths = await Promise.all(imports.map(({ path }) => this._resolveAsync(absolutePath, path))); + + // Recursively parse imported sources + // Note: This will deadlock on cyclical imports. (We will end up awaiting our own promise.) + // TODO: Throw an error instead. + const importInfo = await Promise.all(importPaths.map(path => this._readSourceAsync(path))); + + // Compute global scope include imports + // TODO: Support `SomeContract as SomeAlias` in import directives. + let contracts: { [name: string]: S.ContractDefinition } = {}; + importInfo.forEach(({ contracts: importedContracts }) => { + contracts = { ...contracts, ...importedContracts }; + }); + + // Add local contracts + const localContracts = parsed.children.filter( + ({ type }) => type === S.NodeType.ContractDefinition, + ) as S.ContractDefinition[]; + localContracts.forEach(contract => { + contracts[contract.name] = contract; + }); + + // Resolve deferred promise and return resolved promise + deffered.resolve({ + source, + parsed, + contracts, + }); + return deffered.promise; + } +} + +export const readSources = async ( + sources: string[], + options?: Partial, +): Promise => new ContractReader({ sources, ...(options || {}) }).processSourcesAsync(); From 12a8ffde25b08eeb9bad340591089e4920df4394 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:43:37 +0100 Subject: [PATCH 43/76] Add missing ABIv2 pragmas --- .../protocol/Exchange/MixinSignatureValidator.sol | 1 + .../contracts/protocol/Exchange/MixinTransactions.sol | 1 + .../protocol/Exchange/libs/LibFillResults.sol | 1 + .../contracts/protocol/Exchange/libs/LibOrder.sol | 11 ++++++----- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol index bd5b011b68..28fe81faf1 100644 --- a/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol @@ -17,6 +17,7 @@ */ pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "../../utils/ReentrancyGuard/ReentrancyGuard.sol"; diff --git a/packages/contracts/contracts/protocol/Exchange/MixinTransactions.sol b/packages/contracts/contracts/protocol/Exchange/MixinTransactions.sol index 3a76ca2020..e73eb91ae9 100644 --- a/packages/contracts/contracts/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/contracts/protocol/Exchange/MixinTransactions.sol @@ -16,6 +16,7 @@ */ pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; import "./libs/LibExchangeErrors.sol"; import "./mixins/MSignatureValidator.sol"; diff --git a/packages/contracts/contracts/protocol/Exchange/libs/LibFillResults.sol b/packages/contracts/contracts/protocol/Exchange/libs/LibFillResults.sol index 659ae9a699..00c6268e28 100644 --- a/packages/contracts/contracts/protocol/Exchange/libs/LibFillResults.sol +++ b/packages/contracts/contracts/protocol/Exchange/libs/LibFillResults.sol @@ -17,6 +17,7 @@ */ pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; import "../../../utils/SafeMath/SafeMath.sol"; diff --git a/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol index 0fe7c2161f..46ff9d3cce 100644 --- a/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol @@ -17,6 +17,7 @@ */ pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; import "./LibEIP712.sol"; @@ -45,11 +46,11 @@ contract LibOrder is // A valid order remains fillable until it is expired, fully filled, or cancelled. // An order's state is unaffected by external factors, like account balances. enum OrderStatus { - INVALID, // Default value - INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount - INVALID_TAKER_ASSET_AMOUNT, // Order does not have a valid taker asset amount - FILLABLE, // Order is fillable - EXPIRED, // Order has already expired + INVALID, // 0 Default value + INVALID_MAKER_ASSET_AMOUNT, // 1 Order does not have a valid maker asset amount + INVALID_TAKER_ASSET_AMOUNT, // 2 Order does not have a valid taker asset amount + FILLABLE, // 3 Order is fillable + EXPIRED, // 4 Order has already expired FULLY_FILLED, // Order is fully filled CANCELLED // Order has been cancelled } From 1e8688eb24af1aeaad0ca0ee638016aa6a568830 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:44:06 +0100 Subject: [PATCH 44/76] Add documentation in readme --- packages/sol-meta/README.md | 256 ++++++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 11 deletions(-) diff --git a/packages/sol-meta/README.md b/packages/sol-meta/README.md index 303e16fcdd..a1f0a6a842 100644 --- a/packages/sol-meta/README.md +++ b/packages/sol-meta/README.md @@ -1,25 +1,258 @@ -## @0xproject/sol-meta +## 0x/sol-meta -Sol-meta is a Solidity to Solidity compiler to automatically generate testing contracts. It has two modes: +Sol-meta is a Solidity to Solidity compiler to automatically generate testing +contracts. It does a number of things: exposing internal logic, stubbing +abstract functions, scripting abstract functions. -**Mocking.** Given an interface contract (either a public interface or a mixin), it can generate a mock implementation. +### Exposing -**Exposing.** Given an implemented contract it generates a contract that exposes all internal functions with public wrappers. +Given an implemented contract it generates a contract that exposes all internal +functions with public wrappers. -**Flattening.** Given a contract it will pull in all inherited contracts and flatten them into a single contract without inheritance. +#### Exposed Functions -**Combined.** Flatten a contract and then generate a wrapper that adds mocks for all unimplemented functions and exposes all internal members. +All internal functions receive a public wrapper. -## Architecture +Example: -Source --Parser--> AST --transform--> AST --unparser--> Source +```solidity + function safeAddPublic(uint256 a, uint256 b) + public pure + returns (uint256 result) + { + return safeAdd(a, bb); + } +``` + +#### Exposed Modifiers + +For every modifier a testing function is generated. + +Example: + +```solidity + function onlyAuthorizedTest(address user) + public + onlyAuthorized(user) + returns (bool executed) + { + return true; + } +``` + +#### Exposed Events + +For every event, a function is created that will trigger it. + +Example: + +```solidity + function TransferEmit(address from, address to, uint256 value) + public + { + emit Transfer(from, to, value); + } +``` + +#### Exposed Variables + +All variable are given getters and setters. + +Example: + +```solidity + function totalSupplyGet() + public + returns (uint256) + { + return totalSupply; + } + + function totalSupplySet(uint256 value) + public + { + totalSupply = value; + } +``` + +Limitations: + +Currently no support for maps is implemented. + +### Scripting Functions + +Example: + +```json + "scripted": { + "hashZeroExTransaction": [ + { + "inputs": { + "salt": 123 + }, + "outputs": ["0x0123456"] + }, + { + "inputs": { + "salt": 32123, + "signerAddress": "0x0123123213" + }, + "reverts": "Test failed. Should never be called." + } + ] + }, +``` + +```solidity + function hashZeroExTransaction( + uint256 salt, + address signerAddress, + bytes memory data + ) + internal pure + returns (bytes32 result) + { + if (salt == 123) + return 0x0123456; + + if (salt == 32123 && signerAddress == 0x0123123213) + require(false, "Test failed. Should never be called."); + + require(false, "Unscripted input for hashZeroExTransaction"); + } +``` + +### Stubbing + +Given an interface contract (either a public interface or a mixin), it can +generate a mock implementation. + +Note: the computation of what is abstract and what is not is imperfect. In +particular, public variables imply getter functions. + +#### Stubbed Functions + +Example: + +```solidity + event isValidSignatureCalled(uint256 counter, bytes32 hash, address signerAddress, bytes signature); + + struct _isValidSignature_Result { + bool _enabled; // Should always be true + bool _reverts; // When set, revert + bool isValid; // Return value + } + + function isValidSignatureMock(uint256 _counter, _isValidSignature_Result _value) + public + { + // Schedule result number `_counter`. + } + + function isValidSignature(bytes32 hash, address signerAddress, bytes memory signature) + public view + returns (bool isValid) + { + // Log the inputs and return the next schedult result. + // ... + } +``` + +When there is no enabled option, it will revert with the message `Unprogrammed input for isValidSignature`. + +#### Stubbed Actions + +Example: + +```solidity + event _preSign_log(uint256 counter, bytes32 hash, address signerAddress, bytes signature); + + function preSign(bytes32 hash, address signerAddress, bytes signature) + external + { + emit _preSign_log(_preSign_counter, hash, signerAddress, signature); + _preSign_counter++; + } +``` + +TODO: We need some sort of result programmability here anyway because we could +revert. + +#### Stubbed Pure Functions + +Example: + +```solidity + function hashZeroExTransaction(uint256 salt, address signerAddress, bytes memory data) + internal pure + returns (bytes32 result) + { + require(false, "Abstract function hashZeroExTransaction called"); + } +``` + +### Constructors + +A constructor is created that will call all parent constructors +that require arguments. The constructor arguments need to be supplied by config +file. + +```json + "constructors": { + "LibConstants": ["\"ZRXASSETSTRING\""] + }, +``` + +Example: + +```solidity + constructor() public + LibConstants("ZRXASSETSTRING") + { } +``` + +Note: Constructors calling parent constructors is not taken into consideration. +These will incorrectly appear as to-be-call. + +### Non Abstract Forcer + +Solidity by default does not produce any error or warning when a contract is +unintentionally abstract. Instead, output for that contract is silently ignored. +To force Solidity to error out in this case, a special contract is created that +will fail to compile if the original contract is abstract: + +```solidity +contract MixinExchangeCoreMockNonAbstractForcer { + constructor() { + new MixinExchangeCoreMock; + } +} +``` + +## Command line interface + +``` +Usage: sol-meta.js [options] + +Options: + --version Show version number [boolean] + --help Show help [boolean] + --config config file [string] + --remapping path remappings for import statements [array] + --includes search paths for imported source files [array] + --output directory to output too [string] + --test run all generated mock contracts through solc [boolean] +``` +## Configuration file ## Contributing -We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository. +We welcome improvements and fixes from the wider community! To report bugs +within this package, please create an issue in this repository. -Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting +started. ### Install dependencies @@ -37,7 +270,8 @@ yarn install ### Build -To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: +To build this package and all other monorepo packages that it depends on, run +the following from the monorepo root directory: ```bash PKG=@0xproject/sol-compiler yarn build From 5352a53142bd0e4d3ef5e0e66b685762c78f2b5e Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:45:06 +0100 Subject: [PATCH 45/76] Globbbing and separate local from scoped --- packages/sol-meta/src/source_reader.ts | 56 ++++++++++++++------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/sol-meta/src/source_reader.ts b/packages/sol-meta/src/source_reader.ts index d673cc63f1..a06df719e0 100644 --- a/packages/sol-meta/src/source_reader.ts +++ b/packages/sol-meta/src/source_reader.ts @@ -1,7 +1,8 @@ +import * as glob from 'glob'; import * as pathUtils from 'path'; import * as S from 'solidity-parser-antlr'; -import { Deferred, existsAsync, objectPromise, readFileAsync } from './utils'; +import * as utils from './utils'; export interface SourceReaderOptions { remapping: { [prefix: string]: string }; @@ -15,8 +16,15 @@ export interface SourceInfo { contracts: { [name: string]: S.ContractDefinition; }; + scope: { + [name: string]: S.ContractDefinition; + }; } +// TODO: We are missing the remapping and import statements. This information is necessary +// to fully interpret the ImportDirectives. We could solve this by including the +// SourceReaderOptions as part of the SourceCollection, but then we'd need to +// but the path mapping in a separate variable. export interface SourceCollection { [path: string]: SourceInfo; } @@ -39,22 +47,20 @@ export class ContractReader { this._opts = { ...ContractReader.defaultOptions, ...opts }; this._result = {}; + // Unglob the source and include paths + const unglob = (paths: string[]) => + utils.flatMap(paths, pattern => (glob.hasMagic(pattern) ? glob.sync(pattern) : [pattern])); + this._opts.sources = unglob(this._opts.sources); + this._opts.includes = unglob(this._opts.includes); + // Utility to create absolute paths const cwd = process.cwd(); const makeAbsolute = (path: string) => (pathUtils.isAbsolute(path) ? path : pathUtils.join(cwd, path)); - // Make remappings absolute - for (const prefix in this._opts.remapping) { - if (this._opts.remapping.hasOwnProperty(prefix)) { - this._opts.remapping[prefix] = makeAbsolute(this._opts.remapping[prefix]); - } - } - - // Make include dirs absolute - this._opts.includes = this._opts.includes.map(makeAbsolute); - - // Make sources absolute + // Make all paths absolute this._opts.sources = this._opts.sources.map(makeAbsolute); + this._opts.includes = this._opts.includes.map(makeAbsolute); + this._opts.remapping = utils.objectMap(this._opts.remapping, makeAbsolute); } public async processSourcesAsync(): Promise { @@ -62,7 +68,7 @@ export class ContractReader { await Promise.all(this._opts.sources.map(path => this._readSourceAsync(path))); // Resolve the result - return objectPromise(this._result); + return utils.objectPromise(this._result); } // Takes an import path and returns the absolute file path @@ -70,7 +76,7 @@ export class ContractReader { // Try relative path if (importPath.startsWith('./') || importPath.startsWith('../')) { const abs = pathUtils.join(pathUtils.dirname(sourcePath), importPath); - if (await existsAsync(abs)) { + if (await utils.existsAsync(abs)) { return Promise.resolve(abs); } else { throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); @@ -83,7 +89,7 @@ export class ContractReader { if (importPath.startsWith(prefix)) { const replacement = this._opts.remapping[prefix]; const abs = pathUtils.normalize(importPath.replace(prefix, replacement)); - if (await existsAsync(abs)) { + if (await utils.existsAsync(abs)) { return Promise.resolve(abs); } else { throw new Error(`Import ${importPath} from ${sourcePath} could not be found.`); @@ -95,7 +101,7 @@ export class ContractReader { // Try global include directories for (const include of this._opts.includes) { const abs = pathUtils.join(include, importPath); - if (await existsAsync(abs)) { + if (await utils.existsAsync(abs)) { return Promise.resolve(abs); } } @@ -111,11 +117,11 @@ export class ContractReader { // Create promise here so it will act as a mutex. // When we recursively re-enter this function below, the deferred // promise will already be there and we will not repeat the work. - const deffered = new Deferred(); + const deffered = new utils.Deferred(); this._result[absolutePath] = deffered.promise; // Read and save in cache - const source = await readFileAsync(absolutePath); + const source = await utils.readFileAsync(absolutePath); const parsed = S.parse(source, {}); // Resolve import statments paths @@ -131,17 +137,16 @@ export class ContractReader { // Compute global scope include imports // TODO: Support `SomeContract as SomeAlias` in import directives. - let contracts: { [name: string]: S.ContractDefinition } = {}; - importInfo.forEach(({ contracts: importedContracts }) => { - contracts = { ...contracts, ...importedContracts }; + let scope: { [name: string]: S.ContractDefinition } = {}; + importInfo.forEach(({ scope: importedContracts }) => { + scope = { ...scope, ...importedContracts }; }); // Add local contracts - const localContracts = parsed.children.filter( - ({ type }) => type === S.NodeType.ContractDefinition, - ) as S.ContractDefinition[]; - localContracts.forEach(contract => { + let contracts: { [name: string]: S.ContractDefinition } = {}; + utils.contracts(parsed).forEach(contract => { contracts[contract.name] = contract; + scope[contract.name] = contract; }); // Resolve deferred promise and return resolved promise @@ -149,6 +154,7 @@ export class ContractReader { source, parsed, contracts, + scope, }); return deffered.promise; } From 7f72146b39b67c89db4ab5e9b5b2742d047af02c Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:45:30 +0100 Subject: [PATCH 46/76] Rename mocker to stubber --- .../sol-meta/src/{mocker.ts => stubber.ts} | 223 ++++++++++-------- 1 file changed, 120 insertions(+), 103 deletions(-) rename packages/sol-meta/src/{mocker.ts => stubber.ts} (58%) diff --git a/packages/sol-meta/src/mocker.ts b/packages/sol-meta/src/stubber.ts similarity index 58% rename from packages/sol-meta/src/mocker.ts rename to packages/sol-meta/src/stubber.ts index 052ea797ed..bf63b4ad5b 100644 --- a/packages/sol-meta/src/mocker.ts +++ b/packages/sol-meta/src/stubber.ts @@ -1,26 +1,21 @@ import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; +import { makeConstructor, ConstructorArguments } from './constructor'; import { identifier, nameParameters, argumentExpressions } from './utils'; import { visit, Visitor } from './visitor'; +import { FunctionScript, scriptFunction } from './scripter'; +import { exposeNode } from './exposer'; -// TODO: both Actions and Functions can throw in addition to returning +// Todo rename to stubber. -const bool: S.ElementaryTypeName = { - type: S.NodeType.ElementaryTypeName, - name: 'bool', -}; - -const uint256: S.ElementaryTypeName = { - type: S.NodeType.ElementaryTypeName, - name: 'uint256', -}; +// TODO: both Actions and Functions can throw in addition to returning. Throwing should take arbitrary data. -const zero: S.NumberLiteral = { - type: S.NodeType.NumberLiteral, - number: '0', - subdenomination: null, // TODO -}; +export const mockContractName = (contractName: string) => `${contractName}Mock`; +export const programmerName = (funcName: string) => `${funcName}Mock`; +export const logEventName = (funcName: string) => `${funcName}Called`; +export const errorNoMock = (funcName: string) => `Abstract function ${funcName} called`; +export const errorUnprogrammedInput = (funcName: string) => `Unprogrammed input for ${funcName}`; const call = (func: S.Expression, ...args: S.Expression[]): S.FunctionCall => ({ type: S.NodeType.FunctionCall, @@ -39,16 +34,16 @@ const makeCounter = (name: string): S.StateVariableDeclaration => ({ variables: [ { type: S.NodeType.VariableDeclaration, - typeName: uint256, + typeName: utils.types.uint256, name, - expression: zero, + expression: utils.litNumber(0), visibility: S.Visibility.Internal, isStateVar: true, isDeclaredConst: false, isIndexed: false, }, ], - initialValue: zero, + initialValue: utils.litNumber(0), }); const makeEvent = (name: string, parameters: S.ParameterList): S.EventDefinition => ({ @@ -65,7 +60,7 @@ const makeCountedEvent = (name: string, parameters: S.ParameterList) => { type: S.NodeType.Parameter, name: 'counter', - typeName: uint256, + typeName: utils.types.uint256, storageLocation: S.StorageLocation.Default, }, ...parameters.parameters.map(param => ({ @@ -88,6 +83,7 @@ const makeIncrement = (name: string): S.ExpressionStatement => ({ const makeAction = ( name: string, visibility: S.Visibility, + stateMutability: S.StateMutability, counterName: string, eventName: string, parameters: S.ParameterList, @@ -106,7 +102,7 @@ const makeAction = ( visibility, modifiers: [], isConstructor: false, - stateMutability: S.StateMutability.Default, + stateMutability, }); const variableDeclaration = (name: string, typeName: S.Type): S.VariableDeclaration => ({ @@ -122,7 +118,8 @@ const makeResultType = (name: string, fields: S.ParameterList): S.StructDefiniti type: S.NodeType.StructDefinition, name, members: [ - variableDeclaration('reverts', bool), + variableDeclaration('_enabled', utils.types.bool), + variableDeclaration('_reverts', utils.types.bool), ...fields.parameters.map(({ name, typeName }) => variableDeclaration(name as string, typeName)), ], }); @@ -153,7 +150,7 @@ const makeSetter = (name: string, resultTypeName: string, resultMapName: string) { type: S.NodeType.Parameter, name: '_counter', - typeName: uint256, + typeName: utils.types.uint256, storageLocation: S.StorageLocation.Default, isStateVar: false, isIndexed: false, @@ -196,6 +193,7 @@ const makeSetter = (name: string, resultTypeName: string, resultMapName: string) const makeFunction = ( name: string, visibility: S.Visibility, + stateMutability: S.StateMutability, counterName: string, eventName: string, resultTypeName: string, @@ -208,9 +206,9 @@ const makeFunction = ( parameters, returnParameters, visibility, + stateMutability, modifiers: [], isConstructor: false, - stateMutability: S.StateMutability.Default, body: { type: S.NodeType.Block, statements: [ @@ -219,7 +217,7 @@ const makeFunction = ( type: S.NodeType.VariableDeclarationStatement, variables: [ { - ...variableDeclaration('result', userType(resultTypeName)), + ...variableDeclaration('_result', userType(resultTypeName)), storageLocation: S.StorageLocation.Storage, }, ], @@ -231,62 +229,120 @@ const makeFunction = ( }, makeIncrement(counterName), { - type: S.NodeType.ExpressionStatement, - expression: call(identifier('require'), { - type: S.NodeType.UnaryOperation, - operator: '!', - isPrefix: true, - subExpression: { - type: S.NodeType.MemberAccess, - expression: identifier('result'), - memberName: 'reverts', - }, - }), + type: S.NodeType.IfStatement, + condition: { + type: S.NodeType.MemberAccess, + expression: identifier('_result'), + memberName: '_enabled', + }, + falseBody: null, + trueBody: { + type: S.NodeType.Block, + statements: [ + { + type: S.NodeType.ExpressionStatement, + expression: call(identifier('require'), { + type: S.NodeType.UnaryOperation, + operator: '!', + isPrefix: true, + subExpression: { + type: S.NodeType.MemberAccess, + expression: identifier('_result'), + memberName: '_reverts', + }, + }), + }, + { + type: S.NodeType.ReturnStatement, + expression: { + type: S.NodeType.TupleExpression, + isArray: false, + components: returnParameters.parameters.map(({ name: memberName }): S.MemberAccess => ({ + type: S.NodeType.MemberAccess, + expression: identifier('_result'), + memberName: memberName as string, + })), + }, + }, + ], + }, }, { - type: S.NodeType.ReturnStatement, - expression: { - type: S.NodeType.TupleExpression, - isArray: false, - components: returnParameters.parameters.map(({ name: memberName }): S.MemberAccess => ({ - type: S.NodeType.MemberAccess, - expression: identifier('result'), - memberName: memberName as string, - })), - }, + type: S.NodeType.ExpressionStatement, + expression: call(identifier('require'), utils.litFalse, utils.litString(errorUnprogrammedInput(name))), }, ], }, }); -const mockAction = (func: S.FunctionDefinition): S.ContractMember[] => { +export const stubThrow = (func: S.FunctionDefinition): S.ContractMember[] => { + if (func.isConstructor || func.name === null) { + throw new Error(`Function can not be mocked because it is a constructor.`); + } + return [ + { + ...func, + body: { + type: S.NodeType.Block, + statements: [ + { + type: S.NodeType.ExpressionStatement, + expression: call( + identifier('require'), + utils.litFalse, + utils.litString(errorNoMock(func.name)), + ), + }, + ], + }, + }, + ]; +}; + +export const stubAction = (func: S.FunctionDefinition): S.ContractMember[] => { + if (func.isConstructor || func.name === null) { + throw new Error(`Function can not be mocked because it is a constructor.`); + } + if (func.stateMutability === S.StateMutability.Pure) { + // Pure actions don't make sense, but check anyway + throw new Error(`Function ${func.name} can not be mocked because it is pure.`); + } + const counterName = `_${func.name}_counter`; const eventName = `_${func.name}_log`; const params = nameParameters(func.parameters); return [ makeCounter(counterName), makeCountedEvent(eventName, params), - makeAction(func.name, func.visibility, counterName, eventName, params), + makeAction(func.name, func.visibility, func.stateMutability, counterName, eventName, params), ]; }; -const mockFunction = (func: S.FunctionDefinition): S.ContractMember[] => { +export const stubFunctionRuntime = (func: S.FunctionDefinition): S.ContractMember[] => { + if (func.isConstructor || func.name === null) { + throw new Error(`Constructors can not be stubbed.`); + } + if (func.stateMutability === S.StateMutability.Pure) { + throw new Error(`Function ${func.name} can not be stubbed because it is pure.`); + } + const counterName = `_${func.name}_counter`; const resultTypeName = `_${func.name}_Result`; const resultMapName = `_${func.name}_results`; - const eventName = `_${func.name}_log`; - const setterName = `_${func.name}_set`; + const eventName = logEventName(func.name); + const setterName = programmerName(func.name); const params = nameParameters(func.parameters); const returns = nameParameters(func.returnParameters as S.ParameterList, '_ret'); return [ makeCounter(counterName), makeCountedEvent(eventName, params), makeResultType(resultTypeName, returns), - makeStorageVariable(resultMapName, mapping(uint256, userType(resultTypeName))), + makeStorageVariable(resultMapName, mapping(utils.types.uint256, userType(resultTypeName))), makeSetter(setterName, resultTypeName, resultMapName), makeFunction( func.name, func.visibility, + func.stateMutability, counterName, eventName, resultTypeName, @@ -301,56 +357,17 @@ const isDeclaration = (func: S.FunctionDefinition) => func.body === null; const hasReturns = (func: S.FunctionDefinition) => func.returnParameters !== null; -const visitor: Visitor = { - FunctionDefinition: (func: S.FunctionDefinition) => - isDeclaration(func) ? (hasReturns(func) ? mockFunction(func) : mockAction(func)) : [], - - ContractMember: (node: S.ContractMember) => [node], +export const stubFunction = (func: S.FunctionDefinition): S.ContractMember[] => { + if (!isDeclaration(func)) { + throw new Error(`Can only stub abstract functions.`); + } + if (func.stateMutability === S.StateMutability.Pure) { + return stubThrow(func); + } else { + if (hasReturns(func)) { + return stubFunctionRuntime(func); + } else { + return stubAction(func); + } + } }; - -const pragmaSolVersion: S.PragmaDirective = { - type: S.NodeType.PragmaDirective, - name: 'solidity', - value: '^0.4.24', -}; - -const pragmaAbiV2: S.PragmaDirective = { - type: S.NodeType.PragmaDirective, - name: 'experimental', - value: 'ABIEncoderV2', -}; - -const importDirective = (path: string): S.ImportDirective => ({ - type: S.NodeType.ImportDirective, - path, - symbolAliases: null, -}); - -export function mock(ast: S.SourceUnit): S.SourceUnit { - // TODO: go down inheritance hierarchy and expose those events. etc as well. - // We probably want a separate `flattenInheritance` function or something - // that traces the imports and does a C3 linearization. - return { - type: S.NodeType.SourceUnit, - children: [ - pragmaSolVersion, - pragmaAbiV2, - importDirective('interface.sol'), - ...utils.contracts(ast).map(ctr => ({ - type: S.NodeType.ContractDefinition, - kind: S.ContractKind.Contract, - name: `${ctr.name}Mock`, - baseContracts: [ - { - type: S.NodeType.InheritanceSpecifier, - baseName: { - type: S.NodeType.UserDefinedTypeName, - namePath: ctr.name, - }, - }, - ], - subNodes: utils.flatMap(ctr.subNodes, (node: S.ContractMember) => visit(node, visitor)), - })), - ], - }; -} From ef502299631c06744c3e0bf7644a1d41b6459385 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:46:04 +0100 Subject: [PATCH 47/76] Refactor CLI --- packages/sol-meta/src/cli.ts | 140 ++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 27 deletions(-) diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts index 2c1551be7c..e5fee4e88b 100644 --- a/packages/sol-meta/src/cli.ts +++ b/packages/sol-meta/src/cli.ts @@ -1,56 +1,142 @@ import 'source-map-support/register'; -import { logUtils } from '@0xproject/utils'; -import * as _ from 'lodash'; +import * as pathUtils from 'path'; +import * as process from 'process'; +import * as S from 'solidity-parser-antlr'; import * as yargs from 'yargs'; -import { Compiler } from './compiler'; - -const SEPARATOR = ','; +import { ContractMockOptions, mockContract, mockContractName } from './contractMocker'; +import { compile } from './solcWrapper'; +import { readSources, SourceReaderOptions } from './source_reader'; +import { unparse } from './unparser'; +import * as utils from './utils'; +import { visit, Visitor } from './visitor'; (async () => { // Parse command line arguments and handle help const argv = yargs - .option('remapping', { + .usage('Usage: $0 [options]') + .help() + .option('config', { type: 'string', - description: 'search path remappings for contracts', + description: 'config file', }) - .option('includes', { + .option('remapping', { type: 'string', - description: 'search path for contracts', + array: true, + description: 'path remappings for import statements', }) - .option('sources', { + .option('includes', { type: 'string', - description: 'comma separated list of Solidity files to process', + array: true, + description: 'search paths for imported source files', }) .option('output', { type: 'string', description: 'directory to output too', }) - .help().argv; + .options('test', { + type: 'boolean', + description: 'run all generated mock contracts through solc', + }).argv; - // Handle command line arguments - const options = Compiler.defaultOptions; - if (argv.paths) { - options.includes = argv.paths.split(SEPARATOR) as string[]; + // TODO: CommandLineOptions object + let options: Partial = {}; + let defaults: ContractMockOptions = { + constructors: {}, + scripted: {}, + }; + let contracts: { [contractName: string]: ContractMockOptions } = {}; + let outputPath = `${process.cwd()}/out`; + + // Handle config file + if (argv.config) { + const config = JSON.parse(await utils.readFileAsync(argv.config)); + if (config.options) { + options = { ...options, ...config.options }; + } + if (config.constructors) { + defaults.constructors = { ...defaults.constructors, ...config.constructors }; + } + if (config.scripted) { + defaults.scripted = { ...defaults.scripted, ...config.scripted }; + } + if (config.contracts) { + contracts = { ...contracts, ...config.contracts }; + } } - if (argv.sources) { - options.sources = argv.sources.split(SEPARATOR) as string[]; + + // Handle command line arguments + if (argv.remapping) { + options.remapping = (argv.remapping as string[]) + .map((str: string) => str.split('=', 2)) + .reduce((a, [from, to]) => ({ ...a, [from]: to }), {}); } - if (argv.output) { - options.output = argv.output as string; + if (argv.includes) { + options.includes = argv.includes; } + options.sources = [...options.sources, ...argv._]; - // Instantiate and run compiler - console.time('Compilation'); - const compiler = new Compiler(options); - await compiler.compileAsync(); - console.timeEnd('Compilation'); + // Parse all sources + console.time('Parsing sources'); + const sources = await readSources(options.sources, options); + console.timeEnd('Parsing sources'); + + for (const contractName in contracts) { + if (contracts.hasOwnProperty(contractName)) { + console.log(`\n${contractName}`); + const spec = { + constructors: { + ...defaults.constructors, + ...contracts[contractName].constructors, + }, + scripted: { + ...defaults.scripted, + ...contracts[contractName].scripted, + }, + }; + + // Find path + const path = Object.keys(sources).find(path => contractName in sources[path].contracts); + if (path === undefined) { + throw new Error(`Could not find contract ${contractName}.`); + } + + // Create mock contract + console.time('Generating mocks'); + const mock = mockContract(sources, path, contractName, spec); + console.timeEnd('Generating mocks'); + + // Optionally test + if (argv.test) { + console.time('Test compile'); + await compile(sources, mock); + console.timeEnd('Test compile'); + } + + // Make import paths relative + mock.children = mock.children.map(node => + visit(node, { + ImportDirective: importDirective => ({ + ...importDirective, + path: pathUtils.relative(outputPath, importDirective.path), + }), + SourceMember: n => n, + }), + ); + + // Write file + console.time('Writing mocks'); + await utils.writeFileAsync(`${outputPath}/${mockContractName(contractName)}.sol`, unparse(mock)); + console.timeEnd('Writing mocks'); + } + } // Exit successfully process.exit(0); + + // Catch errors, log and exit with failure })().catch(err => { - // Catch and log errorsm exit with failure - logUtils.log(err); + console.error(err); process.exit(1); }); From a8dab3de0e2ead1fa773698840b48418752cd94a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:46:39 +0100 Subject: [PATCH 48/76] Export getSolcAsync --- packages/sol-compiler/src/compiler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 8ee7fa4a91..a59c7d9414 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -110,6 +110,9 @@ export class Compiler { const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename)); return { solcInstance, fullSolcVersion }; } + public static async getSolcAsync(solcVersion: string): Promise { + return (await this._getSolcAsync(solcVersion)).solcInstance; + } private static _addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void { if (!_.isUndefined(compiledContract.evm)) { if (!_.isUndefined(compiledContract.evm.bytecode) && !_.isUndefined(compiledContract.evm.bytecode.object)) { From 6835719fe155115aa6fef0c21cfc551bd439270e Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:47:33 +0100 Subject: [PATCH 49/76] Update package.json --- packages/sol-meta/package.json | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/sol-meta/package.json b/packages/sol-meta/package.json index d22819401b..6188b47f8b 100644 --- a/packages/sol-meta/package.json +++ b/packages/sol-meta/package.json @@ -1,5 +1,5 @@ { - "name": "@0xproject/sol-meta", + "name": "@0x/sol-meta", "version": "1.1.7", "engines": { "node": ">=6.12" @@ -42,8 +42,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-meta/README.md", "devDependencies": { - "@0xproject/dev-utils": "^1.0.12", - "@0xproject/tslint-config": "^1.0.8", + "@0x/dev-utils": "^1.0.12", + "@0x/tslint-config": "^1.0.8", "@types/mkdirp": "^0.5.2", "@types/require-from-string": "^1.2.0", "@types/semver": "^5.5.0", @@ -62,18 +62,21 @@ "typescript": "3.0.1" }, "dependencies": { - "@0xproject/assert": "^1.0.13", - "@0xproject/json-schemas": "^1.0.7", - "@0xproject/sol-resolver": "^1.0.14", - "@0xproject/types": "^1.1.4", - "@0xproject/typescript-typings": "^3.0.2", - "@0xproject/utils": "^2.0.2", + "@0x/assert": "^1.0.13", + "@0x/json-schemas": "^1.0.7", + "@0x/sol-compiler": "^1.1.9", + "@0x/sol-resolver": "^1.0.14", + "@0x/types": "^1.1.4", + "@0x/typescript-typings": "^3.0.2", + "@0x/utils": "^2.0.2", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", + "glob": "^7.1.3", "lodash": "^4.17.5", "mkdirp": "^0.5.1", "require-from-string": "^2.0.1", "semver": "5.5.0", + "solc": "0.4.24", "solidity-parser-antlr": "^0.3.2", "source-map-support": "^0.5.0", "yargs": "^10.0.3" From 79e7f7dde5e6d03983862fe9c1527b340ced384d Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:47:56 +0100 Subject: [PATCH 50/76] Generate constructors and force non-abstractness --- packages/sol-meta/src/constructor.ts | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 packages/sol-meta/src/constructor.ts diff --git a/packages/sol-meta/src/constructor.ts b/packages/sol-meta/src/constructor.ts new file mode 100644 index 0000000000..bc8eef871a --- /dev/null +++ b/packages/sol-meta/src/constructor.ts @@ -0,0 +1,80 @@ +import * as S from 'solidity-parser-antlr'; + +import * as utils from './utils'; + +export interface ConstructorArguments { + [contractName: string]: utils.Litteral[]; +} + +export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefinition { + // TODO: Only include actually used constructors. + return { + type: S.NodeType.FunctionDefinition, + name: null, + parameters: { + type: S.NodeType.ParameterList, + parameters: [], + }, + returnParameters: null, + visibility: S.Visibility.Public, + stateMutability: S.StateMutability.Default, + modifiers: Object.keys(consArgs).map(name => ({ + type: S.NodeType.ModifierInvocation, + name, + arguments: consArgs[name].map(utils.litteral), + })), + isConstructor: true, + body: { + type: S.NodeType.Block, + statements: [], + }, + }; +} + +// Solidity by itself does not give an error when a contract is unintentionally +// abstract. We can force Solidity to produce an error and point us to the +// abstract function by trying to runtime instantiate the contract. This function +// produces a small contract that tries to instantiate the given contract. +export function nonAbstractForcer(contractName: string): S.ContractDefinition { + // contract SomeContractNonAbstractForcer { + // constructor() { + // new SomeContract(); + // } + // } + return { + type: S.NodeType.ContractDefinition, + kind: S.ContractKind.Contract, + name: `${contractName}NonAbstractForcer`, + baseContracts: [], + subNodes: [ + { + type: S.NodeType.FunctionDefinition, + name: null, + parameters: { + type: S.NodeType.ParameterList, + parameters: [], + }, + returnParameters: null, + visibility: S.Visibility.Default, + stateMutability: S.StateMutability.Default, + modifiers: [], + isConstructor: true, + body: { + type: S.NodeType.Block, + statements: [ + { + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.NewExpression, + typeName: { + type: S.NodeType.UserDefinedTypeName, + namePath: contractName, + }, + }, + }, + ], + }, + }, + ], + }; +} From ddf32d76c8769836bc6e17c623b22ec88ee0e5a1 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:48:33 +0100 Subject: [PATCH 51/76] Parameterize strings --- packages/sol-meta/src/exposer.ts | 118 ++++++++++++------------------- 1 file changed, 45 insertions(+), 73 deletions(-) diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index 944f53f948..65d692a1c0 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -1,13 +1,20 @@ import * as S from 'solidity-parser-antlr'; -import * as utils from './utils'; -import { identifier, nameParameters, argumentExpressions } from './utils'; + +import { argumentExpressions, identifier, nameParameters } from './utils'; +import { visit, Visitor } from './visitor'; + +const getterName = (name: string) => `${name}Get`; +const setterName = (name: string) => `${name}Set`; +const publicName = (name: string) => `${name}Public`; +const logEmitterName = (name: string) => `${name}Emit`; +const modifierTestName = (name: string) => `${name}Test`; // Creates a public getter for a state variable const getter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { const [{ name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, - name: `get_${name}`, + name: getterName(name), visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.View, @@ -44,7 +51,7 @@ const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { const [{ name, typeName }] = stateVar.variables; return { type: S.NodeType.FunctionDefinition, - name: `set_${name}`, + name: setterName(name), visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.Default, @@ -80,16 +87,19 @@ const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { // Creates a public wrapper for a function const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { + if (func.name === null) { + throw new Error('Anonymous function.'); + } const params = nameParameters(func.parameters); const call: S.FunctionCall = { type: S.NodeType.FunctionCall, - expression: identifier(func.name), + expression: identifier(func.name as string), arguments: argumentExpressions(params), names: [], }; return { ...func, - name: `public_${func.name}`, + name: publicName(func.name), visibility: S.Visibility.Public, parameters: params, modifiers: [], @@ -115,7 +125,7 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { const { name, parameters } = event; return { type: S.NodeType.FunctionDefinition, - name: `emit_${name}`, + name: logEmitterName(name), visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.Default, @@ -150,17 +160,16 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { const { name, parameters } = modifier; return { type: S.NodeType.FunctionDefinition, - name: `modifier_${name}`, + name: modifierTestName(name), visibility: S.Visibility.Public, isConstructor: false, stateMutability: S.StateMutability.Default, - parameters: - parameters === null - ? { - type: S.NodeType.ParameterList, - parameters: [], - } - : parameters, + parameters: Array.isArray(parameters) + ? { + type: S.NodeType.ParameterList, + parameters: [], + } + : parameters, returnParameters: { type: S.NodeType.ParameterList, parameters: [ @@ -179,7 +188,7 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { { type: S.NodeType.ModifierInvocation, name, - arguments: Array.isArray(parameters) ? argumentExpressions(parameters) : [], + arguments: Array.isArray(parameters) ? [] : argumentExpressions(parameters), }, ], body: { @@ -197,65 +206,28 @@ const testModifier = (modifier: S.ModifierDefinition): S.FunctionDefinition => { }; }; -const exposeNode = (ast: S.ContractMember): S.ContractMember[] => { - switch (ast.type) { - default: { +const visitor: Visitor = { + StateVariableDeclaration: (node: S.StateVariableDeclaration) => { + const [vardecl] = node.variables; + if (vardecl.visibility !== 'internal') { return []; } - case 'StateVariableDeclaration': { - const [vardecl] = ast.variables; - if (vardecl.visibility !== 'internal') { - return []; - } - return [getter(ast as S.StateVariableDeclaration), setter(ast as S.StateVariableDeclaration)]; - // TODO: handle mappings: The keys become additional - // function arguments to the getter and setter. - } - case 'EventDefinition': { - return [emitEvent(ast)]; - } - case 'ModifierDefinition': { - return [testModifier(ast as S.ModifierDefinition)]; + if (vardecl.isDeclaredConst) { + return [getter(node)]; } - case 'FunctionDefinition': { - const func = ast; - if (func.visibility !== 'internal') { - return []; - } - return [wrapFunction(func)]; - } - } -}; + return [getter(node), setter(node)]; + // TODO: handle mappings: The keys become additional + // function arguments to the getter and setter. + }, -export function expose(filePath: string, ast: S.SourceUnit): S.SourceUnit { - // TODO: gp down inheritance hierarchy and expose those events. etc as well - // we probably want a separate `flattenInheritance` function or something - // that traces the imports and does a fairly simple concatenation. + EventDefinition: (node: S.EventDefinition) => [emitEvent(node)], - return { - type: S.NodeType.SourceUnit, - children: [ - ...utils.pragmaNodes(ast), - { - type: S.NodeType.ImportDirective, - path: filePath, - unitAliases: null, - symbolAliases: null, - }, - ...utils.contracts(ast).map(ctr => ({ - type: S.NodeType.ContractDefinition, - kind: 'contract', - name: `${ctr.name}Exposed`, - baseContracts: [ - { - type: S.NodeType.InheritanceSpecifier, - baseName: { - namePath: ctr.name, - }, - }, - ], - subNodes: utils.flatMap(ctr.subNodes, exposeNode), - })), - ], - }; -} + ModifierDefinition: (node: S.ModifierDefinition) => [testModifier(node)], + + FunctionDefinition: (func: S.FunctionDefinition) => + func.visibility === S.Visibility.Internal ? [wrapFunction(func)] : [], + + ASTNode: (node: S.ASTNode) => [], +}; + +export const exposeNode = (node: S.ContractMember): S.ContractMember[] => visit(node, visitor); From eb7a98f4cfdb551112fd1167229ed55f0cd741b9 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:48:45 +0100 Subject: [PATCH 52/76] Solc wrapper for test compiles --- packages/sol-meta/src/solcWrapper.ts | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 packages/sol-meta/src/solcWrapper.ts diff --git a/packages/sol-meta/src/solcWrapper.ts b/packages/sol-meta/src/solcWrapper.ts new file mode 100644 index 0000000000..b6cb8a2cf4 --- /dev/null +++ b/packages/sol-meta/src/solcWrapper.ts @@ -0,0 +1,60 @@ +import * as S from 'solidity-parser-antlr'; +import { ImportContents, StandardOutput } from 'solc'; + +import { Compiler as Solc } from '@0x/sol-compiler'; + +import { SourceCollection } from './source_reader'; +import { unparse } from './unparser'; +import * as utils from './utils'; + +export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { + // Extract required version from pragma of ast + const version = + utils + .pragmaNodes(ast) + .filter(({ name }) => name === 'solidity') + .map(({ value }) => value)[0] || 'latest'; + + // Get Solidity compiler + console.time('Loading solc-js'); + const compiler = await Solc.getSolcAsync(version); + console.timeEnd('Loading solc-js'); + + // Solidity standard JSON input + // TODO: Typescript typings + // See: https://solidity.readthedocs.io/en/develop/using-the-compiler.html#compiler-input-and-output-json-description + const input = { + language: 'Solidity', + sources: { + ...utils.objectMap(sources, ({ source: content }) => ({ content })), + TARGET_: { content: unparse(ast) }, + }, + settings: { + remappings: { + // TODO + }, + }, + outputSelection: { + TARGET_: { + '*': [], + }, + }, + }; + + // All imports should be accounted for in sources, throw an error in the callback + const findImportsCallback = (importPath: string): ImportContents => { + throw new Error(`Could not find ${importPath}.`); + }; + + console.time('Compiling'); + const result: StandardOutput = JSON.parse( + compiler.compileStandardWrapper(JSON.stringify(input), findImportsCallback), + ); + console.timeEnd('Compiling'); + + // Throw errors + const errors = result.errors.filter(({ severity }) => severity === 'error'); + if (errors.length > 0) { + throw new Error(errors.map(({ formattedMessage }) => formattedMessage).join('\n')); + } +}; From d2192fc6427d12a6ef5c69049473395c3c71798d Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:49:03 +0100 Subject: [PATCH 53/76] Main contract exposing / stubbing logic --- packages/sol-meta/src/contractMocker.ts | 123 ++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 packages/sol-meta/src/contractMocker.ts diff --git a/packages/sol-meta/src/contractMocker.ts b/packages/sol-meta/src/contractMocker.ts new file mode 100644 index 0000000000..1e660a55a3 --- /dev/null +++ b/packages/sol-meta/src/contractMocker.ts @@ -0,0 +1,123 @@ +import * as S from 'solidity-parser-antlr'; + +import { makeConstructor, nonAbstractForcer } from './constructor'; +import { exposeNode } from './exposer'; +import { flattenContract } from './flattener'; +import { stubFunction } from './stubber'; +import { FunctionScript, scriptFunction } from './scripter'; +import { SourceCollection } from './source_reader'; +import * as utils from './utils'; +import { unparse } from './unparser'; + +export const mockContractName = (contractName: string) => `${contractName}Mock`; + +export interface ContractMockOptions { + constructors: { + [parentName: string]: utils.Litteral[]; + }; + scripted: { + [functionName: string]: FunctionScript[]; + }; +} + +export function mockContract( + sources: SourceCollection, + path: string, + contractName: string, + options: ContractMockOptions, +): S.SourceUnit { + const sourceInfo = sources[path]; + + // Flatten target contract, this collapses the inheritane hierarchy + if (!(contractName in sourceInfo.contracts)) { + throw new Error(`Could not find contract ${contractName} in "${path}".`); + } + const [flat, parents] = flattenContract(sourceInfo.contracts[contractName], name => { + if (!(name in sourceInfo.scope)) { + console.log(Object.keys(sourceInfo.scope)); + throw new Error(`Contract ${name} not in scope of ${path}.`); + } + return sourceInfo.scope[name]; + }); + + // Find all constructors that require arguments + // TODO: Ignore constructors already called from other constructors + const constructors = parents + .filter(({ subNodes }) => + subNodes.some( + node => + node.type === S.NodeType.FunctionDefinition && + node.isConstructor && + node.parameters.parameters.length > 0, + ), + ) + .map(({ name }) => name); + + // Check that we have arguments for all constructors that require them + constructors.forEach(name => { + if (!(name in options.constructors)) { + throw new Error(`No arguments for constructor ${name} in ${contractName}.`); + } + }); + + // Find all abstract functions + // TODO: Public member variables generate implicit getter functions that can satisfy an abstract. + const abstracts = flat.subNodes.filter( + node => node.type === S.NodeType.FunctionDefinition && node.body === null, + ) as S.FunctionDefinition[]; + + // Separated in scripted and mocked functions + const scripted = abstracts.filter(({ name }) => name && name in options.scripted); + const mocked = abstracts.filter(({ name }) => name && !(name in options.scripted)); + + // Create mock contract + const mock: S.ContractDefinition = { + type: S.NodeType.ContractDefinition, + kind: S.ContractKind.Contract, + name: mockContractName(contractName), + + // Inherit from source contract + baseContracts: [ + { + type: S.NodeType.InheritanceSpecifier, + baseName: utils.userType(contractName), + }, + ], + subNodes: [ + // Call parent constructors + makeConstructor(utils.objectFilter(options.constructors, (key, _) => constructors.includes(key))), + + // Expose things marked `internal` + ...utils.flatMap(flat.subNodes, exposeNode), + + // Compile-time specified scripted functions + ...scripted.map(func => scriptFunction(func, options.scripted[func.name || ''])), + + // Mock all remaining abstract functions + ...utils.flatMap(mocked, stubFunction), + ], + }; + + // Create source file + return { + type: S.NodeType.SourceUnit, + children: [ + // Copy over pragmas from the source file + ...sources[path].parsed.children.filter(({ type }) => type === S.NodeType.PragmaDirective), + + // Add an import to include the source file + // TODO: Make relative + { + type: S.NodeType.ImportDirective, + path, + symbolAliases: null, + }, + + // Include our mock contract + mock, + + // Add a utility contract to force an abstract mock contract into a compile error. + nonAbstractForcer(mockContractName(contractName)), + ], + }; +} From d0188a5cb61a74a808c78da520a9d8489a56684a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:49:25 +0100 Subject: [PATCH 54/76] Handle modifiers in flattener --- packages/sol-meta/src/flattener.ts | 96 ++++++++++++------------------ 1 file changed, 37 insertions(+), 59 deletions(-) diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts index f9b99ec5b4..3f5948e874 100644 --- a/packages/sol-meta/src/flattener.ts +++ b/packages/sol-meta/src/flattener.ts @@ -1,20 +1,29 @@ import * as S from 'solidity-parser-antlr'; import { linearize, linearizeAsync } from './linearization'; -import { flatMap } from './utils'; +import * as utils from './utils'; // See: https://solidity.readthedocs.io/en/v0.4.25/contracts.html#inheritance // Merge a base contract into a derived contract -function merge( - base: S.ContractMember[], - derived: S.ContractMember[], -): S.ContractMember[] { +function merge(base: S.ContractMember[], derived: S.ContractMember[]): S.ContractMember[] { // Extracts functions by name from contract members const functions = (contract: S.ContractMember[]): { [name: string]: S.FunctionDefinition } => - (contract - .filter(({ type }) => type === S.NodeType.FunctionDefinition) as S.FunctionDefinition[]) - .reduce((a, func: S.FunctionDefinition) => ({ ...a, [func.name]: func }), {}); + (contract.filter( + node => node.type === S.NodeType.FunctionDefinition && !node.isConstructor, + ) as S.FunctionDefinition[]).reduce( + (a, func: S.FunctionDefinition) => ({ ...a, [func.name as string]: func }), + {}, + ); + + const modifiers = (contract: S.ContractMember[]): { [name: string]: S.FunctionDefinition } => + (contract.filter(({ type }) => type === S.NodeType.ModifierDefinition) as S.ModifierDefinition[]).reduce( + (a, mod: S.ModifierDefinition) => ({ ...a, [mod.name]: mod }), + {}, + ); + + const others = (contract: S.ContractMember[]): S.ContractMember[] => + contract.filter(({ type }) => type !== S.NodeType.ModifierDefinition && type !== S.NodeType.FunctionDefinition); // Solidity method lookup is as if all functions are virtual. The most // derived version is always called. We can implement this by overriding @@ -24,6 +33,12 @@ function merge( ...functions(derived), }; + // The same applies to modifiers + const mergedModifiers = { + ...modifiers(base), + ...modifiers(derived), + }; + // TODO: Merge constructors // TODO: Implement rules that enforce type signature and visibility etc. // to be preserved when overriding. @@ -31,69 +46,32 @@ function merge( // TODO: Sort members by type // TODO: Less derived functions remain available through `super` // and `SomeBase.functionName(...)`. - return [ - ...base.filter(({ type }) => type !== S.NodeType.FunctionDefinition), - ...derived.filter(({ type }) => type !== S.NodeType.FunctionDefinition), - ...Object.values(mergedFunctions), - ]; + return [...others(base), ...others(derived), ...Object.values(mergedModifiers), ...Object.values(mergedFunctions)]; } -// Inline all inheritance for a contract -function flattenContract( +// Inline the inheritance hierarchy of a contract +export function flattenContract( contract: S.ContractDefinition, resolve: (name: string) => S.ContractDefinition, -): S.ContractDefinition { - +): [S.ContractDefinition, S.ContractDefinition[]] { // Close over resolve to create a parents function const parents = (child: S.ContractDefinition) => - child.baseContracts.map(({ baseName: { namePath } }) => - resolve(namePath)); + child.baseContracts.map(({ baseName: { namePath } }) => resolve(namePath)); // Linearize the contract inheritance tree from least to most derived const linear = linearize(contract, parents); // Merge contract members according to linearization - const members: S.ContractMember[] = linear.reduce( - (a: S.ContractMember[], { subNodes }) => merge(a, subNodes), []); + const members: S.ContractMember[] = linear.reduce((a: S.ContractMember[], { subNodes }) => merge(a, subNodes), []); // Concatenate contract bodies - return { - ...contract, - baseContracts: [], - - subNodes: members, - }; -} - -function sourceResolver( - source: S.SourceUnit, -): ((name: string) => S.ContractDefinition) { - return (name: string): S.ContractDefinition => { - const result = source.children.find(x => - x.type === S.NodeType.ContractDefinition && - x.name === name); - if (result === undefined) { - throw new Error(`Could not resolve ${name}`); - } - return result; - }; -} - -export function flattenSource( - source: S.SourceUnit, - contractName: string, -): S.ContractDefinition { - const resolver = sourceResolver(source); - return flattenContract(resolver(contractName), resolver); -} - -// Chase down import statements and produce a single source unit. -// NOTE: Does not support `import as` statements -function flattenFile( - contract: S.SourceUnit, - resolver: (name: string) => S.SourceUnit, -): S.SourceUnit { - // TODO: symbolAliases + return [ + { + ...contract, + baseContracts: [], - return contract; // TODO + subNodes: members, + }, + linear, + ]; } From 6b98ba6d4cbec8b8b0a96e2f111fe689e73c2b28 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:49:35 +0100 Subject: [PATCH 55/76] Lint linearization --- packages/sol-meta/src/linearization.ts | 33 ++++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/sol-meta/src/linearization.ts b/packages/sol-meta/src/linearization.ts index 1b579f2d86..9488e0a0e8 100644 --- a/packages/sol-meta/src/linearization.ts +++ b/packages/sol-meta/src/linearization.ts @@ -8,45 +8,45 @@ // NOTE: merge(a.reverse(), b.reversed(), ...) = merge(a, b, ...).reversed() // (or at least equal up to the implied partial order) export function merge(...ilists: T[][]): T[] { - // Only consider non-empty lists const lists = ilists.filter(x => x.length > 0); if (lists.length === 0) { return []; } - + // The first item of each list are heads, the remainders are tails const heads = lists.map(([head, ...tail]) => head); const tails = lists.map(([head, ...tail]) => tail); - + // A good head is one that does not occur in any tail. - const goodHead = heads.find(head => - tails.every(tail => !tail.includes(head))); - + const goodHead = heads.find(head => tails.every(tail => !tail.includes(head))); + // If there is no valid head the hierarchy can not be linearized. if (goodHead === undefined) { - throw new Error("Hierarchy can not be linearized."); + throw new Error('Hierarchy can not be linearized.'); } - + // Remove the good head from the lists const newLists = lists.map(list => list.filter(elem => elem !== goodHead)); - + // Prepend head to the linearization and repeat return [goodHead, ...merge(...newLists)]; } -// Given a final element and an parents function, compute the C3 linearization +// Given a final element and a parents function, compute the C3 linearization // of all ancestors of the final element. // // NOTE: Solidity has its ancestors in reverse order (base to most derived) // compared to the Python C3 Linearization algorithm (most derived to // base). This version uses the Solidity convention. // -// NOTE: The nature of the algorithm makes it so that some cases where a // linearization does exists, it is not found. The way merge picks +// NOTE: The nature of the algorithm makes it so that some cases where a +// linearization does exists, it is not found. The way merge picks // an arbitrary solution adds additional constraints wich can lead // to conflicts later on. It would be better if instead of recursion, -// a single large merge was done with all the constraints. -export function linearize(final: T, parents: (_:T) => T[]) : T[] { +// a single large merge was done with all the constraints. But then +// it would no longer be C3 linearization. +export function linearize(final: T, parents: (_: T) => T[]): T[] { // TODO: Memoization // TODO: Throw on cycles (instead of stack overflowing) const p = parents(final); @@ -54,11 +54,8 @@ export function linearize(final: T, parents: (_:T) => T[]) : T[] { return [...merge(p, ...recurse), final]; } -export async function linearizeAsync( - final: T, parents: (_:T) => Promise -): Promise { +export async function linearizeAsync(final: T, parents: (_: T) => Promise): Promise { const p = await parents(final); - const recurse = await Promise.all( - p.map(a => linearizeAsync(a, parents))); + const recurse = await Promise.all(p.map(a => linearizeAsync(a, parents))); return [...merge(p, ...recurse), final]; } From d989c2c87deef205b6ba51c93c14b8aab073120c Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:49:51 +0100 Subject: [PATCH 56/76] Compile-time script certain functions --- packages/sol-meta/src/scripter.ts | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 packages/sol-meta/src/scripter.ts diff --git a/packages/sol-meta/src/scripter.ts b/packages/sol-meta/src/scripter.ts new file mode 100644 index 0000000000..e3ccb4e5a6 --- /dev/null +++ b/packages/sol-meta/src/scripter.ts @@ -0,0 +1,103 @@ +import * as S from 'solidity-parser-antlr'; + +import * as utils from './utils'; +import { visit, Visitor } from './visitor'; + +export interface FunctionScriptReturn { + inputs: { [argName: string]: string }; + outputs: [string]; +} + +export interface FunctionScriptRevert { + inputs: { [argName: string]: string }; + revert: string; +} + +export type FunctionScript = FunctionScriptReturn | FunctionScriptRevert; + +export const isFunctionScriptReturn = (fs: FunctionScript): fs is FunctionScriptReturn => 'outputs' in fs; + +export const isFunctionScriptRevert = (fs: FunctionScript): fs is FunctionScriptRevert => 'revert' in fs; + +export const errorUnscriptedInput = (funcName: string) => `Unscripted input for ${funcName}`; + +export const guard = (clauses: { [argName: string]: string }, statements: S.Statement[]): S.IfStatement => ({ + type: S.NodeType.IfStatement, + condition: Object.keys(clauses).reduce( + (cond, argName) => ({ + type: S.NodeType.BinaryOperation, + operator: '&&', + left: cond, + right: { + type: S.NodeType.BinaryOperation, + operator: '==', + left: utils.identifier(argName), + right: utils.litteral(clauses[argName]), + }, + }), + utils.litTrue, + ), + trueBody: { + type: S.NodeType.Block, + statements, + }, + falseBody: null, +}); + +export const scriptStatementReturn = (s: FunctionScriptReturn): S.IfStatement => + guard(s.inputs, [ + { + type: S.NodeType.ReturnStatement, + expression: { + type: S.NodeType.TupleExpression, + isArray: false, + components: s.outputs.map(utils.litteral), + }, + }, + ]); + +export const scriptStatementRevert = (s: FunctionScriptRevert): S.IfStatement => + guard(s.inputs, [ + { + type: S.NodeType.ExpressionStatement, + expression: { + type: S.NodeType.FunctionCall, + expression: utils.identifier('require'), + arguments: [utils.litFalse, utils.litString(s.revert)], + }, + }, + ] as S.ExpressionStatement[]); + +export const scriptStatement = (s: FunctionScript): S.IfStatement => + isFunctionScriptReturn(s) ? scriptStatementReturn(s) : scriptStatementRevert(s); + +export const scriptFunction = (func: S.FunctionDefinition, script: FunctionScript[]): S.FunctionDefinition => { + if (func.isConstructor || func.name === null) { + throw new Error(`Function can not be scripted because it is a constructor.`); + } + + const catchAllScript = [ + ...script, + { + inputs: {}, + revert: errorUnscriptedInput(func.name), + }, + ]; + + const params = utils.nameParameters(func.parameters); + const returns = utils.nameParameters(func.returnParameters as S.ParameterList, '_ret'); + return { + type: S.NodeType.FunctionDefinition, + name: func.name, + parameters: params, + returnParameters: returns, + visibility: func.visibility, + stateMutability: func.stateMutability, + modifiers: [], + isConstructor: false, + body: { + type: S.NodeType.Block, + statements: catchAllScript.map(scriptStatement), + }, + }; +}; From 733bced4b046ca758897a39e380c792b0cd20901 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:50:05 +0100 Subject: [PATCH 57/76] Various utilities --- packages/sol-meta/src/utils.ts | 134 +++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index e45f1e18d3..8cb00178b1 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -1,22 +1,144 @@ +import * as fs from 'fs'; import * as S from 'solidity-parser-antlr'; // TODO: Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).concat(...a.map(f)); -export const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => - ast.children.filter(({ type }) => type === S.NodeType.PragmaDirective); +// TODO: Use Map instead? +export const objectZip = (keys: string[], values: T[]): { [key: string]: T } => + keys.reduce( + (others, key, index) => ({ + ...others, + [key]: values[index], + }), + {}, + ); -export const importNodes = (ast: S.SourceUnit): S.ImportDirective[] => - ast.children.filter(({ type }) => type === S.NodeType.ImportDirective); +// TODO: Is the order in Object.keys and Object.values equal? +export const objectMap = (obj: { [key: string]: A }, f: (v: A) => B): { [key: string]: B } => + objectZip(Object.keys(obj), Object.values(obj).map(f)); -export const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => - ast.children.filter(({ type }) => type === S.NodeType.ContractDefinition); +export const objectPromise = async (obj: { [key: string]: Promise }): Promise<{ [key: string]: T }> => + objectZip(Object.keys(obj), await Promise.all(Object.values(obj))); + +export const objectFilter = (obj: { [key: string]: A }, f: (key: string, v: A) => boolean): { [key: string]: A } => + Object.keys(obj) + .filter(key => f(key, obj[key])) + .reduce((a, key) => ({ ...a, [key]: obj[key] }), {}); + +export class Deferred { + public promise: Promise; + public reject: (reason: any) => void; + public resolve: (value: T) => void; + constructor() { + // Hack(recmo): Define reject and resolve here so TS does not complain + // about them not being defined in the constructor. The + // promise we create will overwrite them. + this.reject = () => { + throw new Error('Unimplemented reject.'); + }; + this.resolve = () => { + throw new Error('Unimplemented resolve.'); + }; + this.promise = new Promise((resolve, reject) => { + this.reject = reject; + this.resolve = resolve; + }); + } +} + +export const existsAsync = (path: string): Promise => + new Promise((resolve, reject) => + fs.access(path, fs.constants.R_OK, error => (error ? resolve(false) : resolve(true))), + ); + +export const readFileAsync = (path: string): Promise => + new Promise((resolve, reject) => + fs.readFile(path, 'utf-8', (error, contents) => (error ? reject(error) : resolve(contents))), + ); + +export const writeFileAsync = (path: string, contents: string): Promise => + new Promise((resolve, reject) => + fs.writeFile(path, contents, 'utf-8', error => (error ? reject(error) : resolve())), + ); + +export const isPragmaDirective = (node: S.ASTNode): node is S.PragmaDirective => + node.type === S.NodeType.PragmaDirective; + +export const isImportDirective = (node: S.ASTNode): node is S.ImportDirective => + node.type === S.NodeType.ImportDirective; + +export const isContractDefinition = (node: S.ASTNode): node is S.ContractDefinition => + node.type === S.NodeType.ContractDefinition; + +export const pragmaNodes = (ast: S.SourceUnit): S.PragmaDirective[] => ast.children.filter(isPragmaDirective); + +export const importNodes = (ast: S.SourceUnit): S.ImportDirective[] => ast.children.filter(isImportDirective); + +export const contracts = (ast: S.SourceUnit): S.ContractDefinition[] => ast.children.filter(isContractDefinition); export const identifier = (name: string): S.Identifier => ({ type: S.NodeType.Identifier, name, }); +export const elementaryType = (name: string): S.ElementaryTypeName => ({ + type: S.NodeType.ElementaryTypeName, + name, +}); + +export const userType = (name: string): S.UserDefinedTypeName => ({ + type: S.NodeType.UserDefinedTypeName, + namePath: name, +}); + +export const types = { + bool: elementaryType('bool'), + uint256: elementaryType('uint256'), +}; + +export const litBool = (value: boolean): S.BooleanLiteral => ({ + type: S.NodeType.BooleanLiteral, + value, +}); + +export const litFalse = litBool(false); + +export const litTrue = litBool(true); + +export const isNumber = (value: string | number): value is number => typeof value === 'number'; + +const hexadecimalBase = 16; + +export const litNumber = (value: string | number): S.NumberLiteral => ({ + type: S.NodeType.NumberLiteral, + number: isNumber(value) ? `0x${value.toString(hexadecimalBase)}` : value, + subdenomination: null, +}); + +export const litString = (value: string): S.StringLiteral => ({ + type: S.NodeType.StringLiteral, + value, +}); + +export type Litteral = string | number; + +export const litteral = (value: Litteral): S.Expression => { + if (isNumber(value)) { + return litNumber(value); + } + if (value.startsWith('"')) { + return litString(value.slice(1, -1)); + } + if (value === 'true') { + return litTrue; + } + if (value === 'false') { + return litFalse; + } + return litNumber(value); +}; + export const nameParameters = (params: S.ParameterList, prefix: string = '_arg'): S.ParameterList => ({ ...params, parameters: params.parameters.map((param, i) => ({ From f18f13fde183b0f0c09994dd380a41e9b5bb5ab0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:50:26 +0100 Subject: [PATCH 58/76] Add version to Solc instance type --- packages/typescript-typings/types/solc/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typescript-typings/types/solc/index.d.ts b/packages/typescript-typings/types/solc/index.d.ts index f4c05cd7c9..eb206c5f81 100644 --- a/packages/typescript-typings/types/solc/index.d.ts +++ b/packages/typescript-typings/types/solc/index.d.ts @@ -90,6 +90,7 @@ declare module 'solc' { settings: CompilerSettings; } export interface SolcInstance { + version(): string; compile( sources: InputSources, optimizerEnabled: number, From b18d9bdb7f18189d491e37c1ad01ef3ee83026a0 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 15 Nov 2018 19:51:10 +0100 Subject: [PATCH 59/76] Improve typings --- .../types/solidity-parser-antlr/index.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts index d6a20f5f5f..39333eadd4 100644 --- a/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts +++ b/packages/typescript-typings/types/solidity-parser-antlr/index.d.ts @@ -173,7 +173,7 @@ declare module 'solidity-parser-antlr' { export interface SourceUnit extends BaseASTNode { type: NodeType.SourceUnit; - children: SourceMembers[]; + children: SourceMember[]; } export interface PragmaDirective extends BaseASTNode { type: NodeType.PragmaDirective; @@ -254,12 +254,12 @@ declare module 'solidity-parser-antlr' { export interface ModifierDefinition extends BaseASTNode { type: NodeType.ModifierDefinition; name: string; - parameters: ParameterList; + parameters: ParameterList | []; body: Block; } export interface FunctionDefinition extends BaseASTNode { type: NodeType.FunctionDefinition; - name: string; + name: string | null; // Null for constructors parameters: ParameterList; returnParameters: ParameterList | null; body: Block | null; @@ -336,7 +336,7 @@ declare module 'solidity-parser-antlr' { type: NodeType.IfStatement; condition: Expression; trueBody: Statement; - falseBody: Statement; + falseBody: Statement | null; } export interface WhileStatement extends BaseASTNode { type: NodeType.WhileStatement; @@ -803,6 +803,6 @@ declare module 'solidity-parser-antlr' { range?: boolean; loc?: boolean; } - export function parse(sourceCode: string, parserOpts: ParserOpts): ASTNode; - export function visit(ast: ASTNode, visitor: Visitor): void; + export function parse(sourceCode: string, parserOpts: ParserOpts): SourceUnit; + export function visit(ast: ASTNode, visitor: Visitor): void; } From cc6b064293bfdab20a966b7904153e2e335030d9 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 16 Nov 2018 16:17:22 +0100 Subject: [PATCH 60/76] Add to readme --- packages/sol-meta/README.md | 172 +++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 44 deletions(-) diff --git a/packages/sol-meta/README.md b/packages/sol-meta/README.md index a1f0a6a842..05adc10520 100644 --- a/packages/sol-meta/README.md +++ b/packages/sol-meta/README.md @@ -1,8 +1,83 @@ -## 0x/sol-meta +# 0x / sol-meta Sol-meta is a Solidity to Solidity compiler to automatically generate testing -contracts. It does a number of things: exposing internal logic, stubbing -abstract functions, scripting abstract functions. +contracts. It does two things: exposing internal logic and stubbing/scripting +of abstract functions. + +It works by generating a new contract that inherits the one under test. The +advantage of this approach is that it does not modify the code under test. The +downside is that we can not directly test contract members marked `private`. + +## Usage + +### Command line interface + +``` +Usage: sol-meta.js [options] + +Options: + --version Show version number [boolean] + --help Show help [boolean] + --config config file [string] + --remapping path remappings for import statements [array] + --includes search paths for imported source files [array] + --output directory to output too [string] + --test run all generated mock contracts through solc [boolean] +``` + +### Configuration file + +Example: + +```json +{ + "options": { + "sources": ["../contracts/contracts/protocol/**/*.sol", "../contracts/contracts/utils/**/*.sol"] + }, + "constructors": { + "LibConstants": ["\"ZRXASSETSTRING\""] + }, + "scripted": { + "hashZeroExTransaction": [ + { + "inputs": { + "salt": 32123, + "signerAddress": "0x0123123213" + }, + "revert": "Test failed. Should never be called." + }, + { + "inputs": { + "salt": 123 + }, + "outputs": ["0x0123456"] + } + ] + }, + "contracts": { + "LibEIP712": {}, + "MixinAssetProxyDispatcher": {}, + "MixinExchangeCore": {}, + "MixinMatchOrders": { + "constructors": { + "LibConstants": ["\"Different string\""] + } + }, + "MixinSignatureValidator": {}, + "MixinTransactions": {} + } +} +``` + +## Testing stategies + +The two main ways sol-meta helps with testing is by exposing internals and +stubbing abstract functions. Exposing makes it possible to access otherwise +unaccesible variables and functoins from a test. Stubbing implements missing +functions for you so you can test a partial contract such as a mixin in +isolation. + +The ### Exposing @@ -11,9 +86,14 @@ functions with public wrappers. #### Exposed Functions -All internal functions receive a public wrapper. +All functions marked `internal` functions receive a public wrapper. The name of +the wrapper is `functionNamePublic`. The wrapped function signature is +identical to the original. -Example: +Private functions can not be exposed. There is currently no way to test them +directly due to the inheritance approach. + +Example generated wrapper: ```solidity function safeAddPublic(uint256 a, uint256 b) @@ -26,9 +106,12 @@ Example: #### Exposed Modifiers -For every modifier a testing function is generated. +For every modifier a testing function is generated that allows you to test if +the modifier executes or not. The name of the tester function is +`modifierNameTest` and it's arguments are the arguments of the modifier. When +the modifier allows execution, it will simply return `true`. -Example: +Example generated tester: ```solidity function onlyAuthorizedTest(address user) @@ -42,9 +125,10 @@ Example: #### Exposed Events -For every event, a function is created that will trigger it. +For every event, a function is created that will trigger it. The name of this +function is `EventNameEmit` and the arguments are the log event arguments. -Example: +Example generated emitter: ```solidity function TransferEmit(address from, address to, uint256 value) @@ -56,9 +140,14 @@ Example: #### Exposed Variables -All variable are given getters and setters. +All contract variable are given getters and setters to allow the tester to +manipulate state variables directly. The getter is named `variableNameGet`, +takes no arguments and returns the current value. The setter is named +`variableNameSet` and takes an instance of the variable type as argument. -Example: +Currently no support for maps is implemented. + +Example generated getter and setter: ```solidity function totalSupplyGet() @@ -75,13 +164,14 @@ Example: } ``` -Limitations: - -Currently no support for maps is implemented. - ### Scripting Functions -Example: +Any abstract function can be scripted. For this, an entry in the configuration +file is required listing inputs and outputs the stubbed function should have. +In place of an output a revert reason can be given to make the stub throw. The +list of inputs can be partial to act as a filter. + +Example configuration: ```json "scripted": { @@ -97,12 +187,14 @@ Example: "salt": 32123, "signerAddress": "0x0123123213" }, - "reverts": "Test failed. Should never be called." + "revert": "Test failed: invalid signer" } ] }, ``` +Example generate stub for above configuration: + ```solidity function hashZeroExTransaction( uint256 salt, @@ -116,23 +208,34 @@ Example: return 0x0123456; if (salt == 32123 && signerAddress == 0x0123123213) - require(false, "Test failed. Should never be called."); + require(false, "Test failed: invalid signer"); require(false, "Unscripted input for hashZeroExTransaction"); } ``` -### Stubbing +#### Stubbed Functions -Given an interface contract (either a public interface or a mixin), it can -generate a mock implementation. +For any abstract function that is not scripted, a stub is generated. Depending +on wheter the function is `pure` and has return values, the behaviour is +slightly different. -Note: the computation of what is abstract and what is not is imperfect. In -particular, public variables imply getter functions. +For non-pure functions, the stub will log the call with all the arugments and +give the next from a sequence of responses. The responses are programmed using +as separate function. When no response is scheduled, it will revert with +`Unprogrammed input for `. -#### Stubbed Functions +Due to usage of logs and storage, this can not be used to implement `pure` +functions. Pure functions will always revert with the reason +`Abstract function called`. Different behaviour can be +achieved with a scripted function. -Example: +There is currently an unsolved issue in the stubber where it will not detect a +public variable as implementing an abstract function. This happens for example +when `IERC20Token` requires a function `totalSupply() public returns (uint256)` +and contract contains a state variable `public uint256 totalSupply;`. + +Example stub: ```solidity event isValidSignatureCalled(uint256 counter, bytes32 hash, address signerAddress, bytes signature); @@ -158,8 +261,6 @@ Example: } ``` -When there is no enabled option, it will revert with the message `Unprogrammed input for isValidSignature`. - #### Stubbed Actions Example: @@ -229,23 +330,6 @@ contract MixinExchangeCoreMockNonAbstractForcer { } ``` -## Command line interface - -``` -Usage: sol-meta.js [options] - -Options: - --version Show version number [boolean] - --help Show help [boolean] - --config config file [string] - --remapping path remappings for import statements [array] - --includes search paths for imported source files [array] - --output directory to output too [string] - --test run all generated mock contracts through solc [boolean] -``` - -## Configuration file - ## Contributing We welcome improvements and fixes from the wider community! To report bugs From ec9f18a08e54c746e8f4daf87483b1dbe25c7b18 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 15:10:34 +0100 Subject: [PATCH 61/76] name can be null for fallback and constructor --- packages/sol-cov/src/ast_visitor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-cov/src/ast_visitor.ts b/packages/sol-cov/src/ast_visitor.ts index 6293cab2ab..dcb56b96d3 100644 --- a/packages/sol-cov/src/ast_visitor.ts +++ b/packages/sol-cov/src/ast_visitor.ts @@ -156,7 +156,7 @@ export class ASTVisitor { } const loc = this._getExpressionRange(ast); this._fnMap[this._entryId++] = { - name: ast.name, + name: ast.name || '', line: loc.start.line, loc, }; From 27a1eb6ed23a5094256f2555f8cfa0ab47eb312a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 15:10:44 +0100 Subject: [PATCH 62/76] rename to callData --- .../protocol/Exchange/MixinSignatureValidator.sol | 12 ++++++------ yarn.lock | 13 +++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol index 28fe81faf1..0989dda59e 100644 --- a/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/contracts/protocol/Exchange/MixinSignatureValidator.sol @@ -240,18 +240,18 @@ contract MixinSignatureValidator is view returns (bool isValid) { - bytes memory cdata = abi.encodeWithSelector( + bytes memory callData = abi.encodeWithSelector( IWallet(walletAddress).isValidSignature.selector, hash, signature ); assembly { - let cdStart := add(cdata, 32) + let cdStart := add(callData, 32) let success := staticcall( gas, // forward all gas walletAddress, // address of Wallet contract cdStart, // pointer to start of input - mload(cdata), // length of input + mload(callData), // length of input cdStart, // write output over input 32 // output size is 32 bytes ) @@ -289,19 +289,19 @@ contract MixinSignatureValidator is view returns (bool isValid) { - bytes memory cdata = abi.encodeWithSelector( + bytes memory callData = abi.encodeWithSelector( IValidator(signerAddress).isValidSignature.selector, hash, signerAddress, signature ); assembly { - let cdStart := add(cdata, 32) + let cdStart := add(callData, 32) let success := staticcall( gas, // forward all gas validatorAddress, // address of Validator contract cdStart, // pointer to start of input - mload(cdata), // length of input + mload(callData), // length of input cdStart, // write output over input 32 // output size is 32 bytes ) diff --git a/yarn.lock b/yarn.lock index a7a55a7b3d..3ceca26ca1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -472,6 +472,14 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" +"@0x/json-schemas@^1.0.7": + version "2.1.2" + dependencies: + "@0x/typescript-typings" "^3.0.4" + "@types/node" "*" + jsonschema "^1.2.0" + lodash.values "^4.3.0" + "@0xproject/npm-cli-login@^0.0.11": version "0.0.11" resolved "https://registry.yarnpkg.com/@0xproject/npm-cli-login/-/npm-cli-login-0.0.11.tgz#3f1ec06112ce62aad300ff0575358f68aeecde2e" @@ -13996,6 +14004,11 @@ solidity-parser-antlr@^0.2.12: version "0.2.12" resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.2.12.tgz#1154f183d5cdda2c7677549ee584dbdb7fb2269c" +solidity-parser-antlr@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/solidity-parser-antlr/-/solidity-parser-antlr-0.3.2.tgz#1cf9d019280550a31299dc380e87a310dc4ca154" + integrity sha512-aO/lbnc14A81cQigN5sKNuwbxohPyJOq7kpLirYT/6emCw5Gjb0TJoZ3TzL1tYdIX6gjTAMlQ1UZwOcrzOAp4w== + sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" From 3af23eaea786cc651c35ce69374a33ac4dc607d3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 18:20:49 +0100 Subject: [PATCH 63/76] Improve readme --- packages/sol-meta/README.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/sol-meta/README.md b/packages/sol-meta/README.md index 05adc10520..3414cbfa18 100644 --- a/packages/sol-meta/README.md +++ b/packages/sol-meta/README.md @@ -1,4 +1,4 @@ -# 0x / sol-meta +# 0x/sol-meta Sol-meta is a Solidity to Solidity compiler to automatically generate testing contracts. It does two things: exposing internal logic and stubbing/scripting @@ -13,12 +13,12 @@ downside is that we can not directly test contract members marked `private`. ### Command line interface ``` -Usage: sol-meta.js [options] +Usage: sol-meta [options] Options: --version Show version number [boolean] --help Show help [boolean] - --config config file [string] + --config path to config file [string] --remapping path remappings for import statements [array] --includes search paths for imported source files [array] --output directory to output too [string] @@ -77,8 +77,6 @@ unaccesible variables and functoins from a test. Stubbing implements missing functions for you so you can test a partial contract such as a mixin in isolation. -The - ### Exposing Given an implemented contract it generates a contract that exposes all internal @@ -86,7 +84,7 @@ functions with public wrappers. #### Exposed Functions -All functions marked `internal` functions receive a public wrapper. The name of +All functions marked `internal` receive a public wrapper. The name of the wrapper is `functionNamePublic`. The wrapped function signature is identical to the original. @@ -140,7 +138,7 @@ Example generated emitter: #### Exposed Variables -All contract variable are given getters and setters to allow the tester to +All contract variables are given getters and setters to allow the tester to manipulate state variables directly. The getter is named `variableNameGet`, takes no arguments and returns the current value. The setter is named `variableNameSet` and takes an instance of the variable type as argument. @@ -151,7 +149,7 @@ Example generated getter and setter: ```solidity function totalSupplyGet() - public + public view returns (uint256) { return totalSupply; @@ -217,12 +215,12 @@ Example generate stub for above configuration: #### Stubbed Functions For any abstract function that is not scripted, a stub is generated. Depending -on wheter the function is `pure` and has return values, the behaviour is +on whether the function is `pure` and has return values, the behaviour is slightly different. -For non-pure functions, the stub will log the call with all the arugments and +For non-pure functions, the stub will log the call with all the arguments and give the next from a sequence of responses. The responses are programmed using -as separate function. When no response is scheduled, it will revert with +a separate function. When no response is scheduled, it will revert with `Unprogrammed input for `. Due to usage of logs and storage, this can not be used to implement `pure` @@ -256,7 +254,7 @@ Example stub: public view returns (bool isValid) { - // Log the inputs and return the next schedult result. + // Log the inputs and return the next scheduled result. // ... } ``` @@ -298,13 +296,15 @@ A constructor is created that will call all parent constructors that require arguments. The constructor arguments need to be supplied by config file. +Example, the following config file: + ```json "constructors": { "LibConstants": ["\"ZRXASSETSTRING\""] }, ``` -Example: +will allow sol-meta to construct contracts inheriting from `LibConstants`: ```solidity constructor() public @@ -312,8 +312,10 @@ Example: { } ``` -Note: Constructors calling parent constructors is not taken into consideration. -These will incorrectly appear as to-be-call. +Note: Currently `sol-meta` assumes that all parent constructors need to be +called. If you have a contract `A is B` and `B is C` where `B` calls the +constructor of `C`, then `sol-meta` incorrectly assumes that `C` still needs to +be called. ### Non Abstract Forcer @@ -324,7 +326,7 @@ will fail to compile if the original contract is abstract: ```solidity contract MixinExchangeCoreMockNonAbstractForcer { - constructor() { + constructor() public { new MixinExchangeCoreMock; } } From b15f26a43f66c280e4228decc2f44e9dbc79586b Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 18:21:04 +0100 Subject: [PATCH 64/76] number all enum entries --- .../contracts/contracts/protocol/Exchange/libs/LibOrder.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol b/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol index 46ff9d3cce..58e0e9b3b1 100644 --- a/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol +++ b/packages/contracts/contracts/protocol/Exchange/libs/LibOrder.sol @@ -51,8 +51,8 @@ contract LibOrder is INVALID_TAKER_ASSET_AMOUNT, // 2 Order does not have a valid taker asset amount FILLABLE, // 3 Order is fillable EXPIRED, // 4 Order has already expired - FULLY_FILLED, // Order is fully filled - CANCELLED // Order has been cancelled + FULLY_FILLED, // 5 Order is fully filled + CANCELLED // 6 Order has been cancelled } // solhint-disable max-line-length From 25b33490c0382bc1f6f979a4d0c2c425e48a8e7c Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 18:22:26 +0100 Subject: [PATCH 65/76] add Deferred to utils --- packages/utils/src/deferred.ts | 20 ++++++++++++++++++++ packages/utils/src/index.ts | 1 + 2 files changed, 21 insertions(+) create mode 100644 packages/utils/src/deferred.ts diff --git a/packages/utils/src/deferred.ts b/packages/utils/src/deferred.ts new file mode 100644 index 0000000000..bd9a54ad2e --- /dev/null +++ b/packages/utils/src/deferred.ts @@ -0,0 +1,20 @@ +export class Deferred { + public promise: Promise; + public reject: (reason: any) => void; + public resolve: (value: T) => void; + constructor() { + // Hack(recmo): Define reject and resolve here so TS does not complain + // about them not being defined in the constructor. The + // promise we create will overwrite them. + this.reject = () => { + throw new Error('Unimplemented reject.'); + }; + this.resolve = () => { + throw new Error('Unimplemented resolve.'); + }; + this.promise = new Promise((resolve, reject) => { + this.reject = reject; + this.resolve = resolve; + }); + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 082aff6bbe..aa0034fbb6 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -10,4 +10,5 @@ export { NULL_BYTES } from './constants'; export { errorUtils } from './error_utils'; export { fetchAsync } from './fetch_async'; export { signTypedDataUtils } from './sign_typed_data_utils'; +export { Deferred } from './deferred'; export import AbiEncoder = require('./abi_encoder'); From 5d7fc0c68dc167bb57d42fde42702373e86adfbf Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 18:22:38 +0100 Subject: [PATCH 66/76] fix version --- packages/sol-meta/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-meta/package.json b/packages/sol-meta/package.json index 6188b47f8b..7149ae7557 100644 --- a/packages/sol-meta/package.json +++ b/packages/sol-meta/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-meta", - "version": "1.1.7", + "version": "1.0.0", "engines": { "node": ">=6.12" }, From 56c8544a6589fbd3f6c8c009f55d262920804ff7 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 18:23:32 +0100 Subject: [PATCH 67/76] review improvements --- packages/sol-compiler/src/compiler.ts | 3 +- packages/sol-meta/src/cli.ts | 118 +++++++++--------- packages/sol-meta/src/constructor.ts | 9 +- .../{contractMocker.ts => contract_mocker.ts} | 14 ++- packages/sol-meta/src/exposer.ts | 5 +- packages/sol-meta/src/flattener.ts | 3 +- packages/sol-meta/src/linearization.ts | 12 +- packages/sol-meta/src/scripter.ts | 7 +- .../src/{solcWrapper.ts => solc_wrapper.ts} | 8 +- packages/sol-meta/src/source_reader.ts | 17 +-- packages/sol-meta/src/stubber.ts | 18 +-- packages/sol-meta/src/unparser.ts | 41 +++--- packages/sol-meta/src/utils.ts | 34 ++--- packages/sol-meta/test/index.ts | 13 +- .../sol-meta/test/interface.expected.ast.js | 2 +- packages/sol-meta/tsconfig.json | 8 +- 16 files changed, 153 insertions(+), 159 deletions(-) rename packages/sol-meta/src/{contractMocker.ts => contract_mocker.ts} (93%) rename packages/sol-meta/src/{solcWrapper.ts => solc_wrapper.ts} (87%) diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index a59c7d9414..3c3206c700 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -111,7 +111,8 @@ export class Compiler { return { solcInstance, fullSolcVersion }; } public static async getSolcAsync(solcVersion: string): Promise { - return (await this._getSolcAsync(solcVersion)).solcInstance; + const solcInstance = (await this._getSolcAsync(solcVersion)).solcInstance; + return solcInstance; } private static _addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void { if (!_.isUndefined(compiledContract.evm)) { diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts index e5fee4e88b..de7c53c18e 100644 --- a/packages/sol-meta/src/cli.ts +++ b/packages/sol-meta/src/cli.ts @@ -1,12 +1,14 @@ import 'source-map-support/register'; +import * as _ from 'lodash'; import * as pathUtils from 'path'; import * as process from 'process'; import * as S from 'solidity-parser-antlr'; import * as yargs from 'yargs'; +import { logUtils } from '@0x/utils'; -import { ContractMockOptions, mockContract, mockContractName } from './contractMocker'; -import { compile } from './solcWrapper'; +import { ContractMockOptions, mockContract, mockContractName } from './contract_mocker'; +import { compile } from './solc_wrapper'; import { readSources, SourceReaderOptions } from './source_reader'; import { unparse } from './unparser'; import * as utils from './utils'; @@ -19,7 +21,7 @@ import { visit, Visitor } from './visitor'; .help() .option('config', { type: 'string', - description: 'config file', + description: 'path to config file', }) .option('remapping', { type: 'string', @@ -35,12 +37,12 @@ import { visit, Visitor } from './visitor'; type: 'string', description: 'directory to output too', }) - .options('test', { + .options('shouldTest', { type: 'boolean', description: 'run all generated mock contracts through solc', }).argv; - // TODO: CommandLineOptions object + // TODO(recmo): Refactor to use a typed CommandLineOptions object let options: Partial = {}; let defaults: ContractMockOptions = { constructors: {}, @@ -50,7 +52,7 @@ import { visit, Visitor } from './visitor'; let outputPath = `${process.cwd()}/out`; // Handle config file - if (argv.config) { + if (!_.isUndefined(argv.config)) { const config = JSON.parse(await utils.readFileAsync(argv.config)); if (config.options) { options = { ...options, ...config.options }; @@ -67,14 +69,18 @@ import { visit, Visitor } from './visitor'; } // Handle command line arguments - if (argv.remapping) { - options.remapping = (argv.remapping as string[]) - .map((str: string) => str.split('=', 2)) - .reduce((a, [from, to]) => ({ ...a, [from]: to }), {}); + if (!_.isUndefined(argv.remapping)) { + options.remapping = _.reduce( + _.map(argv.remapping as string[], (str: string) => str.split('=', 2)), + (remappingsAccumulator, [from, to]) => ({ ...remappingsAccumulator, [from]: to }), + {}, + ); } - if (argv.includes) { + if (!_.isUndefined(argv.includes)) { options.includes = argv.includes; } + + // Sources can be passed as --sources or as default argument `argv._` options.sources = [...options.sources, ...argv._]; // Parse all sources @@ -82,54 +88,52 @@ import { visit, Visitor } from './visitor'; const sources = await readSources(options.sources, options); console.timeEnd('Parsing sources'); - for (const contractName in contracts) { - if (contracts.hasOwnProperty(contractName)) { - console.log(`\n${contractName}`); - const spec = { - constructors: { - ...defaults.constructors, - ...contracts[contractName].constructors, - }, - scripted: { - ...defaults.scripted, - ...contracts[contractName].scripted, - }, - }; - - // Find path - const path = Object.keys(sources).find(path => contractName in sources[path].contracts); - if (path === undefined) { - throw new Error(`Could not find contract ${contractName}.`); - } - - // Create mock contract - console.time('Generating mocks'); - const mock = mockContract(sources, path, contractName, spec); - console.timeEnd('Generating mocks'); - - // Optionally test - if (argv.test) { - console.time('Test compile'); - await compile(sources, mock); - console.timeEnd('Test compile'); - } - - // Make import paths relative - mock.children = mock.children.map(node => - visit(node, { - ImportDirective: importDirective => ({ - ...importDirective, - path: pathUtils.relative(outputPath, importDirective.path), - }), - SourceMember: n => n, - }), - ); + for (const contractName of _.keys(contracts)) { + logUtils.log(`\n${contractName}`); + const spec = { + constructors: { + ...defaults.constructors, + ...contracts[contractName].constructors, + }, + scripted: { + ...defaults.scripted, + ...contracts[contractName].scripted, + }, + }; + + // Find path + const path = _.find(_.keys(sources), path => contractName in sources[path].contracts); + if (_.isUndefined(path)) { + throw new Error(`Could not find contract ${contractName}.`); + } - // Write file - console.time('Writing mocks'); - await utils.writeFileAsync(`${outputPath}/${mockContractName(contractName)}.sol`, unparse(mock)); - console.timeEnd('Writing mocks'); + // Create mock contract + console.time('Generating mocks'); + const mock = mockContract(sources, path, contractName, spec); + console.timeEnd('Generating mocks'); + + // Optionally test + if (!_.isUndefined(argv.test)) { + console.time('Test compile'); + await compile(sources, mock); + console.timeEnd('Test compile'); } + + // Make import paths relative + mock.children = _.map(mock.children, node => + visit(node, { + ImportDirective: importDirective => ({ + ...importDirective, + path: pathUtils.relative(outputPath, importDirective.path), + }), + SourceMember: n => n, + }), + ); + + // Write file + console.time('Writing mocks'); + await utils.writeFileAsync(`${outputPath}/${mockContractName(contractName)}.sol`, unparse(mock)); + console.timeEnd('Writing mocks'); } // Exit successfully diff --git a/packages/sol-meta/src/constructor.ts b/packages/sol-meta/src/constructor.ts index bc8eef871a..c0ee2570fe 100644 --- a/packages/sol-meta/src/constructor.ts +++ b/packages/sol-meta/src/constructor.ts @@ -1,9 +1,10 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; export interface ConstructorArguments { - [contractName: string]: utils.Litteral[]; + [contractName: string]: utils.Literal[]; } export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefinition { @@ -18,10 +19,10 @@ export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefin returnParameters: null, visibility: S.Visibility.Public, stateMutability: S.StateMutability.Default, - modifiers: Object.keys(consArgs).map(name => ({ + modifiers: _.map(_.keys(consArgs), name => ({ type: S.NodeType.ModifierInvocation, name, - arguments: consArgs[name].map(utils.litteral), + arguments: _.map(consArgs[name], utils.literal), })), isConstructor: true, body: { @@ -55,7 +56,7 @@ export function nonAbstractForcer(contractName: string): S.ContractDefinition { parameters: [], }, returnParameters: null, - visibility: S.Visibility.Default, + visibility: S.Visibility.Public, stateMutability: S.StateMutability.Default, modifiers: [], isConstructor: true, diff --git a/packages/sol-meta/src/contractMocker.ts b/packages/sol-meta/src/contract_mocker.ts similarity index 93% rename from packages/sol-meta/src/contractMocker.ts rename to packages/sol-meta/src/contract_mocker.ts index 1e660a55a3..014decf454 100644 --- a/packages/sol-meta/src/contractMocker.ts +++ b/packages/sol-meta/src/contract_mocker.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import { makeConstructor, nonAbstractForcer } from './constructor'; @@ -13,7 +14,7 @@ export const mockContractName = (contractName: string) => `${contractName}Mock`; export interface ContractMockOptions { constructors: { - [parentName: string]: utils.Litteral[]; + [parentName: string]: utils.Literal[]; }; scripted: { [functionName: string]: FunctionScript[]; @@ -42,16 +43,17 @@ export function mockContract( // Find all constructors that require arguments // TODO: Ignore constructors already called from other constructors - const constructors = parents - .filter(({ subNodes }) => + const constructors = _.map( + _.filter(parents, ({ subNodes }) => subNodes.some( node => node.type === S.NodeType.FunctionDefinition && node.isConstructor && node.parameters.parameters.length > 0, ), - ) - .map(({ name }) => name); + ), + ({ name }) => name, + ); // Check that we have arguments for all constructors that require them constructors.forEach(name => { @@ -91,7 +93,7 @@ export function mockContract( ...utils.flatMap(flat.subNodes, exposeNode), // Compile-time specified scripted functions - ...scripted.map(func => scriptFunction(func, options.scripted[func.name || ''])), + ..._.map(scripted, func => scriptFunction(func, options.scripted[func.name || ''])), // Mock all remaining abstract functions ...utils.flatMap(mocked, stubFunction), diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index 65d692a1c0..ae43ea1464 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import { argumentExpressions, identifier, nameParameters } from './utils'; @@ -54,7 +55,7 @@ const setter = (stateVar: S.StateVariableDeclaration): S.FunctionDefinition => { name: setterName(name), visibility: S.Visibility.Public, isConstructor: false, - stateMutability: S.StateMutability.Default, + stateMutability: S.StateMutability.View, parameters: { type: S.NodeType.ParameterList, parameters: [ @@ -131,7 +132,7 @@ const emitEvent = (event: S.EventDefinition): S.FunctionDefinition => { stateMutability: S.StateMutability.Default, parameters: { type: S.NodeType.ParameterList, - parameters: parameters.parameters.map(param => ({ + parameters: _.map(parameters.parameters, param => ({ ...param, isIndexed: false, })), diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts index 3f5948e874..d3dadf58cb 100644 --- a/packages/sol-meta/src/flattener.ts +++ b/packages/sol-meta/src/flattener.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import { linearize, linearizeAsync } from './linearization'; @@ -56,7 +57,7 @@ export function flattenContract( ): [S.ContractDefinition, S.ContractDefinition[]] { // Close over resolve to create a parents function const parents = (child: S.ContractDefinition) => - child.baseContracts.map(({ baseName: { namePath } }) => resolve(namePath)); + _.map(child.baseContracts, ({ baseName: { namePath } }) => resolve(namePath)); // Linearize the contract inheritance tree from least to most derived const linear = linearize(contract, parents); diff --git a/packages/sol-meta/src/linearization.ts b/packages/sol-meta/src/linearization.ts index 9488e0a0e8..42d3356d44 100644 --- a/packages/sol-meta/src/linearization.ts +++ b/packages/sol-meta/src/linearization.ts @@ -1,3 +1,5 @@ +import * as _ from 'lodash'; + // Implements C3 Linearization as used in Solidity // see: https://www.python.org/download/releases/2.3/mro/ @@ -15,8 +17,8 @@ export function merge(...ilists: T[][]): T[] { } // The first item of each list are heads, the remainders are tails - const heads = lists.map(([head, ...tail]) => head); - const tails = lists.map(([head, ...tail]) => tail); + const heads = _.map(lists, ([head, ...tail]) => head); + const tails = _.map(lists, ([head, ...tail]) => tail); // A good head is one that does not occur in any tail. const goodHead = heads.find(head => tails.every(tail => !tail.includes(head))); @@ -27,7 +29,7 @@ export function merge(...ilists: T[][]): T[] { } // Remove the good head from the lists - const newLists = lists.map(list => list.filter(elem => elem !== goodHead)); + const newLists = _.map(lists, list => list.filter(elem => elem !== goodHead)); // Prepend head to the linearization and repeat return [goodHead, ...merge(...newLists)]; @@ -50,12 +52,12 @@ export function linearize(final: T, parents: (_: T) => T[]): T[] { // TODO: Memoization // TODO: Throw on cycles (instead of stack overflowing) const p = parents(final); - const recurse = p.map(a => linearize(a, parents)); + const recurse = _.map(p, a => linearize(a, parents)); return [...merge(p, ...recurse), final]; } export async function linearizeAsync(final: T, parents: (_: T) => Promise): Promise { const p = await parents(final); - const recurse = await Promise.all(p.map(a => linearizeAsync(a, parents))); + const recurse = await Promise.all(_.map(p, a => linearizeAsync(a, parents))); return [...merge(p, ...recurse), final]; } diff --git a/packages/sol-meta/src/scripter.ts b/packages/sol-meta/src/scripter.ts index e3ccb4e5a6..55baf54d4d 100644 --- a/packages/sol-meta/src/scripter.ts +++ b/packages/sol-meta/src/scripter.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; @@ -32,7 +33,7 @@ export const guard = (clauses: { [argName: string]: string }, statements: S.Stat type: S.NodeType.BinaryOperation, operator: '==', left: utils.identifier(argName), - right: utils.litteral(clauses[argName]), + right: utils.literal(clauses[argName]), }, }), utils.litTrue, @@ -51,7 +52,7 @@ export const scriptStatementReturn = (s: FunctionScriptReturn): S.IfStatement => expression: { type: S.NodeType.TupleExpression, isArray: false, - components: s.outputs.map(utils.litteral), + components: _.map(s.outputs, utils.literal), }, }, ]); @@ -97,7 +98,7 @@ export const scriptFunction = (func: S.FunctionDefinition, script: FunctionScrip isConstructor: false, body: { type: S.NodeType.Block, - statements: catchAllScript.map(scriptStatement), + statements: _.map(catchAllScript, scriptStatement), }, }; }; diff --git a/packages/sol-meta/src/solcWrapper.ts b/packages/sol-meta/src/solc_wrapper.ts similarity index 87% rename from packages/sol-meta/src/solcWrapper.ts rename to packages/sol-meta/src/solc_wrapper.ts index b6cb8a2cf4..21e5dca1f9 100644 --- a/packages/sol-meta/src/solcWrapper.ts +++ b/packages/sol-meta/src/solc_wrapper.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import { ImportContents, StandardOutput } from 'solc'; @@ -10,10 +11,7 @@ import * as utils from './utils'; export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { // Extract required version from pragma of ast const version = - utils - .pragmaNodes(ast) - .filter(({ name }) => name === 'solidity') - .map(({ value }) => value)[0] || 'latest'; + _.map(utils.pragmaNodes(ast).filter(({ name }) => name === 'solidity'), ({ value }) => value)[0] || 'latest'; // Get Solidity compiler console.time('Loading solc-js'); @@ -55,6 +53,6 @@ export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { // Throw errors const errors = result.errors.filter(({ severity }) => severity === 'error'); if (errors.length > 0) { - throw new Error(errors.map(({ formattedMessage }) => formattedMessage).join('\n')); + throw new Error(_.map(errors, ({ formattedMessage }) => formattedMessage).join('\n')); } }; diff --git a/packages/sol-meta/src/source_reader.ts b/packages/sol-meta/src/source_reader.ts index a06df719e0..6894007dd6 100644 --- a/packages/sol-meta/src/source_reader.ts +++ b/packages/sol-meta/src/source_reader.ts @@ -1,6 +1,8 @@ import * as glob from 'glob'; +import * as _ from 'lodash'; import * as pathUtils from 'path'; import * as S from 'solidity-parser-antlr'; +import { Deferred } from '@0x/utils'; import * as utils from './utils'; @@ -24,7 +26,7 @@ export interface SourceInfo { // TODO: We are missing the remapping and import statements. This information is necessary // to fully interpret the ImportDirectives. We could solve this by including the // SourceReaderOptions as part of the SourceCollection, but then we'd need to -// but the path mapping in a separate variable. +// put the path mapping in a separate variable. export interface SourceCollection { [path: string]: SourceInfo; } @@ -58,14 +60,14 @@ export class ContractReader { const makeAbsolute = (path: string) => (pathUtils.isAbsolute(path) ? path : pathUtils.join(cwd, path)); // Make all paths absolute - this._opts.sources = this._opts.sources.map(makeAbsolute); - this._opts.includes = this._opts.includes.map(makeAbsolute); + this._opts.sources = _.map(this._opts.sources, makeAbsolute); + this._opts.includes = _.map(this._opts.includes, makeAbsolute); this._opts.remapping = utils.objectMap(this._opts.remapping, makeAbsolute); } public async processSourcesAsync(): Promise { // Read all contract sources and imports - await Promise.all(this._opts.sources.map(path => this._readSourceAsync(path))); + await Promise.all(_.map(this._opts.sources, path => this._readSourceAsync(path))); // Resolve the result return utils.objectPromise(this._result); @@ -117,7 +119,7 @@ export class ContractReader { // Create promise here so it will act as a mutex. // When we recursively re-enter this function below, the deferred // promise will already be there and we will not repeat the work. - const deffered = new utils.Deferred(); + const deffered = new Deferred(); this._result[absolutePath] = deffered.promise; // Read and save in cache @@ -128,12 +130,12 @@ export class ContractReader { const imports = parsed.children.filter( ({ type }) => type === S.NodeType.ImportDirective, ) as S.ImportDirective[]; - const importPaths = await Promise.all(imports.map(({ path }) => this._resolveAsync(absolutePath, path))); + const importPaths = await Promise.all(_.map(imports, ({ path }) => this._resolveAsync(absolutePath, path))); // Recursively parse imported sources // Note: This will deadlock on cyclical imports. (We will end up awaiting our own promise.) // TODO: Throw an error instead. - const importInfo = await Promise.all(importPaths.map(path => this._readSourceAsync(path))); + const importInfo = await Promise.all(_.map(importPaths, path => this._readSourceAsync(path))); // Compute global scope include imports // TODO: Support `SomeContract as SomeAlias` in import directives. @@ -160,6 +162,7 @@ export class ContractReader { } } +// TODO(remco): Deduplicate with sol-compiler export const readSources = async ( sources: string[], options?: Partial, diff --git a/packages/sol-meta/src/stubber.ts b/packages/sol-meta/src/stubber.ts index bf63b4ad5b..3f660279c5 100644 --- a/packages/sol-meta/src/stubber.ts +++ b/packages/sol-meta/src/stubber.ts @@ -1,3 +1,4 @@ +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; @@ -63,7 +64,7 @@ const makeCountedEvent = (name: string, parameters: S.ParameterList) => typeName: utils.types.uint256, storageLocation: S.StorageLocation.Default, }, - ...parameters.parameters.map(param => ({ + ..._.map(parameters.parameters, param => ({ ...param, storageLocation: S.StorageLocation.Default, })), @@ -120,7 +121,7 @@ const makeResultType = (name: string, fields: S.ParameterList): S.StructDefiniti members: [ variableDeclaration('_enabled', utils.types.bool), variableDeclaration('_reverts', utils.types.bool), - ...fields.parameters.map(({ name, typeName }) => variableDeclaration(name as string, typeName)), + ..._.map(fields.parameters, ({ name, typeName }) => variableDeclaration(name as string, typeName)), ], }); @@ -257,11 +258,14 @@ const makeFunction = ( expression: { type: S.NodeType.TupleExpression, isArray: false, - components: returnParameters.parameters.map(({ name: memberName }): S.MemberAccess => ({ - type: S.NodeType.MemberAccess, - expression: identifier('_result'), - memberName: memberName as string, - })), + components: _.map( + returnParameters.parameters, + ({ name: memberName }): S.MemberAccess => ({ + type: S.NodeType.MemberAccess, + expression: identifier('_result'), + memberName: memberName as string, + }), + ), }, }, ], diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 2f74f1f5d1..9ca24b215d 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -1,5 +1,6 @@ // TODO: instead use https://github.com/prettier-solidity/prettier-plugin-solidity/blob/master/src/printer.js +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import { visit, Visitor } from './visitor'; @@ -12,21 +13,21 @@ const unparen = (s: string) => s.replace(/^\((.*)\)$/, '$1'); const visitor: Visitor = { // Source level - SourceUnit: ({ children }) => children.map(unparse).join('\n'), + SourceUnit: ({ children }) => _.map(children, unparse).join('\n'), PragmaDirective: ({ name, value }) => `pragma ${name} ${value};`, ImportDirective: ({ path, symbolAliases }) => `import ` + (symbolAliases - ? `{${symbolAliases.map(([from, to]) => from + (to ? ` as ${to}` : '')).join(', ')}} from ` + ? `{${_.map(symbolAliases, ([from, to]) => from + (to ? ` as ${to}` : '')).join(', ')}} from ` : '') + `${stresc(path)};`, ContractDefinition: ({ name, kind, baseContracts, subNodes }) => `${kind} ${name} ${baseContracts.length > 0 ? 'is ' : ''}` + - baseContracts.map(unparse).join(', ') + - block('\n' + subNodes.map(unparse).join('\n\n')), + _.map(baseContracts, unparse).join(', ') + + block('\n' + _.map(subNodes, unparse).join('\n\n')), InheritanceSpecifier: ({ baseName: { namePath } }) => namePath, @@ -34,11 +35,11 @@ const visitor: Visitor = { UsingForDeclaration: ({ typeName, libraryName }) => `using ${libraryName} for ${unparse(typeName)};`, - StateVariableDeclaration: ({ variables }) => variables.map(unparse).join(', ') + ';', + StateVariableDeclaration: ({ variables }) => _.map(variables, unparse).join(', ') + ';', - StructDefinition: ({ name, members }) => `struct ${name} ${block(members.map(unparse).join(';\n') + ';')}`, + StructDefinition: ({ name, members }) => `struct ${name} ${block(_.map(members, unparse).join(';\n') + ';')}`, - EnumDefinition: ({ name, members }) => `enum ${name} ${block(members.map(unparse).join(',\n'))}`, + EnumDefinition: ({ name, members }) => `enum ${name} ${block(_.map(members, unparse).join(',\n'))}`, EnumValue: ({ name }) => name, @@ -64,24 +65,24 @@ const visitor: Visitor = { indent( (visibility && visibility != 'default' ? visibility + ' ' : '') + (stateMutability || '') + - modifiers.map(unparse).join('\n') + + _.map(modifiers, unparse).join('\n') + (returnParameters ? `\nreturns ${unparse(returnParameters)}` : ''), ) + '\n' + (body ? unparse(body) : ';'), - ParameterList: ({ parameters }) => `(${parameters.map(unparse).join(', ')})`, + ParameterList: ({ parameters }) => `(${_.map(parameters, unparse).join(', ')})`, Parameter: ({ typeName, name, storageLocation }) => `${unparse(typeName)} ${storageLocation || ''} ${name || ''}`, - ModifierInvocation: ({ name, arguments: args }) => `${name}(${args.map(unparse).join(', ')})`, + ModifierInvocation: ({ name, arguments: args }) => `${name}(${_.map(args, unparse).join(', ')})`, // Statements - Block: ({ statements }) => block(statements.map(unparse).join('\n')), + Block: ({ statements }) => block(_.map(statements, unparse).join('\n')), VariableDeclarationStatement: ({ variables, initialValue }) => - variables.map(unparse) + (initialValue ? ` = ${unparse(initialValue)};` : ';'), + _.map(variables, unparse) + (initialValue ? ` = ${unparse(initialValue)};` : ';'), ExpressionStatement: ({ expression }) => `${unparen(unparse(expression))};`, @@ -129,7 +130,7 @@ const visitor: Visitor = { FunctionCall: ( { expression, arguments: args, names }, // TODO: names - ) => `(${unparse(expression)}(${args.map(unparse).join(', ')}))`, + ) => `(${unparse(expression)}(${_.map(args, unparse).join(', ')}))`, Conditional: ({ condition, trueExpression, falseExpression }) => `(${unparse(condition)} ? ${unparse(trueExpression)} : ${unparse(falseExpression)})`, @@ -157,25 +158,25 @@ const visitor: Visitor = { NewExpression: ({ typeName }) => `(new ${unparse(typeName)})`, TupleExpression: ({ isArray, components }) => - isArray ? `[${components.map(unparse).join(', ')}]` : `(${components.map(unparse).join(', ')})`, + isArray ? `[${_.map(components, unparse).join(', ')}]` : `(${_.map(components, unparse).join(', ')})`, // Assembly - AssemblyBlock: ({ operations }) => block(operations.map(unparse).join('\n')), + AssemblyBlock: ({ operations }) => block(_.map(operations, unparse).join('\n')), - AssemblyAssignment: ({ names, expression }) => `${names.map(unparse).join(', ')} := ${unparse(expression)}`, + AssemblyAssignment: ({ names, expression }) => `${_.map(names, unparse).join(', ')} := ${unparse(expression)}`, AssemblyLocalDefinition: ({ names, expression }) => - `let ${names.map(unparse).join(', ')} := ${unparse(expression)}`, + `let ${_.map(names, unparse).join(', ')} := ${unparse(expression)}`, AssemblyCall: ({ functionName, arguments: args }) => - args.length == 0 ? functionName : `${functionName}(${args.map(unparse).join(', ')})`, + args.length == 0 ? functionName : `${functionName}(${_.map(args, unparse).join(', ')})`, AssemblyIf: ({ condition, body }) => `if ${unparse(condition)} ${unparse(body)}`, - AssemblyFor: ({ pre, condition, post, body }) => `for ${[pre, condition, post, body].map(unparse).join(' ')}`, + AssemblyFor: ({ pre, condition, post, body }) => `for ${_.map([pre, condition, post, body], unparse).join(' ')}`, - AssemblySwitch: ({ expression, cases }) => `switch ${unparse(expression)}\n${cases.map(unparse).join('\n')}`, + AssemblySwitch: ({ expression, cases }) => `switch ${unparse(expression)}\n${_.map(cases, unparse).join('\n')}`, AssemblyCase: ({ value, block }) => `case ${unparse(value)} ${unparse(block)}`, diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 8cb00178b1..657aea093d 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -1,8 +1,9 @@ import * as fs from 'fs'; +import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; // TODO: Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap -export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).concat(...a.map(f)); +export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).concat(..._.map(a, f)); // TODO: Use Map instead? export const objectZip = (keys: string[], values: T[]): { [key: string]: T } => @@ -16,7 +17,7 @@ export const objectZip = (keys: string[], values: T[]): { [key: string]: T } // TODO: Is the order in Object.keys and Object.values equal? export const objectMap = (obj: { [key: string]: A }, f: (v: A) => B): { [key: string]: B } => - objectZip(Object.keys(obj), Object.values(obj).map(f)); + objectZip(Object.keys(obj), _.map(_.values(obj), f)); export const objectPromise = async (obj: { [key: string]: Promise }): Promise<{ [key: string]: T }> => objectZip(Object.keys(obj), await Promise.all(Object.values(obj))); @@ -26,27 +27,6 @@ export const objectFilter = (obj: { [key: string]: A }, f: (key: string, v: A .filter(key => f(key, obj[key])) .reduce((a, key) => ({ ...a, [key]: obj[key] }), {}); -export class Deferred { - public promise: Promise; - public reject: (reason: any) => void; - public resolve: (value: T) => void; - constructor() { - // Hack(recmo): Define reject and resolve here so TS does not complain - // about them not being defined in the constructor. The - // promise we create will overwrite them. - this.reject = () => { - throw new Error('Unimplemented reject.'); - }; - this.resolve = () => { - throw new Error('Unimplemented resolve.'); - }; - this.promise = new Promise((resolve, reject) => { - this.reject = reject; - this.resolve = resolve; - }); - } -} - export const existsAsync = (path: string): Promise => new Promise((resolve, reject) => fs.access(path, fs.constants.R_OK, error => (error ? resolve(false) : resolve(true))), @@ -121,9 +101,9 @@ export const litString = (value: string): S.StringLiteral => ({ value, }); -export type Litteral = string | number; +export type Literal = string | number; -export const litteral = (value: Litteral): S.Expression => { +export const literal = (value: Literal): S.Expression => { if (isNumber(value)) { return litNumber(value); } @@ -141,14 +121,14 @@ export const litteral = (value: Litteral): S.Expression => { export const nameParameters = (params: S.ParameterList, prefix: string = '_arg'): S.ParameterList => ({ ...params, - parameters: params.parameters.map((param, i) => ({ + parameters: _.map(params.parameters, (param, i) => ({ ...param, name: param.name || `${prefix}${i}`, })), }); export const argumentExpressions = (params: S.ParameterList): S.Expression[] => - params.parameters.map(({ name }) => { + _.map(params.parameters, ({ name }) => { // TODO: rewrite using throw expressions or do notation if (name !== null) { return identifier(name); diff --git a/packages/sol-meta/test/index.ts b/packages/sol-meta/test/index.ts index bdf12b1102..3ded81ce9e 100644 --- a/packages/sol-meta/test/index.ts +++ b/packages/sol-meta/test/index.ts @@ -9,11 +9,6 @@ import { unparse } from '../src/unparser'; const expect = chai.expect; -const promisify = (func: ((...args: any[]) => void)) => (...args: any[]) => - new Promise((resolve, reject) => - func(...args, (error: any, result: T) => (error ? reject(error) : resolve(result))), - ); - const findContracts = (searchPath: string) => glob.sync(searchPath).map(file => ({ name: path.basename(file, '.sol') + ` (${file})`, @@ -34,13 +29,17 @@ describe('Parser', () => { ); }); -describe.only('Unparser', () => { +describe('Unparser', () => { contracts.forEach(({ name, source }) => it(`should unparse ${name}`, () => { const ast = parse(source); const src = unparse(ast); const ast2 = parse(src); - //expect(ast2).to.deep.equal(ast); + // Ideally, we would test the following: + // expect(ast2).to.deep.equal(ast); + // But this fails on on expressiong like `2 * 3 + 1` which get rewritten + // to `((2 * 2) + 1)`. This prevents the ASTs from being identicall in + // syntax, even though they should be identical in meaning. }), ); }); diff --git a/packages/sol-meta/test/interface.expected.ast.js b/packages/sol-meta/test/interface.expected.ast.js index 0c1ac3d272..539a0b71f5 100644 --- a/packages/sol-meta/test/interface.expected.ast.js +++ b/packages/sol-meta/test/interface.expected.ast.js @@ -1,4 +1,4 @@ -x={ type: 'SourceUnit', +{ type: 'SourceUnit', children: [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, { type: 'PragmaDirective', diff --git a/packages/sol-meta/tsconfig.json b/packages/sol-meta/tsconfig.json index 7968153719..d67a5f3684 100644 --- a/packages/sol-meta/tsconfig.json +++ b/packages/sol-meta/tsconfig.json @@ -4,9 +4,5 @@ "outDir": "lib", "rootDir": "." }, - "include": [ - "./src/**/*", - "./test/**/*", - "../typescript-typings/types/solidity-parser-antlr/*" - ] -} \ No newline at end of file + "include": ["./src/**/*", "./test/**/*", "../typescript-typings/types/solidity-parser-antlr/*"] +} From 15cd9249a62549faa9fd068a08828faa99cabc00 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 21:13:00 +0100 Subject: [PATCH 68/76] remove console log from compiler --- packages/sol-meta/src/solc_wrapper.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/sol-meta/src/solc_wrapper.ts b/packages/sol-meta/src/solc_wrapper.ts index 21e5dca1f9..2b4e76aa1f 100644 --- a/packages/sol-meta/src/solc_wrapper.ts +++ b/packages/sol-meta/src/solc_wrapper.ts @@ -14,9 +14,7 @@ export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { _.map(utils.pragmaNodes(ast).filter(({ name }) => name === 'solidity'), ({ value }) => value)[0] || 'latest'; // Get Solidity compiler - console.time('Loading solc-js'); const compiler = await Solc.getSolcAsync(version); - console.timeEnd('Loading solc-js'); // Solidity standard JSON input // TODO: Typescript typings @@ -44,11 +42,10 @@ export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { throw new Error(`Could not find ${importPath}.`); }; - console.time('Compiling'); + // Run the compiler const result: StandardOutput = JSON.parse( compiler.compileStandardWrapper(JSON.stringify(input), findImportsCallback), ); - console.timeEnd('Compiling'); // Throw errors const errors = result.errors.filter(({ severity }) => severity === 'error'); From ba6603d981280548404e7ef4861144d98708eb88 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 21:14:37 +0100 Subject: [PATCH 69/76] remove temp files --- packages/sol-meta/test/dump | 0 packages/sol-meta/test/interface.expected.ast | 339 ------------------ .../sol-meta/test/interface.expected.ast.js | 289 --------------- packages/sol-meta/test/interface.expected.sol | 60 ---- packages/sol-meta/test/interface.sol | 54 --- packages/sol-meta/test/out.sol | 43 --- packages/sol-meta/test/test.out.sol | 57 --- packages/sol-meta/test/test.sol | 48 --- 8 files changed, 890 deletions(-) delete mode 100644 packages/sol-meta/test/dump delete mode 100644 packages/sol-meta/test/interface.expected.ast delete mode 100644 packages/sol-meta/test/interface.expected.ast.js delete mode 100644 packages/sol-meta/test/interface.expected.sol delete mode 100644 packages/sol-meta/test/interface.sol delete mode 100644 packages/sol-meta/test/out.sol delete mode 100644 packages/sol-meta/test/test.out.sol delete mode 100644 packages/sol-meta/test/test.sol diff --git a/packages/sol-meta/test/dump b/packages/sol-meta/test/dump deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/sol-meta/test/interface.expected.ast b/packages/sol-meta/test/interface.expected.ast deleted file mode 100644 index f20f293afb..0000000000 --- a/packages/sol-meta/test/interface.expected.ast +++ /dev/null @@ -1,339 +0,0 @@ -{ type: 'SourceUnit', - children: - [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, - { type: 'PragmaDirective', - name: 'experimental', - value: 'ABIEncoderV2' }, - { type: 'ContractDefinition', - name: 'MAssetProxyDispatcher', - baseContracts: [], - subNodes: - [ { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'dispatchTransferFrom_counter', - expression: - { type: 'NumberLiteral', number: '0', subdenomination: null }, - visibility: 'default', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: - { type: 'NumberLiteral', number: '0', subdenomination: null } }, - { type: 'EventDefinition', - name: 'dispatchTransferFromCalled', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: 'assetData', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'from', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'to', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'amount', - isStateVar: false, - isIndexed: false } ] }, - isAnonymous: false }, - { type: 'FunctionDefinition', - name: 'dispatchTransferFrom', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: 'assetData', - storageLocation: 'memory', - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'from', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'to', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'amount', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: null, - body: - { type: 'Block', - statements: - [ { type: 'EmitStatement', - eventCall: - { type: 'FunctionCall', - expression: { type: 'Identifier', name: 'dispatchTransferFromCalled' }, - arguments: - [ { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, - { type: 'Identifier', name: 'assetData' }, - { type: 'Identifier', name: 'from' }, - { type: 'Identifier', name: 'to' }, - { type: 'Identifier', name: 'amount' } ], - names: [] } }, - { type: 'ExpressionStatement', - expression: - { type: 'UnaryOperation', - operator: '++', - subExpression: { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, - isPrefix: false } } ] }, - visibility: 'internal', - modifiers: [], - isConstructor: false, - stateMutability: null }, - { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'someFunction_counter', - expression: - { type: 'NumberLiteral', number: '0', subdenomination: null }, - visibility: 'internal', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: - { type: 'NumberLiteral', number: '0', subdenomination: null } }, - { type: 'StructDefinition', - name: 'someFunction_ReturnType', - members: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: '_arg_0', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: '_arg_1', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: - { type: 'Mapping', - keyType: { type: 'ElementaryTypeName', name: 'uint256' }, - valueType: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' } }, - name: 'someFunction_returns', - expression: null, - visibility: 'default', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: null }, - { type: 'EventDefinition', - name: 'someFunctionCalled', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'a', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'b', - isStateVar: false, - isIndexed: false } ] }, - isAnonymous: false }, - { type: 'FunctionDefinition', - name: 'someFunctionSet', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' }, - name: 'values', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: null, - body: - { type: 'Block', - statements: - [ { type: 'ExpressionStatement', - expression: - { type: 'BinaryOperation', - operator: '=', - left: - { type: 'IndexAccess', - base: { type: 'Identifier', name: 'someFunction_returns' }, - index: { type: 'Identifier', name: 'counter' } }, - right: { type: 'Identifier', name: 'values' } } } ] }, - visibility: 'public', - modifiers: [], - isConstructor: false, - stateMutability: null }, - { type: 'FunctionDefinition', - name: 'someFunction', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'a', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'b', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: null, - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: null, - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - body: - { type: 'Block', - statements: - [ { type: 'EmitStatement', - eventCall: - { type: 'FunctionCall', - expression: { type: 'Identifier', name: 'someFunctionCalled' }, - arguments: - [ { type: 'Identifier', name: 'someFunction_counter' }, - { type: 'Identifier', name: 'a' }, - { type: 'Identifier', name: 'b' } ], - names: [] } }, - { type: 'VariableDeclarationStatement', - variables: - [ { type: 'VariableDeclaration', - typeName: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' }, - name: 'returnValues', - storageLocation: 'storage', - isStateVar: false, - isIndexed: false } ], - initialValue: - { type: 'IndexAccess', - base: { type: 'Identifier', name: 'someFunction_returns' }, - index: { type: 'Identifier', name: 'someFunction_counter' } } }, - { type: 'ExpressionStatement', - expression: - { type: 'UnaryOperation', - operator: '++', - subExpression: { type: 'Identifier', name: 'someFunction_counter' }, - isPrefix: false } }, - { type: 'ReturnStatement', - expression: - { type: 'TupleExpression', - components: - [ { type: 'MemberAccess', - expression: { type: 'Identifier', name: 'returnValues' }, - memberName: '_arg_0' }, - { type: 'MemberAccess', - expression: { type: 'Identifier', name: 'returnValues' }, - memberName: '_arg_1' } ], - isArray: false } } ] }, - visibility: 'internal', - modifiers: [], - isConstructor: false, - stateMutability: null } ], - kind: 'contract' } ] } -{ type: 'SourceUnit', - children: - [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, - { type: 'PragmaDirective', - name: 'experimental', - value: 'ABIEncoderV2' }, - { type: 'ContractDefinition', - kind: 'contract', - name: 'MAssetProxyDispatcherMock', - baseContracts: [ { type: 'InheritanceSpecifier', baseName: [Object] } ], - subNodes: - [ { type: 'StateVariableDeclaration', - variables: [Array], - initialValue: [Object] }, - { type: 'EventDefinition', - name: 'dispatchTransferFromCalled', - parameters: [Object], - isAnonymous: false }, - { type: 'StateVariableDeclaration', - variables: [Array], - initialValue: [Object] }, - { type: 'StructDefinition', - name: 'someFunction_ReturnType', - members: [Array] }, - { type: 'StateVariableDeclaration', - variables: [Array], - initialValue: null }, - { type: 'EventDefinition', - name: 'someFunctionCalled', - parameters: [Object], - isAnonymous: false } ] } ] } -pragma solidity ^0.4.23; -pragma experimental ABIEncoderV2; -contract MAssetProxyDispatcherMock is MAssetProxyDispatcher{ - - uint256 dispatchTransferFrom_counter = 0; - - event dispatchTransferFromCalled(uint256 counter, bytes assetData, address from, address to, uint256 amount); - - uint256 internal someFunction_counter = 0; - - struct someFunction_ReturnType { - uint256 _arg_0; - bytes _arg_1; - } - - mapping (uint256 => someFunction_ReturnType) someFunction_returns; - - event someFunctionCalled(uint256 counter, uint256 a, uint256 b); -} diff --git a/packages/sol-meta/test/interface.expected.ast.js b/packages/sol-meta/test/interface.expected.ast.js deleted file mode 100644 index 539a0b71f5..0000000000 --- a/packages/sol-meta/test/interface.expected.ast.js +++ /dev/null @@ -1,289 +0,0 @@ -{ type: 'SourceUnit', - children: - [ { type: 'PragmaDirective', name: 'solidity', value: '^0.4.23' }, - { type: 'PragmaDirective', - name: 'experimental', - value: 'ABIEncoderV2' }, - { type: 'ContractDefinition', - name: 'MAssetProxyDispatcher', - baseContracts: [], - subNodes: - [ { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'dispatchTransferFrom_counter', - expression: - { type: 'NumberLiteral', number: '0', subdenomination: null }, - visibility: 'default', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: - { type: 'NumberLiteral', number: '0', subdenomination: null } }, - { type: 'EventDefinition', - name: 'dispatchTransferFromCalled', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: 'assetData', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'from', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'to', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'amount', - isStateVar: false, - isIndexed: false } ] }, - isAnonymous: false }, - { type: 'FunctionDefinition', - name: 'dispatchTransferFrom', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: 'assetData', - storageLocation: 'memory', - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'from', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'address' }, - name: 'to', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'amount', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: null, - body: - { type: 'Block', - statements: - [ { type: 'EmitStatement', - eventCall: - { type: 'FunctionCall', - expression: { type: 'Identifier', name: 'dispatchTransferFromCalled' }, - arguments: - [ { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, - { type: 'Identifier', name: 'assetData' }, - { type: 'Identifier', name: 'from' }, - { type: 'Identifier', name: 'to' }, - { type: 'Identifier', name: 'amount' } ], - names: [] } }, - { type: 'ExpressionStatement', - expression: - { type: 'UnaryOperation', - operator: '++', - subExpression: { type: 'Identifier', name: 'dispatchTransferFrom_counter' }, - isPrefix: false } } ] }, - visibility: 'internal', - modifiers: [], - isConstructor: false, - stateMutability: null }, - { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'someFunction_counter', - expression: - { type: 'NumberLiteral', number: '0', subdenomination: null }, - visibility: 'internal', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: - { type: 'NumberLiteral', number: '0', subdenomination: null } }, - { type: 'StructDefinition', - name: 'someFunction_ReturnType', - members: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: '_arg_0', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: '_arg_1', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - { type: 'StateVariableDeclaration', - variables: - [ { type: 'VariableDeclaration', - typeName: - { type: 'Mapping', - keyType: { type: 'ElementaryTypeName', name: 'uint256' }, - valueType: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' } }, - name: 'someFunction_returns', - expression: null, - visibility: 'default', - isStateVar: true, - isDeclaredConst: false, - isIndexed: false } ], - initialValue: null }, - { type: 'EventDefinition', - name: 'someFunctionCalled', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'a', - isStateVar: false, - isIndexed: false }, - { type: 'VariableDeclaration', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'b', - isStateVar: false, - isIndexed: false } ] }, - isAnonymous: false }, - { type: 'FunctionDefinition', - name: 'someFunctionSet', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'counter', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' }, - name: 'values', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: null, - body: - { type: 'Block', - statements: - [ { type: 'ExpressionStatement', - expression: - { type: 'BinaryOperation', - operator: '=', - left: - { type: 'IndexAccess', - base: { type: 'Identifier', name: 'someFunction_returns' }, - index: { type: 'Identifier', name: 'counter' } }, - right: { type: 'Identifier', name: 'values' } } } ] }, - visibility: 'public', - modifiers: [], - isConstructor: false, - stateMutability: null }, - { type: 'FunctionDefinition', - name: 'someFunction', - parameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'a', - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: 'b', - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - returnParameters: - { type: 'ParameterList', - parameters: - [ { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'uint256' }, - name: null, - storageLocation: null, - isStateVar: false, - isIndexed: false }, - { type: 'Parameter', - typeName: { type: 'ElementaryTypeName', name: 'bytes' }, - name: null, - storageLocation: null, - isStateVar: false, - isIndexed: false } ] }, - body: - { type: 'Block', - statements: - [ { type: 'EmitStatement', - eventCall: - { type: 'FunctionCall', - expression: { type: 'Identifier', name: 'someFunctionCalled' }, - arguments: - [ { type: 'Identifier', name: 'someFunction_counter' }, - { type: 'Identifier', name: 'a' }, - { type: 'Identifier', name: 'b' } ], - names: [] } }, - { type: 'VariableDeclarationStatement', - variables: - [ { type: 'VariableDeclaration', - typeName: - { type: 'UserDefinedTypeName', - namePath: 'someFunction_ReturnType' }, - name: 'returnValues', - storageLocation: 'storage', - isStateVar: false, - isIndexed: false } ], - initialValue: - { type: 'IndexAccess', - base: { type: 'Identifier', name: 'someFunction_returns' }, - index: { type: 'Identifier', name: 'someFunction_counter' } } }, - { type: 'ExpressionStatement', - expression: - { type: 'UnaryOperation', - operator: '++', - subExpression: { type: 'Identifier', name: 'someFunction_counter' }, - isPrefix: false } }, - { type: 'ReturnStatement', - expression: - { type: 'TupleExpression', - components: - [ { type: 'MemberAccess', - expression: { type: 'Identifier', name: 'returnValues' }, - memberName: '_arg_0' }, - { type: 'MemberAccess', - expression: { type: 'Identifier', name: 'returnValues' }, - memberName: '_arg_1' } ], - isArray: false } } ] }, - visibility: 'internal', - modifiers: [], - isConstructor: false, - stateMutability: null } ], - kind: 'contract' } ] } diff --git a/packages/sol-meta/test/interface.expected.sol b/packages/sol-meta/test/interface.expected.sol deleted file mode 100644 index b364cc060c..0000000000 --- a/packages/sol-meta/test/interface.expected.sol +++ /dev/null @@ -1,60 +0,0 @@ -pragma solidity ^0.4.23; -pragma experimental ABIEncoderV2; - -contract MAssetProxyDispatcher { - - uint256 dispatchTransferFrom_counter = 0; - - event dispatchTransferFromCalled(uint256 counter, - bytes assetData, - address from, - address to, - uint256 amount); - - function dispatchTransferFrom( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal - { - emit dispatchTransferFromCalled( - dispatchTransferFrom_counter, - assetData, - from, - to, - amount - ); - dispatchTransferFrom_counter++; - } - - uint256 internal someFunction_counter = 0; - - struct someFunction_ReturnType { - bool reverts; - uint256 _arg_0; - bytes _arg_1; - } - - mapping (uint256 => someFunction_ReturnType) someFunction_returns; - - event someFunctionCalled(uint256 counter, uint256 a, uint256 b); - - function someFunctionSet(uint256 counter, someFunction_ReturnType values) - public - { - (someFunction_returns[counter]) = values; - } - - function someFunction(uint256 a, uint256 b) - internal - returns (uint256, bytes) - { - emit someFunctionCalled(someFunction_counter, a, b); - (someFunction_ReturnType storage returnValues) = (someFunction_returns[someFunction_counter]); - someFunction_counter++; - require(!returnValues.reverts); - return ((returnValues._arg_0), (returnValues._arg_1)); - } -} diff --git a/packages/sol-meta/test/interface.sol b/packages/sol-meta/test/interface.sol deleted file mode 100644 index 9015089566..0000000000 --- a/packages/sol-meta/test/interface.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity ^0.4.24; - - -contract IOwnable { - - function transferOwnership(address newOwner) - public; -} - -contract Ownable is - IOwnable -{ - address public owner; - - constructor () - public - { - owner = msg.sender; - } - - modifier onlyOwner() { - require( - msg.sender == owner, - "ONLY_CONTRACT_OWNER" - ); - _; - } - - function transferOwnership(address newOwner) - public - onlyOwner - { - if (newOwner != address(0)) { - owner = newOwner; - } - } -} - - -contract MAssetProxyDispatcher is Ownable { - - function dispatchTransferFrom( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal; - - - function someFunction(uint256 a, uint256 b) - internal - returns (uint256, bytes); -} diff --git a/packages/sol-meta/test/out.sol b/packages/sol-meta/test/out.sol deleted file mode 100644 index 55de391fc0..0000000000 --- a/packages/sol-meta/test/out.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; -import "interface.sol"; -contract MAssetProxyDispatcherMock is MAssetProxyDispatcher{ - - uint256 internal _dispatchTransferFrom_counter = 0; - - event _dispatchTransferFrom_log(uint256 counter, bytes assetData, address from, address to, uint256 amount); - - function dispatchTransferFrom(bytes memory assetData, address from, address to, uint256 amount) - internal - { - emit _dispatchTransferFrom_log(_dispatchTransferFrom_counter, assetData, from, to, amount); - _dispatchTransferFrom_counter++; - } - - uint256 internal _someFunction_counter = 0; - - event _someFunction_log(uint256 counter, uint256 a, uint256 b); - - struct _someFunction_Result { - uint256 _ret0; - bytes _ret1; - } - - mapping (uint256 => _someFunction_Result) _someFunction_results; - - function _someFunction_set(uint256 _counter, _someFunction_Result _value) - public - { - (_someFunction_results[_counter]) = _value; - } - - function someFunction(uint256 a, uint256 b) - internal - returns (uint256 _ret0, bytes _ret1) - { - emit _someFunction_log(_someFunction_counter, a, b); - _someFunction_Result storage result = (_someFunction_results[_someFunction_counter]); - _someFunction_counter++; - return ((result._ret0), (result._ret1)); - } -} diff --git a/packages/sol-meta/test/test.out.sol b/packages/sol-meta/test/test.out.sol deleted file mode 100644 index 0f9b72d17d..0000000000 --- a/packages/sol-meta/test/test.out.sol +++ /dev/null @@ -1,57 +0,0 @@ -pragma solidity ^0.4.0; -import "./test.sol"; -contract TestExposed is Test{ - - function get_hiddenState() - public view - returns (uint256 ) - { - return hiddenState; - } - - function set_hiddenState(uint256 setterNewValue) - public - { - hiddenState = setterNewValue; - } - - function emit_SomeLogEvent(uint256 withValues, address canBeIndexed) - public - { - emit SomeLogEvent(withValues, canBeIndexed); - } - - function modifier_modWithoutParameters() - public modWithoutParameters() - returns (bool executed) - { - return true; - } - - function modifier_modWithParameters(bytes32 hash) - public modWithParameters(hash) - returns (bool executed) - { - return true; - } - - function public_someInternalAction(uint256[4] withParameters) - public - { - someInternalAction(withParameters); - } - - function public_someInternalFunction(uint256 x) - public pure - returns (uint256 y) - { - return (someInternalFunction(x)); - } - - function public_someMultiFunction(uint256 a) - public view - returns (uint256 x, uint256 y) - { - return (someMultiFunction(a)); - } -} diff --git a/packages/sol-meta/test/test.sol b/packages/sol-meta/test/test.sol deleted file mode 100644 index 067ef11919..0000000000 --- a/packages/sol-meta/test/test.sol +++ /dev/null @@ -1,48 +0,0 @@ -pragma solidity ^0.4.0; - -contract Test { - - uint256 internal hiddenState; - - event SomeLogEvent(uint256 withValues, address indexed canBeIndexed); - - constructor () public { - hiddenState = 42; - } - - modifier modWithoutParameters { - if(msg.sender == 0x3) { - _; - } - } - - modifier modWithParameters(bytes32 hash) { - if (keccak256(abi.encodePacked(msg.sender)) == hash) { - revert(); - } - _; - } - - function someInternalAction(uint256[4] withParameters) - internal - modWithoutParameters - { - hiddenState = withParameters[3]; - } - - function someInternalFunction(uint256 x) - internal pure - returns (uint256 y) - { - y = x * x + 5; - } - - function someMultiFunction(uint256 a) - internal view - returns (uint256 x, uint256 y) - { - x = hiddenState + a; - y = x * x + 5; - } - -} From f00ae95cfd01529772f23f6db2a63455c88aa902 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 30 Nov 2018 21:15:20 +0100 Subject: [PATCH 70/76] test mock generator --- packages/sol-meta/test/index.ts | 45 +++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/sol-meta/test/index.ts b/packages/sol-meta/test/index.ts index 3ded81ce9e..84c66954f6 100644 --- a/packages/sol-meta/test/index.ts +++ b/packages/sol-meta/test/index.ts @@ -1,21 +1,26 @@ import * as chai from 'chai'; import * as fs from 'fs'; import * as glob from 'glob'; +import * as _ from 'lodash'; import 'mocha'; import * as path from 'path'; +import * as S from 'solidity-parser-antlr'; import { parse } from '../src/parser'; +import { readSources, SourceInfo, SourceCollection } from '../src/source_reader'; import { unparse } from '../src/unparser'; +import { compile } from '../src/solc_wrapper'; +import { mockContract } from '../src/contract_mocker'; const expect = chai.expect; const findContracts = (searchPath: string) => glob.sync(searchPath).map(file => ({ - name: path.basename(file, '.sol') + ` (${file})`, + name: path.basename(file, '.sol'), source: fs.readFileSync(file, 'utf8'), })); -const contracts = findContracts('../contracts/src/**/*.sol'); +const contracts = findContracts('../contracts/contracts/**/*.sol'); describe('Parser', () => { it('should have test contracts', () => { @@ -43,3 +48,39 @@ describe('Unparser', () => { }), ); }); + +describe('Mocker', () => { + const sourcePath = '../contracts/contracts/protocol/Exchange/'; + const toMock = ['Exchange', 'MixinExchangeCore', 'MixinSignatureValidator', 'MixinWrapperFunctions']; + const path = (name: string) => `${sourcePath}/${name}.sol`; + let sources: SourceCollection; + let mocks: { [name: string]: S.SourceUnit } = {}; + + it('should read sources', async () => { + sources = await readSources(_.map(toMock, path)); + _.map(toMock, name => expect(_.keys(sources).some(absPath => absPath.endsWith(`${name}.sol`)))); + }); + _.map(toMock, name => + it(`should generate mocks for ${name}`, () => { + mocks[name] = mockContract( + sources, + _.keys(sources).find(absPath => absPath.endsWith(`${name}.sol`)) || '', + name, + { + constructors: { + LibConstants: ['"ZRXASSETSTRING"'], + Exchange: ['"ZRXASSETSTRING"'], + }, + scripted: {}, + }, + ); + }), + ); + // Note(recmo): These tests are slow + describe.skip('Compiling', () => + _.map(toMock, name => + it(`should compile mock for ${name}`, async () => { + await compile(sources, mocks[name]); + }).timeout(30000), + )); +}); From 7e59676978f00381deaafad71126a351d3232faf Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 12 Dec 2018 17:00:13 +0100 Subject: [PATCH 71/76] tsling-config is in @0x/ --- packages/sol-meta/tslint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sol-meta/tslint.json b/packages/sol-meta/tslint.json index ffaefe83a6..dd9053357e 100644 --- a/packages/sol-meta/tslint.json +++ b/packages/sol-meta/tslint.json @@ -1,3 +1,3 @@ { - "extends": ["@0xproject/tslint-config"] + "extends": ["@0x/tslint-config"] } From 3f81ed4b99a02be21d4e5e8b92d4c257fc038fe5 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 12 Dec 2018 22:48:01 +0100 Subject: [PATCH 72/76] Update yarn.lock --- yarn.lock | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3ceca26ca1..e2c5ca2bf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -473,13 +473,26 @@ write-file-atomic "^2.3.0" "@0x/json-schemas@^1.0.7": - version "2.1.2" + version "2.1.3" + resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-2.1.3.tgz#07cf30f19c61c11c843f5ef9a1e653defb3f12c7" + integrity sha512-nBIPpbikfgiT1CoNnOxcc9wV7rutCaUg1tnFxccTVGGazVu6AOXeWGa/LqbOLOcAfX4cQoFpW3yJdNo1lDNzLw== dependencies: - "@0x/typescript-typings" "^3.0.4" + "@0x/typescript-typings" "^3.0.5" "@types/node" "*" jsonschema "^1.2.0" lodash.values "^4.3.0" +"@0x/typescript-typings@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-3.0.5.tgz#77fe522315ae21b795dfa6c181148f25f0b98c49" + integrity sha512-aMG5WJEAcfisdfw13RPdeYEyLv/7IValxWLcqiLqRcleU91P1GWPppTSamPdtxM7rIxURDOaUHvxOqibZmze6g== + dependencies: + "@types/bn.js" "^4.11.0" + "@types/react" "*" + bignumber.js "~4.1.0" + ethereum-types "^1.1.3" + popper.js "1.14.3" + "@0xproject/npm-cli-login@^0.0.11": version "0.0.11" resolved "https://registry.yarnpkg.com/@0xproject/npm-cli-login/-/npm-cli-login-0.0.11.tgz#3f1ec06112ce62aad300ff0575358f68aeecde2e" @@ -5837,6 +5850,14 @@ ethereum-common@^0.0.18: version "0.0.18" resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" +ethereum-types@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-1.1.3.tgz#0e069874d91e5845a8ac0d3af732b8f9e6a213af" + integrity sha512-wGMAdw1hby/aixUCWEkp6rDJUZpRtiMfasOha/2nQq+2aFk5xOkLrk1TlTn9Dr4ybcLftGPjKs4aimNARcMLQA== + dependencies: + "@types/node" "*" + bignumber.js "~4.1.0" + ethereumjs-abi@0.6.5, ethereumjs-abi@^0.6.5: version "0.6.5" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" From 5a6da6f2ea775c6276f5006c3efc2dfc02d20a4e Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Wed, 12 Dec 2018 22:48:21 +0100 Subject: [PATCH 73/76] ts-lint fix sol-compiler --- packages/sol-compiler/src/compiler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 3c3206c700..bb366542a2 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -82,6 +82,10 @@ export class Compiler { private readonly _artifactsDir: string; private readonly _solcVersionIfExists: string | undefined; private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER; + public static async getSolcAsync(solcVersion: string): Promise { + const solcInstance = (await this._getSolcAsync(solcVersion)).solcInstance; + return solcInstance; + } private static async _getSolcAsync( solcVersion: string, ): Promise<{ solcInstance: solc.SolcInstance; fullSolcVersion: string }> { @@ -110,10 +114,6 @@ export class Compiler { const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename)); return { solcInstance, fullSolcVersion }; } - public static async getSolcAsync(solcVersion: string): Promise { - const solcInstance = (await this._getSolcAsync(solcVersion)).solcInstance; - return solcInstance; - } private static _addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void { if (!_.isUndefined(compiledContract.evm)) { if (!_.isUndefined(compiledContract.evm.bytecode) && !_.isUndefined(compiledContract.evm.bytecode.object)) { From 8731a0618f2e9bfcf0ec8a82e87c2e3e9061f798 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 13 Dec 2018 11:34:35 +0100 Subject: [PATCH 74/76] Satisfy linter --- packages/sol-meta/src/cli.ts | 26 ++++------ packages/sol-meta/src/constructor.ts | 15 ++++-- packages/sol-meta/src/contract_mocker.ts | 13 +++-- packages/sol-meta/src/exposer.ts | 2 +- packages/sol-meta/src/flattener.ts | 22 +++++---- packages/sol-meta/src/linearization.ts | 60 ++++++++++++++---------- packages/sol-meta/src/parser.ts | 5 +- packages/sol-meta/src/scripter.ts | 1 - packages/sol-meta/src/solc_wrapper.ts | 2 +- packages/sol-meta/src/source_reader.ts | 16 ++++--- packages/sol-meta/src/stubber.ts | 12 ++--- packages/sol-meta/src/unparser.ts | 16 +++---- packages/sol-meta/src/utils.ts | 18 +++---- packages/sol-meta/src/visitor.ts | 6 ++- packages/sol-meta/test/index.ts | 22 +++++---- 15 files changed, 129 insertions(+), 107 deletions(-) diff --git a/packages/sol-meta/src/cli.ts b/packages/sol-meta/src/cli.ts index de7c53c18e..6a3ab636de 100644 --- a/packages/sol-meta/src/cli.ts +++ b/packages/sol-meta/src/cli.ts @@ -1,18 +1,17 @@ import 'source-map-support/register'; +import { logUtils } from '@0x/utils'; import * as _ from 'lodash'; import * as pathUtils from 'path'; import * as process from 'process'; -import * as S from 'solidity-parser-antlr'; import * as yargs from 'yargs'; -import { logUtils } from '@0x/utils'; import { ContractMockOptions, mockContract, mockContractName } from './contract_mocker'; import { compile } from './solc_wrapper'; import { readSources, SourceReaderOptions } from './source_reader'; import { unparse } from './unparser'; import * as utils from './utils'; -import { visit, Visitor } from './visitor'; +import { visit } from './visitor'; (async () => { // Parse command line arguments and handle help @@ -44,12 +43,12 @@ import { visit, Visitor } from './visitor'; // TODO(recmo): Refactor to use a typed CommandLineOptions object let options: Partial = {}; - let defaults: ContractMockOptions = { + const defaults: ContractMockOptions = { constructors: {}, scripted: {}, }; let contracts: { [contractName: string]: ContractMockOptions } = {}; - let outputPath = `${process.cwd()}/out`; + const outputPath = `${process.cwd()}/out`; // Handle config file if (!_.isUndefined(argv.config)) { @@ -70,7 +69,7 @@ import { visit, Visitor } from './visitor'; // Handle command line arguments if (!_.isUndefined(argv.remapping)) { - options.remapping = _.reduce( + options.remapping = _.reduce( _.map(argv.remapping as string[], (str: string) => str.split('=', 2)), (remappingsAccumulator, [from, to]) => ({ ...remappingsAccumulator, [from]: to }), {}, @@ -84,9 +83,7 @@ import { visit, Visitor } from './visitor'; options.sources = [...options.sources, ...argv._]; // Parse all sources - console.time('Parsing sources'); const sources = await readSources(options.sources, options); - console.timeEnd('Parsing sources'); for (const contractName of _.keys(contracts)) { logUtils.log(`\n${contractName}`); @@ -102,21 +99,17 @@ import { visit, Visitor } from './visitor'; }; // Find path - const path = _.find(_.keys(sources), path => contractName in sources[path].contracts); - if (_.isUndefined(path)) { + const contractPath = _.find(_.keys(sources), path => contractName in sources[path].contracts); + if (_.isUndefined(contractPath)) { throw new Error(`Could not find contract ${contractName}.`); } // Create mock contract - console.time('Generating mocks'); - const mock = mockContract(sources, path, contractName, spec); - console.timeEnd('Generating mocks'); + const mock = mockContract(sources, contractPath, contractName, spec); // Optionally test if (!_.isUndefined(argv.test)) { - console.time('Test compile'); await compile(sources, mock); - console.timeEnd('Test compile'); } // Make import paths relative @@ -131,9 +124,7 @@ import { visit, Visitor } from './visitor'; ); // Write file - console.time('Writing mocks'); await utils.writeFileAsync(`${outputPath}/${mockContractName(contractName)}.sol`, unparse(mock)); - console.timeEnd('Writing mocks'); } // Exit successfully @@ -141,6 +132,7 @@ import { visit, Visitor } from './visitor'; // Catch errors, log and exit with failure })().catch(err => { + // tslint:disable-next-line:no-console console.error(err); process.exit(1); }); diff --git a/packages/sol-meta/src/constructor.ts b/packages/sol-meta/src/constructor.ts index c0ee2570fe..5abcb52305 100644 --- a/packages/sol-meta/src/constructor.ts +++ b/packages/sol-meta/src/constructor.ts @@ -7,6 +7,9 @@ export interface ConstructorArguments { [contractName: string]: utils.Literal[]; } +/** + * Creates a constructor function AST node with a given parameter list + */ export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefinition { // TODO: Only include actually used constructors. return { @@ -32,10 +35,14 @@ export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefin }; } -// Solidity by itself does not give an error when a contract is unintentionally -// abstract. We can force Solidity to produce an error and point us to the -// abstract function by trying to runtime instantiate the contract. This function -// produces a small contract that tries to instantiate the given contract. +/** + * Solidity by itself does not give an error when a contract is unintentionally + * abstract. We can force Solidity to produce an error and point us to the + * abstract function by trying to runtime instantiate the contract. This function + * produces a small contract that tries to instantiate the given contract. + * + * @param contractName Name of the contract to force not-abstract. + */ export function nonAbstractForcer(contractName: string): S.ContractDefinition { // contract SomeContractNonAbstractForcer { // constructor() { diff --git a/packages/sol-meta/src/contract_mocker.ts b/packages/sol-meta/src/contract_mocker.ts index 014decf454..f47ac3bc4b 100644 --- a/packages/sol-meta/src/contract_mocker.ts +++ b/packages/sol-meta/src/contract_mocker.ts @@ -4,11 +4,10 @@ import * as S from 'solidity-parser-antlr'; import { makeConstructor, nonAbstractForcer } from './constructor'; import { exposeNode } from './exposer'; import { flattenContract } from './flattener'; -import { stubFunction } from './stubber'; import { FunctionScript, scriptFunction } from './scripter'; import { SourceCollection } from './source_reader'; +import { stubFunction } from './stubber'; import * as utils from './utils'; -import { unparse } from './unparser'; export const mockContractName = (contractName: string) => `${contractName}Mock`; @@ -21,6 +20,13 @@ export interface ContractMockOptions { }; } +/** + * Mock a contract by implementing all abstract functions. + * @param sources The collection of relevant sources. + * @param path Absolute path to the contract source. + * @param contractName Name of the contract in the source. + * @param options Mocking options such as constructors and scripted behaviour. + */ export function mockContract( sources: SourceCollection, path: string, @@ -35,7 +41,6 @@ export function mockContract( } const [flat, parents] = flattenContract(sourceInfo.contracts[contractName], name => { if (!(name in sourceInfo.scope)) { - console.log(Object.keys(sourceInfo.scope)); throw new Error(`Contract ${name} not in scope of ${path}.`); } return sourceInfo.scope[name]; @@ -87,7 +92,7 @@ export function mockContract( ], subNodes: [ // Call parent constructors - makeConstructor(utils.objectFilter(options.constructors, (key, _) => constructors.includes(key))), + makeConstructor(utils.objectFilter(options.constructors, key => constructors.includes(key))), // Expose things marked `internal` ...utils.flatMap(flat.subNodes, exposeNode), diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index ae43ea1464..c21836badf 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -94,7 +94,7 @@ const wrapFunction = (func: S.FunctionDefinition): S.FunctionDefinition => { const params = nameParameters(func.parameters); const call: S.FunctionCall = { type: S.NodeType.FunctionCall, - expression: identifier(func.name as string), + expression: identifier(func.name), arguments: argumentExpressions(params), names: [], }; diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts index d3dadf58cb..11be0d2019 100644 --- a/packages/sol-meta/src/flattener.ts +++ b/packages/sol-meta/src/flattener.ts @@ -1,27 +1,25 @@ import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; -import { linearize, linearizeAsync } from './linearization'; -import * as utils from './utils'; +import { linearize } from './linearization'; // See: https://solidity.readthedocs.io/en/v0.4.25/contracts.html#inheritance // Merge a base contract into a derived contract function merge(base: S.ContractMember[], derived: S.ContractMember[]): S.ContractMember[] { // Extracts functions by name from contract members - const functions = (contract: S.ContractMember[]): { [name: string]: S.FunctionDefinition } => + const functions = (contract: S.ContractMember[]) => (contract.filter( node => node.type === S.NodeType.FunctionDefinition && !node.isConstructor, - ) as S.FunctionDefinition[]).reduce( + ) as S.FunctionDefinition[]).reduce<{ [name: string]: S.FunctionDefinition }>( (a, func: S.FunctionDefinition) => ({ ...a, [func.name as string]: func }), {}, ); - const modifiers = (contract: S.ContractMember[]): { [name: string]: S.FunctionDefinition } => - (contract.filter(({ type }) => type === S.NodeType.ModifierDefinition) as S.ModifierDefinition[]).reduce( - (a, mod: S.ModifierDefinition) => ({ ...a, [mod.name]: mod }), - {}, - ); + const modifiers = (contract: S.ContractMember[]) => + (contract.filter(({ type }) => type === S.NodeType.ModifierDefinition) as S.ModifierDefinition[]).reduce<{ + [name: string]: S.ModifierDefinition; + }>((a, mod: S.ModifierDefinition) => ({ ...a, [mod.name]: mod }), {}); const others = (contract: S.ContractMember[]): S.ContractMember[] => contract.filter(({ type }) => type !== S.NodeType.ModifierDefinition && type !== S.NodeType.FunctionDefinition); @@ -50,7 +48,11 @@ function merge(base: S.ContractMember[], derived: S.ContractMember[]): S.Contrac return [...others(base), ...others(derived), ...Object.values(mergedModifiers), ...Object.values(mergedFunctions)]; } -// Inline the inheritance hierarchy of a contract +/** + * Inline the inheritance hierarchy of a contract + * @param contract Contract to flatten + * @param resolve Function to lookup contract definition by name + */ export function flattenContract( contract: S.ContractDefinition, resolve: (name: string) => S.ContractDefinition, diff --git a/packages/sol-meta/src/linearization.ts b/packages/sol-meta/src/linearization.ts index 42d3356d44..4f18176e76 100644 --- a/packages/sol-meta/src/linearization.ts +++ b/packages/sol-meta/src/linearization.ts @@ -3,12 +3,16 @@ import * as _ from 'lodash'; // Implements C3 Linearization as used in Solidity // see: https://www.python.org/download/releases/2.3/mro/ -// Produce an array such that each input array is a subarray of the result. -// In other words, if the inputs specify lists sorted according to some partial -// order produce a sorted list of all elements compatible. -// -// NOTE: merge(a.reverse(), b.reversed(), ...) = merge(a, b, ...).reversed() -// (or at least equal up to the implied partial order) +/** + * Produce an array such that each input array is a subarray of the result. + * In other words, if the inputs specify lists sorted according to some partial + * order produce a sorted list of all elements compatible. + * + * NOTE: merge(a.reverse(), b.reversed(), ...) = merge(a, b, ...).reversed() + * (or at least equal up to the implied partial order) + * + * @param ilists List of input lists. + */ export function merge(...ilists: T[][]): T[] { // Only consider non-empty lists const lists = ilists.filter(x => x.length > 0); @@ -35,29 +39,37 @@ export function merge(...ilists: T[][]): T[] { return [goodHead, ...merge(...newLists)]; } -// Given a final element and a parents function, compute the C3 linearization -// of all ancestors of the final element. -// -// NOTE: Solidity has its ancestors in reverse order (base to most derived) -// compared to the Python C3 Linearization algorithm (most derived to -// base). This version uses the Solidity convention. -// -// NOTE: The nature of the algorithm makes it so that some cases where a -// linearization does exists, it is not found. The way merge picks -// an arbitrary solution adds additional constraints wich can lead -// to conflicts later on. It would be better if instead of recursion, -// a single large merge was done with all the constraints. But then -// it would no longer be C3 linearization. +/** + * Given a final element and a parents function, compute the C3 linearization + * of all ancestors of the final element. + * + * NOTE: Solidity has its ancestors in reverse order (base to most derived) + * compared to the Python C3 Linearization algorithm (most derived to + * base). This version uses the Solidity convention. + * + * NOTE: The nature of the algorithm makes it so that some cases where a + * linearization does exists, it is not found. The way merge picks + * an arbitrary solution adds additional constraints wich can lead + * to conflicts later on. It would be better if instead of recursion, + * a single large merge was done with all the constraints. But then + * it would no longer be C3 linearization. + * + * @param final The element to compute the linearization for. + * @param parents A function mapping the ordered list of parents for a given element. + */ export function linearize(final: T, parents: (_: T) => T[]): T[] { - // TODO: Memoization - // TODO: Throw on cycles (instead of stack overflowing) + // TODO(recmo): Memoization + // TODO(recmo): Throw on cycles (instead of stack overflowing) const p = parents(final); const recurse = _.map(p, a => linearize(a, parents)); return [...merge(p, ...recurse), final]; } -export async function linearizeAsync(final: T, parents: (_: T) => Promise): Promise { - const p = await parents(final); - const recurse = await Promise.all(_.map(p, a => linearizeAsync(a, parents))); +/** + * Asynchronous version of linearize. See linearize. + */ +export async function linearizeAsync(final: T, parentsAsync: (_: T) => Promise): Promise { + const p = await parentsAsync(final); + const recurse = await Promise.all(_.map(p, async a => linearizeAsync(a, parentsAsync))); return [...merge(p, ...recurse), final]; } diff --git a/packages/sol-meta/src/parser.ts b/packages/sol-meta/src/parser.ts index e53d17467f..8c00bf3dfd 100644 --- a/packages/sol-meta/src/parser.ts +++ b/packages/sol-meta/src/parser.ts @@ -1,8 +1,7 @@ import * as parser from 'solidity-parser-antlr'; -export const parse = (source: string): parser.SourceUnit => - parser.parse(source, {}); +export const parse = (source: string): parser.SourceUnit => parser.parse(source, {}); // TODO: Replace [] with empty parameter list on modifiers -// TODO: +// TODO: // TODO: Push these fixes to upstream and remove them here. diff --git a/packages/sol-meta/src/scripter.ts b/packages/sol-meta/src/scripter.ts index 55baf54d4d..3fa0d53f57 100644 --- a/packages/sol-meta/src/scripter.ts +++ b/packages/sol-meta/src/scripter.ts @@ -2,7 +2,6 @@ import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; -import { visit, Visitor } from './visitor'; export interface FunctionScriptReturn { inputs: { [argName: string]: string }; diff --git a/packages/sol-meta/src/solc_wrapper.ts b/packages/sol-meta/src/solc_wrapper.ts index 2b4e76aa1f..557e626460 100644 --- a/packages/sol-meta/src/solc_wrapper.ts +++ b/packages/sol-meta/src/solc_wrapper.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash'; -import * as S from 'solidity-parser-antlr'; import { ImportContents, StandardOutput } from 'solc'; +import * as S from 'solidity-parser-antlr'; import { Compiler as Solc } from '@0x/sol-compiler'; diff --git a/packages/sol-meta/src/source_reader.ts b/packages/sol-meta/src/source_reader.ts index 6894007dd6..98755d0dcb 100644 --- a/packages/sol-meta/src/source_reader.ts +++ b/packages/sol-meta/src/source_reader.ts @@ -1,8 +1,8 @@ +import { Deferred } from '@0x/utils'; import * as glob from 'glob'; import * as _ from 'lodash'; import * as pathUtils from 'path'; import * as S from 'solidity-parser-antlr'; -import { Deferred } from '@0x/utils'; import * as utils from './utils'; @@ -41,7 +41,7 @@ export class ContractReader { private readonly _opts: SourceReaderOptions; - private _result: { + private readonly _result: { [path: string]: Promise; }; @@ -67,10 +67,10 @@ export class ContractReader { public async processSourcesAsync(): Promise { // Read all contract sources and imports - await Promise.all(_.map(this._opts.sources, path => this._readSourceAsync(path))); + await Promise.all(_.map(this._opts.sources, async path => this._readSourceAsync(path))); // Resolve the result - return utils.objectPromise(this._result); + return utils.objectPromiseAsync(this._result); } // Takes an import path and returns the absolute file path @@ -130,12 +130,14 @@ export class ContractReader { const imports = parsed.children.filter( ({ type }) => type === S.NodeType.ImportDirective, ) as S.ImportDirective[]; - const importPaths = await Promise.all(_.map(imports, ({ path }) => this._resolveAsync(absolutePath, path))); + const importPaths = await Promise.all( + _.map(imports, async ({ path }) => this._resolveAsync(absolutePath, path)), + ); // Recursively parse imported sources // Note: This will deadlock on cyclical imports. (We will end up awaiting our own promise.) // TODO: Throw an error instead. - const importInfo = await Promise.all(_.map(importPaths, path => this._readSourceAsync(path))); + const importInfo = await Promise.all(_.map(importPaths, async path => this._readSourceAsync(path))); // Compute global scope include imports // TODO: Support `SomeContract as SomeAlias` in import directives. @@ -145,7 +147,7 @@ export class ContractReader { }); // Add local contracts - let contracts: { [name: string]: S.ContractDefinition } = {}; + const contracts: { [name: string]: S.ContractDefinition } = {}; utils.contracts(parsed).forEach(contract => { contracts[contract.name] = contract; scope[contract.name] = contract; diff --git a/packages/sol-meta/src/stubber.ts b/packages/sol-meta/src/stubber.ts index 3f660279c5..8f08ed6290 100644 --- a/packages/sol-meta/src/stubber.ts +++ b/packages/sol-meta/src/stubber.ts @@ -2,11 +2,9 @@ import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; import * as utils from './utils'; -import { makeConstructor, ConstructorArguments } from './constructor'; -import { identifier, nameParameters, argumentExpressions } from './utils'; -import { visit, Visitor } from './visitor'; -import { FunctionScript, scriptFunction } from './scripter'; -import { exposeNode } from './exposer'; +// Note(recmo): False positive, see https://github.com/palantir/tslint/issues/3418 +// tslint:disable-next-line:no-duplicate-imports +import { argumentExpressions, identifier, nameParameters } from './utils'; // Todo rename to stubber. @@ -121,7 +119,9 @@ const makeResultType = (name: string, fields: S.ParameterList): S.StructDefiniti members: [ variableDeclaration('_enabled', utils.types.bool), variableDeclaration('_reverts', utils.types.bool), - ..._.map(fields.parameters, ({ name, typeName }) => variableDeclaration(name as string, typeName)), + ..._.map(fields.parameters, ({ name: memberName, typeName }) => + variableDeclaration(memberName as string, typeName), + ), ], }); diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 9ca24b215d..0483102964 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -63,7 +63,7 @@ const visitor: Visitor = { unparse(parameters) + '\n' + indent( - (visibility && visibility != 'default' ? visibility + ' ' : '') + + (visibility && visibility !== 'default' ? visibility + ' ' : '') + (stateMutability || '') + _.map(modifiers, unparse).join('\n') + (returnParameters ? `\nreturns ${unparse(returnParameters)}` : ''), @@ -82,7 +82,7 @@ const visitor: Visitor = { Block: ({ statements }) => block(_.map(statements, unparse).join('\n')), VariableDeclarationStatement: ({ variables, initialValue }) => - _.map(variables, unparse) + (initialValue ? ` = ${unparse(initialValue)};` : ';'), + _.map(variables, unparse).join(' ') + (initialValue ? ` = ${unparse(initialValue)};` : ';'), ExpressionStatement: ({ expression }) => `${unparen(unparse(expression))};`, @@ -120,7 +120,7 @@ const visitor: Visitor = { Identifier: ({ name }) => name, - BooleanLiteral: ({ value }) => (value ? 'true' : 'false'), + BooleanLiteral: ({ value: isTrue }) => (isTrue ? 'true' : 'false'), NumberLiteral: ( { number: value, subdenomination }, // TODO subdenomination @@ -150,7 +150,7 @@ const visitor: Visitor = { `${unparse(typeName)} ` + (isIndexed ? 'indexed ' : '') + (storageLocation ? storageLocation + ' ' : '') + - (visibility && visibility != 'default' ? visibility + ' ' : '') + + (visibility && visibility !== 'default' ? visibility + ' ' : '') + (isDeclaredConst ? 'constant ' : '') + `${name}` + (expression ? ` = ${unparse(expression)}` : ''), @@ -170,7 +170,7 @@ const visitor: Visitor = { `let ${_.map(names, unparse).join(', ')} := ${unparse(expression)}`, AssemblyCall: ({ functionName, arguments: args }) => - args.length == 0 ? functionName : `${functionName}(${_.map(args, unparse).join(', ')})`, + args.length === 0 ? functionName : `${functionName}(${_.map(args, unparse).join(', ')})`, AssemblyIf: ({ condition, body }) => `if ${unparse(condition)} ${unparse(body)}`, @@ -178,16 +178,14 @@ const visitor: Visitor = { AssemblySwitch: ({ expression, cases }) => `switch ${unparse(expression)}\n${_.map(cases, unparse).join('\n')}`, - AssemblyCase: ({ value, block }) => `case ${unparse(value)} ${unparse(block)}`, + AssemblyCase: ({ value, block: asmBlock }) => `case ${unparse(value)} ${unparse(asmBlock)}`, DecimalNumber: ({ value }) => value, HexNumber: ({ value }) => value, ASTNode: node => { - console.log(node); - console.trace(); - return `<${node.type}>`; + throw new Error(`Unsupported node type ${node.type}`); }, }; diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 657aea093d..0042b968ed 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -7,7 +7,7 @@ export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).co // TODO: Use Map instead? export const objectZip = (keys: string[], values: T[]): { [key: string]: T } => - keys.reduce( + keys.reduce<{ [key: string]: T }>( (others, key, index) => ({ ...others, [key]: values[index], @@ -19,26 +19,26 @@ export const objectZip = (keys: string[], values: T[]): { [key: string]: T } export const objectMap = (obj: { [key: string]: A }, f: (v: A) => B): { [key: string]: B } => objectZip(Object.keys(obj), _.map(_.values(obj), f)); -export const objectPromise = async (obj: { [key: string]: Promise }): Promise<{ [key: string]: T }> => +export const objectPromiseAsync = async (obj: { [key: string]: Promise }): Promise<{ [key: string]: T }> => objectZip(Object.keys(obj), await Promise.all(Object.values(obj))); export const objectFilter = (obj: { [key: string]: A }, f: (key: string, v: A) => boolean): { [key: string]: A } => Object.keys(obj) .filter(key => f(key, obj[key])) - .reduce((a, key) => ({ ...a, [key]: obj[key] }), {}); + .reduce<{ [key: string]: A }>((a, key) => ({ ...a, [key]: obj[key] }), {}); -export const existsAsync = (path: string): Promise => - new Promise((resolve, reject) => +export const existsAsync = async (path: string): Promise => + new Promise((resolve, reject) => fs.access(path, fs.constants.R_OK, error => (error ? resolve(false) : resolve(true))), ); -export const readFileAsync = (path: string): Promise => - new Promise((resolve, reject) => +export const readFileAsync = async (path: string): Promise => + new Promise((resolve, reject) => fs.readFile(path, 'utf-8', (error, contents) => (error ? reject(error) : resolve(contents))), ); -export const writeFileAsync = (path: string, contents: string): Promise => - new Promise((resolve, reject) => +export const writeFileAsync = async (path: string, contents: string): Promise => + new Promise((resolve, reject) => fs.writeFile(path, contents, 'utf-8', error => (error ? reject(error) : resolve())), ); diff --git a/packages/sol-meta/src/visitor.ts b/packages/sol-meta/src/visitor.ts index 440ea3a27b..38f01489c0 100644 --- a/packages/sol-meta/src/visitor.ts +++ b/packages/sol-meta/src/visitor.ts @@ -87,7 +87,11 @@ export const isAssemblyExpression = (node: S.ASTNode): node is S.AssemblyExpress S.NodeType.HexNumber, ].includes(node); -// Dispatches based on node type +/** + * Dispatches based on node type. + * @param node The node to dispatch on. + * @param visitor A structure containing handlers for different node types. + */ export function visit(node: S.ASTNode, visitor: Visitor): T { // Try to dispatch on the exact type const indexed = visitor as { [type: string]: (node: S.ASTNode) => T }; diff --git a/packages/sol-meta/test/index.ts b/packages/sol-meta/test/index.ts index 84c66954f6..7a5fe6e10c 100644 --- a/packages/sol-meta/test/index.ts +++ b/packages/sol-meta/test/index.ts @@ -3,20 +3,20 @@ import * as fs from 'fs'; import * as glob from 'glob'; import * as _ from 'lodash'; import 'mocha'; -import * as path from 'path'; +import * as pathUtils from 'path'; import * as S from 'solidity-parser-antlr'; +import { mockContract } from '../src/contract_mocker'; import { parse } from '../src/parser'; -import { readSources, SourceInfo, SourceCollection } from '../src/source_reader'; -import { unparse } from '../src/unparser'; import { compile } from '../src/solc_wrapper'; -import { mockContract } from '../src/contract_mocker'; +import { readSources, SourceCollection } from '../src/source_reader'; +import { unparse } from '../src/unparser'; const expect = chai.expect; const findContracts = (searchPath: string) => glob.sync(searchPath).map(file => ({ - name: path.basename(file, '.sol'), + name: pathUtils.basename(file, '.sol'), source: fs.readFileSync(file, 'utf8'), })); @@ -24,7 +24,8 @@ const contracts = findContracts('../contracts/contracts/**/*.sol'); describe('Parser', () => { it('should have test contracts', () => { - expect(contracts).to.have.lengthOf.above(10); + const MINIMUM_CONTRACTS_FOR_TESTS = 10; + expect(contracts).to.have.lengthOf.above(MINIMUM_CONTRACTS_FOR_TESTS); }); contracts.forEach(({ name, source }) => @@ -39,9 +40,9 @@ describe('Unparser', () => { it(`should unparse ${name}`, () => { const ast = parse(source); const src = unparse(ast); - const ast2 = parse(src); + parse(src); // Ideally, we would test the following: - // expect(ast2).to.deep.equal(ast); + // expect(parse(src)).to.deep.equal(ast); // But this fails on on expressiong like `2 * 3 + 1` which get rewritten // to `((2 * 2) + 1)`. This prevents the ASTs from being identicall in // syntax, even though they should be identical in meaning. @@ -54,7 +55,7 @@ describe('Mocker', () => { const toMock = ['Exchange', 'MixinExchangeCore', 'MixinSignatureValidator', 'MixinWrapperFunctions']; const path = (name: string) => `${sourcePath}/${name}.sol`; let sources: SourceCollection; - let mocks: { [name: string]: S.SourceUnit } = {}; + const mocks: { [name: string]: S.SourceUnit } = {}; it('should read sources', async () => { sources = await readSources(_.map(toMock, path)); @@ -77,10 +78,11 @@ describe('Mocker', () => { }), ); // Note(recmo): These tests are slow + const MAX_TIME_MILLISECONDS = 60000; describe.skip('Compiling', () => _.map(toMock, name => it(`should compile mock for ${name}`, async () => { await compile(sources, mocks[name]); - }).timeout(30000), + }).timeout(MAX_TIME_MILLISECONDS), )); }); From fe3761efe78e63f6336a142a51876f62dfb34c43 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 13 Dec 2018 11:40:40 +0100 Subject: [PATCH 75/76] Tag todos --- packages/sol-meta/src/constructor.ts | 2 +- packages/sol-meta/src/contract_mocker.ts | 6 +++--- packages/sol-meta/src/exposer.ts | 2 +- packages/sol-meta/src/flattener.ts | 10 +++++----- packages/sol-meta/src/parser.ts | 5 ++--- packages/sol-meta/src/solc_wrapper.ts | 4 ++-- packages/sol-meta/src/source_reader.ts | 4 ++-- packages/sol-meta/src/stubber.ts | 4 +--- packages/sol-meta/src/unparser.ts | 8 ++++---- packages/sol-meta/src/utils.ts | 8 ++++---- 10 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/sol-meta/src/constructor.ts b/packages/sol-meta/src/constructor.ts index 5abcb52305..0be50d0c36 100644 --- a/packages/sol-meta/src/constructor.ts +++ b/packages/sol-meta/src/constructor.ts @@ -11,7 +11,7 @@ export interface ConstructorArguments { * Creates a constructor function AST node with a given parameter list */ export function makeConstructor(consArgs: ConstructorArguments): S.FunctionDefinition { - // TODO: Only include actually used constructors. + // TODO(recmo): Only include actually used constructors. return { type: S.NodeType.FunctionDefinition, name: null, diff --git a/packages/sol-meta/src/contract_mocker.ts b/packages/sol-meta/src/contract_mocker.ts index f47ac3bc4b..a0717d2983 100644 --- a/packages/sol-meta/src/contract_mocker.ts +++ b/packages/sol-meta/src/contract_mocker.ts @@ -47,7 +47,7 @@ export function mockContract( }); // Find all constructors that require arguments - // TODO: Ignore constructors already called from other constructors + // TODO(recmo): Ignore constructors already called from other constructors const constructors = _.map( _.filter(parents, ({ subNodes }) => subNodes.some( @@ -68,7 +68,7 @@ export function mockContract( }); // Find all abstract functions - // TODO: Public member variables generate implicit getter functions that can satisfy an abstract. + // TODO(recmo): Public member variables generate implicit getter functions that can satisfy an abstract. const abstracts = flat.subNodes.filter( node => node.type === S.NodeType.FunctionDefinition && node.body === null, ) as S.FunctionDefinition[]; @@ -113,7 +113,7 @@ export function mockContract( ...sources[path].parsed.children.filter(({ type }) => type === S.NodeType.PragmaDirective), // Add an import to include the source file - // TODO: Make relative + // TODO(recmo): Make relative { type: S.NodeType.ImportDirective, path, diff --git a/packages/sol-meta/src/exposer.ts b/packages/sol-meta/src/exposer.ts index c21836badf..d7b0898e4d 100644 --- a/packages/sol-meta/src/exposer.ts +++ b/packages/sol-meta/src/exposer.ts @@ -217,7 +217,7 @@ const visitor: Visitor = { return [getter(node)]; } return [getter(node), setter(node)]; - // TODO: handle mappings: The keys become additional + // TODO(recmo): handle mappings: The keys become additional // function arguments to the getter and setter. }, diff --git a/packages/sol-meta/src/flattener.ts b/packages/sol-meta/src/flattener.ts index 11be0d2019..d26c2b62d6 100644 --- a/packages/sol-meta/src/flattener.ts +++ b/packages/sol-meta/src/flattener.ts @@ -38,12 +38,12 @@ function merge(base: S.ContractMember[], derived: S.ContractMember[]): S.Contrac ...modifiers(derived), }; - // TODO: Merge constructors - // TODO: Implement rules that enforce type signature and visibility etc. + // TODO(recmo): Merge constructors + // TODO(recmo): Implement rules that enforce type signature and visibility etc. // to be preserved when overriding. - // TODO: Check other objects than functions. - // TODO: Sort members by type - // TODO: Less derived functions remain available through `super` + // TODO(recmo): Check other objects than functions. + // TODO(recmo): Sort members by type + // TODO(recmo): Less derived functions remain available through `super` // and `SomeBase.functionName(...)`. return [...others(base), ...others(derived), ...Object.values(mergedModifiers), ...Object.values(mergedFunctions)]; } diff --git a/packages/sol-meta/src/parser.ts b/packages/sol-meta/src/parser.ts index 8c00bf3dfd..2313dff381 100644 --- a/packages/sol-meta/src/parser.ts +++ b/packages/sol-meta/src/parser.ts @@ -2,6 +2,5 @@ import * as parser from 'solidity-parser-antlr'; export const parse = (source: string): parser.SourceUnit => parser.parse(source, {}); -// TODO: Replace [] with empty parameter list on modifiers -// TODO: -// TODO: Push these fixes to upstream and remove them here. +// TODO(recmo): Replace [] with empty parameter list on modifiers +// TODO(recmo): Push these fixes to upstream and remove them here. diff --git a/packages/sol-meta/src/solc_wrapper.ts b/packages/sol-meta/src/solc_wrapper.ts index 557e626460..04714090ed 100644 --- a/packages/sol-meta/src/solc_wrapper.ts +++ b/packages/sol-meta/src/solc_wrapper.ts @@ -17,7 +17,7 @@ export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { const compiler = await Solc.getSolcAsync(version); // Solidity standard JSON input - // TODO: Typescript typings + // TODO(recmo): Typescript typings // See: https://solidity.readthedocs.io/en/develop/using-the-compiler.html#compiler-input-and-output-json-description const input = { language: 'Solidity', @@ -27,7 +27,7 @@ export const compile = async (sources: SourceCollection, ast: S.SourceUnit) => { }, settings: { remappings: { - // TODO + // TODO(recmo) }, }, outputSelection: { diff --git a/packages/sol-meta/src/source_reader.ts b/packages/sol-meta/src/source_reader.ts index 98755d0dcb..25f31ed010 100644 --- a/packages/sol-meta/src/source_reader.ts +++ b/packages/sol-meta/src/source_reader.ts @@ -136,11 +136,11 @@ export class ContractReader { // Recursively parse imported sources // Note: This will deadlock on cyclical imports. (We will end up awaiting our own promise.) - // TODO: Throw an error instead. + // TODO(recmo): Throw an error instead. const importInfo = await Promise.all(_.map(importPaths, async path => this._readSourceAsync(path))); // Compute global scope include imports - // TODO: Support `SomeContract as SomeAlias` in import directives. + // TODO(recmo): Support `SomeContract as SomeAlias` in import directives. let scope: { [name: string]: S.ContractDefinition } = {}; importInfo.forEach(({ scope: importedContracts }) => { scope = { ...scope, ...importedContracts }; diff --git a/packages/sol-meta/src/stubber.ts b/packages/sol-meta/src/stubber.ts index 8f08ed6290..58071b91c8 100644 --- a/packages/sol-meta/src/stubber.ts +++ b/packages/sol-meta/src/stubber.ts @@ -6,9 +6,7 @@ import * as utils from './utils'; // tslint:disable-next-line:no-duplicate-imports import { argumentExpressions, identifier, nameParameters } from './utils'; -// Todo rename to stubber. - -// TODO: both Actions and Functions can throw in addition to returning. Throwing should take arbitrary data. +// TODO(recmo): both Actions and Functions can throw in addition to returning. Throwing should take arbitrary data. export const mockContractName = (contractName: string) => `${contractName}Mock`; export const programmerName = (funcName: string) => `${funcName}Mock`; diff --git a/packages/sol-meta/src/unparser.ts b/packages/sol-meta/src/unparser.ts index 0483102964..32b37c964a 100644 --- a/packages/sol-meta/src/unparser.ts +++ b/packages/sol-meta/src/unparser.ts @@ -1,4 +1,4 @@ -// TODO: instead use https://github.com/prettier-solidity/prettier-plugin-solidity/blob/master/src/printer.js +// TODO(recmo): instead use https://github.com/prettier-solidity/prettier-plugin-solidity/blob/master/src/printer.js import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; @@ -103,7 +103,7 @@ const visitor: Visitor = { `for (${unparse(i).replace(';', '')}; ${unparse(c)}; ${unparse(l).replace(';', '')}) ${unparse(body)}`, InlineAssemblyStatement: ( - { language, body }, // TODO language + { language, body }, // TODO(recmo): use language ) => `assembly ${unparse(body)}`, // Types @@ -123,13 +123,13 @@ const visitor: Visitor = { BooleanLiteral: ({ value: isTrue }) => (isTrue ? 'true' : 'false'), NumberLiteral: ( - { number: value, subdenomination }, // TODO subdenomination + { number: value, subdenomination }, // TODO(recmo): subdenomination ) => value, StringLiteral: ({ value }) => stresc(value), FunctionCall: ( - { expression, arguments: args, names }, // TODO: names + { expression, arguments: args, names }, // TODO(recmo): names ) => `(${unparse(expression)}(${_.map(args, unparse).join(', ')}))`, Conditional: ({ condition, trueExpression, falseExpression }) => diff --git a/packages/sol-meta/src/utils.ts b/packages/sol-meta/src/utils.ts index 0042b968ed..0976559205 100644 --- a/packages/sol-meta/src/utils.ts +++ b/packages/sol-meta/src/utils.ts @@ -2,10 +2,10 @@ import * as fs from 'fs'; import * as _ from 'lodash'; import * as S from 'solidity-parser-antlr'; -// TODO: Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap +// TODO(recmo): Replace with Array.flatMap https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap export const flatMap = (a: A[], f: ((a: A) => B[])): B[] => ([] as B[]).concat(..._.map(a, f)); -// TODO: Use Map instead? +// TODO(recmo): Use Map instead? export const objectZip = (keys: string[], values: T[]): { [key: string]: T } => keys.reduce<{ [key: string]: T }>( (others, key, index) => ({ @@ -15,7 +15,7 @@ export const objectZip = (keys: string[], values: T[]): { [key: string]: T } {}, ); -// TODO: Is the order in Object.keys and Object.values equal? +// TODO(recmo): Is the order in Object.keys and Object.values equal? export const objectMap = (obj: { [key: string]: A }, f: (v: A) => B): { [key: string]: B } => objectZip(Object.keys(obj), _.map(_.values(obj), f)); @@ -129,7 +129,7 @@ export const nameParameters = (params: S.ParameterList, prefix: string = '_arg') export const argumentExpressions = (params: S.ParameterList): S.Expression[] => _.map(params.parameters, ({ name }) => { - // TODO: rewrite using throw expressions or do notation + // TODO(recmo): rewrite using throw expressions or do notation if (name !== null) { return identifier(name); } else { From ad3b0b0ce99bc33baffab80e31504f6120ddf2a9 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 13 Dec 2018 11:42:24 +0100 Subject: [PATCH 76/76] Set codeowner on sol-meta --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 3cf75fb2df..94af97d9d6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -25,6 +25,7 @@ packages/monorepo-scripts/ @fabioberger packages/order-utils/ @fabioberger @LogvinovLeon packages/sol-compiler/ @LogvinovLeon packages/sol-cov/ @LogvinovLeon +packages/sol-meta/ @recmo packages/sol-resolver/ @LogvinovLeon packages/subproviders/ @fabioberger @dekz packages/verdaccio/ @albrow