From 85dd58d42b218353029e01e0e50efb7f8b0b60d5 Mon Sep 17 00:00:00 2001 From: Stephen Lineker-Miller Date: Sun, 3 Apr 2022 20:34:05 +0100 Subject: [PATCH] Add type support to wrapper.ts and solc module --- bindings/compile.ts | 18 ++- bindings/core.ts | 21 +-- bindings/helpers.ts | 11 +- bindings/index.ts | 8 +- common/types.ts | 344 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 + tsconfig.json | 6 +- wrapper.ts | 11 +- 8 files changed, 393 insertions(+), 30 deletions(-) diff --git a/bindings/compile.ts b/bindings/compile.ts index 5f3a2d54..9fb39dce 100644 --- a/bindings/compile.ts +++ b/bindings/compile.ts @@ -1,9 +1,13 @@ import assert from 'assert'; +import { + CompileBindings, CompileJson, CompileJsonCallback, CompileJsonMulti, CompileJsonStandard, CoreBindings, SolJson +} from '../common/types'; + import { isNil } from '../common/helpers'; import { bindSolcMethod } from './helpers'; -export function setupCompile (solJson, core) { +export function setupCompile (solJson: SolJson, core: CoreBindings): CompileBindings { return { compileJson: bindCompileJson(solJson), compileJsonCallback: bindCompileJsonCallback(solJson, core), @@ -22,7 +26,7 @@ export function setupCompile (solJson, core) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindCompileJson (solJson) { +function bindCompileJson (solJson: SolJson): CompileJson { return bindSolcMethod( solJson, 'compileJSON', @@ -38,7 +42,7 @@ function bindCompileJson (solJson) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindCompileJsonMulti (solJson) { +function bindCompileJsonMulti (solJson: SolJson): CompileJsonMulti { return bindSolcMethod( solJson, 'compileJSONMulti', @@ -55,7 +59,7 @@ function bindCompileJsonMulti (solJson) { * @param solJson The Emscripten compiled Solidity object. * @param coreBindings The core bound Solidity methods. */ -function bindCompileJsonCallback (solJson, coreBindings) { +function bindCompileJsonCallback (solJson: SolJson, coreBindings: CoreBindings): CompileJsonCallback { const compileInternal = bindSolcMethod( solJson, 'compileJSONCallback', @@ -79,7 +83,7 @@ function bindCompileJsonCallback (solJson, coreBindings) { * @param solJson The Emscripten compiled Solidity object. * @param coreBindings The core bound Solidity methods. */ -function bindCompileStandard (solJson, coreBindings) { +function bindCompileStandard (solJson: SolJson, coreBindings: CoreBindings): CompileJsonStandard { let boundFunctionStandard: any = null; let boundFunctionSolidity: any = null; @@ -128,7 +132,7 @@ function bindCompileStandard (solJson, coreBindings) { } /********************** - * CALL BACKS + * CALLBACKS **********************/ function wrapCallback (coreBindings, callback) { @@ -162,7 +166,7 @@ function wrapCallbackWithKind (coreBindings, callback) { } // calls compile() with args || cb -function runWithCallbacks (solJson, coreBindings, callbacks, compile, args) { +function runWithCallbacks (solJson: SolJson, coreBindings: CoreBindings, callbacks, compile, args) { if (callbacks) { assert(typeof callbacks === 'object', 'Invalid callback object specified.'); } else { diff --git a/bindings/core.ts b/bindings/core.ts index 674fb20e..84404875 100644 --- a/bindings/core.ts +++ b/bindings/core.ts @@ -2,8 +2,9 @@ import { bindSolcMethod, bindSolcMethodWithFallbackFunc } from './helpers'; import translate from '../translate'; import * as semver from 'semver'; import { isNil } from '../common/helpers'; +import { Alloc, CoreBindings, License, Reset, SolJson, Version, VersionToSemver } from '../common/types'; -export function setupCore (solJson) { +export function setupCore (solJson: SolJson): CoreBindings { const core = { alloc: bindAlloc(solJson), license: bindLicense(solJson), @@ -39,7 +40,7 @@ export function setupCore (solJson) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindAlloc (solJson) { +function bindAlloc (solJson: SolJson): Alloc { const allocBinding = bindSolcMethod( solJson, 'solidity_alloc', @@ -62,7 +63,7 @@ function bindAlloc (solJson) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindVersion (solJson) { +function bindVersion (solJson: SolJson): Version { return bindSolcMethodWithFallbackFunc( solJson, 'solidity_version', @@ -72,7 +73,7 @@ function bindVersion (solJson) { ); } -function versionToSemver (version) { +function versionToSemver (version: string): VersionToSemver { return translate.versionToSemver.bind(this, version); } @@ -83,7 +84,7 @@ function versionToSemver (version) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindLicense (solJson) { +function bindLicense (solJson: SolJson): License { return bindSolcMethodWithFallbackFunc( solJson, 'solidity_license', @@ -100,7 +101,7 @@ function bindLicense (solJson) { * * @param solJson The Emscripten compiled Solidity object. */ -function bindReset (solJson) { +function bindReset (solJson: SolJson): Reset { return bindSolcMethod( solJson, 'solidity_reset', @@ -131,7 +132,7 @@ function bindReset (solJson) { * @param str The source string being copied to a C string. * @param ptr The pointer location where the C string will be set. */ -function unboundCopyToCString (solJson, alloc, str, ptr) { +function unboundCopyToCString (solJson: SolJson, alloc, str: string, ptr: number): void { const length = solJson.lengthBytesUTF8(str); const buffer = alloc(length + 1); @@ -147,15 +148,15 @@ function unboundCopyToCString (solJson, alloc, str, ptr) { * @param solJson The Emscripten compiled Solidity object. * @param ptr The pointer location where the C string will be referenced. */ -function unboundCopyFromCString (solJson, ptr) { +function unboundCopyFromCString (solJson: SolJson, ptr: any): string { const copyFromCString = solJson.UTF8ToString || solJson.Pointer_stringify; return copyFromCString(ptr); } -function unboundAddFunction (solJson, func, signature?) { +function unboundAddFunction (solJson: SolJson, func: (...args: any[]) => any, signature?: string): number { return (solJson.addFunction || solJson.Runtime.addFunction)(func, signature); } -function unboundRemoveFunction (solJson, ptr) { +function unboundRemoveFunction (solJson: SolJson, ptr: number) { return (solJson.removeFunction || solJson.Runtime.removeFunction)(ptr); } diff --git a/bindings/helpers.ts b/bindings/helpers.ts index b3e6ade9..b11637da 100644 --- a/bindings/helpers.ts +++ b/bindings/helpers.ts @@ -1,6 +1,7 @@ +import { SolJson, SupportedMethods } from '../common/types'; import { isNil } from '../common/helpers'; -export function bindSolcMethod (solJson, method, returnType, args, defaultValue) { +export function bindSolcMethod (solJson: SolJson, method: string, returnType: string, args: string[], defaultValue: T): T { if (isNil(solJson[`_${method}`]) && defaultValue !== undefined) { return defaultValue; } @@ -8,7 +9,7 @@ export function bindSolcMethod (solJson, method, returnType, args, defaultValue) return solJson.cwrap(method, returnType, args); } -export function bindSolcMethodWithFallbackFunc (solJson, method, returnType, args, fallbackMethod, finalFallback = undefined) { +export function bindSolcMethodWithFallbackFunc (solJson: SolJson, method: string, returnType: string, args: string[], fallbackMethod: string, finalFallback: any = undefined): T { const methodFunc = bindSolcMethod(solJson, method, returnType, args, null); if (!isNil(methodFunc)) { @@ -18,7 +19,7 @@ export function bindSolcMethodWithFallbackFunc (solJson, method, returnType, arg return bindSolcMethod(solJson, fallbackMethod, returnType, args, finalFallback); } -export function getSupportedMethods (solJson) { +export function getSupportedMethods (solJson: SolJson): SupportedMethods { return { licenseSupported: anyMethodExists(solJson, 'solidity_license'), versionSupported: anyMethodExists(solJson, 'solidity_version'), @@ -26,11 +27,11 @@ export function getSupportedMethods (solJson) { resetSupported: anyMethodExists(solJson, 'solidity_reset'), compileJsonSupported: anyMethodExists(solJson, 'compileJSON'), compileJsonMultiSupported: anyMethodExists(solJson, 'compileJSONMulti'), - compileJsonCallbackSuppported: anyMethodExists(solJson, 'compileJSONCallback'), + compileJsonCallbackSupported: anyMethodExists(solJson, 'compileJSONCallback'), compileJsonStandardSupported: anyMethodExists(solJson, 'compileStandard', 'solidity_compile') }; } -function anyMethodExists (solJson, ...names) { +function anyMethodExists (solJson: SolJson, ...names: string[]): boolean { return names.some(name => !isNil(solJson[`_${name}`])); } diff --git a/bindings/index.ts b/bindings/index.ts index 63d4c074..97d0d4d3 100644 --- a/bindings/index.ts +++ b/bindings/index.ts @@ -1,8 +1,14 @@ +import { CompileBindings, CoreBindings, SolJson, SupportedMethods } from '../common/types'; + import { setupCore } from './core'; import { getSupportedMethods } from './helpers'; import { setupCompile } from './compile'; -export default function setupBindings (solJson) { +export default function setupBindings (solJson: SolJson): { + coreBindings: CoreBindings, + compileBindings: CompileBindings, + methodFlags: SupportedMethods, +} { const coreBindings = setupCore(solJson); const compileBindings = setupCompile(solJson, coreBindings); const methodFlags = getSupportedMethods(solJson); diff --git a/common/types.ts b/common/types.ts index 254484a1..655902f7 100644 --- a/common/types.ts +++ b/common/types.ts @@ -24,3 +24,347 @@ export interface LibraryAddresses { export interface LinkReferences { [libraryLabel: string]: Array<{ start: number, length: number }>; } + +export interface SolJson { + /** + * Returns a native JavaScript wrapper for a C function. + * + * This is similar to ccall(), but returns a JavaScript function that can be + * reused as many times as needed. The C function can be defined in a C file, + * or be a C-compatible C++ function defined using extern "C" (to prevent + * name mangling). + * + * @param ident The name of the C function to be called. + * + * @param returnType The return type of the function. This can be "number", + * "string" or "array", which correspond to the appropriate JavaScript + * types (use "number" for any C pointer, and "array" for JavaScript arrays + * and typed arrays; note that arrays are 8-bit), or for a void function it + * can be null (note: the JavaScript null value, * not a string containing + * the word “null”). + * + * @param argTypes An array of the types of arguments for the function (if + * there are no arguments, this can be omitted). Types are as in returnType, + * except that array is not supported as there is no way for us to know the + * length of the array). + * + * @returns A JavaScript function that can be used for running the C function. + */ + cwrap (ident: string, returnType: string | null, argTypes: string[]): T; + + /** + * Sets a value at a specific memory address at run-time. + * + * Note: + * setValue() and getValue() only do aligned writes and reads. + * + * The type is an LLVM IR type (one of i8, i16, i32, i64, float, double, or + * a pointer type like i8* or just *), not JavaScript types as used in ccall() + * or cwrap(). This is a lower-level operation, and we do need to care what + * specific type is being used. + * + * @param ptr A pointer (number) representing the memory address. + * + * @param value The value to be stored + * + * @param type An LLVM IR type as a string (see “note” above). + * + * @param noSafe Developers should ignore this variable. It is only + * used in SAFE_HEAP compilation mode, where it can help avoid infinite recursion + * in some specialist use cases. + */ + setValue (ptr: number, value: any, type: string, noSafe?: boolean): void; + + /** + * Given a pointer ptr to a null-terminated UTF8-encoded string in the + * Emscripten HEAP, returns a copy of that string as a JavaScript String + * object. + * + * @param ptr A pointer to a null-terminated UTF8-encoded string in the + * Emscripten HEAP. + * + * @param maxBytesToRead An optional length that specifies the maximum number + * of bytes to read. You can omit this parameter to scan the string until the + * first 0 byte. If maxBytesToRead is passed, and the string at + * [ptr, ptr+maxBytesToReadr) contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead)) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using + * one style or the other. + */ + UTF8ToString (ptr: number, maxBytesToRead?: number): string; + + /** + * v1.38.27: 02/10/2019 (emscripten) + * -------------------- + * - Remove deprecated Pointer_stringify (use UTF8ToString instead). See #8011 + * + * @param ptr + * @param length + * @constructor + * + * @deprecated use UTF8ToString instead + */ + // eslint-disable-next-line camelcase + Pointer_stringify (ptr: number, length?: number): string; + + /** + * Given a string input return the current length of the given UTF8 bytes. + * Used when performing stringToUTF8 since stringToUTF8 will require at most + * str.length*4+1 bytes of space in the HEAP. + * + * @param str The input string. + */ + lengthBytesUTF8 (str: string): number; + + /** + * Copies the given JavaScript String object str to the Emscripten HEAP at + * address outPtr, null-terminated and encoded in UTF8 form. + * + * The copy will require at most str.length*4+1 bytes of space in the HEAP. + * You can use the function lengthBytesUTF8() to compute the exact amount + * of bytes (excluding the null terminator) needed to encode the string. + * + * @param str A JavaScript String object. + * + * @param outPtr Pointer to data copied from str, encoded in UTF8 format and + * null-terminated. + * + * @param maxBytesToWrite A limit on the number of bytes that this function + * can at most write out. If the string is longer than this, the output is + * truncated. The outputted string will always be null terminated, even if + * truncation occurred, as long as maxBytesToWrite > 0 + */ + stringToUTF8 (str: string, outPtr: number, maxBytesToWrite?: number): void; + + /** + * Allocates size bytes of uninitialized storage. + * + * If allocation succeeds, returns a pointer that is suitably aligned for any + * object type with fundamental alignment. + * + * @param size number of bytes to allocate + * + * @returns On success, returns the pointer to the beginning of newly + * allocated memory. To avoid a memory leak, the returned pointer must be + * deallocated with free() or realloc(). + */ + _malloc (size: number): number; + + /** + * Use addFunction to return an integer value that represents a function + * pointer. Passing that integer to C code then lets it call that value as a + * function pointer, and the JavaScript function you sent to addFunction will + * be called. + * + * when using addFunction on LLVM wasm backend, you need to provide an + * additional second argument, a Wasm function signature string. Each + * character within a signature string represents a type. The first character + * represents the return type of the function, and remaining characters are for + * parameter types. + * + * 'v': void type + * 'i': 32-bit integer type + * 'j': 64-bit integer type (currently does not exist in JavaScript) + * 'f': 32-bit float type + * 'd': 64-bit float type + * + * @param func + * @param signature + */ + addFunction (func: (...args: any[]) => any, signature?: string): number; + + /** + * Removes an allocated function by the provided function pointer. + * + * @param funcPtr + */ + removeFunction (funcPtr: number): void; + + /** + * Fallback runtime which can contain the add/remove functions + */ + Runtime: { + addFunction (func: (...args: any[]) => any, signature?: string): number; + removeFunction (funcPtr: number): void; + }; +} + +/************************** + * core binding functions + *************************/ + +/** + * Allocates a chunk of memory of size bytes. + * + * Use this function inside callbacks to allocate data that is to be passed to + * the compiler. You may use solidity_free() or solidity_reset() to free this + * memory again, but it is not required as the compiler takes ownership for any + * data passed to it via callbacks. + * + * This function will return NULL if the requested memory region could not be + * allocated. + * + * @param size The size of bytes to be allocated. + */ +export type Alloc = (size: number) => number; + +/** + * Returns the complete license document. + */ +export type License = () => string | undefined; + +/** + * This should be called right before each compilation, but not at the end, + * so additional memory can be freed. + */ +export type Reset = () => string; + +/** + * Returns the compiler version. + */ +export type Version = () => string; + +/** + * Returns the compiler version as a semver version style. + */ +export type VersionToSemver = () => string; + +// compile binding functions +export type ReadCallbackResult = { contents: string } | { error: string }; +export type ReadCallback = (path: string) => ReadCallbackResult; +export type Callbacks = { [x: string]: ReadCallback }; + +/** + * Compile a single file. + * + * @solidityMaxVersion 0.5.0 + * + * @param input + * @param optimize + */ +export type CompileJson = (input: string, optimize: boolean) => string; + +/** + * Compile a single file with a callback. + * + * @solidityMinVersion 0.2.1 + * @solidityMaxVersion 0.5.0 + * + * @param input + * @param optimize + * @param readCallbackPtr + */ +export type CompileJsonCallback = (input: string, optimize: boolean, readCallbackPtr: number) => string; + +/** + * Compile multiple files. + * + * @solidityMinVersion 0.1.6 + * @solidityMaxVersion 0.5.0 + * + * @param input + * @param optimize + */ +export type CompileJsonMulti = (input: string, optimize: boolean) => string; + +/** + * Will attempt to bind into compileStandard before falling back to solidity_compile. + * compileStandard - solidityMaxVersion 0.5.0 + * + * @solidityMinVersion 0.4.11 + * + * @param input + * @param callbackPtr + * @param contextPtr + */ +export type CompileJsonStandard = (input: string, callbackPtr: number, contextPtr?: number) => string; + +/** + * Compile the provided input, using the best case implementation based on the + * current binary. + * + * @param input + * @param readCallback + */ +export type CompileSolidity = (input: string, readCallback?: Callbacks) => string; + +export interface CompileBindings { + compileJson: CompileJson; + compileJsonCallback: CompileJsonCallback; + compileJsonMulti: CompileJsonMulti; + compileStandard: CompileJsonStandard; +} + +export interface CoreBindings { + alloc: Alloc; + license: License; + reset: Reset; + + version: Version; + versionToSemver: VersionToSemver; + + copyFromCString: (ptr: string) => string; + copyToCString: (input: string, ptr: string) => string; + + addFunction: (func: (...args: any[]) => any, signature: string) => number; + removeFunction: (ptr: number) => void; + + isVersion6OrNewer: boolean, +} + +export interface SupportedMethods { + licenseSupported: boolean; + versionSupported: boolean; + allocSupported: boolean; + resetSupported: boolean; + compileJsonSupported: boolean; + compileJsonMultiSupported: boolean; + compileJsonCallbackSupported: boolean; + compileJsonStandardSupported: boolean; +} + +export interface Wrapper { + /** + * Returns the complete license document. + */ + license (): string | undefined; + + /** + * Returns the compiler version. + */ + version (): string; + + /** + * Returns the compiler version as a semver version style. + */ + semver (): string; + + /** + * Compile the provided input, using the best case implementation based on the + * current binary. + * + * @param input + * @param readCallback + */ + compile (input: string, readCallback?: Callbacks): string; + + lowlevel: { + compileSingle?: CompileJson; + compileMulti?: CompileJsonMulti; + compileCallback?: CompileJsonCallback; + compileStandard?: CompileJsonStandard; + }; + + features: { + legacySingleInput: boolean; + multipleInputs: boolean; + importCallback: boolean; + nativeStandardJSON: boolean; + }; + + loadRemoteVersion (version: string, callback: (error: Error, solc: SolJson) => void): void; + + setupMethods (soljson: SolJson): Wrapper; +} diff --git a/package.json b/package.json index 8a85ef3f..e7de02d3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "coverage": "nyc npm run test", "coveralls": "npm run coverage && coveralls =10.0.0" }, "files": [ + "common/*.d.ts", "common/*.js", + "bindings/*.d.ts", "bindings/*.js", + "*.d.ts", "*.js" ], "author": "chriseth", diff --git a/tsconfig.json b/tsconfig.json index a4420494..6f644a76 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "esModuleInterop": true, "outDir": "./dist", "forceConsistentCasingInFileNames": true, + // Allow JS must be included to ensure that the built binary is included // in the output. This could be copied directly in the future if required. "allowJs": true, @@ -17,12 +18,13 @@ // In order to gracefully move our project to TypeScript without having // TS immediately yell at you, we'll disable strict mode for now. "strict": false, - "noImplicitAny": false + "noImplicitAny": false, + "declaration": true }, "include": [ "**/*.js", "**/*.ts", - "**/*.json" + "**/*.json", ], "exclude": [ "coverage", diff --git a/wrapper.ts b/wrapper.ts index e9bf471e..73525ea1 100755 --- a/wrapper.ts +++ b/wrapper.ts @@ -1,14 +1,15 @@ import MemoryStream from 'memorystream'; import { https } from 'follow-redirects'; -import { formatFatalError } from './formatters'; -import { isNil } from './common/helpers'; import setupBindings from './bindings'; import translate from './translate'; +import { CompileBindings, SolJson, Wrapper } from './common/types'; +import { formatFatalError } from './formatters'; +import { isNil } from './common/helpers'; const Module = module.constructor as any; -function wrapper (soljson) { +function wrapper (soljson: SolJson): Wrapper { const { coreBindings, compileBindings, @@ -28,7 +29,7 @@ function wrapper (soljson) { features: { legacySingleInput: methodFlags.compileJsonStandardSupported, multipleInputs: methodFlags.compileJsonMultiSupported || methodFlags.compileJsonStandardSupported, - importCallback: methodFlags.compileJsonCallbackSuppported || methodFlags.compileJsonStandardSupported, + importCallback: methodFlags.compileJsonCallbackSupported || methodFlags.compileJsonStandardSupported, nativeStandardJSON: methodFlags.compileJsonStandardSupported }, compile: compileStandardWrapper.bind(this, compileBindings), @@ -69,7 +70,7 @@ function loadRemoteVersion (versionString, callback) { } // Expects a Standard JSON I/O but supports old compilers -function compileStandardWrapper (compile, inputRaw, readCallback) { +function compileStandardWrapper (compile: CompileBindings, inputRaw: string, readCallback?: number) { if (!isNil(compile.compileStandard)) { return compile.compileStandard(inputRaw, readCallback); }