Skip to content

Commit 2cef020

Browse files
authored
fix: typegen and storage slots integration (#3396)
1 parent 29f5135 commit 2cef020

File tree

8 files changed

+135
-70
lines changed

8 files changed

+135
-70
lines changed

.changeset/cyan-panthers-carry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@fuel-ts/abi-typegen": patch
3+
"@fuel-ts/contract": patch
4+
"fuels": patch
5+
---
6+
7+
fix: typegen and storage slots integration

internal/benchmarks/src/cost-estimation.bench.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Cost Estimation Benchmarks', () => {
2929
const wallet = new WalletUnlocked(process.env.DEVNET_WALLET_PVT_KEY as string, provider);
3030

3131
const contractFactory = new CallTestContractFactory(wallet);
32-
const { waitForResult } = await contractFactory.deploy<CallTestContract>();
32+
const { waitForResult } = await contractFactory.deploy();
3333
const { contract: deployedContract } = await waitForResult();
3434
contract = deployedContract;
3535
} else {

packages/abi-typegen/src/templates/contract/factory.hbs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
11
{{header}}
22

3-
import { Contract, ContractFactory, decompressBytecode } from "fuels";
4-
import type { Provider, Account, DeployContractOptions, DeployContractResult } from "fuels";
3+
import { ContractFactory, decompressBytecode } from "fuels";
4+
import type { Provider, Account, DeployContractOptions } from "fuels";
55

66
import { {{capitalizedName}} } from "./{{capitalizedName}}";
77

88
const bytecode = decompressBytecode("{{compressedBytecode}}");
99

10-
export class {{capitalizedName}}Factory extends ContractFactory {
10+
export class {{capitalizedName}}Factory extends ContractFactory<{{capitalizedName}}> {
1111

1212
static readonly bytecode = bytecode;
1313

1414
constructor(accountOrProvider: Account | Provider) {
15-
super(bytecode, {{capitalizedName}}.abi, accountOrProvider);
15+
super(
16+
bytecode,
17+
{{capitalizedName}}.abi,
18+
accountOrProvider,
19+
{{capitalizedName}}.storageSlots
20+
);
1621
}
1722

18-
override deploy<TContract extends Contract = Contract>(
19-
deployOptions?: DeployContractOptions
20-
): Promise<DeployContractResult<TContract>> {
21-
return super.deploy({
22-
storageSlots: {{capitalizedName}}.storageSlots,
23-
...deployOptions,
24-
});
25-
}
26-
27-
static async deploy (
23+
static deploy (
2824
wallet: Account,
2925
options: DeployContractOptions = {}
30-
): Promise<DeployContractResult<{{capitalizedName}}>> {
26+
) {
3127
const factory = new {{capitalizedName}}Factory(wallet);
3228
return factory.deploy(options);
3329
}

packages/abi-typegen/test/fixtures/templates/contract/factory.hbs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,30 @@
1010
Fuel-Core version: 33.33.33
1111
*/
1212

13-
import { Contract, ContractFactory, decompressBytecode } from "fuels";
14-
import type { Provider, Account, DeployContractOptions, DeployContractResult } from "fuels";
13+
import { ContractFactory, decompressBytecode } from "fuels";
14+
import type { Provider, Account, DeployContractOptions } from "fuels";
1515

1616
import { MyContract } from "./MyContract";
1717

1818
const bytecode = decompressBytecode("0x-bytecode-here");
1919

20-
export class MyContractFactory extends ContractFactory {
20+
export class MyContractFactory extends ContractFactory<MyContract> {
2121

2222
static readonly bytecode = bytecode;
2323

2424
constructor(accountOrProvider: Account | Provider) {
25-
super(bytecode, MyContract.abi, accountOrProvider);
25+
super(
26+
bytecode,
27+
MyContract.abi,
28+
accountOrProvider,
29+
MyContract.storageSlots
30+
);
2631
}
2732

28-
override deploy<TContract extends Contract = Contract>(
29-
deployOptions?: DeployContractOptions
30-
): Promise<DeployContractResult<TContract>> {
31-
return super.deploy({
32-
storageSlots: MyContract.storageSlots,
33-
...deployOptions,
34-
});
35-
}
36-
37-
static async deploy (
33+
static deploy (
3834
wallet: Account,
3935
options: DeployContractOptions = {}
40-
): Promise<DeployContractResult<MyContract>> {
36+
) {
4137
const factory = new MyContractFactory(wallet);
4238
return factory.deploy(options);
4339
}

packages/contract/src/contract-factory.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,12 @@ export type DeployContractResult<TContract extends Contract = Contract> = {
5252
/**
5353
* `ContractFactory` provides utilities for deploying and configuring contracts.
5454
*/
55-
export default class ContractFactory {
55+
export default class ContractFactory<TContract extends Contract = Contract> {
5656
bytecode: BytesLike;
5757
interface: Interface;
5858
provider!: Provider | null;
5959
account!: Account | null;
60+
storageSlots: StorageSlot[];
6061

6162
/**
6263
* Create a ContractFactory instance.
@@ -68,7 +69,8 @@ export default class ContractFactory {
6869
constructor(
6970
bytecode: BytesLike,
7071
abi: JsonAbi | Interface,
71-
accountOrProvider: Account | Provider | null = null
72+
accountOrProvider: Account | Provider | null = null,
73+
storageSlots: StorageSlot[] = []
7274
) {
7375
// Force the bytecode to be a byte array
7476
this.bytecode = arrayify(bytecode);
@@ -99,6 +101,8 @@ export default class ContractFactory {
99101
this.provider = accountOrProvider;
100102
this.account = null;
101103
}
104+
105+
this.storageSlots = storageSlots;
102106
}
103107

104108
/**
@@ -118,17 +122,19 @@ export default class ContractFactory {
118122
* @returns The CreateTransactionRequest object for deploying the contract.
119123
*/
120124
createTransactionRequest(deployOptions?: DeployContractOptions & { bytecode?: BytesLike }) {
121-
const storageSlots = deployOptions?.storageSlots
122-
?.map(({ key, value }) => ({
125+
const storageSlots = (deployOptions?.storageSlots ?? [])
126+
.concat(this.storageSlots)
127+
.map(({ key, value }) => ({
123128
key: hexlifyWithPrefix(key),
124129
value: hexlifyWithPrefix(value),
125130
}))
131+
.filter((el, index, self) => self.findIndex((s) => s.key === el.key) === index)
126132
.sort(({ key: keyA }, { key: keyB }) => keyA.localeCompare(keyB));
127133

128134
const options = {
129135
salt: randomBytes(32),
130-
...deployOptions,
131-
storageSlots: storageSlots || [],
136+
...(deployOptions ?? {}),
137+
storageSlots,
132138
};
133139

134140
if (!this.provider) {
@@ -192,16 +198,16 @@ export default class ContractFactory {
192198
* @param deployOptions - Options for deploying the contract.
193199
* @returns A promise that resolves to the deployed contract instance.
194200
*/
195-
async deploy<TContract extends Contract = Contract>(
201+
async deploy<T extends Contract = TContract>(
196202
deployOptions: DeployContractOptions = {}
197-
): Promise<DeployContractResult<TContract>> {
203+
): Promise<DeployContractResult<T>> {
198204
const account = this.getAccount();
199205
const { consensusParameters } = account.provider.getChain();
200206
const maxContractSize = consensusParameters.contractParameters.contractMaxSize.toNumber();
201207

202208
return this.bytecode.length > maxContractSize
203209
? this.deployAsBlobTx(deployOptions)
204-
: this.deployAsCreateTx(deployOptions);
210+
: this.deployAsCreateTx<T>(deployOptions);
205211
}
206212

207213
/**
@@ -210,9 +216,9 @@ export default class ContractFactory {
210216
* @param deployOptions - Options for deploying the contract.
211217
* @returns A promise that resolves to the deployed contract instance.
212218
*/
213-
async deployAsCreateTx<TContract extends Contract = Contract>(
219+
async deployAsCreateTx<T extends Contract = TContract>(
214220
deployOptions: DeployContractOptions = {}
215-
): Promise<DeployContractResult<TContract>> {
221+
): Promise<DeployContractResult<T>> {
216222
const account = this.getAccount();
217223
const { consensusParameters } = account.provider.getChain();
218224
const maxContractSize = consensusParameters.contractParameters.contractMaxSize.toNumber();
@@ -230,7 +236,7 @@ export default class ContractFactory {
230236

231237
const waitForResult = async () => {
232238
const transactionResult = await transactionResponse.waitForResult<TransactionType.Create>();
233-
const contract = new Contract(contractId, this.interface, account) as TContract;
239+
const contract = new Contract(contractId, this.interface, account) as T;
234240

235241
return { contract, transactionResult };
236242
};
@@ -248,11 +254,11 @@ export default class ContractFactory {
248254
* @param deployOptions - Options for deploying the contract.
249255
* @returns A promise that resolves to the deployed contract instance.
250256
*/
251-
async deployAsBlobTx<TContract extends Contract = Contract>(
257+
async deployAsBlobTx<T extends Contract = TContract>(
252258
deployOptions: DeployContractOptions = {
253259
chunkSizeMultiplier: CHUNK_SIZE_MULTIPLIER,
254260
}
255-
): Promise<DeployContractResult<TContract>> {
261+
): Promise<DeployContractResult<T>> {
256262
const account = this.getAccount();
257263
const { configurableConstants, chunkSizeMultiplier } = deployOptions;
258264
if (configurableConstants) {
@@ -362,7 +368,7 @@ export default class ContractFactory {
362368
txIdResolver(createRequest.getTransactionId(account.provider.getChainId()));
363369
const transactionResponse = await account.sendTransaction(createRequest);
364370
const transactionResult = await transactionResponse.waitForResult<TransactionType.Create>();
365-
const contract = new Contract(contractId, this.interface, account) as TContract;
371+
const contract = new Contract(contractId, this.interface, account) as T;
366372

367373
return { contract, transactionResult };
368374
};

packages/fuel-gauge/src/contract-factory.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ describe('Contract Factory', () => {
283283
const factory = new ContractFactory(LargeContractFactory.bytecode, LargeContract.abi, wallet);
284284
expect(factory.bytecode.length % 8 === 0).toBe(true);
285285

286-
const deploy = await factory.deployAsBlobTx<LargeContract>();
286+
const deploy = await factory.deployAsBlobTx();
287287

288288
const { contract } = await deploy.waitForResult();
289289

@@ -311,7 +311,7 @@ describe('Contract Factory', () => {
311311
} = launched;
312312

313313
const factory = new ContractFactory(LargeContractFactory.bytecode, LargeContract.abi, wallet);
314-
const deploy = await factory.deployAsBlobTx<LargeContract>();
314+
const deploy = await factory.deployAsBlobTx();
315315
const initTxId = deploy.waitForTransactionId();
316316
expect(initTxId).toStrictEqual(new Promise(() => {}));
317317
const { contract } = await deploy.waitForResult();
@@ -339,7 +339,7 @@ describe('Contract Factory', () => {
339339
const bytecode = concat([arrayify(LargeContractFactory.bytecode), new Uint8Array(3)]);
340340
const factory = new ContractFactory(bytecode, LargeContract.abi, wallet);
341341
expect(factory.bytecode.length % 8 === 0).toBe(false);
342-
const deploy = await factory.deployAsBlobTx<LargeContract>({ chunkSizeMultiplier: 0.5 });
342+
const deploy = await factory.deployAsBlobTx({ chunkSizeMultiplier: 0.5 });
343343

344344
const { contract } = await deploy.waitForResult();
345345
expect(contract.id).toBeDefined();
@@ -361,7 +361,7 @@ describe('Contract Factory', () => {
361361
const chunkSizeMultiplier = 2;
362362

363363
await expectToThrowFuelError(
364-
() => factory.deployAsBlobTx<LargeContract>({ chunkSizeMultiplier }),
364+
() => factory.deployAsBlobTx({ chunkSizeMultiplier }),
365365
new FuelError(
366366
ErrorCode.INVALID_CHUNK_SIZE_MULTIPLIER,
367367
'Chunk size multiplier must be between 0 and 1'
@@ -534,12 +534,12 @@ describe('Contract Factory', () => {
534534
const sendTransactionSpy = vi.spyOn(wallet, 'sendTransaction');
535535
const factory = new ContractFactory(LargeContractFactory.bytecode, LargeContract.abi, wallet);
536536

537-
const firstDeploy = await factory.deployAsBlobTx<LargeContract>({
537+
const firstDeploy = await factory.deployAsBlobTx({
538538
salt: concat(['0x01', new Uint8Array(31)]),
539539
});
540540
const { contract: firstContract } = await firstDeploy.waitForResult();
541541
const firstDeployCalls = sendTransactionSpy.mock.calls.length;
542-
const secondDeploy = await factory.deployAsBlobTx<LargeContract>({
542+
const secondDeploy = await factory.deployAsBlobTx({
543543
salt: concat(['0x02', new Uint8Array(31)]),
544544
});
545545
const { contract: secondContract } = await secondDeploy.waitForResult();

packages/fuel-gauge/src/storage-test-contract.test.ts

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,16 @@ describe('StorageTestContract', () => {
124124

125125
it('should allow for overriding storage slots', async () => {
126126
const { storageSlots } = StorageTestContract;
127-
const expectedStorageSlots = storageSlots.map(({ key }) => ({
127+
128+
expect(storageSlots.length).toBeGreaterThan(2);
129+
const modifiedStorageSlots = storageSlots.slice(1).map(({ key }) => ({
128130
key: `0x${key}`,
129131
value: ZeroBytes32,
130132
}));
133+
const expectedStorageSlots = [
134+
{ key: `0x${storageSlots[0].key}`, value: `0x${storageSlots[0].value}` },
135+
...modifiedStorageSlots,
136+
];
131137

132138
using launched = await launchTestNode();
133139

@@ -138,18 +144,76 @@ describe('StorageTestContract', () => {
138144
// via constructor
139145
const storageContractFactory = new StorageTestContractFactory(wallet);
140146
const deployConstructor = await storageContractFactory.deploy({
141-
storageSlots: expectedStorageSlots,
147+
storageSlots: modifiedStorageSlots,
142148
});
143149
const { transactionResult: transactionResultConstructor } =
144150
await deployConstructor.waitForResult();
145151
expect(transactionResultConstructor.transaction.storageSlots).toEqual(expectedStorageSlots);
146152

147153
// via static deploy
148154
const deployStatically = await StorageTestContractFactory.deploy(wallet, {
149-
storageSlots: expectedStorageSlots,
155+
storageSlots: modifiedStorageSlots,
150156
});
151157
const { transactionResult: transactionResultStatically } =
152158
await deployStatically.waitForResult();
153159
expect(transactionResultStatically.transaction.storageSlots).toEqual(expectedStorageSlots);
160+
161+
// via deployAsBlobTx
162+
const deployBlob = await storageContractFactory.deployAsBlobTx({
163+
storageSlots: modifiedStorageSlots,
164+
});
165+
166+
const { transactionResult: txResultBlob } = await deployBlob.waitForResult();
167+
expect(txResultBlob.transaction.storageSlots).toEqual(expectedStorageSlots);
168+
169+
// via deployAsCreateTx
170+
const deployCreate = await storageContractFactory.deployAsBlobTx({
171+
storageSlots: modifiedStorageSlots,
172+
});
173+
174+
const { transactionResult: txResultCreate } = await deployCreate.waitForResult();
175+
expect(txResultCreate.transaction.storageSlots).toEqual(expectedStorageSlots);
176+
});
177+
178+
test('automatically loads storage slots when using deployAsCreateTx', async () => {
179+
const { storageSlots } = StorageTestContract;
180+
const expectedStorageSlots = storageSlots.map(({ key, value }) => ({
181+
key: `0x${key}`,
182+
value: `0x${value}`,
183+
}));
184+
185+
using launched = await launchTestNode();
186+
187+
const {
188+
wallets: [wallet],
189+
} = launched;
190+
191+
// via constructor
192+
const storageContractFactory = new StorageTestContractFactory(wallet);
193+
const deployConstructor = await storageContractFactory.deployAsCreateTx();
194+
const { transactionResult: transactionResultConstructor } =
195+
await deployConstructor.waitForResult();
196+
expect(transactionResultConstructor.transaction.storageSlots).toEqual(expectedStorageSlots);
197+
});
198+
199+
test('automatically loads storage slots when using deployAsBlobTx', async () => {
200+
const { storageSlots } = StorageTestContract;
201+
const expectedStorageSlots = storageSlots.map(({ key, value }) => ({
202+
key: `0x${key}`,
203+
value: `0x${value}`,
204+
}));
205+
206+
using launched = await launchTestNode();
207+
208+
const {
209+
wallets: [wallet],
210+
} = launched;
211+
212+
// via constructor
213+
const storageContractFactory = new StorageTestContractFactory(wallet);
214+
const deployConstructor = await storageContractFactory.deployAsBlobTx();
215+
const { transactionResult: transactionResultConstructor } =
216+
await deployConstructor.waitForResult();
217+
expect(transactionResultConstructor.transaction.storageSlots).toEqual(expectedStorageSlots);
154218
});
155219
});

0 commit comments

Comments
 (0)