Skip to content

Commit

Permalink
feat: deploying scripts and predicates (#3251)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
8 people authored Oct 7, 2024
1 parent a35d644 commit c2f0599
Show file tree
Hide file tree
Showing 41 changed files with 1,391 additions and 264 deletions.
10 changes: 10 additions & 0 deletions .changeset/sharp-radios-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@fuel-ts/account": patch
"@fuel-ts/contract": patch
"fuels": patch
"@fuel-ts/program": patch
"@fuel-ts/versions": patch
"create-fuels": patch
---

feat: deploying scripts and predicates
3 changes: 3 additions & 0 deletions .knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"/apps/docs/*",
"/packages/abi-typegen/test/**",
"templates/**",
"/apps/docs-snippets/**",
"/apps/docs-snippets2/**/*.test.ts",
"apps/create-fuels-counter-guide/**"
],
Expand All @@ -28,6 +29,8 @@
"eslint-plugin-react",
"eslint-plugin-react-hooks",
"dotenv",
"kill",
"lsof",
"memfs",
"open",
"textlint",
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"pretest": "pnpm original:build"
},
"dependencies": {
"@fuels/vm-asm": "0.57.1",
"@fuels/vm-asm": "0.58.0",
"@types/node": "^22.5.5",
"@types/react-dom": "^18.3",
"@types/react": "^18.3.10",
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-react-cra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.29",
"private": true,
"dependencies": {
"@fuels/vm-asm": "0.57.1",
"@fuels/vm-asm": "0.58.0",
"@testing-library/react": "^16.0.1",
"@types/node": "^22.5.5",
"@types/react": "^18.3.10",
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-react-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"pretest": "pnpm original:build"
},
"dependencies": {
"@fuels/vm-asm": "0.57.1",
"@fuels/vm-asm": "0.58.0",
"fuels": "workspace:*",
"react-dom": "^18.3.1",
"react": "^18.3.1"
Expand Down
8 changes: 6 additions & 2 deletions apps/docs-snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"description": "",
"private": true,
"scripts": {
"pretest": "run-s build:forc type:check",
"build:forc": "pnpm fuels build",
"pretest": "run-s fuels:build type:check",
"xpretest": "run-s kill-node fuels:build fuels:node fuels:deploy kill-node type:check",
"kill-node": "lsof -t -i:4000 | xargs -r kill",
"fuels:deploy": "pnpm fuels deploy",
"fuels:node": "pnpm fuels node > /dev/null 2>&1 &",
"fuels:build": "pnpm fuels build",
"type:check": "tsc --noEmit"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { readFileSync } from 'fs';
import { ContractFactory, Predicate, Provider, Wallet, hexlify } from 'fuels';
import { launchTestNode } from 'fuels/test-utils';
import { join } from 'path';

import { ConfigurablePin as TypegenPredicate } from '../../../test/typegen';

/**
* @group browser
* @group node
*
* TODO: enable the test and reintroduce the docs
*/
describe.skip('Deploying Predicates', () => {
it('deploys a predicate via loader and calls', async () => {
using launched = await launchTestNode();

const {
provider: testProvider,
wallets: [testWallet, receiver],
} = launched;

const recieverInitialBalance = await receiver.getBalance();

const providerUrl = testProvider.url;
const WALLET_PVT_KEY = hexlify(testWallet.privateKey);

const factory = new ContractFactory(
TypegenPredicate.bytecode,
TypegenPredicate.abi,
testWallet
);
const { waitForResult: waitForDeploy } = await factory.deployAsBlobTxForScript();
await waitForDeploy();

const loaderBytecode = hexlify(
readFileSync(
join(
__dirname,
'../../../test/fixtures/forc-projects/configurable-pin/out/release/configurable-pin.deployed.bin'
)
)
);

// #region deploying-predicates
// #import { Provider, Wallet, hexlify };
// #context import { readFileSync } from 'fs';
// #context import { WALLET_PVT_KEY } from 'path/to/my/env/file';
// #context import { TypegenPredicate } from 'path/to/typegen/outputs';

// First, we will need the loader bytecode that is generated by `fuels deploy`
// #context const loaderBytecode = hexlify(readFileSync('path/to/forc/build/outputs')));

const provider = await Provider.create(providerUrl);
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);

// Then we will instantiate the predicate using both the scripts bytecode and it's loader bytecode,
// now we are free to interact with the predicate as we would normally, such as overriding the configurables
const predicate = new Predicate({
bytecode: loaderBytecode,
abi: TypegenPredicate.abi,
data: [1337],
provider,
});

// First, let's fund the predicate
const { waitForResult: waitForFund } = await wallet.transfer(predicate.address, 100_000);
await waitForFund();

const { waitForResult: waitForTransfer } = await predicate.transfer(receiver.address, 1000);
const { gasUsed } = await waitForTransfer();
// #endregion deploying-predicates

const anotherPredicate = new Predicate({
bytecode: TypegenPredicate.bytecode,
abi: TypegenPredicate.abi,
data: [1337],
provider,
});

const { waitForResult: waitForAnotherFund } = await wallet.transfer(
anotherPredicate.address,
100_000
);
await waitForAnotherFund();

const { waitForResult: waitForAnotherTransfer } = await anotherPredicate.transfer(
receiver.address,
1000
);
const { gasUsed: anotherGasUsed } = await waitForAnotherTransfer();

expect(recieverInitialBalance.toNumber()).toBeLessThan(recieverInitialBalance.toNumber());
expect(gasUsed.toNumber()).toBeLessThan(anotherGasUsed.toNumber());
});
});
73 changes: 73 additions & 0 deletions apps/docs-snippets/src/guide/scripts/deploying-scripts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { readFileSync } from 'fs';
import { ContractFactory, Provider, Script, Wallet, hexlify } from 'fuels';
import { launchTestNode } from 'fuels/test-utils';
import { join } from 'path';

import { SumScript as TypegenScript } from '../../../test/typegen';

/**
* @group browser
* @group node
*
* TODO: enable the test and reintroduce the docs
*/
describe.skip('Deploying Scripts', () => {
it('deploys a script via loader and calls', async () => {
using launched = await launchTestNode();

const {
provider: testProvider,
wallets: [testWallet],
} = launched;

const providerUrl = testProvider.url;
const WALLET_PVT_KEY = hexlify(testWallet.privateKey);

const factory = new ContractFactory(TypegenScript.bytecode, TypegenScript.abi, testWallet);
const { waitForResult: waitForDeploy } = await factory.deployAsBlobTxForScript();
await waitForDeploy();

const loaderBytecode = hexlify(
readFileSync(
join(
__dirname,
'../../../test/fixtures/forc-projects/sum-script/out/release/sum-script-loader.bin'
)
)
);

// #region deploying-scripts
// #import { Provider, Wallet, hexlify };
// #context import { readFileSync } from 'fs';
// #context import { WALLET_PVT_KEY } from 'path/to/my/env/file';
// #context import { TypegenScript } from 'path/to/typegen/outputs';

// First, we will need the loader bytecode that is generated by `fuels deploy`
// #context const loaderBytecode = hexlify(readFileSync('path/to/forc/build/outputs')));

const provider = await Provider.create(providerUrl);
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);

// Then we will instantiate the script using both the scripts bytecode and it's loader bytecode
const script = new Script(loaderBytecode, TypegenScript.abi, wallet);

// Now we are free to interact with the script as we would normally, such as overriding the configurables
const configurable = {
AMOUNT: 20,
};
script.setConfigurableConstants(configurable);

const { waitForResult } = await script.functions.main(10).call();
const { value, gasUsed } = await waitForResult();
// #endregion deploying-scripts

const scriptWithoutLoader = new Script(TypegenScript.bytecode, TypegenScript.abi, wallet);
scriptWithoutLoader.setConfigurableConstants(configurable);
const { waitForResult: waitForAnotherResult } = await script.functions.main(10).call();
const { value: anotherValue, gasUsed: anotherGasUsed } = await waitForAnotherResult();

expect(value).toBe(30);
expect(anotherValue).toBe(30);
expect(gasUsed.toNumber()).toBeLessThan(anotherGasUsed.toNumber());
});
});
2 changes: 1 addition & 1 deletion internal/fuel-core/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.36.0
0.37.0
3 changes: 2 additions & 1 deletion nodemon.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"**/out/release/**",
"apps/demo-typegen/src/contract-types/**",
"apps/demo-typegen/src/predicate-types/**",
"apps/demo-typegen/src/script-types/**"
"apps/demo-typegen/src/script-types/**",
"packages/fuels/src/cli/commands/deploy/proxy/types/**"
]
}
2 changes: 1 addition & 1 deletion packages/account/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"@fuel-ts/transactions": "workspace:*",
"@fuel-ts/utils": "workspace:*",
"@fuel-ts/versions": "workspace:*",
"@fuels/vm-asm": "0.57.1",
"@fuels/vm-asm": "0.58.0",
"@noble/curves": "^1.6.0",
"events": "^3.3.0",
"graphql": "^16.9.0",
Expand Down
41 changes: 39 additions & 2 deletions packages/account/src/predicate/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Interface } from '@fuel-ts/abi-coder';
import { Address } from '@fuel-ts/address';
import { ErrorCode, FuelError } from '@fuel-ts/errors';
import type { BytesLike } from '@fuel-ts/interfaces';
import { arrayify, hexlify } from '@fuel-ts/utils';
import { arrayify, hexlify, concat } from '@fuel-ts/utils';

