Skip to content

Commit

Permalink
feat: Parse Logs and LogData from all contracts used (#720)
Browse files Browse the repository at this point in the history
* setup contracts for test

* adjust contract flow

* make u8

* update tests

* refactor

* cs

* revise from pr review
  • Loading branch information
Cameron Manavian authored Jan 11, 2023
1 parent 52e62e4 commit 5a08f80
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/short-feet-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/abi-coder": minor
"@fuel-ts/contract": minor
---

Adjust contract interface to parse logs from external contracts - breaking change for adding contracts to a call
18 changes: 16 additions & 2 deletions packages/abi-coder/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,21 @@ export default class Interface {
readonly abi: ABI | null;
readonly types: ReadonlyArray<JsonFlatAbiFragmentType>;
readonly loggedTypes: ReadonlyArray<JsonAbiLogFragment>;
/*
Same as the `loggedTypes` above, but dedicated to external contracts
added via `<base-invocation-scope.ts>.addContracts()` method. This is
used to decode logs from contracts other than the main contract
we're interacting with.
*/
private externalLoggedTypes: { [id: string]: ReadonlyArray<JsonAbiLogFragment> };

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

this.types = this.abi ? this.abi.types : [];
this.loggedTypes = this.abi ? this.abi.unflattenLoggedTypes() : [];
this.externalLoggedTypes = {};

this.abiCoder = new AbiCoder();
this.functions = {};
Expand Down Expand Up @@ -152,8 +160,10 @@ export default class Interface {
return this.abiCoder.decode(fragment.outputs, bytes);
}

decodeLog(data: BytesLike, logId: number): any {
const logType = this.loggedTypes.find((type) => type.logId === logId);
decodeLog(data: BytesLike, logId: number, receiptId: string): any {
const loggedTypes = this.externalLoggedTypes[receiptId] || this.loggedTypes;

const logType = loggedTypes.find((type) => type.logId === logId);
if (!logType?.abiFragmentType) {
throw new Error(`Log ID - ${logId} unknown`);
}
Expand All @@ -173,4 +183,8 @@ export default class Interface {

return this.abiCoder.encode(fragment.outputs, values);
}

updateExternalLoggedTypes(id: string, loggedTypes: JsonAbiLogFragment[]) {
this.externalLoggedTypes[id] = loggedTypes;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { InputValue } from '@fuel-ts/abi-coder';
import type { ContractIdLike } from '@fuel-ts/interfaces';
import { bn, toNumber } from '@fuel-ts/math';
import type { Provider, CoinQuantity, TransactionRequest } from '@fuel-ts/providers';
import { transactionRequestify, ScriptTransactionRequest } from '@fuel-ts/providers';
Expand Down Expand Up @@ -179,8 +178,13 @@ export class BaseInvocationScope<TReturn = any> {
return this;
}

addContracts(contracts: Array<ContractIdLike>) {
contracts.forEach((contract) => this.transactionRequest.addContract(contract));
addContracts(contracts: Array<Contract>) {
contracts.forEach((contract) => {
this.transactionRequest.addContract(contract.id);
this.contract.interface.updateExternalLoggedTypes(contract.id.toB256(), [
...contract.interface.loggedTypes,
]);
});
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ export class InvocationResult<T = any> {
}

const { contract } = this.functionScopes[0].getCallConfig();

return receipts.reduce((logs, r) => {
if (r.type === ReceiptType.LogData) {
return logs.concat(...contract.interface.decodeLog(r.data, r.val1.toNumber()));
return logs.concat(...contract.interface.decodeLog(r.data, r.val1.toNumber(), r.id));
}

if (r.type === ReceiptType.Log) {
return logs.concat(
...contract.interface.decodeLog(new U64Coder().encode(r.val0), r.val1.toNumber())
...contract.interface.decodeLog(new U64Coder().encode(r.val0), r.val1.toNumber(), r.id)
);
}

Expand Down
19 changes: 19 additions & 0 deletions packages/fuel-gauge/src/advanced-logging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { ScriptResultDecoderError } from 'fuels';
import { getSetupContract } from './utils';

const setupContract = getSetupContract('advanced-logging');
const setupOtherContract = getSetupContract('advanced-logging-other-contract');

let contractInstance: Contract;
let otherContractInstance: Contract;

beforeAll(async () => {
contractInstance = await setupContract();
otherContractInstance = await setupOtherContract({ cache: false });
});

describe('Advanced Logging', () => {
Expand Down Expand Up @@ -93,4 +96,20 @@ describe('Advanced Logging', () => {
}
}
});

it('can get log data from a downstream Contract', async () => {
const INPUT = 3;
const { value, logs } = await contractInstance.functions
.test_log_from_other_contract(INPUT, otherContractInstance.id)
.addContracts([otherContractInstance])
.call();

expect(value).toBeTruthy();
expect(logs).toEqual([
'Hello from main Contract',
'Hello from other Contract',
'Received value from main Contract:',
INPUT,
]);
});
});
6 changes: 2 additions & 4 deletions packages/fuel-gauge/src/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe('Contract', () => {
// #endregion

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

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

const scope = contract
.multiCall([contract.functions.foo(1336)])
.addContracts([otherContract.id]);
const scope = contract.multiCall([contract.functions.foo(1336)]).addContracts([otherContract]);

expect(scope.transactionRequest.getContractInputs()).toEqual([
{ contractId: contract.id.toB256(), type: 1, txPointer },
Expand Down
6 changes: 4 additions & 2 deletions packages/fuel-gauge/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ export const createSetupConfig =
...config,
});

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

export const getSetupContract = (contractName: string) =>
getFullPath(contractName, (fullPath: string) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
license = "Apache-2.0"
name = "advanced-logging-other-contract-abi"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
library advanced_logging_other_contract_abi;

abi AdvancedLoggingOtherContract {
fn msg_from_other_contract(a:u8);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
license = "Apache-2.0"
name = "advanced-logging-other-contract"

[dependencies]
advanced-logging-other-contract-abi = { path = "../advanced-logging-other-contract-abi" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
contract;

use std::logging::log;

use advanced_logging_other_contract_abi::AdvancedLoggingOtherContract;

impl AdvancedLoggingOtherContract for Contract {
fn msg_from_other_contract(a: u8) {
log("Hello from other Contract");
log("Received value from main Contract:");
log(a);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ license = "Apache-2.0"
name = "advanced-logging"

[dependencies]
advanced-logging-other-contract-abi = { path = "../advanced-logging-other-contract-abi" }
10 changes: 10 additions & 0 deletions packages/fuel-gauge/test-projects/advanced-logging/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::revert::require;
use std::logging::log;
use std::contract_id::ContractId;

use advanced_logging_other_contract_abi::AdvancedLoggingOtherContract;

enum GameState {
Playing: u8,
GameOver: u8,
Expand All @@ -29,6 +31,7 @@ pub struct Game {
abi AdvancedLogging {
fn test_function() -> bool;
fn test_function_with_require(a: u64, b: u64) -> bool;
fn test_log_from_other_contract(a:u8, contract_id: b256) -> bool;
}

impl AdvancedLogging for Contract {
Expand Down Expand Up @@ -101,4 +104,11 @@ impl AdvancedLogging for Contract {

true
}

fn test_log_from_other_contract(a:u8, contract_id: b256) -> bool {
let other_contract = abi(AdvancedLoggingOtherContract, contract_id);
log("Hello from main Contract");
other_contract.msg_from_other_contract(a);
true
}
}

0 comments on commit 5a08f80

Please sign in to comment.