Skip to content

Commit c2f0599

Browse files
arboleyaTorres-ssfdanielbateluizstaciopetertonysmith94
authored
feat: deploying scripts and predicates (#3251)
Co-authored-by: Sergio Torres <30977845+Torres-ssf@users.noreply.github.com> Co-authored-by: Daniel Bate <djbate23@gmail.com> Co-authored-by: Luiz Estácio | stacio.eth <luizstacio@gmail.com> Co-authored-by: Peter Smith <peter@blueoceancomputing.co.uk> Co-authored-by: Sérgio Torres <30977845+Torres-ssf@users.noreply.github.com> Co-authored-by: chad <chad.nehemiah94@gmail.com> Co-authored-by: Dhaiwat <dhaiwatpandya@gmail.com> Co-authored-by: nedsalk <nedim.salkic@fuel.sh>
1 parent a35d644 commit c2f0599

File tree

41 files changed

+1391
-264
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1391
-264
lines changed

.changeset/sharp-radios-fry.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@fuel-ts/account": patch
3+
"@fuel-ts/contract": patch
4+
"fuels": patch
5+
"@fuel-ts/program": patch
6+
"@fuel-ts/versions": patch
7+
"create-fuels": patch
8+
---
9+
10+
feat: deploying scripts and predicates

.knip.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"/apps/docs/*",
77
"/packages/abi-typegen/test/**",
88
"templates/**",
9+
"/apps/docs-snippets/**",
910
"/apps/docs-snippets2/**/*.test.ts",
1011
"apps/create-fuels-counter-guide/**"
1112
],
@@ -28,6 +29,8 @@
2829
"eslint-plugin-react",
2930
"eslint-plugin-react-hooks",
3031
"dotenv",
32+
"kill",
33+
"lsof",
3134
"memfs",
3235
"open",
3336
"textlint",

