Skip to content

Commit

Permalink
feat: auto-loading storage slots based on ABI filepath (#1346)
Browse files Browse the repository at this point in the history
  • Loading branch information
arboleya authored Oct 17, 2023
1 parent 314eae3 commit a62c4b1
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-insects-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-typegen": patch
---

Auto-loading `*-storage_slots.json` based on `*-abi.json` filepaths
7 changes: 1 addition & 6 deletions apps/demo-typegen/src/demo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { generateTestWallet } from '@fuel-ts/wallet/test-utils';
import type { BN } from 'fuels';
import { ContractFactory, Provider, toHex, BaseAssetId, Wallet, FUEL_NETWORK_URL } from 'fuels';

import storageSlots from '../contract/out/debug/demo-contract-storage_slots.json';

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

Expand Down Expand Up @@ -43,10 +41,7 @@ describe('ExampleContract', () => {
const wallet = await generateTestWallet(provider, [[500_000, BaseAssetId]]);

// Deploy
const contract = await DemoContractAbi__factory.deployContract(bytecode, wallet, {
gasPrice,
storageSlots,
});
const contract = await DemoContractAbi__factory.deployContract(bytecode, wallet, { gasPrice });

// Call
const { value } = await contract.functions.return_input(1337).txParams({ gasPrice }).call();
Expand Down
12 changes: 8 additions & 4 deletions apps/docs/src/guide/abi-typegen/using-generated-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ const { transactionId, value } = await contract.functions.my_fn(1).call();
console.log(transactionId, value);
```

## Using the Generated Contract Factory to Deploy a Contract
## Contract

Let's use the Contract class to deploy a contract:

```ts
import { Wallet } from "fuels";
Expand All @@ -40,7 +42,9 @@ const contract = await MyContract__factory.deployContract(bytecode, wallet);
console.log(contract.id);
```

You can also pass in [`DeployContractOptions`](https://github.com/FuelLabs/fuels-ts/blob/a64b67b9fb2d7f764ab9151a21d2266bf2df3643/packages/contract/src/contract-factory.ts#L19-L24) like storage slots and configurable constants to the `deployContract` method:
### Autoloading of Storage Slots

Typegen tries to resolve, auto-load, and embed the [Storage Slots](../contracts//storage-slots.md) for your Contract within the `MyContract__factory` class. Still, you can override it alongside other options from [`DeployContractOptions`](https://github.com/FuelLabs/fuels-ts/blob/a64b67b9fb2d7f764ab9151a21d2266bf2df3643/packages/contract/src/contract-factory.ts#L19-L24), when calling the `deployContract` method:

```ts
import storageSlots from "../contract/out/debug/storage-slots.json";
Expand All @@ -50,7 +54,7 @@ const contract = await MyContract__factory.deployContract(bytecode, wallet, {
});
```

## Using Generated Script Types
## Script

After generating types via:

Expand All @@ -74,7 +78,7 @@ const { value, logs } = await script.functions.main(1).call();
console.log({ value, logs });
```

## Using Generated Predicate Types
## Predicate

Consider the following predicate:

Expand Down
6 changes: 6 additions & 0 deletions apps/docs/src/guide/contracts/storage-slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ When deploying a contract, you can specify the custom storage slots that you wan

<<< @/../../../packages/fuel-gauge/src/storage-test-contract.test.ts#contract-deployment-storage-slots{ts:line-numbers}

## Using plain Javascript

In the above example, we directly imported the storage slots from a JSON file generated by the Sway compiler.

Instead of importing from a file, you can also specify the custom storage slots directly in your code:

<<< @/../../../packages/fuel-gauge/src/storage-test-contract.test.ts#contract-deployment-storage-slots-inline{ts:line-numbers}

## Auto-load of Storage Slots

Code generated using [Typegen](../abi-typegen//generating-types-from-abi.md) automatically [load](../abi-typegen/using-generated-types.md#autoloading-of-storage-slots) Storage Slots for you.
11 changes: 10 additions & 1 deletion packages/abi-typegen/src/AbiTypeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,36 @@ export class AbiTypeGen {
public readonly abis: Abi[];
public readonly abiFiles: IFile[];
public readonly binFiles: IFile[];
public readonly storageSlotsFiles: IFile[];
public readonly outputDir: string;

public readonly files: IFile[];

constructor(params: {
abiFiles: IFile[];
binFiles: IFile[];
storageSlotsFiles: IFile[];
outputDir: string;
programType: ProgramTypeEnum;
}) {
const { abiFiles, binFiles, outputDir, programType } = params;
const { abiFiles, binFiles, outputDir, programType, storageSlotsFiles } = params;

this.outputDir = outputDir;

this.abiFiles = abiFiles;
this.binFiles = binFiles;
this.storageSlotsFiles = storageSlotsFiles;

// Creates a `Abi` for each abi file
this.abis = this.abiFiles.map((abiFile) => {
const binFilepath = abiFile.path.replace('-abi.json', '.bin');
const relatedBinFile = this.binFiles.find(({ path }) => path === binFilepath);

const storageSlotFilepath = abiFile.path.replace('-abi.json', '-storage_slots.json');
const relatedStorageSlotsFile = this.storageSlotsFiles.find(
({ path }) => path === storageSlotFilepath
);

if (!relatedBinFile) {
validateBinFile({
abiFilepath: abiFile.path,
Expand All @@ -50,6 +58,7 @@ export class AbiTypeGen {
filepath: abiFile.path,
rawContents: JSON.parse(abiFile.contents as string),
hexlifiedBinContents: relatedBinFile?.contents,
storageSlotsContents: relatedStorageSlotsFile?.contents,
outputDir,
programType,
});
Expand Down
12 changes: 11 additions & 1 deletion packages/abi-typegen/src/abi/Abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class Abi {

public rawContents: IRawAbi;
public hexlifiedBinContents?: string;
public storageSlotsContents?: string;

public types: IType[];
public functions: IFunction[];
Expand All @@ -34,9 +35,17 @@ export class Abi {
programType: ProgramTypeEnum;
rawContents: IRawAbi;
hexlifiedBinContents?: string;
storageSlotsContents?: string;
outputDir: string;
}) {
const { filepath, outputDir, rawContents, hexlifiedBinContents, programType } = params;
const {
filepath,
outputDir,
rawContents,
hexlifiedBinContents,
programType,
storageSlotsContents,
} = params;

const abiNameRegex = /([^/]+)-abi\.json$/m;
const abiName = filepath.match(abiNameRegex);
Expand All @@ -58,6 +67,7 @@ export class Abi {
this.filepath = filepath;
this.rawContents = rawContents;
this.hexlifiedBinContents = hexlifiedBinContents;
this.storageSlotsContents = storageSlotsContents;
this.outputDir = outputDir;

const { types, functions, configurables } = this.parse();
Expand Down
4 changes: 4 additions & 0 deletions packages/abi-typegen/src/runTypegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AbiTypeGen } from './AbiTypeGen';
import type { ProgramTypeEnum } from './types/enums/ProgramTypeEnum';
import type { IFile } from './types/interfaces/IFile';
import { collectBinFilepaths } from './utils/collectBinFilePaths';
import { collectStorageSlotsFilepaths } from './utils/collectStorageSlotsFilePaths';

export interface IGenerateFilesParams {
cwd: string;
Expand Down Expand Up @@ -58,13 +59,16 @@ export function runTypegen(params: IGenerateFilesParams) {

const binFiles = collectBinFilepaths({ filepaths, programType });

const storageSlotsFiles = collectStorageSlotsFilepaths({ filepaths, programType });

/*
Starting the engine
*/
const abiTypeGen = new AbiTypeGen({
outputDir: output,
abiFiles,
binFiles,
storageSlotsFiles,
programType,
});

Expand Down
22 changes: 18 additions & 4 deletions packages/abi-typegen/src/templates/contract/factory.hbs
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
{{header}}

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

const _abi = {{abiJsonString}}
const _abi = {{abiJsonString}};

const _storageSlots: StorageSlot[] = {{storageSlotsJsonString}};

export class {{capitalizedName}}__factory {
static readonly abi = _abi
static readonly abi = _abi;

static readonly storageSlots = _storageSlots;

static createInterface(): {{capitalizedName}}Interface {
return new Interface(_abi) as unknown as {{capitalizedName}}Interface
}

static connect(
id: string | AbstractAddress,
accountOrProvider: Account | Provider
): {{capitalizedName}} {
return new Contract(id, _abi, accountOrProvider) as unknown as {{capitalizedName}}
}

static async deployContract(
bytecode: BytesLike,
wallet: Account,
options: DeployContractOptions = {}
): Promise<{{capitalizedName}}> {
const factory = new ContractFactory(bytecode, _abi, wallet);
const contract = await factory.deployContract(options);

const { storageSlots } = {{capitalizedName}}__factory;

const contract = await factory.deployContract({
storageSlots,
...options,
});

return contract as unknown as {{capitalizedName}};
}
}
5 changes: 3 additions & 2 deletions packages/abi-typegen/src/templates/contract/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { renderHbsTemplate } from '../renderHbsTemplate';
import factoryTemplate from './factory.hbs';

export function renderFactoryTemplate(params: { abi: Abi }) {
const { name: capitalizedName, rawContents } = params.abi;
const { name: capitalizedName, rawContents, storageSlotsContents } = params.abi;
const abiJsonString = JSON.stringify(rawContents, null, 2);
const storageSlotsJsonString = storageSlotsContents ?? '[]';

const text = renderHbsTemplate({
template: factoryTemplate,
data: { capitalizedName, abiJsonString },
data: { capitalizedName, abiJsonString, storageSlotsJsonString },
});

return text;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { getProjectResources, ForcProjectsEnum } from '../../test/fixtures/forc-projects';
import { ProgramTypeEnum } from '../types/enums/ProgramTypeEnum';

import { collectStorageSlotsFilepaths } from './collectStorageSlotsFilePaths';

describe('collectStorageSlotsFilePaths.ts', () => {
const script = getProjectResources(ForcProjectsEnum.SCRIPT);
const predicate = getProjectResources(ForcProjectsEnum.PREDICATE);
const contract = getProjectResources(ForcProjectsEnum.MINIMAL);

afterEach(jest.restoreAllMocks);

test('should collect storage slot files', () => {
const contractStorageSlots = collectStorageSlotsFilepaths({
filepaths: [contract.abiPath],
programType: ProgramTypeEnum.CONTRACT,
});

const predicateStorageSlots = collectStorageSlotsFilepaths({
filepaths: [predicate.abiPath],
programType: ProgramTypeEnum.PREDICATE,
});

const scriptStorageSlots = collectStorageSlotsFilepaths({
filepaths: [script.abiPath],
programType: ProgramTypeEnum.SCRIPT,
});

expect(contractStorageSlots.length).toEqual(1);
expect(predicateStorageSlots.length).toEqual(0);
expect(scriptStorageSlots.length).toEqual(0);
});
});
35 changes: 35 additions & 0 deletions packages/abi-typegen/src/utils/collectStorageSlotsFilePaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { existsSync, readFileSync } from 'fs';

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

export const collectStorageSlotsFilepaths = (params: {
filepaths: string[];
programType: ProgramTypeEnum;
}) => {
const { filepaths, programType } = params;

// collect filepaths for storage slots JSON files
const storageSlotsFiles: IFile[] = [];

// abort unless we're dealing with contract types
if (programType !== ProgramTypeEnum.CONTRACT) {
return storageSlotsFiles;
}

filepaths.forEach((abiFilepath) => {
const storageSlotsFilepath = abiFilepath.replace('-abi.json', '-storage_slots.json');
const storageSlotsExists = existsSync(storageSlotsFilepath);

if (storageSlotsExists) {
const storageSlots: IFile = {
path: storageSlotsFilepath,
contents: readFileSync(storageSlotsFilepath, 'utf-8'),
};

storageSlotsFiles.push(storageSlots);
}
});

return storageSlotsFiles;
};
22 changes: 18 additions & 4 deletions packages/abi-typegen/test/fixtures/templates/contract/factory.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

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

const _abi = {
Expand Down Expand Up @@ -54,26 +54,40 @@ const _abi = {
"loggedTypes": [],
"messagesTypes": [],
"configurables": []
}
};

const _storageSlots: StorageSlot[] = [];

export class MyContractAbi__factory {
static readonly abi = _abi
static readonly abi = _abi;

static readonly storageSlots = _storageSlots;

static createInterface(): MyContractAbiInterface {
return new Interface(_abi) as unknown as MyContractAbiInterface
}

static connect(
id: string | AbstractAddress,
accountOrProvider: Account | Provider
): MyContractAbi {
return new Contract(id, _abi, accountOrProvider) as unknown as MyContractAbi
}

static async deployContract(
bytecode: BytesLike,
wallet: Account,
options: DeployContractOptions = {}
): Promise<MyContractAbi> {
const factory = new ContractFactory(bytecode, _abi, wallet);
const contract = await factory.deployContract(options);

const { storageSlots } = MyContractAbi__factory;

const contract = await factory.deployContract({
storageSlots,
...options,
});

return contract as unknown as MyContractAbi;
}
}
Loading

0 comments on commit a62c4b1

Please sign in to comment.