import type { FakeResources } from '../account';
import { Account } from '../account';
Expand Down Expand Up @@ -35,8 +35,16 @@ export type PredicateParams<
abi?: JsonAbi;
data?: TData;
configurableConstants?: TConfigurables;
loaderBytecode?: BytesLike;
};

function getDataOffset(binary: Uint8Array): number {
const buffer = binary.buffer.slice(binary.byteOffset + 8, binary.byteOffset + 16);
const dataView = new DataView(buffer);
const dataOffset = dataView.getBigUint64(0, false); // big-endian
return Number(dataOffset);
}

/**
* `Predicate` provides methods to populate transaction data with predicate information and sending transactions with them.
*/
Expand All @@ -47,6 +55,7 @@ export class Predicate<
bytes: Uint8Array;
predicateData: TData = [] as unknown as TData;
interface?: Interface;
loaderBytecode: BytesLike = '';

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

this.bytes = predicateBytes;
this.interface = predicateInterface;
this.loaderBytecode = loaderBytecode;
if (data !== undefined && data.length > 0) {
this.predicateData = data;
}
Expand Down Expand Up @@ -230,7 +246,8 @@ export class Predicate<
private static setConfigurableConstants(
bytes: Uint8Array,
configurableConstants: { [name: string]: unknown },
abiInterface?: Interface
abiInterface?: Interface,
loaderBytecode?: BytesLike
) {
const mutatedBytes = bytes;

Expand Down Expand Up @@ -263,6 +280,26 @@ export class Predicate<

mutatedBytes.set(encoded, offset);
});

