Skip to content

Commit

Permalink
chore!: remove externalLoggedTypes from Interface class (#1936)
Browse files Browse the repository at this point in the history
  • Loading branch information
Torres-ssf committed Mar 26, 2024
1 parent 2db036e commit 3ebb9bc
Show file tree
Hide file tree
Showing 20 changed files with 213 additions and 61 deletions.
8 changes: 8 additions & 0 deletions .changeset/hungry-bees-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@fuel-ts/abi-coder": minor
"@fuel-ts/account": minor
"@fuel-ts/interfaces": minor
"@fuel-ts/program": minor
---

chore!: remove `externalLoggedTypes` from `Interface` class
23 changes: 2 additions & 21 deletions packages/abi-coder/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,12 @@ export class Interface<TAbi extends JsonAbi = JsonAbi> {
readonly functions!: Record<string, FunctionFragment>;

readonly configurables: Record<string, JsonAbiConfigurable>;
/*
TODO: Refactor so that there's no need for externalLoggedTypes
This is 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: Record<string, Interface>;

readonly jsonAbi: TAbi;

constructor(jsonAbi: TAbi) {
this.jsonAbi = jsonAbi;

this.externalLoggedTypes = {};

this.functions = Object.fromEntries(
this.jsonAbi.functions.map((x) => [x.name, new FunctionFragment(this.jsonAbi, x.name)])
);
Expand Down Expand Up @@ -91,24 +82,14 @@ export class Interface<TAbi extends JsonAbi = JsonAbi> {
return fragment.decodeOutput(data);
}

decodeLog(data: BytesLike, logId: number, receiptId: string): any {
const isExternalLoggedType = this.externalLoggedTypes[receiptId];
if (isExternalLoggedType) {
const externalInterface = this.externalLoggedTypes[receiptId];
return externalInterface.decodeLog(data, logId, receiptId);
}

decodeLog(data: BytesLike, logId: number): any {
const { loggedType } = findOrThrow(this.jsonAbi.loggedTypes, (type) => type.logId === logId);

return AbiCoder.decode(this.jsonAbi, loggedType, arrayify(data), 0, {
encoding: this.jsonAbi.encoding,
});
}

updateExternalLoggedTypes(id: string, loggedTypes: Interface) {
this.externalLoggedTypes[id] = loggedTypes;
}

encodeConfigurable(name: string, value: InputValue) {
const configurable = findOrThrow(
this.jsonAbi.configurables,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import type { Interface } from '@fuel-ts/abi-coder';
import { BigNumberCoder } from '@fuel-ts/abi-coder';
import type { JsonAbi } from '@fuel-ts/abi-coder';
import { Interface, BigNumberCoder } from '@fuel-ts/abi-coder';
import { ReceiptType } from '@fuel-ts/transactions';

import type { TransactionResultReceipt } from './transaction-response';

/** @hidden */
export function getDecodedLogs<T = unknown>(
receipts: Array<TransactionResultReceipt>,
abiInterface: Interface
mainAbi: JsonAbi,
externalAbis: Record<string, JsonAbi> = {}
): T[] {
return receipts.reduce((logs: T[], r) => {
if (r.type === ReceiptType.LogData) {
logs.push(abiInterface.decodeLog(r.data, r.val1.toNumber(), r.id)[0]);
}
/**
* This helper decodes logs from transaction receipts.
* It loops through all receipts and decodes two types of logs:
* - ReceiptType.LogData
* - ReceiptType.Log
*
* The "mainAbi" parameter represents the ABI of the main contract used to create the transaction
* or the ABI from a script used within a "BaseInvocationScope" context.
* The "externalAbis" parameter is a record of contract ABIs that are also part of the transaction.
* These ABIs were added using `contract.addContracts` or through a multicall with `contract.multiCall`.
*
* @param receipts - The array of transaction result receipts.
* @param mainAbi - The ABI of the main contract.
* @param externalAbis - The record of external contract ABIs.
* @returns An array of decoded logs from Sway projects.
*/
return receipts.reduce((logs: T[], receipt) => {
if (receipt.type === ReceiptType.LogData || receipt.type === ReceiptType.Log) {
const interfaceToUse = externalAbis[receipt.id]
? new Interface(externalAbis[receipt.id])
: new Interface(mainAbi);

const data =
receipt.type === ReceiptType.Log
? new BigNumberCoder('u64').encode(receipt.val0)
: receipt.data;

if (r.type === ReceiptType.Log) {
logs.push(
abiInterface.decodeLog(new BigNumberCoder('u64').encode(r.val0), r.val1.toNumber(), r.id)[0]
);
const [decodedLog] = interfaceToUse.decodeLog(data, receipt.val1.toNumber());
logs.push(decodedLog);
}

return logs;
Expand Down
98 changes: 86 additions & 12 deletions packages/fuel-gauge/src/advanced-logging.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import type { Contract } from 'fuels';
import { RequireRevertError, ScriptResultDecoderError } from 'fuels';
import { generateTestWallet } from '@fuel-ts/account/test-utils';
import type { Contract, Provider } from 'fuels';
import { RequireRevertError, Script, ScriptResultDecoderError, bn } from 'fuels';

import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures';

import { getSetupContract } from './utils';

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

let contractInstance: Contract;
let otherContractInstance: Contract;
let provider: Provider;
let advancedLogContract: Contract;
let otherAdvancedLogContract: Contract;
let advancedLogId: string;
let otherLogId: string;

beforeAll(async () => {
contractInstance = await setupContract();
otherContractInstance = await setupOtherContract({ cache: false });
advancedLogContract = await setupContract();
otherAdvancedLogContract = await setupOtherContract({ cache: false });
provider = advancedLogContract.provider;
advancedLogId = advancedLogContract.id.toB256();
otherLogId = otherAdvancedLogContract.id.toB256();
});

/**
* @group node
*/
describe('Advanced Logging', () => {
it('can get log data', async () => {
const { value, logs } = await contractInstance.functions.test_function().call();
const { value, logs } = await advancedLogContract.functions.test_function().call();

expect(value).toBeTruthy();
logs[5].game_id = logs[5].game_id.toHex();
Expand Down Expand Up @@ -63,7 +72,7 @@ describe('Advanced Logging', () => {
});

it('can get log data from require [condition=true]', async () => {
const { value, logs } = await contractInstance.functions
const { value, logs } = await advancedLogContract.functions
.test_function_with_require(1, 1)
.call();

Expand All @@ -72,7 +81,7 @@ describe('Advanced Logging', () => {
});

it('can get log data from require [condition=false]', async () => {
const invocation = contractInstance.functions.test_function_with_require(1, 3);
const invocation = advancedLogContract.functions.test_function_with_require(1, 3);
try {
await invocation.call();

Expand Down Expand Up @@ -102,9 +111,9 @@ 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.toB256())
.addContracts([otherContractInstance])
const { value, logs } = await advancedLogContract.functions
.test_log_from_other_contract(INPUT, otherAdvancedLogContract.id.toB256())
.addContracts([otherAdvancedLogContract])
.call();

expect(value).toBeTruthy();
Expand All @@ -115,4 +124,69 @@ describe('Advanced Logging', () => {
INPUT,
]);
});

it('should properly decode all logs in a multicall with inter-contract calls', async () => {
const setupCallTest = getSetupContract(FuelGaugeProjectsEnum.CALL_TEST_CONTRACT);
const setupConfigurable = getSetupContract(FuelGaugeProjectsEnum.CONFIGURABLE_CONTRACT);
const setupCoverage = getSetupContract(FuelGaugeProjectsEnum.COVERAGE_CONTRACT);

const callTest = await setupCallTest({ cache: false });
const configurable = await setupConfigurable({ cache: false });
const coverage = await setupCoverage({ cache: false });

const testStruct = {
a: true,
b: 100000,
};

const { logs } = await callTest
.multiCall([
advancedLogContract.functions
.test_log_from_other_contract(10, otherLogId)
.addContracts([otherAdvancedLogContract]),
callTest.functions.boo(testStruct),
configurable.functions.echo_struct(),
coverage.functions.echo_str_8('fuelfuel'),
])
.call();

const expectedLogs = [
'Hello from main Contract',
'Hello from other Contract',
'Received value from main Contract:',
10,
bn(100000),
{ tag: '000', age: 21, scores: [1, 3, 4] },
'fuelfuel',
];

logs.forEach((log, i) => {
expect(JSON.stringify(log)).toBe(JSON.stringify(expectedLogs[i]));
});
});

it('should decode logs from a script set to manually call other contracts', async () => {
const { abiContents, binHexlified } = getFuelGaugeForcProject(
FuelGaugeProjectsEnum.SCRIPT_CALL_CONTRACT
);

const wallet = await generateTestWallet(provider, [[1_000]]);

const script = new Script(binHexlified, abiContents, wallet);

const amount = Math.floor(Math.random() * 10) + 1;

const { logs } = await script.functions
.main(advancedLogId, otherLogId, amount)
.addContracts([advancedLogContract, otherAdvancedLogContract])
.call();

expect(logs).toStrictEqual([
'Hello from script',
'Hello from main Contract',
'Hello from other Contract',
'Received value from main Contract:',
amount,
]);
});
});
1 change: 1 addition & 0 deletions packages/fuel-gauge/src/contract-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ describe('Contract Factory', () => {
program: expect.objectContaining({ id: contract.id }),
func: expect.objectContaining({ name: 'increment_counter' }),
args: [1],
externalAbis: {},
callParameters: undefined,
txParameters: undefined,
forward: undefined,
Expand Down
2 changes: 2 additions & 0 deletions packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"advanced-logging-abi",
"advanced-logging-other-contract-abi",
"advanced-logging-other-contract",
"advanced-logging",
Expand Down Expand Up @@ -33,6 +34,7 @@ members = [
"raw-slice",
"revert-error",
"script-bytes",
"script-call-contract",
"script-main-args",
"script-main-return-struct",
"script-main-two-args",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
license = "Apache-2.0"
name = "advanced-logging-abi"

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

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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ license = "Apache-2.0"
name = "advanced-logging"

[dependencies]
advanced-logging-abi = { path = "../advanced-logging-abi" }
advanced-logging-other-contract-abi = { path = "../advanced-logging-other-contract-abi" }
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::revert::require;
use std::logging::log;
use std::contract_id::ContractId;

use advanced_logging_abi::AdvancedLogging;
use advanced_logging_other_contract_abi::AdvancedLoggingOtherContract;

enum GameState {
Expand All @@ -28,12 +29,6 @@ pub struct Game {
difficulty: Difficulty,
}

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 {
fn test_function() -> bool {
let state = GameState::Playing(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ impl ConfigurableContract for Contract {
}

fn echo_struct() -> Struct1 {
log(STRUCT_1);
STRUCT_1
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ impl CoverageContract for Contract {
input
}
fn echo_str_8(input: str[8]) -> str[8] {
log(input);
input
}
fn echo_str_9(input: str[9]) -> str[9] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "script-call-contract"

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

use advanced_logging_abi::AdvancedLogging;

fn main(
contract_id: b256,
other_contract_id: b256,
amount_to_contract: u8,
) {
log(__to_str_array("Hello from script"));

let log_contract = abi(AdvancedLogging, contract_id);

log_contract.test_log_from_other_contract(amount_to_contract, other_contract_id);
}
1 change: 1 addition & 0 deletions packages/fuel-gauge/test/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export enum FuelGaugeProjectsEnum {
RAW_SLICE = 'raw-slice',
REVERT_ERROR = 'revert-error',
SCRIPT_BYTES = 'script-bytes',
SCRIPT_CALL_CONTRACT = 'script-call-contract',
SCRIPT_MAIN_ARGS = 'script-main-args',
SCRIPT_MAIN_RETURN_STRUCT = 'script-main-return-struct',
SCRIPT_MAIN_TWO_ARGS = 'script-main-two-args',
Expand Down
2 changes: 1 addition & 1 deletion packages/interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export abstract class AbstractAccount {
export abstract class AbstractProgram {
abstract account: AbstractAccount | null;
abstract interface: {
updateExternalLoggedTypes: (id: string, abiInterface: any) => any;
readonly jsonAbi: any;
};

abstract provider: {
Expand Down
Loading

0 comments on commit 3ebb9bc

Please sign in to comment.