Skip to content

Commit

Permalink
fix(core): subtractTokenMaps now properly subtract when there is an a…
Browse files Browse the repository at this point in the history
…sset missing in one map
  • Loading branch information
AngelCastilloB committed Jan 19, 2024
1 parent dc6e2ea commit 03e84bb
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 37 deletions.
88 changes: 74 additions & 14 deletions packages/core/src/Asset/util/subtractTokenMaps.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,80 @@
import { TokenMap } from '../../Cardano';
import { isNotNil } from '@cardano-sdk/util';
/* eslint-disable complexity,sonarjs/cognitive-complexity */
import * as Cardano from '../../Cardano';
import uniq from 'lodash/uniq';

/** Subtract asset quantities in order */
export const subtractTokenMaps = (assets: (TokenMap | undefined)[]): TokenMap | undefined => {
if (assets.length <= 0 || !isNotNil(assets[0])) return undefined;
const result: TokenMap = new Map(assets[0]);
const rest: TokenMap[] = assets.slice(1).filter(isNotNil);
for (const assetTotals of rest) {
for (const [assetId, assetQuantity] of assetTotals.entries()) {
const total = result.get(assetId) ?? 0n;
const diff = total - assetQuantity;
diff === 0n ? result.delete(assetId) : result.set(assetId, diff);
/**
* Given two Cardano.TokenMaps, compute a Cardano.TokenMap with the difference between the left-hand side and the right-hand side.
*
* @param lhs the left-hand side of the subtraction operation.
* @param rhs the right-hand side of the subtraction operation.
* @returns The difference between both Cardano.TokenMaps.
*/
export const subtractMaps = (
lhs: Cardano.TokenMap | undefined,
rhs: Cardano.TokenMap | undefined
): Cardano.TokenMap | undefined => {
if (!rhs) {
if (!lhs) return undefined;

const nonEmptyValues = new Map<Cardano.AssetId, bigint>();

for (const [key, value] of lhs.entries()) {
if (value !== 0n) nonEmptyValues.set(key, value);
}

return nonEmptyValues;
}

if (!lhs) {
const negativeValues = new Map<Cardano.AssetId, bigint>();

for (const [key, value] of rhs.entries()) {
if (value !== 0n) negativeValues.set(key, -value);
}

return negativeValues;
}

const result = new Map<Cardano.AssetId, bigint>();
const intersection = new Array<Cardano.AssetId>();

// any element that is present in the lhs and not in the rhs will be added as a positive value
for (const [key, value] of lhs.entries()) {
if (rhs.has(key)) {
intersection.push(key);
continue;
}

if (value !== 0n) result.set(key, value);
}
if (result.size === 0) {
return undefined;

// any element that is present in the rhs and not in the lhs will be added as a negative value
for (const [key, value] of rhs.entries()) {
if (lhs.has(key)) {
intersection.push(key);
continue;
}

if (value !== 0n) result.set(key, -value);
}

// Elements present in both maps will be subtracted (lhs - rhs)
const uniqIntersection = uniq(intersection);

for (const id of uniqIntersection) {
const lshVal = lhs.get(id);
const rshVal = rhs.get(id);
const remainingCoins = lshVal! - rshVal!;

if (remainingCoins !== 0n) result.set(id, remainingCoins);
}

return result;
};

/** Subtract asset quantities in order */
export const subtractTokenMaps = (assets: (Cardano.TokenMap | undefined)[]): Cardano.TokenMap | undefined => {
if (!assets || assets.length === 0) return undefined;

return assets.reduce(subtractMaps);
};
69 changes: 47 additions & 22 deletions packages/core/test/Asset/util/subtractTokenMaps.test.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,86 @@
import * as AssetId from '../../AssetId';
import * as AssetIds from '../../AssetId';
import { Asset } from '../../../src';

describe('Asset', () => {
describe('util', () => {
describe('subtractTokenMaps', () => {
it('should subtract quantities correctly when all assets have the same tokens', () => {
const initialAsset = new Map([
[AssetId.PXL, 100n],
[AssetId.Unit, 50n]
[AssetIds.PXL, 100n],
[AssetIds.Unit, 50n]
]);
const asset2 = new Map([
[AssetId.PXL, 23n],
[AssetId.Unit, 20n]
[AssetIds.PXL, 23n],
[AssetIds.Unit, 20n]
]);
expect(Asset.util.subtractTokenMaps([initialAsset, asset2])).toEqual(
new Map([
[AssetId.PXL, 77n],
[AssetId.Unit, 30n]
[AssetIds.PXL, 77n],
[AssetIds.Unit, 30n]
])
);
});
it('should delete tokens from result when quantity is 0', () => {
const initialAsset = new Map([
[AssetId.PXL, 100n],
[AssetId.Unit, 50n]
[AssetIds.PXL, 100n],
[AssetIds.Unit, 50n]
]);
const asset2 = new Map([
[AssetId.PXL, 23n],
[AssetId.Unit, 50n]
[AssetIds.PXL, 23n],
[AssetIds.Unit, 50n]
]);
expect(Asset.util.subtractTokenMaps([initialAsset, asset2])).toEqual(new Map([[AssetId.PXL, 77n]]));
expect(Asset.util.subtractTokenMaps([initialAsset, asset2])).toEqual(new Map([[AssetIds.PXL, 77n]]));
});
it('should be able to return negative quantities', () => {
const initialAsset = new Map([
[AssetId.PXL, 100n],
[AssetId.Unit, 50n]
[AssetIds.PXL, 100n],
[AssetIds.Unit, 50n]
]);
const asset2 = new Map([
[AssetId.PXL, 173n],
[AssetId.Unit, 50n]
[AssetIds.PXL, 173n],
[AssetIds.Unit, 50n]
]);
const asset3 = new Map([[AssetId.TSLA, 44n]]);
const asset3 = new Map([[AssetIds.TSLA, 44n]]);
expect(Asset.util.subtractTokenMaps([initialAsset, asset2, asset3])).toEqual(
new Map([
[AssetId.PXL, -73n],
[AssetId.TSLA, -44n]
[AssetIds.PXL, -73n],
[AssetIds.TSLA, -44n]
])
);
});
it('should not change the first element of the array', () => {
const asset1 = new Map([[AssetId.PXL, 10n]]);
const asset1 = new Map([[AssetIds.PXL, 10n]]);

const asset2 = new Map([[AssetId.PXL, 5n]]);
const asset2 = new Map([[AssetIds.PXL, 5n]]);

Asset.util.subtractTokenMaps([asset1, asset2]);

expect(asset1.get(AssetId.PXL)).toBe(10n);
expect(asset1.get(AssetIds.PXL)).toBe(10n);
});

it('should subtract even if the asset is missing in one of the maps', () => {
const asset1 = new Map([
[AssetIds.PXL, 1000n],
[AssetIds.Unit, 12n]
]);

const asset2 = new Map([
[AssetIds.TSLA, 2n],
[AssetIds.PXL, 9n],
[AssetIds.Unit, 1120n]
]);

const result = Asset.util.subtractTokenMaps([asset1, asset2]);

expect(result!.get(AssetIds.PXL)).toBe(991n);
expect(result!.get(AssetIds.Unit)).toBe(-1108n);
expect(result!.get(AssetIds.TSLA)).toBe(-2n);

const result2 = Asset.util.subtractTokenMaps([asset2, asset1]);

expect(result2!.get(AssetIds.PXL)).toBe(-991n);
expect(result2!.get(AssetIds.Unit)).toBe(1108n);
expect(result2!.get(AssetIds.TSLA)).toBe(2n);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Cardano.util.subtractValueQuantities', () => {
]),
coins: 200n
};
expect(subtractValueQuantities([q1, q1])).toEqual({ assets: undefined, coins: 0n });
expect(subtractValueQuantities([q1, q1])).toEqual({ assets: new Map(), coins: 0n });
});
it('returns negative quantities', () => {
const q1: Cardano.Value = {
Expand Down

0 comments on commit 03e84bb

Please sign in to comment.