Skip to content

Commit

Permalink
Address review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
limemloh committed Aug 30, 2023
1 parent 783d1f1 commit 03764d6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 36 deletions.
2 changes: 1 addition & 1 deletion examples/ccd-js-gen/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ const contractAddress: SDK.ContractAddress = {
const filePath = Url.fileURLToPath(import.meta.url);
const outDir = Path.join(Path.dirname(filePath), 'lib');
await Gen.generateContractClients(mod, 'wCCD', outDir, {
output: 'Typescript',
output: 'TypeScript',
});
})();
45 changes: 31 additions & 14 deletions packages/ccd-js-gen/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import * as tsm from 'ts-morph';
import * as SDK from '@concordium/common-sdk';
import { Buffer } from 'buffer/';

/** Output options for the generated code */
/**
* Output options for the generated code.
* - 'TypeScript' Only produce a module in TypeScript.
* - 'JavaScript' Only produce a module in JavaScript.
* - 'TypedJavaScript' Produce a JavaScript module and TypeScript declarations.
* - 'Everything' Produce all of the above.
*/
export type OutputOptions =
| 'Typescript'
| 'Javascript'
| 'TypedJavascript'
| 'TypeScript'
| 'JavaScript'
| 'TypedJavaScript'
| 'Everything';

/** Options for generating clients */
export type GenerateContractClientsOptions = {
/** Options for the output */
output?: OutputOptions;
};

Expand All @@ -27,16 +34,20 @@ export async function generateContractClientsFromFile(
outDirPath: string,
options: GenerateContractClientsOptions = {}
): Promise<void> {
// TODO catch if file does not exist and produce better error.
const fileBytes = await fs.readFile(modulePath);
const fileBytes = await fs.readFile(modulePath).catch((e) => {
if ('code' in e && e.code === 'ENOENT') {
throw new Error(`No such module '${modulePath}'`);
}
throw e;
});
const outputName = path.basename(modulePath, '.wasm.v1');
const module = SDK.Module.fromRawBytes(Buffer.from(fileBytes));
return generateContractClients(module, outputName, outDirPath, options);
const scModule = SDK.Module.fromRawBytes(Buffer.from(fileBytes));
return generateContractClients(scModule, outputName, outDirPath, options);
}

/**
* Generate smart contract client code for a given smart contract module.
* @param s Buffer with bytes for the smart contract module.
* @param scModule Buffer with bytes for the smart contract module.
* @param outName Name for the output file.
* @param outDirPath Path to the directory to use for the output.
* @param options Options for generating the clients.
Expand All @@ -58,17 +69,17 @@ export async function generateContractClients(
const compilerOptions: tsm.CompilerOptions = {
outDir: outDirPath,
declaration:
outputOption === 'Everything' || outputOption === 'TypedJavascript',
outputOption === 'Everything' || outputOption === 'TypedJavaScript',
};
const project = new tsm.Project({ compilerOptions });
const sourceFile = project.createSourceFile(outputFilePath, '', {
overwrite: true,
});
addModuleClients(sourceFile, moduleInterface);
if (outputOption === 'Everything' || outputOption === 'Typescript') {
if (outputOption === 'Everything' || outputOption === 'TypeScript') {
await project.save();
}
if (outputOption === 'Everything' || outputOption === 'Javascript') {
if (outputOption === 'Everything' || outputOption === 'JavaScript') {
await project.emit();
}
}
Expand Down Expand Up @@ -238,15 +249,21 @@ function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.substring(1);
}

/** Convert a string in snake_case or kebab-case into camelCase. */
/**
* Convert a string in snake_case or kebab-case into camelCase.
* This is used to transform entrypoint names in the smart contract to follow formatting javascript convention.
*/
function toCamelCase(str: string): string {
return str
.split(/[-_]/g)
.map((word, index) => (index === 0 ? word : capitalize(word)))
.join('');
}