if (loaderBytecode) {
/**
* TODO: We mutate the predicate bytes here to be the loader bytes only if the configurables are being set.
* What we actually need to do here is to mutate the loader bytes to include the configurables.
*/
const offset = getDataOffset(bytes);

// update the dataSection here as necessary (with configurables)
const dataSection = mutatedBytes.slice(offset);

const dataSectionLen = dataSection.length;

// Convert dataSectionLen to big-endian bytes
const dataSectionLenBytes = new Uint8Array(8);
const dataSectionLenDataView = new DataView(dataSectionLenBytes.buffer);
dataSectionLenDataView.setBigUint64(0, BigInt(dataSectionLen), false);

mutatedBytes.set(concat([loaderBytecode, dataSectionLenBytes, dataSection]));
}
} catch (err) {
throw new FuelError(
ErrorCode.INVALID_CONFIGURABLE_CONSTANTS,
Expand Down
2 changes: 1 addition & 1 deletion packages/account/src/providers/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ describe('Provider', () => {

const version = await provider.getVersion();

expect(version).toEqual('0.36.0');
expect(version).toEqual('0.37.0');
});

it('can call()', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@fuel-ts/transactions": "workspace:*",
"@fuel-ts/utils": "workspace:*",
"@fuel-ts/versions": "workspace:*",
"@fuels/vm-asm": "0.57.1",
"@fuels/vm-asm": "0.58.0",
"ramda": "^0.30.1"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit c2f0599

Please sign in to comment.