Skip to content

Commit

Permalink
feat: ABI Gen
Browse files Browse the repository at this point in the history
  • Loading branch information
nedsalk committed Dec 12, 2024
1 parent c9ffae1 commit 1d84672
Show file tree
Hide file tree
Showing 84 changed files with 6,982 additions and 3,212 deletions.
4 changes: 4 additions & 0 deletions .changeset/poor-years-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

docs: migrate `guide/types` snippets
4 changes: 3 additions & 1 deletion packages/abi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
"postbuild": "tsx ../../scripts/postbuild.ts"
},
"dependencies": {
"@fuel-ts/utils": "workspace:*",
"@fuel-ts/errors": "workspace:*",
"@fuel-ts/utils": "workspace:*"
"@fuel-ts/versions": "workspace:*",
"handlebars": "^4.7.8"
},
"devDependencies": {}
}
32 changes: 31 additions & 1 deletion packages/abi/src/gen/abi-gen.ts
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
export class AbiGen {}
import type { BinaryVersions } from '@fuel-ts/versions';

import type { Abi } from '../parser';

import { getRenderer } from './renderers/getRenderer';

export interface AbiGenInput {
programDetails: ProgramDetails[];
versions: BinaryVersions;
mode?: 'ts';
}

export interface AbiGenResult {
filename: string;
content: string;
}

export interface ProgramDetails {
name: string;
binCompressed: string;
abi: Abi;
abiContents: string;
storageSlots?: string;
}

export class AbiGen {
public static generate({ programDetails, mode, versions }: AbiGenInput): AbiGenResult[] {
const render = getRenderer(mode);
return render(programDetails, versions);
}
}
5 changes: 5 additions & 0 deletions packages/abi/src/gen/hbs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// informs TS about Handlebar `.hbs` templates extension
declare module '*.hbs' {
const value: string;
export default value;
}
1 change: 1 addition & 0 deletions packages/abi/src/gen/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { AbiGen } from './abi-gen';
export type { ProgramDetails } from './abi-gen';
16 changes: 16 additions & 0 deletions packages/abi/src/gen/renderers/getRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { assertUnreachable } from '@fuel-ts/utils';

import type { AbiGenInput } from '../abi-gen';

import { renderTs } from './ts/render-ts';
import type { Renderer } from './types';

export function getRenderer(mode: AbiGenInput['mode']): Renderer {
switch (mode) {
case 'ts':
case undefined:
return renderTs;
default:
return assertUnreachable(mode);
}
}
1 change: 1 addition & 0 deletions packages/abi/src/gen/renderers/ts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
describe core concepts, find out where to put them
17 changes: 17 additions & 0 deletions packages/abi/src/gen/renderers/ts/render-ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { AbiGenResult, ProgramDetails } from '../../abi-gen';
import type { Renderer } from '../types';

import { renderPrograms } from './renderers/render-programs';
import { templateRenderer } from './renderers/template-renderer';
import commonTemplate from './templates/common.hbs';

