diff --git a/src/types/ast/builtin_struct_type.ts b/src/types/ast/builtin_struct_type.ts new file mode 100644 index 00000000..16885733 --- /dev/null +++ b/src/types/ast/builtin_struct_type.ts @@ -0,0 +1,25 @@ +import { Range } from "../../misc"; +import { VersionDependentType } from "../utils"; +import { BuiltinType } from "./builtin_type"; + +/** + * A utility type for referencing builtin structs. + * It is not directly referenced in typeString parser grammar. + */ +export class BuiltinStructType extends BuiltinType { + readonly members: Map; + + constructor(name: string, members: Map, src?: Range) { + super(name, src); + + this.members = members; + } + + pp(): string { + return `builtin_struct ${this.name}`; + } + + getFields(): any[] { + return [this.name]; + } +} diff --git a/src/types/ast/import_ref_type.ts b/src/types/ast/import_ref_type.ts new file mode 100644 index 00000000..f85c2900 --- /dev/null +++ b/src/types/ast/import_ref_type.ts @@ -0,0 +1,33 @@ +import { ImportDirective } from "../../ast"; +import { assert, Range } from "../../misc"; +import { TypeNode } from "./type"; + +/** + * A utility type for referencing aliased unit member access. + * It is not directly referenced in typeString parser grammar. + */ +export class ImportRefType extends TypeNode { + readonly importStmt: ImportDirective; + + constructor(importStmt: ImportDirective, src?: Range) { + super(src); + + assert( + importStmt.vSymbolAliases.length === 0 && importStmt.unitAlias !== "", + `ImportRefTypes only applicable to unit alias imports, not ${importStmt.print()}` + ); + + this.importStmt = importStmt; + } + + getFields(): any[] { + return [this.importStmt]; + } + + pp(): string { + const path = this.importStmt.vSourceUnit.sourceEntryKey; + const alias = this.importStmt.unitAlias; + + return ``; + } +} diff --git a/src/types/ast/index.ts b/src/types/ast/index.ts index 26b9fd0e..78eb353f 100644 --- a/src/types/ast/index.ts +++ b/src/types/ast/index.ts @@ -1,10 +1,12 @@ export * from "./address"; export * from "./array"; export * from "./bool"; +export * from "./builtin_struct_type"; export * from "./builtin_type"; export * from "./bytes"; export * from "./fixed_bytes"; export * from "./function_type"; +export * from "./import_ref_type"; export * from "./int_literal"; export * from "./int_type"; export * from "./mapping_type"; diff --git a/src/types/builtins.ts b/src/types/builtins.ts new file mode 100644 index 00000000..8857e1c8 --- /dev/null +++ b/src/types/builtins.ts @@ -0,0 +1,246 @@ +import { DataLocation, FunctionStateMutability, FunctionVisibility } from "../ast/constants"; +import { + AddressType, + BoolType, + BuiltinStructType, + BytesType, + FixedBytesType, + FunctionType, + IntType, + PointerType +} from "./ast"; +import { VersionDependentType } from "./utils"; + +/** + * @see https://docs.soliditylang.org/en/latest/units-and-global-variables.html#special-variables-and-functions + * @see https://github.com/ethereum/solidity/releases/ + */ +export const BuiltinSymbols = new Map([ + /** + * @todo Add support for encode(), decode(), encodePacked(), encodeWithSelector(), encodeWithSignature() + * @see https://github.com/ethereum/solidity/releases/tag/v0.4.22 + */ + ["abi", [new BuiltinStructType("abi", new Map()), ">=0.4.22"]], + + /** + * @todo Add support for concat() + * @see https://github.com/ethereum/solidity/releases/tag/v0.8.4 + */ + ["bytes", [new BuiltinStructType("bytes", new Map()), ">=0.8.4"]], + [ + "block", + [ + new BuiltinStructType( + "block", + new Map([ + ["coinbase", [new AddressType(true), ">=0.4.13"]], + ["difficulty", [new IntType(256, false), ">=0.4.13"]], + ["gaslimit", [new IntType(256, false), ">=0.4.13"]], + ["number", [new IntType(256, false), ">=0.4.13"]], + ["timestamp", [new IntType(256, false), ">=0.4.13"]], + [ + "blockhash", + [ + new FunctionType( + undefined, + [new IntType(256, false)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.View + ), + "<0.5.0" + ] + ], + ["chainid", [new IntType(256, false), ">=0.8.0"]], + ["basefee", [new IntType(256, false), ">=0.8.7"]] + ]) + ), + ">=0.4.13" + ] + ], + [ + "msg", + [ + new BuiltinStructType( + "msg", + new Map([ + ["data", [new PointerType(new BytesType(), DataLocation.CallData), ">=0.4.13"]], + ["sender", [new AddressType(true), ">=0.4.13"]], + ["sig", [new FixedBytesType(4), ">=0.4.13"]], + ["value", [new IntType(256, false), ">=0.4.13"]], + ["gas", [new IntType(256, false), "<0.5.0"]] + ]) + ), + ">=0.4.13" + ] + ], + [ + "tx", + [ + new BuiltinStructType( + "tx", + new Map([ + ["gasprice", [new IntType(256, false), ">=0.4.13"]], + ["origin", [new AddressType(true), ">=0.4.13"]] + ]) + ), + ">=0.4.13" + ] + ], + [ + "blockhash", + [ + new FunctionType( + undefined, + [new IntType(256, false)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.View + ), + ">=0.4.22" + ] + ], + [ + "gasleft", + [ + new FunctionType( + undefined, + [], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.View + ), + ">=0.4.21" + ] + ], + [ + "now", + [ + new FunctionType( + undefined, + [], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.View + ), + "<0.7.0" + ] + ], + [ + "addmod", + [ + new FunctionType( + undefined, + [new IntType(256, false), new IntType(256, false), new IntType(256, false)], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ], + [ + "mulmod", + [ + new FunctionType( + undefined, + [new IntType(256, false), new IntType(256, false), new IntType(256, false)], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ], + /** + * @todo Add support for sha3() and suicide() before Solidity 0.5.0 + * @see https://github.com/ethereum/solidity/releases/tag/v0.5.0 + * @see https://docs.soliditylang.org/en/latest/050-breaking-changes.html + */ + [ + "keccak256", + [ + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ], + [ + "sha256", + [ + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ], + [ + "ripemd160", + [ + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(20)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ], + [ + "ecrecover", + [ + new FunctionType( + undefined, + [ + new FixedBytesType(32), + new IntType(8, false), + new FixedBytesType(32), + new FixedBytesType(32) + ], + [new AddressType(false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ), + ">=0.4.13" + ] + ] + /** + * @todo Add support for revert() and require(). Note their signature changes. + * @see https://github.com/ethereum/solidity/releases/tag/v0.4.22 + */ +]); + +export const BuiltinAddressMembers = new Map([ + ["balance", [new IntType(256, false), ">=0.4.13"]], + [ + "staticcall", + [ + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new BoolType(), new PointerType(new BytesType(), DataLocation.Memory)], + FunctionVisibility.Default, + FunctionStateMutability.View + ), + ">=0.4.13" + ] + ], + ["code", [new PointerType(new BytesType(), DataLocation.Memory), ">=0.8.0"]] +]); + +/** + * @todo Support type() members: min, max, interfaceId, name, runtimeCode, creationCode + * @see https://github.com/ethereum/solidity/releases/tag/v0.6.8 + * @see https://github.com/ethereum/solidity/releases/tag/v0.6.7 + * @see https://github.com/ethereum/solidity/releases/tag/v0.5.3 + */ diff --git a/src/types/index.ts b/src/types/index.ts index b3ba750a..60a478f3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ export * from "./ast"; +export * from "./builtins"; export * from "./typeStrings"; export * from "./utils"; diff --git a/src/types/utils.ts b/src/types/utils.ts index ae03a2c2..2ed3f0d8 100644 --- a/src/types/utils.ts +++ b/src/types/utils.ts @@ -1,3 +1,4 @@ +import { satisfies } from "semver"; import { ArrayTypeName, ContractDefinition, @@ -31,6 +32,17 @@ import { UserDefinedType } from "./ast"; +export type VersionDependentType = [TypeNode, string]; + +export function getTypeForCompilerVersion( + typing: VersionDependentType, + compilerVersion: string +): TypeNode | undefined { + const [type, version] = typing; + + return satisfies(compilerVersion, version) ? type : undefined; +} + /** * Given a general type 'pattern' that doesn't contain any data locations, and a data location, * produce a concrete instance of the general type for the target location. diff --git a/test/unit/types/builtins.spec.ts b/test/unit/types/builtins.spec.ts new file mode 100644 index 00000000..2752133e --- /dev/null +++ b/test/unit/types/builtins.spec.ts @@ -0,0 +1,260 @@ +import expect from "expect"; +import { + AddressType, + BoolType, + BuiltinAddressMembers, + BuiltinStructType, + BuiltinSymbols, + BytesType, + CompilerVersions, + DataLocation, + eq, + FixedBytesType, + FunctionStateMutability, + FunctionType, + FunctionVisibility, + getTypeForCompilerVersion, + IntType, + LatestCompilerVersion, + PointerType, + TypeNode, + VersionDependentType +} from "../../../src"; + +const cases: Array<[Map, string, string[], TypeNode | undefined]> = [ + [BuiltinSymbols, "abi", ["0.4.13", "0.4.21"], undefined], + [ + BuiltinSymbols, + "abi", + ["0.4.22", LatestCompilerVersion], + new BuiltinStructType("abi", new Map()) + ], + [BuiltinSymbols, "bytes", ["0.4.13", "0.8.3"], undefined], + [ + BuiltinSymbols, + "bytes", + ["0.8.4", LatestCompilerVersion], + new BuiltinStructType("bytes", new Map()) + ], + [BuiltinSymbols, "block.coinbase", ["0.4.13", LatestCompilerVersion], new AddressType(true)], + [ + BuiltinSymbols, + "block.difficulty", + ["0.4.13", LatestCompilerVersion], + new IntType(256, false) + ], + [BuiltinSymbols, "block.gaslimit", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "block.number", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "block.timestamp", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "block.blockhash", ["0.5.0", LatestCompilerVersion], undefined], + [ + BuiltinSymbols, + "block.blockhash", + ["0.4.13", "0.4.26"], + new FunctionType( + undefined, + [new IntType(256, false)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.View + ) + ], + [BuiltinSymbols, "block.chainid", ["0.4.13", "0.7.6"], undefined], + [BuiltinSymbols, "block.chainid", ["0.8.0", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "block.basefee", ["0.4.13", "0.8.6"], undefined], + [BuiltinSymbols, "block.basefee", ["0.8.7", LatestCompilerVersion], new IntType(256, false)], + [ + BuiltinSymbols, + "msg.data", + ["0.4.13", LatestCompilerVersion], + new PointerType(new BytesType(), DataLocation.CallData) + ], + [BuiltinSymbols, "msg.sender", ["0.4.13", LatestCompilerVersion], new AddressType(true)], + [BuiltinSymbols, "msg.sig", ["0.4.13", LatestCompilerVersion], new FixedBytesType(4)], + [BuiltinSymbols, "msg.value", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "msg.gas", ["0.5.0", LatestCompilerVersion], undefined], + [BuiltinSymbols, "msg.gas", ["0.4.13", "0.4.26"], new IntType(256, false)], + [BuiltinSymbols, "tx.gasprice", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [BuiltinSymbols, "tx.origin", ["0.4.13", LatestCompilerVersion], new AddressType(true)], + [BuiltinSymbols, "blockhash", ["0.4.13", "0.4.21"], undefined], + [ + BuiltinSymbols, + "blockhash", + ["0.4.22", LatestCompilerVersion], + new FunctionType( + undefined, + [new IntType(256, false)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.View + ) + ], + [BuiltinSymbols, "gasleft", ["0.4.13", "0.4.20"], undefined], + [ + BuiltinSymbols, + "gasleft", + ["0.4.21", LatestCompilerVersion], + new FunctionType( + undefined, + [], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.View + ) + ], + [BuiltinSymbols, "now", ["0.7.0", LatestCompilerVersion], undefined], + [ + BuiltinSymbols, + "now", + ["0.4.13", "0.6.12"], + new FunctionType( + undefined, + [], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.View + ) + ], + [ + BuiltinSymbols, + "addmod", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new IntType(256, false), new IntType(256, false), new IntType(256, false)], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [ + BuiltinSymbols, + "mulmod", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new IntType(256, false), new IntType(256, false), new IntType(256, false)], + [new IntType(256, false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [ + BuiltinSymbols, + "keccak256", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [ + BuiltinSymbols, + "sha256", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(32)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [ + BuiltinSymbols, + "ripemd160", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new FixedBytesType(20)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [ + BuiltinSymbols, + "ecrecover", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [ + new FixedBytesType(32), + new IntType(8, false), + new FixedBytesType(32), + new FixedBytesType(32) + ], + [new AddressType(false)], + FunctionVisibility.Default, + FunctionStateMutability.Pure + ) + ], + [BuiltinAddressMembers, "balance", ["0.4.13", LatestCompilerVersion], new IntType(256, false)], + [ + BuiltinAddressMembers, + "staticcall", + ["0.4.13", LatestCompilerVersion], + new FunctionType( + undefined, + [new PointerType(new BytesType(), DataLocation.Memory)], + [new BoolType(), new PointerType(new BytesType(), DataLocation.Memory)], + FunctionVisibility.Default, + FunctionStateMutability.View + ) + ], + [BuiltinAddressMembers, "code", ["0.4.13", "0.7.6"], undefined], + [ + BuiltinAddressMembers, + "code", + ["0.8.0", LatestCompilerVersion], + new PointerType(new BytesType(), DataLocation.Memory) + ] +]; + +describe("getTypeForCompilerVersion() and builtin types", () => { + for (const [mapping, accessor, [startVersion, finishVersion], expectation] of cases) { + it(`${accessor} is ${ + expectation ? expectation.pp() : undefined + } since ${startVersion} up to ${finishVersion}`, () => { + const start = CompilerVersions.indexOf(startVersion); + const finish = CompilerVersions.indexOf(finishVersion); + + expect(start).toBeGreaterThan(-1); + expect(finish).toBeGreaterThan(-1); + + const versions = CompilerVersions.slice(start, finish + 1); + + const misses: string[] = []; + + for (const version of versions) { + const parts = accessor.split("."); + + let result: TypeNode | undefined; + let members = mapping; + + for (const name of parts) { + const typing = members.get(name); + + if (typing) { + result = getTypeForCompilerVersion(typing, version); + } + + if (result instanceof BuiltinStructType) { + members = result.members; + } + } + + const condition = expectation ? eq(result, expectation) : result === undefined; + + if (!condition) { + misses.push(version); + } + } + + expect(misses).toHaveLength(0); + }); + } +}); diff --git a/test/unit/types/import_ref.spec.ts b/test/unit/types/import_ref.spec.ts new file mode 100644 index 00000000..251d58fe --- /dev/null +++ b/test/unit/types/import_ref.spec.ts @@ -0,0 +1,58 @@ +import expect from "expect"; +import { ASTNodeFactory, ImportRefType } from "../../../src"; + +const factory = new ASTNodeFactory(); + +const testUnit = factory.makeSourceUnit("test.sol", 0, "path/to/test.sol", new Map()); +const targetUnit = factory.makeSourceUnit("other.sol", 1, "path/to/other.sol", new Map()); +const aliasedUnitImport = factory.makeImportDirective( + testUnit.sourceEntryKey, + testUnit.absolutePath, + "some", + [], + targetUnit.id, + testUnit.id +); + +const nonAliasedUnitImport = factory.makeImportDirective( + testUnit.sourceEntryKey, + testUnit.absolutePath, + "", + [], + targetUnit.id, + testUnit.id +); + +const aliasedSymbolsImport = factory.makeImportDirective( + testUnit.sourceEntryKey, + testUnit.absolutePath, + "", + [{ foreign: 0, local: "x" }], + targetUnit.id, + testUnit.id +); + +targetUnit.appendChild(aliasedUnitImport); +targetUnit.appendChild(nonAliasedUnitImport); +targetUnit.appendChild(aliasedSymbolsImport); + +describe("ImportRefType", () => { + it("Constructor passes for aliased unit import", () => { + const type = new ImportRefType(aliasedUnitImport); + + expect(type).toBeInstanceOf(ImportRefType); + expect(type.importStmt).toEqual(aliasedUnitImport); + expect(type.getFields()).toEqual([aliasedUnitImport]); + expect(type.pp()).toEqual( + `` + ); + }); + + it("Constructor throws for non-aliased unit import", () => { + expect(() => new ImportRefType(nonAliasedUnitImport)).toThrow(); + }); + + it("Constructor throws for aliased sympols import", () => { + expect(() => new ImportRefType(aliasedSymbolsImport)).toThrow(); + }); +});