Skip to content

Commit 5a08f80

Browse files
author
Cameron Manavian
authored
feat: Parse Logs and LogData from all contracts used (#720)
* setup contracts for test * adjust contract flow * make u8 * update tests * refactor * cs * revise from pr review
1 parent 52e62e4 commit 5a08f80

File tree

13 files changed

+98
-13
lines changed

13 files changed

+98
-13
lines changed

.changeset/short-feet-sell.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@fuel-ts/abi-coder": minor
3+
"@fuel-ts/contract": minor
4+
---
5+
6+
Adjust contract interface to parse logs from external contracts - breaking change for adding contracts to a call

packages/abi-coder/src/interface.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,21 @@ export default class Interface {
4242
readonly abi: ABI | null;
4343
readonly types: ReadonlyArray<JsonFlatAbiFragmentType>;
4444
readonly loggedTypes: ReadonlyArray<JsonAbiLogFragment>;
45+
/*
46+
Same as the `loggedTypes` above, but dedicated to external contracts
47+
added via `<base-invocation-scope.ts>.addContracts()` method. This is
48+
used to decode logs from contracts other than the main contract
49+
we're interacting with.
50+
*/
51+
private externalLoggedTypes: { [id: string]: ReadonlyArray<JsonAbiLogFragment> };
4552

4653
constructor(jsonAbi: JsonAbi | JsonFlatAbi) {
4754
this.abi = isFlatJsonAbi(jsonAbi) ? new ABI(jsonAbi) : null;
4855
this.fragments = coerceFragments(ABI.unflatten(jsonAbi));
4956

5057
this.types = this.abi ? this.abi.types : [];
5158
this.loggedTypes = this.abi ? this.abi.unflattenLoggedTypes() : [];
59+
this.externalLoggedTypes = {};
5260

5361
this.abiCoder = new AbiCoder();
5462
this.functions = {};
@@ -152,8 +160,10 @@ export default class Interface {
152160
return this.abiCoder.decode(fragment.outputs, bytes);
153161
}
154162

155-
decodeLog(data: BytesLike, logId: number): any {
156-
const logType = this.loggedTypes.find((type) => type.logId === logId);
163+
decodeLog(data: BytesLike, logId: number, receiptId: string): any {
164+
const loggedTypes = this.externalLoggedTypes[receiptId] || this.loggedTypes;
165+
166+
const logType = loggedTypes.find((type) => type.logId === logId);
157167
if (!logType?.abiFragmentType) {
158168
throw new Error(`Log ID - ${logId} unknown`);
159169
}
@@ -173,4 +183,8 @@ export default class Interface {
173183

174184
return this.abiCoder.encode(fragment.outputs, values);
175185
}
186+
187+
updateExternalLoggedTypes(id: string, loggedTypes: JsonAbiLogFragment[]) {
188+
this.externalLoggedTypes[id] = loggedTypes;
189+
}
176190
}

packages/contract/src/contracts/functions/base-invocation-scope.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import type { InputValue } from '@fuel-ts/abi-coder';
3-
import type { ContractIdLike } from '@fuel-ts/interfaces';
43
import { bn, toNumber } from '@fuel-ts/math';
54
import type { Provider, CoinQuantity, TransactionRequest } from '@fuel-ts/providers';
65
import { transactionRequestify, ScriptTransactionRequest } from '@fuel-ts/providers';
@@ -179,8 +178,13 @@ export class BaseInvocationScope<TReturn = any> {
179178
return this;
180179
}
181180

182-
addContracts(contracts: Array<ContractIdLike>) {
183-
contracts.forEach((contract) => this.transactionRequest.addContract(contract));
181+
addContracts(contracts: Array<Contract>) {
182+
contracts.forEach((contract) => {
183+
this.transactionRequest.addContract(contract.id);
184+
this.contract.interface.updateExternalLoggedTypes(contract.id.toB256(), [
185+
...contract.interface.loggedTypes,
186+
]);
187+
});
184188
return this;
185189
}
186190

packages/contract/src/contracts/functions/invocation-results.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ export class InvocationResult<T = any> {
5656
}
5757

5858
const { contract } = this.functionScopes[0].getCallConfig();
59+
5960
return receipts.reduce((logs, r) => {
6061
if (r.type === ReceiptType.LogData) {
61-
return logs.concat(...contract.interface.decodeLog(r.data, r.val1.toNumber()));
62+
return logs.concat(...contract.interface.decodeLog(r.data, r.val1.toNumber(), r.id));
6263
}
6364

6465
if (r.type === ReceiptType.Log) {
6566
return logs.concat(
66-
...contract.interface.decodeLog(new U64Coder().encode(r.val0), r.val1.toNumber())
67+
...contract.interface.decodeLog(new U64Coder().encode(r.val0), r.val1.toNumber(), r.id)
6768
);
6869
}
6970

packages/fuel-gauge/src/advanced-logging.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import { ScriptResultDecoderError } from 'fuels';
44
import { getSetupContract } from './utils';
55

66
const setupContract = getSetupContract('advanced-logging');
7+
const setupOtherContract = getSetupContract('advanced-logging-other-contract');
78

89
let contractInstance: Contract;
10+
let otherContractInstance: Contract;
911

1012
beforeAll(async () => {
1113
contractInstance = await setupContract();
14+
otherContractInstance = await setupOtherContract({ cache: false });
1215
});
1316

1417
describe('Advanced Logging', () => {
@@ -93,4 +96,20 @@ describe('Advanced Logging', () => {
9396
}
9497
}
9598
});
99+
100+
it('can get log data from a downstream Contract', async () => {
101+
const INPUT = 3;
102+
const { value, logs } = await contractInstance.functions
103+
.test_log_from_other_contract(INPUT, otherContractInstance.id)
104+
.addContracts([otherContractInstance])
105+
.call();
106+
107+
expect(value).toBeTruthy();
108+
expect(logs).toEqual([
109+
'Hello from main Contract',
110+
'Hello from other Contract',
111+
'Received value from main Contract:',
112+
INPUT,
113+
]);
114+
});
96115
});

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ describe('Contract', () => {
156156
// #endregion
157157

158158
// #region typedoc:Contract-multicall-multiple-contracts-p2
159-
const scope = contract.multiCall(calls).addContracts([otherContract.id]);
159+
const scope = contract.multiCall(calls).addContracts([otherContract]);
160160
// #endregion
161161

162162
expect(scope.transactionRequest.getContractInputs()).toEqual([
@@ -208,9 +208,7 @@ describe('Contract', () => {
208208
const contract = await setupContract();
209209
const otherContract = await setupContract({ cache: false });
210210

211-
const scope = contract
212-
.multiCall([contract.functions.foo(1336)])
213-
.addContracts([otherContract.id]);
211+
const scope = contract.multiCall([contract.functions.foo(1336)]).addContracts([otherContract]);
214212

215213
expect(scope.transactionRequest.getContractInputs()).toEqual([
216214
{ contractId: contract.id.toB256(), type: 1, txPointer },

packages/fuel-gauge/src/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ export const createSetupConfig =
4444
...config,
4545
});
4646

47-
const getFullPath = (contractName: string, next: (fullPath: string) => () => Promise<Contract>) =>
48-
next(join(__dirname, `../test-projects/${contractName}/out/debug/${contractName}`));
47+
const getFullPath = (
48+
contractName: string,
49+
next: (fullPath: string) => (config?: Partial<SetupConfig>) => Promise<Contract>
50+
) => next(join(__dirname, `../test-projects/${contractName}/out/debug/${contractName}`));
4951

5052
export const getSetupContract = (contractName: string) =>
5153
getFullPath(contractName, (fullPath: string) =>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[project]
2+
license = "Apache-2.0"
3+
name = "advanced-logging-other-contract-abi"
4+
5+
[dependencies]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
library advanced_logging_other_contract_abi;
2+
3+
abi AdvancedLoggingOtherContract {
4+
fn msg_from_other_contract(a:u8);
5+
}
6+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[project]
2+
license = "Apache-2.0"
3+
name = "advanced-logging-other-contract"
4+
5+
[dependencies]
6+
advanced-logging-other-contract-abi = { path = "../advanced-logging-other-contract-abi" }

0 commit comments

Comments
 (0)