export const renderTs: Renderer = (details: ProgramDetails[], versions): AbiGenResult[] => {
const results = renderPrograms(details, versions);

results.push({
filename: 'common.ts',
content: templateRenderer({ template: commonTemplate, versions }),
});

return results;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { assertUnreachable } from '@fuel-ts/utils';

import type { Abi } from '../../../../parser';

export function getParentDirWrapper(programType: Abi['programType']): {
parentDir: string;
withParentDir: (file: string) => string;
removeParentDir: (file: string) => string;
} {
let parentDir: string = '';
switch (programType) {
case 'contract':
parentDir = 'contracts';
break;
case 'predicate':
parentDir = 'predicates';
break;
case 'script':
parentDir = 'scripts';
break;
case 'library':
break;
default:
assertUnreachable(programType);
}

return {
parentDir,
withParentDir: (file) => `${parentDir}/${file}`,
removeParentDir: (file) => file.split(`${parentDir}/`)[1],
};
}
49 changes: 49 additions & 0 deletions packages/abi/src/gen/renderers/ts/renderers/render-index-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { BinaryVersions } from '@fuel-ts/versions';

import type { Abi } from '../../../../parser';
import type { AbiGenResult } from '../../../abi-gen';
import indexTemplate from '../templates/index.hbs';

import { getParentDirWrapper } from './get-parent-dir-wrapper';
import { templateRenderer } from './template-renderer';

export function renderIndexFiles(
indexContents: Map<Abi['programType'], string[]>,
versions: BinaryVersions
): AbiGenResult[] {
const results: AbiGenResult[] = [];

indexContents.forEach((files, programType) => {
const { withParentDir, removeParentDir } = getParentDirWrapper(programType);

results.push({
filename: withParentDir('index.ts'),
content: templateRenderer({
versions,
template: indexTemplate,
data: {
paths: files.map(
(filename) =>
// remove .ts extension
removeParentDir(filename).split('.')[0]
),
},
}),
});
});

results.push({
filename: 'index.ts',
content: templateRenderer({
versions,
template: indexTemplate,
data: {
paths: [...indexContents.keys()]
.sort()
.map((programType) => getParentDirWrapper(programType).parentDir),
},
}),
});

return results;
}
90 changes: 90 additions & 0 deletions packages/abi/src/gen/renderers/ts/renderers/render-program.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { assertUnreachable } from '@fuel-ts/utils';
import type { BinaryVersions } from '@fuel-ts/versions';

import type { ProgramDetails } from '../../../abi-gen';
import type { TsAbiGenResult } from '../../types';
import bytecodeTemplate from '../templates/bytecode.hbs';
import contractFactoryTemplate from '../templates/contract-factory.hbs';
import contractTemplate from '../templates/contract.hbs';
import predicateTemplate from '../templates/predicate.hbs';
import scriptTemplate from '../templates/script.hbs';

import { getParentDirWrapper } from './get-parent-dir-wrapper';
import { templateRenderer } from './template-renderer';

export function renderProgram(
{ abi, binCompressed, name, abiContents, storageSlots }: ProgramDetails,
versions: BinaryVersions
): TsAbiGenResult[] {
const results: TsAbiGenResult[] = [
{
filename: `${name}-abi.json`,
content: abiContents,
},
{
filename: `${name}-bytecode.ts`,
content: templateRenderer({ template: bytecodeTemplate, versions, data: { binCompressed } }),
},
];

switch (abi.programType) {
case 'contract':
results.push(
{
filename: `${name}.ts`,
content: templateRenderer({
template: contractTemplate,
versions,
data: { name },
}),
exportInIndexFile: true,
},
{
filename: `${name}Factory.ts`,
content: templateRenderer({
template: contractFactoryTemplate,
versions,
data: { name },
}),
exportInIndexFile: true,
},
{
filename: `${name}-storage-slots.json`,
content: storageSlots as string,
}
);
break;
case 'predicate':
results.push({
filename: `${name}.ts`,
content: templateRenderer({
template: predicateTemplate,
versions,
data: { name },
}),
exportInIndexFile: true,
});
break;
case 'script':
results.push({
filename: `${name}.ts`,
content: templateRenderer({
template: scriptTemplate,
versions,
data: { name },
}),
exportInIndexFile: true,
});
break;
case 'library':
// we do nothing for library
break;
default:
assertUnreachable(abi.programType);
break;
}

const { withParentDir } = getParentDirWrapper(abi.programType);

return results.map((r) => ({ ...r, filename: withParentDir(r.filename) }));
}
35 changes: 35 additions & 0 deletions packages/abi/src/gen/renderers/ts/renderers/render-programs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { BinaryVersions } from '@fuel-ts/versions';

import type { Abi } from '../../../..';
import type { AbiGenResult, ProgramDetails } from '../../../abi-gen';

import { renderIndexFiles } from './render-index-files';
import { renderProgram } from './render-program';
import { renderTypes } from './render-types';

export function renderPrograms(details: ProgramDetails[], versions: BinaryVersions) {
const results: AbiGenResult[] = [];
const indexContents = new Map<Abi['programType'], string[]>();

for (const d of details) {
const program = renderProgram(d, versions);
const types = renderTypes(d, versions);
const renderResults = [program, types].flat();

results.push(...renderResults);

renderResults.forEach((r) => {
if (!r.exportInIndexFile) {
return;
}

const contents = indexContents.get(d.abi.programType) ?? [];
contents.push(r.filename);
indexContents.set(d.abi.programType, contents);
});
}

results.push(...renderIndexFiles(indexContents, versions));

return results;
}
Loading

0 comments on commit 1d84672

Please sign in to comment.