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

fix: getOperation for Transfer Asset #1619

Merged
merged 14 commits into from
Jan 15, 2024
Merged
5 changes: 5 additions & 0 deletions .changeset/wet-dots-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/providers": minor
---

Made getOperations to consider multiple assets transfer for a Transfer Asset operation
128 changes: 127 additions & 1 deletion packages/providers/src/transaction-summary/operations.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getRandomB256 } from '@fuel-ts/address';
import { bn } from '@fuel-ts/math';
import { ReceiptType, TransactionType } from '@fuel-ts/transactions';

Expand Down Expand Up @@ -722,6 +723,9 @@ describe('operations', () => {

const fromAddress = getInputAccountAddress(coinInput[0]);

const assetA = '0x0101010101010101010101010101010101010101010101010101010101010101';
const assetB = '0x0202020202020202020202020202020202020202020202020202020202020202';

const OPERATION_CONTRACT_CALL = {
name: OperationName.contractCall,
from: {
Expand Down Expand Up @@ -749,6 +753,24 @@ describe('operations', () => {
],
};

const OPERATION_TRANSFER = {
name: OperationName.transfer,
from: {
type: 1,
address: getRandomB256(),
},
to: {
type: 1,
address: getRandomB256(),
},
assetsSent: [
{
assetId: '0x0101010101010101010101010101010101010101010101010101010101010101',
amount: bn(100),
},
],
};

it('should just add operation when its the first one', () => {
const operations = addOperation([], OPERATION_CONTRACT_CALL);
expect(operations.length).toEqual(1);
Expand Down Expand Up @@ -808,6 +830,7 @@ describe('operations', () => {
JSON.parse(JSON.stringify(baseOperations[0].assetsSent))
);
});

it('should stack when same asset is added', () => {
const baseOperations = addOperation([], OPERATION_CONTRACT_CALL);
const operationsAddedSameAsset = addOperation(baseOperations, OPERATION_CONTRACT_CALL);
Expand All @@ -819,7 +842,8 @@ describe('operations', () => {
OPERATION_CONTRACT_CALL.assetsSent[0].assetId
);
});
it('should stack when same asset is added together with a different asset', () => {

it('should stack when same asset is added together with a different asset [CONTRACT-CALL]', () => {
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
const DIF_ASSET_ID = '0x0012300000000000000000000000000000000001';
const operationTwoAssets: Operation = {
...OPERATION_CONTRACT_CALL,
Expand Down Expand Up @@ -848,6 +872,108 @@ describe('operations', () => {
);
expect(operationsAddedSameAsset[0].assetsSent?.[1]?.assetId).toEqual(DIF_ASSET_ID);
});

it('ensure operation asset transfer stacks multiple assetSents between same addresses', () => {
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
const operationOne: Operation = {
...OPERATION_TRANSFER,
assetsSent: [
{
assetId: assetA,
amount: bn(100),
},
],
};

const operationTwo: Operation = {
...OPERATION_TRANSFER,
assetsSent: [
{
assetId: assetB,
amount: bn(200),
},
],
};

const baseOperations = addOperation([], operationOne);
const stackedOperation = addOperation(baseOperations, operationTwo);

expect(stackedOperation.length).toEqual(1);
expect(stackedOperation[0].assetsSent?.length).toEqual(2);
expect(stackedOperation[0].assetsSent?.[0]?.amount.valueOf()).toEqual(
operationOne.assetsSent?.[0]?.amount.valueOf()
);
expect(stackedOperation[0].assetsSent?.[0]?.assetId).toEqual(
operationOne.assetsSent?.[0].assetId
);
expect(stackedOperation[0].assetsSent?.[1]?.amount.valueOf()).toEqual(
operationTwo.assetsSent?.[0]?.amount.valueOf()
);
expect(stackedOperation[0].assetsSent?.[1]?.assetId).toEqual(
operationTwo.assetsSent?.[0].assetId
);
});

it('ensure operation asset transfer does not stack multiple assetSents between different addresses', () => {
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved
const fromOne = getRandomB256();
const fromTwo = getRandomB256();
const toAddress2 = getRandomB256();

const operationOne: Operation = {
...OPERATION_TRANSFER,
from: {
address: fromOne,
type: 1,
},
to: {
address: toAddress2,
type: 1,
},
assetsSent: [
{
assetId: assetA,
amount: bn(100),
},
],
};

const operationTwo: Operation = {
...OPERATION_TRANSFER,
from: {
address: fromTwo,
type: 1,
},
to: {
address: toAddress2,
type: 1,
},
assetsSent: [
{
assetId: assetB,
amount: bn(200),
},
],
};

const baseOperation = addOperation([], operationOne);
const multipleOperations = addOperation(baseOperation, operationTwo);

expect(multipleOperations.length).toEqual(2);
expect(multipleOperations[0].assetsSent?.length).toEqual(1);
expect(multipleOperations[0].assetsSent?.[0]?.amount.valueOf()).toEqual(
operationOne.assetsSent?.[0]?.amount.valueOf()
);
expect(multipleOperations[0].assetsSent?.[0]?.assetId).toEqual(
operationOne.assetsSent?.[0].assetId
);
expect(multipleOperations[1].assetsSent?.length).toEqual(1);
expect(multipleOperations[1].assetsSent?.[0]?.amount.valueOf()).toEqual(
operationTwo.assetsSent?.[0]?.amount.valueOf()
);
expect(multipleOperations[1].assetsSent?.[0]?.assetId).toEqual(
operationTwo.assetsSent?.[0].assetId
);
});
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved

it('should always not stack for contract calls', () => {
const baseOperations = addOperation([], OPERATION_CONTRACT_CALL);
const operationsAddedSameContractCall = addOperation(baseOperations, OPERATION_CONTRACT_CALL);
Expand Down
100 changes: 54 additions & 46 deletions packages/providers/src/transaction-summary/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,27 @@ export function getReceiptsMessageOut(receipts: TransactionResultReceipt[]) {
const mergeAssets = (op1: Operation, op2: Operation) => {
const assets1 = op1.assetsSent || [];
const assets2 = op2.assetsSent || [];
const filtered = assets2.filter((c) => !assets1.some(hasSameAssetId(c)));
return assets1
.map((coin) => {
const asset = assets2.find(hasSameAssetId(coin));
if (!asset) {
return coin;
}
return { ...coin, amount: bn(coin.amount).add(asset.amount) };
})
.concat(filtered);

// Getting assets from op2 that don't exist in op1
const filteredAssets = assets2.filter(
(asset2) => !assets1.some((asset1) => asset1.assetId === asset2.assetId)
);

// Merge assets that already exist in op1
const mergedAssets = assets1.map((asset1) => {
// Find matching asset in op2
const matchingAsset = assets2.find((asset2) => asset2.assetId === asset1.assetId);
if (!matchingAsset) {
// No matching asset found, return asset1
return asset1;
}
// Matching asset found, merge amounts
const mergedAmount = bn(asset1.amount).add(matchingAsset.amount);
return { ...asset1, amount: mergedAmount };
});

// Return merged assets from op1 with filtered assets from op2
return mergedAssets.concat(filteredAssets);
};

/** @hidden */
Expand All @@ -128,43 +139,37 @@ function isSameOperation(a: Operation, b: Operation) {

/** @hidden */
export function addOperation(operations: Operation[], toAdd: Operation) {
const ops = operations
.map((op) => {
// if it's not same operation, don't change. we just wanna stack the same operation
if (!isSameOperation(op, toAdd)) {
return null;
}

let newOp = { ...op };

// if it's adding new assets
if (toAdd.assetsSent?.length) {
// if prev op had assets, merge them. Otherwise just add the new assets
newOp = {
...newOp,
assetsSent: op.assetsSent?.length ? mergeAssets(op, toAdd) : toAdd.assetsSent,
};
}
const allOperations = [...operations];

// Verifying if the operation to add already exists.
const index = allOperations.findIndex((op) => isSameOperation(op, toAdd));

if (allOperations[index]) {
// Existent operation, we want to edit it.
const existentOperation = { ...allOperations[index] };

if (toAdd.assetsSent?.length) {
/**
* If the assetSent already exists, we call 'mergeAssets' to merge possible
* entries of the same 'assetId', otherwise we just add the new 'assetSent'.
*/
existentOperation.assetsSent = existentOperation.assetsSent?.length
? mergeAssets(existentOperation, toAdd)
: toAdd.assetsSent;
}

// if it's adding new calls,
if (toAdd.calls?.length) {
/*
[] for calls we don't stack as grouping is not desired.
we wanna show all calls in the same operation
with each respective assets, amounts, functions, arguments.
*/
newOp = {
...newOp,
calls: [...(op.calls || []), ...(toAdd.calls || [])],
};
}
if (toAdd.calls?.length) {
// We need to stack the new call(s) with the possible existent ones.
existentOperation.calls = [...(existentOperation.calls || []), ...toAdd.calls];
}

return newOp;
})
.filter(Boolean) as Operation[];
allOperations[index] = existentOperation;
} else {
// New operation, we can simply add it.
allOperations.push(toAdd);
}

// if this operation didn't exist before just add it to the end
return ops.length ? ops : [...operations, toAdd];
return allOperations;
}
Torres-ssf marked this conversation as resolved.
Show resolved Hide resolved

/** @hidden */
Expand Down Expand Up @@ -372,7 +377,8 @@ export function getTransferOperations({
const input = getInputFromAssetId(inputs, output.assetId);
if (input) {
const inputAddress = getInputAccountAddress(input);
operations = addOperation(operations, {

const operationToAdd: Operation = {
name: OperationName.transfer,
from: {
type: AddressType.account,
Expand All @@ -388,7 +394,9 @@ export function getTransferOperations({
amount: output.amount,
},
],
});
};

operations = addOperation(operations, operationToAdd);
}
});
}
Expand Down
Loading