From 8afee53b04e29b8b721d868e2fadb871caaa8f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:52:31 -0300 Subject: [PATCH 1/9] add proxyContractId to Contract class --- packages/program/src/contract.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/program/src/contract.ts b/packages/program/src/contract.ts index 1d2009e404d..756a86b11f4 100644 --- a/packages/program/src/contract.ts +++ b/packages/program/src/contract.ts @@ -17,6 +17,11 @@ export default class Contract implements AbstractContract { */ id!: AbstractAddress; + /** + * Proxy contract identifier. + */ + proxyContractId?: AbstractAddress; + /** * The provider for interacting with the contract. */ @@ -47,10 +52,14 @@ export default class Contract implements AbstractContract { constructor( id: string | AbstractAddress, abi: JsonAbi | Interface, - accountOrProvider: Account | Provider + accountOrProvider: Account | Provider, + proxyContractId?: string | AbstractAddress ) { this.interface = abi instanceof Interface ? abi : new Interface(abi); this.id = Address.fromAddressOrString(id); + if (proxyContractId) { + this.proxyContractId = Address.fromAddressOrString(proxyContractId); + } /** Instead of using `instanceof` to compare classes, we instead check From c48b4829fc86bba2017251a7439b876fe839e363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:52:58 -0300 Subject: [PATCH 2/9] add proxyContractId to AbstractContract class --- packages/interfaces/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interfaces/src/index.ts b/packages/interfaces/src/index.ts index 527027fb7ef..03864f0237c 100644 --- a/packages/interfaces/src/index.ts +++ b/packages/interfaces/src/index.ts @@ -83,6 +83,7 @@ export abstract class AbstractProgram { export abstract class AbstractContract extends AbstractProgram { abstract id: AbstractAddress; + abstract proxyContractId?: AbstractAddress; } /** From b39bf16f97db981f2b100f74628c666ebc347870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:53:35 -0300 Subject: [PATCH 3/9] add proxyContractId to ContractCall type --- packages/program/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/program/src/types.ts b/packages/program/src/types.ts index 258f98c277f..207d25a584e 100644 --- a/packages/program/src/types.ts +++ b/packages/program/src/types.ts @@ -17,6 +17,7 @@ import type { FunctionInvocationScope } from './functions/invocation-scope'; */ export type ContractCall = { contractId: AbstractAddress; + proxyContractId?: AbstractAddress; data: BytesLike; fnSelectorBytes: Uint8Array; amount?: BigNumberish; From eca3d31099af7133176e498859eb6325f260a54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:55:01 -0300 Subject: [PATCH 4/9] ensure createContractCall returns proxyContractId when assembling contract calls --- packages/program/src/functions/base-invocation-scope.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index e578954d9a0..7c0019f50f7 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -44,6 +44,7 @@ function createContractCall(funcScope: InvocationScopeLike): ContractCall { return { contractId: (program as AbstractContract).id, + proxyContractId: (program as AbstractContract).proxyContractId, fnSelectorBytes: func.selectorBytes, data, assetId: forward?.assetId, From 14906a351b3f32052dd56afb443f8039455e2362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:55:29 -0300 Subject: [PATCH 5/9] ensure updateContractInputAndOutput consider proxyContractId --- packages/program/src/functions/base-invocation-scope.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/program/src/functions/base-invocation-scope.ts b/packages/program/src/functions/base-invocation-scope.ts index 7c0019f50f7..e1bf1dd9dd6 100644 --- a/packages/program/src/functions/base-invocation-scope.ts +++ b/packages/program/src/functions/base-invocation-scope.ts @@ -120,7 +120,9 @@ export class BaseInvocationScope { protected updateContractInputAndOutput() { const calls = this.calls; calls.forEach((c) => { - if (c.contractId) { + if (c.proxyContractId) { + this.transactionRequest.addContractInputAndOutput(c.proxyContractId); + } else if (c.contractId) { this.transactionRequest.addContractInputAndOutput(c.contractId); } if (c.externalContractsAbis) { From b4a02e9865fc68044f9f720b1f4fe3d886b6e7bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:56:08 -0300 Subject: [PATCH 6/9] ensure extractInvocationResult consider proxyContractId --- packages/program/src/response.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/program/src/response.ts b/packages/program/src/response.ts index 2389ae94180..c481c8b0a6b 100644 --- a/packages/program/src/response.ts +++ b/packages/program/src/response.ts @@ -26,11 +26,12 @@ export const extractInvocationResult = ( if (functionScopes.length === 1 && mainCallConfig && 'bytes' in mainCallConfig.program) { return callResultToInvocationResult({ receipts }, mainCallConfig, logs); } - const encodedResults = decodeContractCallScriptResult( - { receipts }, - (mainCallConfig?.program as AbstractContract).id, - logs - ); + + const contractId = + (mainCallConfig?.program as AbstractContract).proxyContractId || + (mainCallConfig?.program as AbstractContract).id; + + const encodedResults = decodeContractCallScriptResult({ receipts }, contractId, logs); const decodedResults = encodedResults.map((encodedResult, i) => { const { func } = functionScopes[i].getCallConfig(); From 1cb83f9263dd1dd9148e86c300df66c933365f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:56:28 -0300 Subject: [PATCH 7/9] build script data with proxy contract id when exists --- packages/program/src/contract-call-script.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/program/src/contract-call-script.ts b/packages/program/src/contract-call-script.ts index 1d892d65c77..a035c2703ef 100644 --- a/packages/program/src/contract-call-script.ts +++ b/packages/program/src/contract-call-script.ts @@ -199,12 +199,14 @@ export const getContractCallScript = ( const encodedArgs = arrayify(call.data); let gasForwardedOffset = 0; + const contractId = call.proxyContractId || call.contractId; + // 1. Amount scriptData.push(new BigNumberCoder('u64').encode(call.amount || 0)); // 2. Asset ID scriptData.push(new B256Coder().encode(call.assetId?.toString() || ZeroBytes32)); // 3. Contract ID - scriptData.push(call.contractId.toBytes()); + scriptData.push(contractId.toBytes()); // 4. Function selector offset scriptData.push(new BigNumberCoder('u64').encode(encodedSelectorOffset)); // 5. Encoded argument offset From 4892026b3adb10da6e1deabf7b6657cd0acd7338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:29:35 -0300 Subject: [PATCH 8/9] add proxy contract --- .../test/fixtures/forc-projects/Forc.toml | 1 + .../proxy-src14-contract/Forc.toml | 8 ++++ .../proxy-src14-contract/src/main.sw | 38 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/Forc.toml create mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/src/main.sw diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml index 1520dd7b675..6c5cd367a77 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml +++ b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml @@ -55,6 +55,7 @@ members = [ "script-raw-slice", "script-std-lib-string", "script-str-slice", + "proxy-src14-contract", "script-with-array", "script-with-configurable", "script-with-vector", diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/Forc.toml new file mode 100644 index 00000000000..841f82b0244 --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "proxy-src14-contract" + +[dependencies] +standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.1" } diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/src/main.sw new file mode 100644 index 00000000000..bd0c32545ee --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/proxy-src14-contract/src/main.sw @@ -0,0 +1,38 @@ +contract; + +use std::execution::run_external; +use standards::{src14::SRC14}; + +pub enum ProxyErrors { + TargetNotSet: (), +} + +configurable { + INITIAL_TARGET: ContractId = ContractId::zero(), +} + +storage { + SRC14 { + target: Option = None, + }, +} + +impl SRC14 for Contract { + #[storage(read, write)] + fn set_proxy_target(new_target: ContractId) { + storage::SRC14.target.write(Some(new_target)); + } + + #[storage(read)] + fn proxy_target() -> Option { + Some(storage::SRC14.target.read().unwrap_or(INITIAL_TARGET)) + } +} + +#[fallback] +#[storage(read)] +fn fallback() { + let target = storage::SRC14.target.read().unwrap_or(INITIAL_TARGET); + require(target.bits() != b256::zero(), ProxyErrors::TargetNotSet); + run_external(target) +} From 420a5c271480ad2cef63fd7afcf703dacf6f9196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Torres?= <30977845+Torres-ssf@users.noreply.github.com> Date: Wed, 18 Sep 2024 23:29:45 -0300 Subject: [PATCH 9/9] add test for proxied contract call --- packages/fuel-gauge/src/contract.test.ts | 64 +++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/packages/fuel-gauge/src/contract.test.ts b/packages/fuel-gauge/src/contract.test.ts index ceddce13951..59ed3e7eb8e 100644 --- a/packages/fuel-gauge/src/contract.test.ts +++ b/packages/fuel-gauge/src/contract.test.ts @@ -13,14 +13,19 @@ import { Predicate, PolicyType, buildFunctionResult, + ReceiptType, } from 'fuels'; -import type { ScriptTransactionRequest, TransferParams } from 'fuels'; +import type { ScriptTransactionRequest, TransactionResultCallReceipt, TransferParams } from 'fuels'; import { expectToThrowFuelError, ASSET_A, ASSET_B, launchTestNode } from 'fuels/test-utils'; import type { DeployContractConfig } from 'fuels/test-utils'; import { CallTestContract, CallTestContractFactory, + CoverageContract, + CoverageContractFactory, + ProxySrc14Contract, + ProxySrc14ContractFactory, StorageTestContract, StorageTestContractFactory, } from '../test/typegen/contracts'; @@ -1111,4 +1116,61 @@ describe('Contract', () => { expect(scriptGasLimit?.toNumber()).toBe(gasLimit); expect(bn(maxFeePolicy?.data).toNumber()).toBe(maxFee); }); + + it('can proper set a proxy contract and use it to call a target contract', async () => { + using launch = await launchTestNode({ + contractsConfigs: [{ factory: CoverageContractFactory }], + }); + + const { + wallets: [wallet], + contracts: [coverageContract], + } = launch; + + const proxyFactory = await ProxySrc14ContractFactory.deploy(wallet, { + storageSlots: ProxySrc14Contract.storageSlots, + }); + + const { contract: proxyContract } = await proxyFactory.waitForResult(); + + // Setting the contract target at the proxy + const call = await proxyContract.functions + .set_proxy_target({ bits: coverageContract.id.toB256() }) + .call(); + + await call.waitForResult(); + + const proxyId = proxyContract.id; + const echoValuesId = coverageContract.id; + + const proxiedConverageContract = new Contract( + echoValuesId, + CoverageContract.abi, + wallet, + proxyId + ); + + const echoAmount = 10; + + const proxiedCall = await proxiedConverageContract.functions + .echo_u8(echoAmount) + .addContracts([proxyContract]) + .call(); + + const { + value, + transactionResult: { receipts }, + } = await proxiedCall.waitForResult(); + + const callReceipt = receipts.filter( + (receipt) => receipt.type === ReceiptType.Call + ) as TransactionResultCallReceipt[]; + + // Ensure called contract was the proxy contract + expect(callReceipt.length).toBe(1); + expect(callReceipt[0].to).toBe(proxyId.toB256()); + + // Ensure returned value is the expected one + expect(value).toBe(echoAmount); + }); });