Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore!: remove externalLoggedTypes from Interface class #1936

Merged
merged 24 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
935e24f
add property externalAbis to BaseInvocationScope
Torres-ssf Mar 25, 2024
92bce80
add externalAbis to CallConfig type
Torres-ssf Mar 25, 2024
8f465ce
create type JsonAbisFromAllCalls
Torres-ssf Mar 25, 2024
aba5067
implement method getAbiFromAllCalls on InvocationResult
Torres-ssf Mar 25, 2024
132faf0
refact helper getDecodedLogs
Torres-ssf Mar 25, 2024
4fc18df
update interface property type at AbstractProgram
Torres-ssf Mar 25, 2024
144414d
remove externalLoggedTypes from Interface class
Torres-ssf Mar 25, 2024
e12ad6e
update test case to conform with last changes
Torres-ssf Mar 25, 2024
c56b8cf
adding some logs to existent sway projects
Torres-ssf Mar 25, 2024
a02a0bf
made abi from advanced-logging contract an external library
Torres-ssf Mar 25, 2024
c324c41
create sway project script-call-contract
Torres-ssf Mar 25, 2024
ea563e3
adding test cases
Torres-ssf Mar 25, 2024
79d2c5d
add changeset
Torres-ssf Mar 25, 2024
f079105
Merge branch 'master' into st/chore/remove-external-logged-from-inter…
Torres-ssf Mar 25, 2024
c08b2fa
rename param
Torres-ssf Mar 25, 2024
661e636
linting sway project
Torres-ssf Mar 25, 2024
fa9ac4c
linting sway project
Torres-ssf Mar 25, 2024
3745165
Merge branch 'master' into st/chore/remove-external-logged-from-inter…
petertonysmith94 Mar 25, 2024
0531b16
Merge branch 'master' into st/chore/remove-external-logged-from-inter…
Torres-ssf Mar 25, 2024
e87cf28
refact test code
Torres-ssf Mar 25, 2024
0a95041
add comment to helper function
Torres-ssf Mar 25, 2024
1828d89
improve method declaration
Torres-ssf Mar 26, 2024
b279b82
Merge branch 'master' into st/chore/remove-external-logged-from-inter…
Torres-ssf Mar 26, 2024
634062d
Merge branch 'master' into st/chore/remove-external-logged-from-inter…
Torres-ssf Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
const interfaceToUse = externalAbis[receipt.id]
? new Interface(externalAbis[receipt.id])
: new Interface(mainAbi);

Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
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);
danielbate marked this conversation as resolved.
Show resolved Hide resolved
}

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);
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
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]));
});
});
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved

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
Loading