apps/demo-nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"pretest": "pnpm original:build"
1111
},
1212
"dependencies": {
13-
"@fuels/vm-asm": "0.57.1",
13+
"@fuels/vm-asm": "0.58.0",
1414
"@types/node": "^22.5.5",
1515
"@types/react-dom": "^18.3",
1616
"@types/react": "^18.3.10",

apps/demo-react-cra/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.29",
44
"private": true,
55
"dependencies": {
6-
"@fuels/vm-asm": "0.57.1",
6+
"@fuels/vm-asm": "0.58.0",
77
"@testing-library/react": "^16.0.1",
88
"@types/node": "^22.5.5",
99
"@types/react": "^18.3.10",

apps/demo-react-vite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"pretest": "pnpm original:build"
1212
},
1313
"dependencies": {
14-
"@fuels/vm-asm": "0.57.1",
14+
"@fuels/vm-asm": "0.58.0",
1515
"fuels": "workspace:*",
1616
"react-dom": "^18.3.1",
1717
"react": "^18.3.1"

apps/docs-snippets/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
"description": "",
55
"private": true,
66
"scripts": {
7-
"pretest": "run-s build:forc type:check",
8-
"build:forc": "pnpm fuels build",
7+
"pretest": "run-s fuels:build type:check",
8+
"xpretest": "run-s kill-node fuels:build fuels:node fuels:deploy kill-node type:check",
9+
"kill-node": "lsof -t -i:4000 | xargs -r kill",
10+
"fuels:deploy": "pnpm fuels deploy",
11+
"fuels:node": "pnpm fuels node > /dev/null 2>&1 &",
12+
"fuels:build": "pnpm fuels build",
913
"type:check": "tsc --noEmit"
1014
},
1115
"devDependencies": {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { readFileSync } from 'fs';
2+
import { ContractFactory, Predicate, Provider, Wallet, hexlify } from 'fuels';
3+
import { launchTestNode } from 'fuels/test-utils';
4+
import { join } from 'path';
5+
6+
import { ConfigurablePin as TypegenPredicate } from '../../../test/typegen';
7+
8+
/**
9+
* @group browser
10+
* @group node
11+
*
12+
* TODO: enable the test and reintroduce the docs
13+
*/
14+
describe.skip('Deploying Predicates', () => {
15+
it('deploys a predicate via loader and calls', async () => {
16+
using launched = await launchTestNode();
17+
18+
const {
19+
provider: testProvider,
20+
wallets: [testWallet, receiver],
21+
} = launched;
22+
23+
const recieverInitialBalance = await receiver.getBalance();
24+
25+
const providerUrl = testProvider.url;
26+
const WALLET_PVT_KEY = hexlify(testWallet.privateKey);
27+
28+
const factory = new ContractFactory(
29+
TypegenPredicate.bytecode,
30+
TypegenPredicate.abi,
31+
testWallet
32+
);
33+
const { waitForResult: waitForDeploy } = await factory.deployAsBlobTxForScript();
34+
await waitForDeploy();
35+
36+
const loaderBytecode = hexlify(
37+
readFileSync(
38+
join(
39+
__dirname,
40+
'../../../test/fixtures/forc-projects/configurable-pin/out/release/configurable-pin.deployed.bin'
41+
)
42+
)
43+
);
44+
45+
// #region deploying-predicates
46+
// #import { Provider, Wallet, hexlify };
47+
// #context import { readFileSync } from 'fs';
48+
// #context import { WALLET_PVT_KEY } from 'path/to/my/env/file';
49+
// #context import { TypegenPredicate } from 'path/to/typegen/outputs';
50+
51+
// First, we will need the loader bytecode that is generated by `fuels deploy`
52+
// #context const loaderBytecode = hexlify(readFileSync('path/to/forc/build/outputs')));
53+
54+
const provider = await Provider.create(providerUrl);
55+
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
56+
57+
// Then we will instantiate the predicate using both the scripts bytecode and it's loader bytecode,
58+
// now we are free to interact with the predicate as we would normally, such as overriding the configurables
59+
const predicate = new Predicate({
60+
bytecode: loaderBytecode,
61+
abi: TypegenPredicate.abi,
62+
data: [1337],
63+
provider,
64+
});
65+
66+
// First, let's fund the predicate
67+
const { waitForResult: waitForFund } = await wallet.transfer(predicate.address, 100_000);
68+
await waitForFund();
69+
70+
const { waitForResult: waitForTransfer } = await predicate.transfer(receiver.address, 1000);
71+
const { gasUsed } = await waitForTransfer();
72+
// #endregion deploying-predicates
73+
74+
const anotherPredicate = new Predicate({
75+
bytecode: TypegenPredicate.bytecode,
76+
abi: TypegenPredicate.abi,
77+
data: [1337],
78+
provider,
79+
});
80+
81+
const { waitForResult: waitForAnotherFund } = await wallet.transfer(
82+
anotherPredicate.address,
83+
100_000
84+
);
85+
await waitForAnotherFund();
86+
87+
const { waitForResult: waitForAnotherTransfer } = await anotherPredicate.transfer(
88+
receiver.address,
89+
1000
90+
);
91+
const { gasUsed: anotherGasUsed } = await waitForAnotherTransfer();
92+
93+
expect(recieverInitialBalance.toNumber()).toBeLessThan(recieverInitialBalance.toNumber());
94+
expect(gasUsed.toNumber()).toBeLessThan(anotherGasUsed.toNumber());
95+
});
96+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { readFileSync } from 'fs';
2+
import { ContractFactory, Provider, Script, Wallet, hexlify } from 'fuels';
3+
import { launchTestNode } from 'fuels/test-utils';
4+
import { join } from 'path';
5+
6+
import { SumScript as TypegenScript } from '../../../test/typegen';
7+
8+
/**
9+
* @group browser
10+
* @group node
11+
*
12+
* TODO: enable the test and reintroduce the docs
13+
*/
14+
describe.skip('Deploying Scripts', () => {
15+
it('deploys a script via loader and calls', async () => {
16+
using launched = await launchTestNode();
17+
18+
const {
19+
provider: testProvider,
20+
wallets: [testWallet],
21+
} = launched;
22+
23+
const providerUrl = testProvider.url;
24+
const WALLET_PVT_KEY = hexlify(testWallet.privateKey);
25+
26+
const factory = new ContractFactory(TypegenScript.bytecode, TypegenScript.abi, testWallet);
27+
const { waitForResult: waitForDeploy } = await factory.deployAsBlobTxForScript();
28+
await waitForDeploy();
29+
30+
const loaderBytecode = hexlify(
31+
readFileSync(
32+
join(
33+
__dirname,
34+
'../../../test/fixtures/forc-projects/sum-script/out/release/sum-script-loader.bin'
35+
)
36+
)
37+
);
38+
39+
// #region deploying-scripts
40+
// #import { Provider, Wallet, hexlify };
41+
// #context import { readFileSync } from 'fs';
42+
// #context import { WALLET_PVT_KEY } from 'path/to/my/env/file';
43+
// #context import { TypegenScript } from 'path/to/typegen/outputs';
44+
45+
// First, we will need the loader bytecode that is generated by `fuels deploy`
46+
// #context const loaderBytecode = hexlify(readFileSync('path/to/forc/build/outputs')));
47+
48+
const provider = await Provider.create(providerUrl);
49+
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
50+
51+
// Then we will instantiate the script using both the scripts bytecode and it's loader bytecode
52+
const script = new Script(loaderBytecode, TypegenScript.abi, wallet);
53+
54+
// Now we are free to interact with the script as we would normally, such as overriding the configurables
55+
const configurable = {
56+
AMOUNT: 20,
57+
};
58+
script.setConfigurableConstants(configurable);
59+
60+
const { waitForResult } = await script.functions.main(10).call();
61+
const { value, gasUsed } = await waitForResult();
62+
// #endregion deploying-scripts
63+
64+
const scriptWithoutLoader = new Script(TypegenScript.bytecode, TypegenScript.abi, wallet);
65+
scriptWithoutLoader.setConfigurableConstants(configurable);
66+
const { waitForResult: waitForAnotherResult } = await script.functions.main(10).call();
67+
const { value: anotherValue, gasUsed: anotherGasUsed } = await waitForAnotherResult();
68+
69+
expect(value).toBe(30);
70+
expect(anotherValue).toBe(30);
71+
expect(gasUsed.toNumber()).toBeLessThan(anotherGasUsed.toNumber());
72+
});
73+
});

internal/fuel-core/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.36.0
1+
0.37.0

nodemon.config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"**/out/release/**",
1515
"apps/demo-typegen/src/contract-types/**",
1616
"apps/demo-typegen/src/predicate-types/**",
17-
"apps/demo-typegen/src/script-types/**"
17+
"apps/demo-typegen/src/script-types/**",
18+
"packages/fuels/src/cli/commands/deploy/proxy/types/**"
1819
]
1920
}

packages/account/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"@fuel-ts/transactions": "workspace:*",
6060
"@fuel-ts/utils": "workspace:*",
6161
"@fuel-ts/versions": "workspace:*",
62-
"@fuels/vm-asm": "0.57.1",
62+
"@fuels/vm-asm": "0.58.0",
6363
"@noble/curves": "^1.6.0",
6464
"events": "^3.3.0",
6565
"graphql": "^16.9.0",

packages/account/src/predicate/predicate.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Interface } from '@fuel-ts/abi-coder';
33
import { Address } from '@fuel-ts/address';
44
import { ErrorCode, FuelError } from '@fuel-ts/errors';
55
import type { BytesLike } from '@fuel-ts/interfaces';
6-
import { arrayify, hexlify } from '@fuel-ts/utils';
6+
import { arrayify, hexlify, concat } from '@fuel-ts/utils';
77

88
import type { FakeResources } from '../account';
99
import { Account } from '../account';
@@ -35,8 +35,16 @@ export type PredicateParams<
3535
abi?: JsonAbi;
3636
data?: TData;
3737
configurableConstants?: TConfigurables;
38+
loaderBytecode?: BytesLike;
3839
};
3940

41+
function getDataOffset(binary: Uint8Array): number {
42+
const buffer = binary.buffer.slice(binary.byteOffset + 8, binary.byteOffset + 16);
43+
const dataView = new DataView(buffer);
44+
const dataOffset = dataView.getBigUint64(0, false); // big-endian
45+
return Number(dataOffset);
46+
}
47+
4048
/**
4149
* `Predicate` provides methods to populate transaction data with predicate information and sending transactions with them.
4250
*/
@@ -47,6 +55,7 @@ export class Predicate<
4755
bytes: Uint8Array;
4856
predicateData: TData = [] as unknown as TData;
4957
interface?: Interface;
58+
loaderBytecode: BytesLike = '';
5059

5160
/**
5261
* Creates an instance of the Predicate class.
@@ -63,6 +72,12 @@ export class Predicate<
6372
provider,
6473
data,
6574
configurableConstants,
75+
/**
76+
* TODO: Implement a getBytes method within the Predicate class. This method should return the loaderBytecode if it is set.
77+
* The getBytes method should be used in all places where we use this.bytes.
78+
* Note: Do not set loaderBytecode to a default string here; it should remain undefined when not provided.
79+
*/
80+
loaderBytecode = '',
6681
}: PredicateParams<TData, TConfigurables>) {
6782
const { predicateBytes, predicateInterface } = Predicate.processPredicateData(
6883
bytecode,
@@ -74,6 +89,7 @@ export class Predicate<
7489

7590
this.bytes = predicateBytes;
7691
this.interface = predicateInterface;
92+
this.loaderBytecode = loaderBytecode;
7793
if (data !== undefined && data.length > 0) {
7894
this.predicateData = data;
7995
}
@@ -230,7 +246,8 @@ export class Predicate<
230246
private static setConfigurableConstants(
231247
bytes: Uint8Array,
232248
configurableConstants: { [name: string]: unknown },
233-
abiInterface?: Interface
249+
abiInterface?: Interface,
250+
loaderBytecode?: BytesLike
234251
) {
235252
const mutatedBytes = bytes;
236253

@@ -263,6 +280,26 @@ export class Predicate<
263280

264281
mutatedBytes.set(encoded, offset);
265282
});
283+
284+
if (loaderBytecode) {
285+
/**
286+
* TODO: We mutate the predicate bytes here to be the loader bytes only if the configurables are being set.
287+
* What we actually need to do here is to mutate the loader bytes to include the configurables.
288+
*/
289+
const offset = getDataOffset(bytes);
290+
291+
// update the dataSection here as necessary (with configurables)
292+
const dataSection = mutatedBytes.slice(offset);
293+
294+
const dataSectionLen = dataSection.length;
295+
296+
// Convert dataSectionLen to big-endian bytes
297+
const dataSectionLenBytes = new Uint8Array(8);
298+
const dataSectionLenDataView = new DataView(dataSectionLenBytes.buffer);
299+
dataSectionLenDataView.setBigUint64(0, BigInt(dataSectionLen), false);
300+
301+
mutatedBytes.set(concat([loaderBytecode, dataSectionLenBytes, dataSection]));
302+
}
266303
} catch (err) {
267304
throw new FuelError(
268305
ErrorCode.INVALID_CONFIGURABLE_CONSTANTS,

packages/account/src/providers/provider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ describe('Provider', () => {
298298

299299
const version = await provider.getVersion();
300300

301-
expect(version).toEqual('0.36.0');
301+
expect(version).toEqual('0.37.0');
302302
});
303303

304304
it('can call()', async () => {

packages/contract/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"@fuel-ts/transactions": "workspace:*",
5252
"@fuel-ts/utils": "workspace:*",
5353
"@fuel-ts/versions": "workspace:*",
54-
"@fuels/vm-asm": "0.57.1",
54+
"@fuels/vm-asm": "0.58.0",
5555
"ramda": "^0.30.1"
5656
},
5757
"devDependencies": {

0 commit comments

Comments
 (0)