/** Convert a string in snake_case or kebab-case into PascalCase. */
/**
* Convert a string in snake_case or kebab-case into PascalCase.
* This is used to transform contract names in the smart contract to follow formatting javascript convention.
*/
function toPascalCase(str: string): string {
return str.split(/[-_]/g).map(capitalize).join('');
}
58 changes: 37 additions & 21 deletions packages/common/src/types/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ export type ModuleInterface = Map<H.ContractName, ContractInterface>;
* A versioned smart contract module.
*/
export class Module {
/** Cache of the parsed WebAssembly module. */
/** Reference to the parsed WebAssembly module. Used to reuse an already compiled wasm module. */
private wasmModule: WebAssembly.Module | undefined;
/** Cache of the module reference. */
/** Reference to the calculated module reference. Used to reuse an already calculated module ref. */
private moduleRef: ModuleReference | undefined;

private constructor(
/** The version of the smart contract module. */
public version: number,
public version: 0 | 1,
/** Bytes for the WebAssembly module. */
public moduleSource: Buffer
) {}
Expand All @@ -36,8 +36,17 @@ export class Module {
* @param bytes Bytes encoding a versioned smart contract module.
*/
public static fromRawBytes(bytes: Buffer): Module {
const version = bytes.readInt32LE(0);
const moduleSource = bytes.subarray(8);
const version = bytes.readUInt32BE(0);
const sourceLength = bytes.readUInt32BE(4);
const moduleSource = bytes.subarray(8, sourceLength);
if (moduleSource.length !== sourceLength) {
throw new Error('Insufficient bytes provided for module.');
}
if (version !== 0 && version !== 1) {
throw new Error(
`Unsupported module version ${version}, The only supported versions are 0 and 1.`
);
}
return new Module(version, Buffer.from(moduleSource));
}

Expand All @@ -51,20 +60,30 @@ export class Module {
return new Module(versionedModule.version, versionedModule.source);
}

/** Calculate the module reference from the module source. The module reference is cached. */
/**
* Calculate the module reference from the module source.
* A reference to the result is reused in future invocations of this method.
*/
public getModuleRef(): ModuleReference {
if (this.moduleRef !== undefined) {
return this.moduleRef;
if (this.moduleRef === undefined) {
const prefix = Buffer.alloc(8);
prefix.writeInt32BE(this.version, 0);
prefix.writeInt32BE(this.moduleSource.length, 4);
const hash = sha256([prefix, this.moduleSource]);
this.moduleRef = ModuleReference.fromBytes(hash);
}
const hash = sha256([this.moduleSource]);
return ModuleReference.fromBytes(hash);
return this.moduleRef;
}

/** Parse the WebAssembly module in the smart contract module. The parsed module is cached. */
public getWasmModule(): Promise<WebAssembly.Module> {
return this.wasmModule !== undefined
? Promise.resolve(this.wasmModule)
: WebAssembly.compile(this.moduleSource);
/**
* Parse the WebAssembly module in the smart contract module. The parsed module is cached.
* A reference to the result is reused in future invocations of this method.
*/
public async getWasmModule(): Promise<WebAssembly.Module> {
if (this.wasmModule === undefined) {
this.wasmModule = await WebAssembly.compile(this.moduleSource);
}
return this.wasmModule;
}

/**
Expand All @@ -73,9 +92,8 @@ export class Module {
*/
public async parseModuleInterface(): Promise<ModuleInterface> {
const map = new Map<string, ContractInterface>();
const wasmExports = WebAssembly.Module.exports(
await this.getWasmModule()
);
const wasmModule = await this.getWasmModule();
const wasmExports = WebAssembly.Module.exports(wasmModule);

for (const exp of wasmExports) {
if (exp.kind !== 'function') {
Expand All @@ -87,9 +105,7 @@ export class Module {
contractName: contractName,
entrypointNames: new Set(),
});
continue;
}
if (H.isReceiveName(exp.name)) {
} else if (H.isReceiveName(exp.name)) {
const parts = H.getNamesFromReceive(exp.name);
const entry = getOrInsert(map, parts.contractName, {
contractName: parts.contractName,
Expand Down

0 comments on commit 03764d6

Please sign in to comment.