Skip to content

Commit

Permalink
feat: Add a deployInstance method to contract factories generated b…
Browse files Browse the repository at this point in the history
…y typegen (#1160)

* feat: export contract bytecode from typegen

* feat: add `deployInstance` method to typegen

* add changeset

* Update runTypegen.test.ts

* rename `deployInstance` to `deployContract`

* add docs
  • Loading branch information
Dhaiwat10 committed Aug 3, 2023
1 parent b3d6d52 commit a157551
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-shirts-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-typegen": patch
---

Add `deployContract` method to contract factories generated via typegen
20 changes: 15 additions & 5 deletions apps/demo-typegen/src/demo.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
// #region Testing-with-jest-ts
import { generateTestWallet } from '@fuel-ts/wallet/test-utils';
import fs from 'fs';
import { ContractFactory, Provider, toHex, BaseAssetId } from 'fuels';
import path from 'path';

import { DemoContractAbi__factory } from './generated-types';
import bytecode from './generated-types/DemoContractAbi.hex';

describe('ExampleContract', () => {
it('should return the input', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');
const wallet = await generateTestWallet(provider, [[1_000, BaseAssetId]]);

// Deploy
const bytecode = fs.readFileSync(
path.join(__dirname, '../contract/out/debug/demo-contract.bin')
);
const factory = new ContractFactory(bytecode, DemoContractAbi__factory.abi, wallet);
const contract = await factory.deployContract();

Expand All @@ -29,5 +25,19 @@ describe('ExampleContract', () => {
const { value: v2 } = await contractInstance.functions.return_input(1337).call();
expect(v2.toHex()).toBe(toHex(1337));
});

it('deployContract method', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');
const wallet = await generateTestWallet(provider, [[1_000, BaseAssetId]]);

// Deploy
const contract = await DemoContractAbi__factory.deployContract(bytecode, wallet);

// Call
const { value } = await contract.functions.return_input(1337).call();

// Assert
expect(value.toHex()).toEqual(toHex(1337));
});
});
// #endregion Testing-with-jest-ts
14 changes: 14 additions & 0 deletions apps/docs/src/guide/abi-typegen/using-generated-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ const { transactionId, value } = await contract.functions.my_fn(1).call();
console.log(transactionId, value);
```

## Using the Generated Contract Factory to Deploy a Contract

```ts
import { Wallet } from "fuels";
import { MyContract__factory } from "./types";
import bytecode from "./types/MyContract.hex.ts";

const wallet = Wallet.fromAddress("...");

const contract = await MyContract__factory.deployContract(bytecode, wallet);

console.log(contract.id);
```

## Using Generated Script Types

After generating types via:
Expand Down
12 changes: 10 additions & 2 deletions packages/abi-typegen/src/runTypegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ describe('runTypegen.js', () => {
const from = project.abiPath;
const to = from.replace('-abi.json', '2-abi.json');

// also duplicates BIN file
const fromBin = project.binPath;
const toBin = fromBin.replace('.bin', '2.bin');

shelljs.cp(from, to);
shelljs.cp(fromBin, toBin);

// executes program
const fn = () =>
Expand All @@ -51,9 +56,11 @@ describe('runTypegen.js', () => {
join(output, `${normalizedName}Abi.d.ts`),
join(output, `${normalizedName}2Abi.d.ts`),
join(output, 'factories', `${normalizedName}Abi__factory.ts`),
join(output, `${normalizedName}Abi.hex.ts`),
join(output, `${normalizedName}2Abi.hex.ts`),
];

expect(files.length).toEqual(5);
expect(files.length).toEqual(7);

files.forEach((f) => {
expect(existsSync(f)).toEqual(true);
Expand Down Expand Up @@ -95,9 +102,10 @@ describe('runTypegen.js', () => {
join(output, 'common.d.ts'),
join(output, `${normalizedName}Abi.d.ts`),
join(output, 'factories', `${normalizedName}Abi__factory.ts`),
join(output, `${normalizedName}Abi.hex.ts`),
];

expect(files.length).toEqual(4);
expect(files.length).toEqual(5);

files.forEach((f) => {
expect(existsSync(f)).toEqual(true);
Expand Down
3 changes: 3 additions & 0 deletions packages/abi-typegen/src/templates/contract/bytecode.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{header}}

export default '{{hexlifiedBytecode}}'
24 changes: 24 additions & 0 deletions packages/abi-typegen/src/templates/contract/bytecode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ForcProjectsEnum, getProjectResources } from '../../../test/fixtures/forc-projects';
import bytecodeTemplte from '../../../test/fixtures/templates/contract/bytecode.hbs';
import { mockVersions } from '../../../test/utils/mockVersions';

import { renderBytecodeTemplate } from './bytecode';

describe('templates/contract/bytecode', () => {
test('should render bytecode template', () => {
// mocking
const { restore } = mockVersions();

// executing
const project = getProjectResources(ForcProjectsEnum.MINIMAL);

const rendered = renderBytecodeTemplate({
hexlifiedBytecode: project.binHexlified,
});

// validating
restore();

expect(rendered).toEqual(bytecodeTemplte);
});
});
16 changes: 16 additions & 0 deletions packages/abi-typegen/src/templates/contract/bytecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { BytesLike } from '@ethersproject/bytes';

import { renderHbsTemplate } from '../renderHbsTemplate';

import bytecodeTemplate from './bytecode.hbs';

export function renderBytecodeTemplate(params: { hexlifiedBytecode: BytesLike }) {
const text = renderHbsTemplate({
template: bytecodeTemplate,
data: {
hexlifiedBytecode: params.hexlifiedBytecode,
},
});

return text;
}
12 changes: 10 additions & 2 deletions packages/abi-typegen/src/templates/contract/factory.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{header}}

import { Interface, Contract } from "fuels";
import type { Provider, Account, AbstractAddress } from "fuels";
import { Interface, Contract, ContractFactory } from "fuels";
import type { Provider, Account, AbstractAddress, BytesLike } from "fuels";
import type { {{capitalizedName}}, {{capitalizedName}}Interface } from "../{{capitalizedName}}";

const _abi = {{abiJsonString}}
Expand All @@ -17,4 +17,12 @@ export class {{capitalizedName}}__factory {
): {{capitalizedName}} {
return new Contract(id, _abi, accountOrProvider) as unknown as {{capitalizedName}}
}
static async deployContract(
bytecode: BytesLike,
wallet: Account
): Promise<{{capitalizedName}}> {
const factory = new ContractFactory(bytecode, _abi, wallet);
const contract = await factory.deployContract();
return contract as unknown as {{capitalizedName}};
}
}
26 changes: 22 additions & 4 deletions packages/abi-typegen/src/utils/assembleContracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getNewAbiTypegen } from '../../test/utils/getNewAbiTypegen';
import * as renderCommonTemplateMod from '../templates/common/common';
import * as renderIndexTemplateMod from '../templates/common/index';
import * as renderBytecodeTemplateMod from '../templates/contract/bytecode';
import * as renderFactoryTemplateMod from '../templates/contract/factory';
import { ProgramTypeEnum } from '../types/enums/ProgramTypeEnum';

Expand All @@ -22,10 +23,15 @@ describe('assembleContracts.ts', () => {
.spyOn(renderIndexTemplateMod, 'renderIndexTemplate')
.mockImplementation();

const renderBytecodeTemplate = jest
.spyOn(renderBytecodeTemplateMod, 'renderBytecodeTemplate')
.mockImplementation();

return {
renderCommonTemplate,
renderFactoryTemplate,
renderIndexTemplate,
renderBytecodeTemplate,
};
}

Expand All @@ -37,15 +43,21 @@ describe('assembleContracts.ts', () => {
includeOptionType: false, // will prevent `common` template from being included
});

const { renderCommonTemplate, renderFactoryTemplate, renderIndexTemplate } = mockAllDeps();
const {
renderCommonTemplate,
renderFactoryTemplate,
renderIndexTemplate,
renderBytecodeTemplate,
} = mockAllDeps();

const files = assembleContracts({ abis, outputDir });

expect(files.length).toEqual(5); // 2x dts, 2x factories, 1x index (no `common`)
expect(files.length).toEqual(7); // 2x dts, 2x factories, 1x index, 2x hex.ts (no `common`)

expect(renderCommonTemplate).toHaveBeenCalledTimes(0);
expect(renderFactoryTemplate).toHaveBeenCalledTimes(2);
expect(renderIndexTemplate).toHaveBeenCalledTimes(1);
expect(renderBytecodeTemplate).toHaveBeenCalledTimes(2);
});

test('should assemble all files from Contract ABI, including `common` file', () => {
Expand All @@ -56,14 +68,20 @@ describe('assembleContracts.ts', () => {
includeOptionType: true, // will cause `common` template to be included
});

const { renderCommonTemplate, renderFactoryTemplate, renderIndexTemplate } = mockAllDeps();
const {
renderCommonTemplate,
renderFactoryTemplate,
renderIndexTemplate,
renderBytecodeTemplate,
} = mockAllDeps();

const files = assembleContracts({ abis, outputDir });

expect(files.length).toEqual(6); // 2x dts, 2x factories, 1x index, 1x common
expect(files.length).toEqual(8); // 2x dts, 2x factories, 1x index, 1x common, 2x hex.ts

expect(renderCommonTemplate).toHaveBeenCalledTimes(1); // must have been called
expect(renderFactoryTemplate).toHaveBeenCalledTimes(2);
expect(renderIndexTemplate).toHaveBeenCalledTimes(1);
expect(renderBytecodeTemplate).toHaveBeenCalledTimes(2);
});
});
10 changes: 10 additions & 0 deletions packages/abi-typegen/src/utils/assembleContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Abi } from '../abi/Abi';
import type { IFile } from '../index';
import { renderCommonTemplate } from '../templates/common/common';
import { renderIndexTemplate } from '../templates/common/index';
import { renderBytecodeTemplate } from '../templates/contract/bytecode';
import { renderDtsTemplate } from '../templates/contract/dts';
import { renderFactoryTemplate } from '../templates/contract/factory';

Expand All @@ -23,6 +24,7 @@ export function assembleContracts(params: { abis: Abi[]; outputDir: string }) {

const dtsFilepath = `${outputDir}/${name}.d.ts`;
const factoryFilepath = `${outputDir}/factories/${name}__factory.ts`;
const hexBinFilePath = `${outputDir}/${name}.hex.ts`;

const dts: IFile = {
path: dtsFilepath,
Expand All @@ -34,8 +36,16 @@ export function assembleContracts(params: { abis: Abi[]; outputDir: string }) {
contents: renderFactoryTemplate({ abi }),
};

const hexBinFile: IFile = {
path: hexBinFilePath,
contents: renderBytecodeTemplate({
hexlifiedBytecode: abi.hexlifiedBinContents as string,
}),
};

files.push(dts);
files.push(factory);
files.push(hexBinFile);
});

// Includes index file
Expand Down
9 changes: 6 additions & 3 deletions packages/abi-typegen/src/utils/collectBinFilePaths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ describe('collectBinFilePaths.ts', () => {
expect(validateBinFile).toHaveBeenCalledTimes(1);
});

test('should not collect bin files for Contracts', () => {
test('should collect bin files for Contracts', () => {
const { validateBinFile } = mockDeps();

const params = {
filepaths: [contract.abiPath],
programType: ProgramTypeEnum.CONTRACT,
};

const path = contract.abiPath.replace('-abi.json', '.bin');
const contents = hexlify(readFileSync(path));

const binFilepaths = collectBinFilepaths(params);

expect(binFilepaths).toStrictEqual([]); // empty array
expect(validateBinFile).toHaveBeenCalledTimes(0); // zero calls this time
expect(binFilepaths).toStrictEqual([{ contents, path }]);
expect(validateBinFile).toHaveBeenCalledTimes(1);
});
});
7 changes: 1 addition & 6 deletions packages/abi-typegen/src/utils/collectBinFilePaths.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { hexlify } from '@ethersproject/bytes';
import { existsSync, readFileSync } from 'fs';

import { ProgramTypeEnum } from '../types/enums/ProgramTypeEnum';
import type { ProgramTypeEnum } from '../types/enums/ProgramTypeEnum';
import type { IFile } from '../types/interfaces/IFile';

import { validateBinFile } from './validateBinFile';
Expand All @@ -12,11 +12,6 @@ export const collectBinFilepaths = (params: {
}) => {
const { filepaths, programType } = params;

const isContract = programType === ProgramTypeEnum.CONTRACT;
if (isContract) {
return []; // bin files not needed
}

// validate and collect bin filepaths for Scripts and/or Predicates
const binFiles = filepaths.map((abiFilepath) => {
const binFilepath = abiFilepath.replace('-abi.json', '.bin');
Expand Down
12 changes: 12 additions & 0 deletions packages/abi-typegen/test/fixtures/templates/contract/bytecode.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Autogenerated file. Do not edit manually. */

/* tslint:disable */
/* eslint-disable */

/*
Fuels version: 11.11.11
Forc version: 22.22.22
Fuel-Core version: 33.33.33
*/

export default '0x900000044700000000000000000000445dfcc00110fff3005d4060495d47f000134904407348000c72f0007b36f000005d44604a504110005041101024040000470000000000000055b7ae10'
12 changes: 10 additions & 2 deletions packages/abi-typegen/test/fixtures/templates/contract/factory.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
Fuel-Core version: 33.33.33
*/

import { Interface, Contract } from "fuels";
import type { Provider, Account, AbstractAddress } from "fuels";
import { Interface, Contract, ContractFactory } from "fuels";
import type { Provider, Account, AbstractAddress, BytesLike } from "fuels";
import type { MyContractAbi, MyContractAbiInterface } from "../MyContractAbi";

const _abi = {
Expand Down Expand Up @@ -67,4 +67,12 @@ export class MyContractAbi__factory {
): MyContractAbi {
return new Contract(id, _abi, accountOrProvider) as unknown as MyContractAbi
}
static async deployContract(
bytecode: BytesLike,
wallet: Account
): Promise<MyContractAbi> {
const factory = new ContractFactory(bytecode, _abi, wallet);
const contract = await factory.deployContract();
return contract as unknown as MyContractAbi;
}
}

0 comments on commit a157551

Please sign in to comment.