From b0e9c8495a8d88ad7e60b11e2a00bc016cc7f820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Thu, 14 Nov 2024 13:01:40 +0100 Subject: [PATCH] chore!: refactor predicate and script deployment (#3389) --- .changeset/little-moons-drive.md | 6 + .../src/predicates/deploying-predicates.ts | 28 +- .../src/scripts/deploying-scripts.ts | 10 +- packages/account/src/index.ts | 1 + packages/account/src/predicate/predicate.ts | 66 ++- .../blob-transaction-request.ts | 4 +- .../src/utils/deployScriptOrPredicate.ts | 118 ++++++ .../predicate-script-loader-instructions.ts | 0 packages/contract/src/contract-factory.ts | 91 +--- packages/contract/src/loader/index.ts | 1 - packages/fuel-gauge/src/blob-deploy.test.ts | 390 +++++------------- .../fuel-gauge/src/check-user-account.test.ts | 7 +- .../test/fixtures/forc-projects/Forc.toml | 1 - .../forc-projects/script-dummy/Forc.toml | 7 - .../forc-projects/script-dummy/src/main.sw | 9 - .../script-with-configurable/src/main.sw | 1 + .../src/cli/commands/deploy/adjustOffsets.ts | 11 - .../cli/commands/deploy/deployPredicates.ts | 48 +-- .../src/cli/commands/deploy/deployScripts.ts | 48 +-- packages/script/src/script.ts | 21 +- 20 files changed, 327 insertions(+), 541 deletions(-) create mode 100644 .changeset/little-moons-drive.md create mode 100644 packages/account/src/utils/deployScriptOrPredicate.ts rename packages/{contract/src/loader => account/src/utils}/predicate-script-loader-instructions.ts (100%) delete mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/Forc.toml delete mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/src/main.sw delete mode 100644 packages/fuels/src/cli/commands/deploy/adjustOffsets.ts diff --git a/.changeset/little-moons-drive.md b/.changeset/little-moons-drive.md new file mode 100644 index 00000000000..0146c440977 --- /dev/null +++ b/.changeset/little-moons-drive.md @@ -0,0 +1,6 @@ +--- +"@fuel-ts/contract": minor +"@fuel-ts/account": patch +--- + +chore!: refactor predicate and script deployment diff --git a/apps/docs-snippets2/src/predicates/deploying-predicates.ts b/apps/docs-snippets2/src/predicates/deploying-predicates.ts index 6df4e5b1a46..1be4a208ee2 100644 --- a/apps/docs-snippets2/src/predicates/deploying-predicates.ts +++ b/apps/docs-snippets2/src/predicates/deploying-predicates.ts @@ -1,5 +1,5 @@ // #region full -import { Provider, Wallet, ContractFactory } from 'fuels'; +import { Provider, Wallet } from 'fuels'; import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../env'; import { ConfigurablePin, ConfigurablePinLoader } from '../typegend/predicates'; @@ -10,20 +10,18 @@ const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); const receiver = Wallet.generate({ provider }); const baseAssetId = provider.getBaseAssetId(); -// We can deploy dyanmically or via `fuels deploy` -const factory = new ContractFactory( - ConfigurablePin.bytecode, - ConfigurablePin.abi, - wallet -); -const { waitForResult: waitForDeploy } = - await factory.deployAsBlobTxForScript(); +// We can deploy dynamically or via `fuels deploy` +const originalPredicate = new ConfigurablePin({ + provider, +}); + +const { waitForResult: waitForDeploy } = await originalPredicate.deploy(wallet); await waitForDeploy(); // First, we will need to instantiate the script via it's loader bytecode. // This can be imported from the typegen outputs that were created on `fuels deploy`. // Then we can use the predicate as we would normally, such as overriding the configurables. -const predicate = new ConfigurablePinLoader({ +const loaderPredicate = new ConfigurablePinLoader({ data: [23], provider, configurableConstants: { @@ -32,11 +30,15 @@ const predicate = new ConfigurablePinLoader({ }); // Now, let's fund the predicate -const fundTx = await wallet.transfer(predicate.address, 100_000, baseAssetId); +const fundTx = await wallet.transfer( + loaderPredicate.address, + 100_000, + baseAssetId +); await fundTx.waitForResult(); // Then we'll execute the transfer and validate the predicate -const transferTx = await predicate.transfer( +const transferTx = await loaderPredicate.transfer( receiver.address, 1000, baseAssetId @@ -44,5 +46,5 @@ const transferTx = await predicate.transfer( const { isStatusSuccess } = await transferTx.waitForResult(); // #endregion full -console.log('Predicate defined', predicate); +console.log('Predicate defined', loaderPredicate); console.log('Should fund predicate successfully', isStatusSuccess); diff --git a/apps/docs-snippets2/src/scripts/deploying-scripts.ts b/apps/docs-snippets2/src/scripts/deploying-scripts.ts index 4e123965baa..51d30054277 100644 --- a/apps/docs-snippets2/src/scripts/deploying-scripts.ts +++ b/apps/docs-snippets2/src/scripts/deploying-scripts.ts @@ -1,4 +1,4 @@ -import { ContractFactory, Provider, Wallet, hexlify } from 'fuels'; +import { Provider, Wallet, hexlify } from 'fuels'; import { launchTestNode } from 'fuels/test-utils'; import { @@ -16,13 +16,9 @@ const { const providerUrl = testProvider.url; const WALLET_PVT_KEY = hexlify(testWallet.privateKey); -const factory = new ContractFactory( - TypegenScript.bytecode, - TypegenScript.abi, - testWallet -); +const originalScript = new TypegenScript(testWallet); const { waitForResult: waitForDeploy } = - await factory.deployAsBlobTxForScript(); + await originalScript.deploy(testWallet); await waitForDeploy(); // #region deploying-scripts diff --git a/packages/account/src/index.ts b/packages/account/src/index.ts index 7c470acebd4..49203ab33c3 100644 --- a/packages/account/src/index.ts +++ b/packages/account/src/index.ts @@ -8,3 +8,4 @@ export * from './wallet-manager'; export * from './predicate'; export * from './providers'; export * from './connectors'; +export { deployScriptOrPredicate } from './utils/deployScriptOrPredicate'; diff --git a/packages/account/src/predicate/predicate.ts b/packages/account/src/predicate/predicate.ts index 3d8957f883b..a8c0b80fb4a 100644 --- a/packages/account/src/predicate/predicate.ts +++ b/packages/account/src/predicate/predicate.ts @@ -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, concat } from '@fuel-ts/utils'; +import { arrayify, hexlify } from '@fuel-ts/utils'; import type { FakeResources } from '../account'; import { Account } from '../account'; @@ -23,6 +23,7 @@ import type { TransactionRequestLike, TransactionResponse, } from '../providers'; +import { deployScriptOrPredicate } from '../utils/deployScriptOrPredicate'; import { getPredicateRoot } from './utils'; @@ -35,16 +36,8 @@ 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. */ @@ -55,7 +48,6 @@ export class Predicate< bytes: Uint8Array; predicateData: TData = [] as unknown as TData; interface: Interface; - loaderBytecode: BytesLike = ''; /** * Creates an instance of the Predicate class. @@ -72,12 +64,6 @@ 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) { const { predicateBytes, predicateInterface } = Predicate.processPredicateData( bytecode, @@ -89,7 +75,6 @@ export class Predicate< this.bytes = predicateBytes; this.interface = predicateInterface; - this.loaderBytecode = loaderBytecode; if (data !== undefined && data.length > 0) { this.predicateData = data; } @@ -243,8 +228,7 @@ export class Predicate< private static setConfigurableConstants( bytes: Uint8Array, configurableConstants: { [name: string]: unknown }, - abiInterface: Interface, - loaderBytecode?: BytesLike + abiInterface: Interface ) { const mutatedBytes = bytes; @@ -270,26 +254,6 @@ 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, @@ -338,4 +302,28 @@ export class Predicate< return index; } + + /** + * + * @param account - The account used to pay the deployment costs. + * @returns The _blobId_ and a _waitForResult_ callback that returns the deployed predicate + * once the blob deployment transaction finishes. + * + * The returned loader predicate will have the same configurable constants + * as the original predicate which was used to generate the loader predicate. + */ + async deploy(account: Account) { + return deployScriptOrPredicate({ + deployer: account, + abi: this.interface.jsonAbi, + bytecode: this.bytes, + loaderInstanceCallback: (loaderBytecode, newAbi) => + new Predicate({ + bytecode: loaderBytecode, + abi: newAbi, + provider: this.provider, + data: this.predicateData, + }) as T, + }); + } } diff --git a/packages/account/src/providers/transaction-request/blob-transaction-request.ts b/packages/account/src/providers/transaction-request/blob-transaction-request.ts index 1eb53255796..db90b4f16d4 100644 --- a/packages/account/src/providers/transaction-request/blob-transaction-request.ts +++ b/packages/account/src/providers/transaction-request/blob-transaction-request.ts @@ -12,7 +12,7 @@ import { BaseTransactionRequest, TransactionType } from './transaction-request'; export interface BlobTransactionRequestLike extends BaseTransactionRequestLike { /** Blob ID */ blobId: string; - /** Witness index of contract bytecode to create */ + /** Witness index of the bytecode to create */ witnessIndex?: number; } @@ -25,7 +25,7 @@ export class BlobTransactionRequest extends BaseTransactionRequest { type = TransactionType.Blob as const; /** Blob ID */ blobId: string; - /** Witness index of contract bytecode to create */ + /** Witness index of the bytecode to create */ witnessIndex: number; /** diff --git a/packages/account/src/utils/deployScriptOrPredicate.ts b/packages/account/src/utils/deployScriptOrPredicate.ts new file mode 100644 index 00000000000..3ef7dad9c70 --- /dev/null +++ b/packages/account/src/utils/deployScriptOrPredicate.ts @@ -0,0 +1,118 @@ +import type { JsonAbi } from '@fuel-ts/abi-coder'; +import { FuelError, ErrorCode } from '@fuel-ts/errors'; +import { hash } from '@fuel-ts/hasher'; +import { bn } from '@fuel-ts/math'; +import { arrayify } from '@fuel-ts/utils'; + +import type { Account } from '../account'; +import { BlobTransactionRequest, calculateGasFee, TransactionStatus } from '../providers'; + +import { + getDataOffset, + getPredicateScriptLoaderInstructions, +} from './predicate-script-loader-instructions'; + +async function fundBlobTx(deployer: Account, blobTxRequest: BlobTransactionRequest) { + // Check the account can afford to deploy all chunks and loader + let totalCost = bn(0); + const chainInfo = deployer.provider.getChain(); + const gasPrice = await deployer.provider.estimateGasPrice(10); + const priceFactor = chainInfo.consensusParameters.feeParameters.gasPriceFactor; + + const minGas = blobTxRequest.calculateMinGas(chainInfo); + + const minFee = calculateGasFee({ + gasPrice, + gas: minGas, + priceFactor, + tip: blobTxRequest.tip, + }).add(1); + + totalCost = totalCost.add(minFee); + + if (totalCost.gt(await deployer.getBalance())) { + throw new FuelError(ErrorCode.FUNDS_TOO_LOW, 'Insufficient balance to deploy predicate.'); + } + + const txCost = await deployer.getTransactionCost(blobTxRequest); + // eslint-disable-next-line no-param-reassign + blobTxRequest.maxFee = txCost.maxFee; + return deployer.fund(blobTxRequest, txCost); +} + +function adjustConfigurableOffsets(jsonAbi: JsonAbi, configurableOffsetDiff: number) { + const { configurables: readOnlyConfigurables } = jsonAbi; + const configurables: JsonAbi['configurables'] = []; + readOnlyConfigurables.forEach((config) => { + // @ts-expect-error shut up the read-only thing + configurables.push({ ...config, offset: config.offset - configurableOffsetDiff }); + }); + return { ...jsonAbi, configurables } as JsonAbi; +} + +interface Deployer { + deployer: Account; + bytecode: Uint8Array; + abi: JsonAbi; + loaderInstanceCallback: (loaderBytecode: Uint8Array, newAbi: JsonAbi) => T; +} + +export async function deployScriptOrPredicate({ + deployer, + bytecode, + abi, + loaderInstanceCallback, +}: Deployer) { + const dataSectionOffset = getDataOffset(arrayify(bytecode)); + const byteCodeWithoutDataSection = bytecode.slice(0, dataSectionOffset); + + // Generate the associated create tx for the loader contract + const blobId = hash(byteCodeWithoutDataSection); + + const blobTxRequest = new BlobTransactionRequest({ + blobId, + witnessIndex: 0, + witnesses: [byteCodeWithoutDataSection], + }); + + const { loaderBytecode, blobOffset } = getPredicateScriptLoaderInstructions( + arrayify(bytecode), + arrayify(blobId) + ); + + const configurableOffsetDiff = byteCodeWithoutDataSection.length - (blobOffset || 0); + const newAbi = adjustConfigurableOffsets(abi, configurableOffsetDiff); + + const blobExists = (await deployer.provider.getBlobs([blobId])).length > 0; + + const loaderInstance = loaderInstanceCallback(loaderBytecode, newAbi); + if (blobExists) { + return { + waitForResult: () => Promise.resolve(loaderInstance), + blobId, + }; + } + + const fundedBlobRequest = await fundBlobTx(deployer, blobTxRequest); + + // Transaction id is unset until we have funded the create tx, which is dependent on the blob tx + const waitForResult = async () => { + try { + const blobTx = await deployer.sendTransaction(fundedBlobRequest); + const result = await blobTx.waitForResult(); + + if (result.status !== TransactionStatus.success) { + throw new Error(); + } + } catch (err: unknown) { + throw new FuelError(ErrorCode.TRANSACTION_FAILED, 'Failed to deploy predicate chunk'); + } + + return loaderInstance; + }; + + return { + waitForResult, + blobId, + }; +} diff --git a/packages/contract/src/loader/predicate-script-loader-instructions.ts b/packages/account/src/utils/predicate-script-loader-instructions.ts similarity index 100% rename from packages/contract/src/loader/predicate-script-loader-instructions.ts rename to packages/account/src/utils/predicate-script-loader-instructions.ts diff --git a/packages/contract/src/contract-factory.ts b/packages/contract/src/contract-factory.ts index c79d338c7bb..9adcfb3a55b 100644 --- a/packages/contract/src/contract-factory.ts +++ b/packages/contract/src/contract-factory.ts @@ -21,14 +21,9 @@ import type { BytesLike } from '@fuel-ts/interfaces'; import { bn } from '@fuel-ts/math'; import { Contract } from '@fuel-ts/program'; import type { StorageSlot } from '@fuel-ts/transactions'; -import { arrayify, isDefined, hexlify } from '@fuel-ts/utils'; +import { arrayify, isDefined } from '@fuel-ts/utils'; -import { - getLoaderInstructions, - getPredicateScriptLoaderInstructions, - getContractChunks, - getDataOffset, -} from './loader'; +import { getLoaderInstructions, getContractChunks } from './loader'; import { getContractId, getContractStorageRoot, hexlifyWithPrefix } from './util'; /** Amount of percentage override for chunk sizes in blob transactions */ @@ -377,88 +372,6 @@ export default class ContractFactory { return { waitForResult, contractId, waitForTransactionId }; } - async deployAsBlobTxForScript(): Promise<{ - waitForResult: () => Promise<{ - loaderBytecode: string; - configurableOffsetDiff: number; - }>; - blobId: string; - }> { - const account = this.getAccount(); - - const dataSectionOffset = getDataOffset(arrayify(this.bytecode)); - const byteCodeWithoutDataSection = this.bytecode.slice(0, dataSectionOffset); - - // Generate the associated create tx for the loader contract - const blobId = hash(byteCodeWithoutDataSection); - - const bloTransactionRequest = this.blobTransactionRequest({ - bytecode: byteCodeWithoutDataSection, - }); - - const { loaderBytecode, blobOffset } = getPredicateScriptLoaderInstructions( - arrayify(this.bytecode), - arrayify(blobId) - ); - - const configurableOffsetDiff = byteCodeWithoutDataSection.length - (blobOffset || 0); - - const blobExists = (await account.provider.getBlobs([blobId])).length > 0; - if (blobExists) { - return { - waitForResult: () => - Promise.resolve({ loaderBytecode: hexlify(loaderBytecode), configurableOffsetDiff }), - blobId, - }; - } - - // Check the account can afford to deploy all chunks and loader - let totalCost = bn(0); - const chainInfo = account.provider.getChain(); - const gasPrice = await account.provider.estimateGasPrice(10); - const priceFactor = chainInfo.consensusParameters.feeParameters.gasPriceFactor; - - const minGas = bloTransactionRequest.calculateMinGas(chainInfo); - const minFee = calculateGasFee({ - gasPrice, - gas: minGas, - priceFactor, - tip: bloTransactionRequest.tip, - }).add(1); - - totalCost = totalCost.add(minFee); - - if (totalCost.gt(await account.getBalance())) { - throw new FuelError(ErrorCode.FUNDS_TOO_LOW, 'Insufficient balance to deploy contract.'); - } - - // Transaction id is unset until we have funded the create tx, which is dependent on the blob txs - const waitForResult = async () => { - // Deploy the chunks as blob txs - const fundedBlobRequest = await this.fundTransactionRequest(bloTransactionRequest); - - let result: TransactionResult; - - try { - const blobTx = await account.sendTransaction(fundedBlobRequest); - result = await blobTx.waitForResult(); - } catch (err: unknown) { - throw new FuelError(ErrorCode.TRANSACTION_FAILED, 'Failed to deploy contract chunk'); - } - - if (!result.status || result.status !== TransactionStatus.success) { - throw new FuelError(ErrorCode.TRANSACTION_FAILED, 'Failed to deploy contract chunk'); - } - - return { loaderBytecode: hexlify(loaderBytecode), configurableOffsetDiff }; - }; - - return { - waitForResult, - blobId, - }; - } - /** * Set configurable constants of the contract with the specified values. * diff --git a/packages/contract/src/loader/index.ts b/packages/contract/src/loader/index.ts index 5c888356c61..6dee5f1765d 100644 --- a/packages/contract/src/loader/index.ts +++ b/packages/contract/src/loader/index.ts @@ -1,4 +1,3 @@ export * from './loader-script'; -export * from './predicate-script-loader-instructions'; export * from './types'; export * from './utils'; diff --git a/packages/fuel-gauge/src/blob-deploy.test.ts b/packages/fuel-gauge/src/blob-deploy.test.ts index d9160eb5312..9bbcd328d66 100644 --- a/packages/fuel-gauge/src/blob-deploy.test.ts +++ b/packages/fuel-gauge/src/blob-deploy.test.ts @@ -1,190 +1,114 @@ -import type { JsonAbi } from 'fuels'; -import { bn, ContractFactory, getRandomB256, hexlify, Predicate, Script, Wallet } from 'fuels'; +import type { Script } from 'fuels'; +import { getRandomB256, hexlify, Predicate, Wallet } from 'fuels'; import { launchTestNode } from 'fuels/test-utils'; import { - ScriptDummy, - PredicateFalseConfigurable, ScriptMainArgBool, PredicateTrue, - PredicateWithMoreConfigurables, ScriptWithMoreConfigurable, + PredicateWithConfigurable, + PredicateWithMoreConfigurables, + ScriptWithConfigurable, } from '../test/typegen'; /** * @group node + * @group browser */ -describe('first try', () => { - const mapToLoaderAbi = (jsonAbi: JsonAbi, configurableOffsetDiff: number) => { - const { configurables: readOnlyConfigurables } = jsonAbi; - const configurables: JsonAbi['configurables'] = []; - readOnlyConfigurables.forEach((config) => { - // @ts-expect-error shut up the read-only thing - configurables.push({ ...config, offset: config.offset - configurableOffsetDiff }); +describe('deploying blobs', () => { + function decodeConfigurables( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + program: Predicate | Script + ): Record { + const configurables: Record = {}; + + Object.entries(program.interface.configurables).forEach(([key, { offset, concreteTypeId }]) => { + const data = program.bytes.slice(offset); + configurables[key] = program.interface.decodeType(concreteTypeId, data)[0]; }); - return { ...jsonAbi, configurables } as JsonAbi; - }; - - it('should ensure deploy the same blob again will not throw error', async () => { - using launch = await launchTestNode(); - - const { - wallets: [wallet], - } = launch; - const spy = vi.spyOn(wallet.provider, 'sendTransaction'); + return configurables; + } - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode } = await waitForResult(); - - const { waitForResult: waitForResult2 } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode: loaderBytecode2 } = await waitForResult2(); - - // Should deploy not deploy the same blob again - expect(spy).toHaveBeenCalledTimes(1); - expect(loaderBytecode).equals(loaderBytecode2); - - vi.restoreAllMocks(); - }); - - it('should deploy blob for a script transaction and submit it', async () => { + it('script blob deployment works (NO CONFIGURABLE)', async () => { using launch = await launchTestNode(); const { + provider, wallets: [wallet], } = launch; - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); + const script = new ScriptMainArgBool(wallet); - const { loaderBytecode } = await waitForResult(); + const { waitForResult, blobId } = await script.deploy(wallet); - expect(loaderBytecode).to.not.equal(hexlify(ScriptDummy.bytecode)); - }); + const loaderScript = await waitForResult(); - it('should deploy blob for a script transaction and submit it (NO CONFIGURABLE)', async () => { - using launch = await launchTestNode(); + const blobDeployed = (await provider.getBlobs([blobId])).length > 0; + expect(blobDeployed).toBe(true); - const { - wallets: [wallet], - } = launch; - - const factory = new ContractFactory(ScriptMainArgBool.bytecode, ScriptMainArgBool.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); + expect(loaderScript.bytes).not.toEqual(script.bytes); - const script = new Script( - loaderBytecode, - mapToLoaderAbi(ScriptMainArgBool.abi, configurableOffsetDiff), - wallet - ); - - const { waitForResult: waitForResult2 } = await script.functions.main(true).call(); + const { waitForResult: waitForResult2 } = await loaderScript.functions.main(true).call(); const { value, transactionResult: { transaction }, } = await waitForResult2(); - expect(transaction.script).equals(loaderBytecode); + expect(transaction.script).equals(hexlify(loaderScript.bytes)); expect(value).toBe(true); }); - it('Should work for setting the configurable constants', async () => { + it('script blob deployment works (CONFIGURABLE)', async () => { using launch = await launchTestNode(); const { wallets: [wallet], } = launch; - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); + const script = new ScriptWithConfigurable(wallet); + const loaderScript = await (await script.deploy(wallet)).waitForResult(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - const script = new Script( - loaderBytecode, - mapToLoaderAbi(ScriptDummy.abi, configurableOffsetDiff), - wallet - ); + expect(decodeConfigurables(loaderScript)).toEqual(decodeConfigurables(script)); - const configurable = { - SECRET_NUMBER: 10001, + // Test that default configurables are included by default + const defaultConfigurable = { + FEE: 5, }; - script.setConfigurableConstants(configurable); - const { waitForResult: waitForResult2 } = await script.functions.main().call(); - const { value } = await waitForResult2(); + const defaultResult = await ( + await loaderScript.functions.main(defaultConfigurable.FEE).call() + ).waitForResult(); - expect(value).toBe(true); - }); - - it('Should return false for incorrectly set configurable constants', async () => { - using launch = await launchTestNode(); + expect(defaultResult.logs[0]).toEqual(defaultConfigurable.FEE); + expect(defaultResult.value).toBe(true); - const { - wallets: [wallet], - } = launch; - - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - - const preScript = new Script( - loaderBytecode, - mapToLoaderAbi(ScriptDummy.abi, configurableOffsetDiff), - wallet - ); + // Test that you can update configurables of the loader script const configurable = { - SECRET_NUMBER: 299, + FEE: 234, }; - preScript.setConfigurableConstants(configurable); - - const { waitForResult: waitForResult2 } = await preScript.functions.main().call(); + loaderScript.setConfigurableConstants(configurable); - const { value, logs } = await waitForResult2(); + const result1 = await ( + await loaderScript.functions.main(configurable.FEE).call() + ).waitForResult(); - expect(logs[0].toNumber()).equal(configurable.SECRET_NUMBER); - expect(value).toBe(false); + expect(result1.logs[0]).toEqual(configurable.FEE); + expect(result1.value).toBe(true); }); - it('it should return false if no configurable constants are set', async () => { + it('script blob deployment works (COMPLEX CONFIGURABLE)', async () => { using launch = await launchTestNode(); const { wallets: [wallet], } = launch; - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - - const script = new Script( - loaderBytecode, - mapToLoaderAbi(ScriptDummy.abi, configurableOffsetDiff), - wallet - ); + const script = new ScriptWithMoreConfigurable(wallet); - const { waitForResult: waitForResult2 } = await script.functions.main().call(); - const { value, logs } = await waitForResult2(); - expect(logs[0].toNumber()).equal(9000); - expect(value).toBe(false); - }); - - it('should set configurables in complicated script', async () => { - using launch = await launchTestNode(); - - const { - wallets: [wallet], - } = launch; + const loaderScript = await (await script.deploy(wallet)).waitForResult(); - const factory = new ContractFactory( - ScriptWithMoreConfigurable.bytecode, - ScriptWithMoreConfigurable.abi, - wallet - ); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); + expect(decodeConfigurables(loaderScript)).toEqual(decodeConfigurables(script)); const configurable = { U8: 16, @@ -207,21 +131,10 @@ describe('first try', () => { }, }; - const normalScript = new Script( - ScriptWithMoreConfigurable.bytecode, - ScriptWithMoreConfigurable.abi, - wallet - ); - - normalScript.setConfigurableConstants(configurable); - - const script = new Script( - loaderBytecode, - mapToLoaderAbi(ScriptWithMoreConfigurable.abi, configurableOffsetDiff), - wallet - ); - script.setConfigurableConstants(configurable); + loaderScript.setConfigurableConstants(configurable); + + expect(decodeConfigurables(loaderScript)).toEqual(decodeConfigurables(script)); const { waitForResult: waitForResult2 } = await script.functions.main().call(); const { value, logs } = await waitForResult2(); @@ -241,126 +154,64 @@ describe('first try', () => { expect(logs[10]).toStrictEqual(configurable.STRUCT_1); }); - it('Should work with predicates', async () => { + test("deploying existing script blob doesn't throw", async () => { using launch = await launchTestNode(); + const { - wallets: [wallet], provider, + wallets: [wallet], } = launch; - const receiver = Wallet.generate({ provider }); + const script = new ScriptMainArgBool(wallet); - const factory = new ContractFactory( - PredicateFalseConfigurable.bytecode, - PredicateFalseConfigurable.abi, - wallet - ); - - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - - expect(loaderBytecode).to.not.equal(hexlify(PredicateFalseConfigurable.bytecode)); - - const configurable = { - SECRET_NUMBER: 8000, - }; - - const predicate = new Predicate({ - data: [configurable.SECRET_NUMBER], - bytecode: loaderBytecode, - abi: mapToLoaderAbi(PredicateFalseConfigurable.abi, configurableOffsetDiff), - provider, - configurableConstants: configurable, - }); + const sendTransactionSpy = vi.spyOn(provider, 'sendTransaction'); - await wallet.transfer(predicate.address, 10_000, provider.getBaseAssetId()); + await (await script.deploy(wallet)).waitForResult(); + await (await script.deploy(wallet)).waitForResult(); - const tx = await predicate.transfer(receiver.address, 1000, provider.getBaseAssetId()); - const response = await tx.waitForResult(); - expect(response.isStatusSuccess).toBe(true); + expect(sendTransactionSpy).toHaveBeenCalledTimes(1); }); - it('Should work with predicate when not setting configurables values', async () => { + it('predicate blob deployment works', async () => { using launch = await launchTestNode(); + const { - wallets: [wallet], provider, + wallets: [deployer], } = launch; - const factory = new ContractFactory( - PredicateFalseConfigurable.bytecode, - PredicateFalseConfigurable.abi, - wallet - ); - - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - - expect(loaderBytecode).to.not.equal(hexlify(PredicateFalseConfigurable.bytecode)); - - const SECRET_NUMBER = 9000; + const configurableConstants = { + FEE: 10, + ADDRESS: '0x38966262edb5997574be45f94c665aedb41a1663f5b0528e765f355086eebf96', + }; - const predicate = new Predicate({ - data: [bn(SECRET_NUMBER)], - bytecode: loaderBytecode, - abi: mapToLoaderAbi(PredicateFalseConfigurable.abi, configurableOffsetDiff), + const predicate = new PredicateWithConfigurable({ provider, + configurableConstants, + data: [configurableConstants.FEE, configurableConstants.ADDRESS], }); - const transfer2 = await wallet.transfer(predicate.address, 10_000, provider.getBaseAssetId()); - await transfer2.waitForResult(); + const { waitForResult, blobId } = await predicate.deploy(deployer); + const loaderPredicate = await waitForResult(); - /** - * When destructuring the response, we get the following error: - * Cannot read properties of undefined (reading 'waitForStatusChange') - * TODO: Fix this! - */ - const transfer3 = await predicate.transfer(wallet.address, 1000, provider.getBaseAssetId()); - const { isStatusSuccess } = await transfer3.waitForResult(); + const blobDeployed = (await provider.getBlobs([blobId])).length > 0; + expect(blobDeployed).toBe(true); - expect(isStatusSuccess).toBe(true); - }); + expect(loaderPredicate.bytes).not.toEqual(predicate.bytes); + expect(loaderPredicate.address.toB256()).not.toEqual(predicate.address.toB256()); + expect(loaderPredicate.predicateData).toEqual(predicate.predicateData); + expect(decodeConfigurables(loaderPredicate)).toEqual(decodeConfigurables(predicate)); - it('Should work with predicate with no configurable constants', async () => { - using launch = await launchTestNode(); - const { - wallets: [wallet], - provider, - } = launch; - - const factory = new ContractFactory( - PredicateFalseConfigurable.bytecode, - PredicateFalseConfigurable.abi, - wallet - ); + await (await deployer.transfer(loaderPredicate.address, 10_000)).waitForResult(); - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode } = await waitForResult(); - - expect(loaderBytecode).to.not.equal(hexlify(PredicateTrue.bytecode)); - - const predicate = new Predicate({ - bytecode: PredicateTrue.bytecode, - abi: PredicateTrue.abi, - provider, - loaderBytecode, - }); - - const transfer2 = await wallet.transfer(predicate.address, 10_000, provider.getBaseAssetId()); - await transfer2.waitForResult(); - - /** - * When destructuring the response, we get the following error: - * Cannot read properties of undefined (reading 'waitForStatusChange') - * TODO: Fix this! - */ - const transfer3 = await predicate.transfer(wallet.address, 1000, provider.getBaseAssetId()); - const { isStatusSuccess } = await transfer3.waitForResult(); + const { isStatusSuccess } = await ( + await loaderPredicate.transfer(deployer.address, 10) + ).waitForResult(); expect(isStatusSuccess).toBe(true); }); - it('can run with loader bytecode with manually modified configurables', async () => { + it('loader predicate can be used with different configurables', async () => { using launch = await launchTestNode(); const { wallets: [wallet], @@ -369,26 +220,23 @@ describe('first try', () => { const receiver = Wallet.generate({ provider }); - const factory = new ContractFactory( - PredicateFalseConfigurable.bytecode, - PredicateFalseConfigurable.abi, - wallet - ); - - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - expect(loaderBytecode).to.not.equal(hexlify(PredicateFalseConfigurable.bytecode)); + const loaderPredicate = await ( + await new PredicateWithMoreConfigurables({ provider }).deploy(wallet) + ).waitForResult(); const configurable = { - SECRET_NUMBER: 8000, + FEE: 99, + ADDRESS: getRandomB256(), + U16: 305, + U32: 101, + U64: 1000000, + BOOL: false, }; - const newAbi = mapToLoaderAbi(PredicateFalseConfigurable.abi, configurableOffsetDiff); - const predicate = new Predicate({ - data: [configurable.SECRET_NUMBER], - bytecode: loaderBytecode, - abi: newAbi, + data: [configurable.FEE, configurable.ADDRESS], + bytecode: loaderPredicate.bytes, + abi: loaderPredicate.interface.jsonAbi, provider, configurableConstants: configurable, }); @@ -400,47 +248,23 @@ describe('first try', () => { expect(response.isStatusSuccess).toBe(true); }); - it('can run with loader bytecode with many manually modified configurables', async () => { + test("deploying existing predicate blob doesn't throw", async () => { using launch = await launchTestNode(); + const { - wallets: [wallet], provider, + wallets: [deployer], } = launch; - const receiver = Wallet.generate({ provider }); - - const factory = new ContractFactory( - PredicateWithMoreConfigurables.bytecode, - PredicateWithMoreConfigurables.abi, - wallet - ); - - const { waitForResult } = await factory.deployAsBlobTxForScript(); - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - expect(loaderBytecode).to.not.equal(hexlify(PredicateWithMoreConfigurables.bytecode)); - const configurable = { - FEE: 99, - ADDRESS: getRandomB256(), - U16: 305, - U32: 101, - U64: 1000000, - BOOL: false, - }; - - const newAbi = mapToLoaderAbi(PredicateWithMoreConfigurables.abi, configurableOffsetDiff); - - const predicate = new Predicate({ - data: [configurable.FEE, configurable.ADDRESS], - bytecode: loaderBytecode, - abi: newAbi, + const predicate = new PredicateTrue({ provider, - configurableConstants: configurable, }); - await wallet.transfer(predicate.address, 10_000, provider.getBaseAssetId()); + const sendTransactionSpy = vi.spyOn(provider, 'sendTransaction'); - const tx = await predicate.transfer(receiver.address, 1000, provider.getBaseAssetId()); - const response = await tx.waitForResult(); - expect(response.isStatusSuccess).toBe(true); + await (await predicate.deploy(deployer)).waitForResult(); + await (await predicate.deploy(deployer)).waitForResult(); + + expect(sendTransactionSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/fuel-gauge/src/check-user-account.test.ts b/packages/fuel-gauge/src/check-user-account.test.ts index de623dfd6f6..e007b2f8508 100644 --- a/packages/fuel-gauge/src/check-user-account.test.ts +++ b/packages/fuel-gauge/src/check-user-account.test.ts @@ -1,8 +1,7 @@ -import { ContractFactory } from 'fuels'; import { launchTestNode } from 'fuels/test-utils'; import { AdvancedLoggingFactory } from '../test/typegen/contracts/AdvancedLoggingFactory'; -import { ScriptDummy } from '../test/typegen/scripts'; +import { ScriptMainArgBool } from '../test/typegen/scripts'; /** * @group browser @@ -17,8 +16,8 @@ describe('User account tests', () => { wallets: [wallet], } = launch; - const factory = new ContractFactory(ScriptDummy.bytecode, ScriptDummy.abi, wallet); - const { waitForResult, blobId } = await factory.deployAsBlobTxForScript(); + const script = new ScriptMainArgBool(wallet); + const { waitForResult, blobId } = await script.deploy(wallet); await waitForResult(); diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml index bb2f195d352..f76d1d3d097 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml +++ b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml @@ -8,7 +8,6 @@ members = [ "auth_testing_contract", "bytecode-sway-lib", "bytes-contract", - "script-dummy", "call-test-contract", "collision_in_fn_names", "complex-predicate", diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/Forc.toml deleted file mode 100644 index 2e82eed1125..00000000000 --- a/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/Forc.toml +++ /dev/null @@ -1,7 +0,0 @@ -[project] -authors = ["Fuel Labs "] -entry = "main.sw" -license = "Apache-2.0" -name = "script-dummy" - -[dependencies] diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/src/main.sw deleted file mode 100644 index af21ada4589..00000000000 --- a/packages/fuel-gauge/test/fixtures/forc-projects/script-dummy/src/main.sw +++ /dev/null @@ -1,9 +0,0 @@ -script; -configurable { - SECRET_NUMBER: u64 = 9000, -} - -fn main() -> bool { - log(SECRET_NUMBER); - SECRET_NUMBER == 10001 -} diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/script-with-configurable/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/script-with-configurable/src/main.sw index d84adc07c73..2c5ee67efd9 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/script-with-configurable/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/script-with-configurable/src/main.sw @@ -5,5 +5,6 @@ configurable { } fn main(inputed_fee: u8) -> bool { + log(FEE); FEE == inputed_fee } diff --git a/packages/fuels/src/cli/commands/deploy/adjustOffsets.ts b/packages/fuels/src/cli/commands/deploy/adjustOffsets.ts deleted file mode 100644 index bb0056df036..00000000000 --- a/packages/fuels/src/cli/commands/deploy/adjustOffsets.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; - -export const adjustOffsets = (jsonAbi: JsonAbi, configurableOffsetDiff: number) => { - const { configurables: readOnlyConfigurables } = jsonAbi; - const configurables: JsonAbi['configurables'] = []; - readOnlyConfigurables.forEach((config) => { - // @ts-expect-error shut up the read-only thing - configurables.push({ ...config, offset: config.offset - configurableOffsetDiff }); - }); - return { ...jsonAbi, configurables } as JsonAbi; -}; diff --git a/packages/fuels/src/cli/commands/deploy/deployPredicates.ts b/packages/fuels/src/cli/commands/deploy/deployPredicates.ts index 3010841ff5b..b3375d85e42 100644 --- a/packages/fuels/src/cli/commands/deploy/deployPredicates.ts +++ b/packages/fuels/src/cli/commands/deploy/deployPredicates.ts @@ -1,36 +1,12 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; -import { getPredicateRoot, type WalletUnlocked } from '@fuel-ts/account'; -import { ContractFactory } from '@fuel-ts/contract'; -import { arrayify } from '@fuel-ts/utils'; +import { getPredicateRoot, Predicate } from '@fuel-ts/account'; import { debug, log } from 'console'; import { readFileSync } from 'fs'; import { getABIPath, getBinaryPath, getPredicateName } from '../../config/forcUtils'; import type { DeployedPredicate, FuelsConfig } from '../../types'; -import { adjustOffsets } from './adjustOffsets'; import { createWallet } from './createWallet'; -/** - * Deploys one predicate. - */ -export async function deployPredicate(wallet: WalletUnlocked, binaryPath: string, abiPath: string) { - debug(`Deploying predicate for ABI: ${abiPath}`); - - const bytecode = readFileSync(binaryPath); - const abi = JSON.parse(readFileSync(abiPath, 'utf-8')); - const factory = new ContractFactory(bytecode, abi, wallet); - - const { waitForResult } = await factory.deployAsBlobTxForScript(); - - const { loaderBytecode, configurableOffsetDiff } = await waitForResult(); - - return { - loaderBytecode, - configurableOffsetDiff, - }; -} - /** * Deploys all predicates. */ @@ -48,26 +24,24 @@ export async function deployPredicates(config: FuelsConfig) { const binaryPath = getBinaryPath(predicatePath, config); const abiPath = getABIPath(predicatePath, config); const projectName = getPredicateName(predicatePath); + const bytecode = readFileSync(binaryPath); + const abi = JSON.parse(readFileSync(abiPath, 'utf-8')); - const { loaderBytecode, configurableOffsetDiff } = await deployPredicate( - wallet, - binaryPath, - abiPath - ); - const predicateRoot = getPredicateRoot(loaderBytecode); + const predicate = new Predicate({ abi, bytecode, provider: wallet.provider }); + const { + bytes: loaderBytecode, + interface: { jsonAbi }, + } = await (await predicate.deploy(wallet)).waitForResult(); - let abi = JSON.parse(readFileSync(abiPath, 'utf-8')) as JsonAbi; - if (configurableOffsetDiff) { - abi = adjustOffsets(abi, configurableOffsetDiff); - } + const predicateRoot = getPredicateRoot(loaderBytecode); debug(`Predicate deployed: ${projectName} - ${predicateRoot}`); predicates.push({ path: predicatePath, predicateRoot, - loaderBytecode: arrayify(loaderBytecode), - abi, + loaderBytecode, + abi: jsonAbi, }); } diff --git a/packages/fuels/src/cli/commands/deploy/deployScripts.ts b/packages/fuels/src/cli/commands/deploy/deployScripts.ts index 62bdb9c386d..557536d5987 100644 --- a/packages/fuels/src/cli/commands/deploy/deployScripts.ts +++ b/packages/fuels/src/cli/commands/deploy/deployScripts.ts @@ -1,36 +1,12 @@ -import type { JsonAbi } from '@fuel-ts/abi-coder'; -import type { WalletUnlocked } from '@fuel-ts/account'; -import { ContractFactory } from '@fuel-ts/contract'; -import { arrayify } from '@fuel-ts/utils'; +import { Script } from '@fuel-ts/script'; import { debug, log } from 'console'; import { readFileSync } from 'fs'; import { getBinaryPath, getABIPath, getScriptName } from '../../config/forcUtils'; import type { FuelsConfig, DeployedScript } from '../../types'; -import { adjustOffsets } from './adjustOffsets'; import { createWallet } from './createWallet'; -/** - * Deploys one script. - */ -export async function deployScript(wallet: WalletUnlocked, binaryPath: string, abiPath: string) { - debug(`Deploying script for ABI: ${abiPath}`); - - const bytecode = readFileSync(binaryPath); - const abi = JSON.parse(readFileSync(abiPath, 'utf-8')); - const factory = new ContractFactory(bytecode, abi, wallet); - - const { waitForResult, blobId } = await factory.deployAsBlobTxForScript(); - const { configurableOffsetDiff, loaderBytecode } = await waitForResult(); - - return { - blobId, - loaderBytecode, - configurableOffsetDiff, - }; -} - /** * Deploys all scripts. */ @@ -49,23 +25,21 @@ export async function deployScripts(config: FuelsConfig) { const abiPath = getABIPath(scriptPath, config); const projectName = getScriptName(scriptPath); - const { blobId, loaderBytecode, configurableOffsetDiff } = await deployScript( - wallet, - binaryPath, - abiPath - ); + const bytecode = readFileSync(binaryPath); + const abi = JSON.parse(readFileSync(abiPath, 'utf-8')); - let abi = JSON.parse(readFileSync(abiPath, 'utf-8')) as JsonAbi; - if (configurableOffsetDiff) { - abi = adjustOffsets(abi, configurableOffsetDiff); - } + const script = new Script(bytecode, abi, wallet); + const { + bytes: loaderBytecode, + interface: { jsonAbi }, + } = await (await script.deploy(wallet)).waitForResult(); - debug(`Script deployed: ${projectName} - ${blobId}`); + debug(`Script deployed: ${projectName}`); scripts.push({ path: scriptPath, - loaderBytecode: arrayify(loaderBytecode), - abi, + loaderBytecode, + abi: jsonAbi, }); } diff --git a/packages/script/src/script.ts b/packages/script/src/script.ts index 01ccb7cad8c..765ce671e51 100644 --- a/packages/script/src/script.ts +++ b/packages/script/src/script.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Interface } from '@fuel-ts/abi-coder'; import type { InputValue, JsonAbi } from '@fuel-ts/abi-coder'; -import type { Account, Provider } from '@fuel-ts/account'; +import { deployScriptOrPredicate, type Account, type Provider } from '@fuel-ts/account'; import { FuelError } from '@fuel-ts/errors'; import { AbstractScript } from '@fuel-ts/interfaces'; import type { BytesLike } from '@fuel-ts/interfaces'; @@ -120,4 +120,23 @@ export class Script, TOutput> extends AbstractScript { return this; } + + /** + * + * @param account - The account used to pay the deployment costs. + * @returns The _blobId_ and a _waitForResult_ callback that returns the deployed predicate + * once the blob deployment transaction finishes. + * + * The returned loader script will have the same configurable constants + * as the original script which was used to generate the loader script. + */ + deploy(account: Account) { + return deployScriptOrPredicate({ + deployer: account, + abi: this.interface.jsonAbi, + bytecode: this.bytes, + loaderInstanceCallback: (loaderBytecode, newAbi) => + new Script(loaderBytecode, newAbi, this.account) as T, + }); + } }