Skip to content

Commit

Permalink
refactor: improve asset id types, convert TokenMap to Map
Browse files Browse the repository at this point in the history
  • Loading branch information
mkazlauskas committed Nov 26, 2021
1 parent 4fc6ad8 commit 9781ae2
Show file tree
Hide file tree
Showing 29 changed files with 219 additions and 170 deletions.
8 changes: 4 additions & 4 deletions packages/blockfrost/src/BlockfrostToCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ export const BlockfrostToCore = {
}),

txOut: (blockfrost: BlockfrostOutput): Cardano.TxOut => {
const assets: Cardano.Value['assets'] = {};
for (const amount of blockfrost.amount) {
if (amount.unit === 'lovelace') continue;
assets[amount.unit] = BigInt(amount.quantity);
const assets: Cardano.TokenMap = new Map();
for (const { quantity, unit } of blockfrost.amount) {
if (unit === 'lovelace') continue;
assets.set(Cardano.AssetId(unit), BigInt(quantity));
}
return {
address: Cardano.Address(blockfrost.address),
Expand Down
10 changes: 5 additions & 5 deletions packages/blockfrost/src/blockfrostAssetProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ const mapMetadata = (
export const blockfrostAssetProvider = (options: Options): AssetProvider => {
const blockfrost = new BlockFrostAPI(options);

const getAssetHistory = async (assetId: string): Promise<Cardano.AssetMintOrBurn[]> =>
const getAssetHistory = async (assetId: Cardano.AssetId): Promise<Cardano.AssetMintOrBurn[]> =>
fetchSequentially({
arg: assetId,
arg: assetId.toString(),
request: blockfrost.assetsHistory,
responseTranslator: (response): Cardano.AssetMintOrBurn[] =>
response.map(({ action, amount, tx_hash }) => ({
Expand All @@ -39,12 +39,12 @@ export const blockfrostAssetProvider = (options: Options): AssetProvider => {
});

const getAsset: AssetProvider['getAsset'] = async (assetId) => {
const response = await blockfrost.assetsById(assetId);
const response = await blockfrost.assetsById(assetId.toString());
const name = Buffer.from(Asset.util.assetNameFromAssetId(assetId), 'hex').toString('utf-8');
const quantity = BigInt(response.quantity);
return {
assetId,
fingerprint: response.fingerprint,
fingerprint: Cardano.AssetFingerprint(response.fingerprint),
history:
response.mint_or_burn_count === 1
? [
Expand All @@ -57,7 +57,7 @@ export const blockfrostAssetProvider = (options: Options): AssetProvider => {
: await getAssetHistory(assetId),
metadata: mapMetadata(response.onchain_metadata, response.metadata),
name,
policyId: response.policy_id,
policyId: Cardano.PolicyId(response.policy_id),
quantity
};
};
Expand Down
20 changes: 12 additions & 8 deletions packages/blockfrost/test/blockfrostAssetProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ describe('blockfrostAssetProvider', () => {
BlockFrostAPI.prototype.assetsById = jest.fn().mockResolvedValue(mockedAssetResponse);

const client = blockfrostAssetProvider({ isTestnet: true, projectId: apiKey });
const response = await client.getAsset('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e');
const response = await client.getAsset(
Cardano.AssetId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e')
);

expect(response).toMatchObject<Cardano.Asset>({
assetId: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e',
fingerprint: 'asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w',
assetId: Cardano.AssetId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e'),
fingerprint: Cardano.AssetFingerprint('asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w'),
history: [
{
action: Cardano.AssetProvisioning.Mint,
Expand All @@ -58,7 +60,7 @@ describe('blockfrostAssetProvider', () => {
url: 'https://www.stakenuts.com/'
},
name: 'nutcoin',
policyId: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7',
policyId: Cardano.PolicyId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7'),
quantity: 12_000n
});
});
Expand All @@ -82,11 +84,13 @@ describe('blockfrostAssetProvider', () => {
] as Responses['asset_history']);

const client = blockfrostAssetProvider({ isTestnet: true, projectId: apiKey });
const response = await client.getAsset('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e');
const response = await client.getAsset(
Cardano.AssetId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e')
);

expect(response).toMatchObject<Cardano.Asset>({
assetId: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e',
fingerprint: 'asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w',
assetId: Cardano.AssetId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e'),
fingerprint: Cardano.AssetFingerprint('asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w'),
history: [
{
action: Cardano.AssetProvisioning.Mint,
Expand All @@ -108,7 +112,7 @@ describe('blockfrostAssetProvider', () => {
url: 'https://www.stakenuts.com/'
},
name: 'nutcoin',
policyId: 'b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7',
policyId: Cardano.PolicyId('b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7'),
quantity: 12_000n
});
});
Expand Down
19 changes: 11 additions & 8 deletions packages/blockfrost/test/blockfrostWalletProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ describe('blockfrostWalletProvider', () => {
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp'
),
value: {
assets: {
b01fb3b8c3dd6b3705a5dc8bcd5a70759f70ad5d97a72005caeac3c652657675746f31333237: BigInt(1)
},
assets: new Map([
[Cardano.AssetId('b01fb3b8c3dd6b3705a5dc8bcd5a70759f70ad5d97a72005caeac3c652657675746f31333237'), 1n]
]),
coins: 50_928_877n
}
});
Expand All @@ -211,7 +211,10 @@ describe('blockfrostWalletProvider', () => {
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp'
),
value: {
assets: {},
// Review: not sure how this was passing originally. Generated UTxO all have an asset.
assets: new Map([
[Cardano.AssetId('b01fb3b8c3dd6b3705a5dc8bcd5a70759f70ad5d97a72005caeac3c652657675746f31333237'), 2n]
]),
coins: 50_928_878n
}
});
Expand Down Expand Up @@ -396,10 +399,10 @@ describe('blockfrostWalletProvider', () => {
'addr_test1qzx9hu8j4ah3auytk0mwcupd69hpc52t0cw39a65ndrah86djs784u92a3m5w475w3w35tyd6v3qumkze80j8a6h5tuqq5xe8y'
),
value: {
assets: {
'06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364': BigInt(63),
'06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464': BigInt(22)
},
assets: new Map([
[Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108617364'), 63n],
[Cardano.AssetId('06f8c5655b4e2b5911fee8ef2fc66b4ce64c8835642695c730a3d108646464'), 22n]
]),
coins: 1_000_000_000n
}
},
Expand Down
4 changes: 2 additions & 2 deletions packages/cardano-graphql-db-sync/src/CardanoGraphqlToCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const txIn = ({ sourceTxIndex, txHash, address }: GraphqlTransaction['inputs'][0
});

const txOut = ({ address, tokens, value }: GraphqlTransaction['outputs'][0]) => {
const assets: Cardano.Value['assets'] = {};
for (const token of tokens) assets[token.asset.assetId] = BigInt(token.quantity);
const assets: Cardano.TokenMap = new Map();
for (const token of tokens) assets.set(Cardano.AssetId(token.asset.assetId), BigInt(token.quantity));
return { address, value: { assets, coins: BigInt(value) } };
};

Expand Down
58 changes: 28 additions & 30 deletions packages/cip2/src/RoundRobinRandomImprove/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type EstimateTxFeeWithOriginalOutputs = (utxo: CSL.TransactionUnspentOutput[], c
interface ChangeComputationArgs {
utxoSelection: UtxoSelection;
outputValues: Cardano.Value[];
uniqueOutputAssetIDs: string[];
uniqueOutputAssetIDs: Cardano.AssetId[];
implicitCoin: ImplicitCoinBigint;
estimateTxFee: EstimateTxFeeWithOriginalOutputs;
computeMinimumCoinQuantity: ComputeMinimumCoinQuantity;
Expand All @@ -31,15 +31,16 @@ interface ChangeComputationResult {
fee: bigint;
}

const getLeftoverAssets = (utxoSelected: UtxoWithValue[], uniqueOutputAssetIDs: string[]) => {
const leftovers: Record<string, Array<bigint>> = {};
const getLeftoverAssets = (utxoSelected: UtxoWithValue[], uniqueOutputAssetIDs: Cardano.AssetId[]) => {
const leftovers: Map<Cardano.AssetId, Array<bigint>> = new Map();
for (const {
value: { assets }
} of utxoSelected) {
if (assets) {
const leftoverAssetKeys = Object.keys(assets).filter((id) => !uniqueOutputAssetIDs.includes(id));
const leftoverAssetKeys = [...assets.keys()].filter((id) => !uniqueOutputAssetIDs.includes(id));
for (const assetKey of leftoverAssetKeys) {
(leftovers[assetKey] ||= []).push(assets[assetKey]);
const assetLeftovers = leftovers.get(assetKey) || [];
leftovers.set(assetKey, [...assetLeftovers, assets.get(assetKey)!]);
}
}
}
Expand All @@ -56,28 +57,26 @@ const getLeftoverAssets = (utxoSelected: UtxoWithValue[], uniqueOutputAssetIDs:
const redistributeLeftoverAssets = (
utxoSelected: UtxoWithValue[],
requestedAssetChangeBundles: Cardano.Value[],
uniqueOutputAssetIDs: string[]
uniqueOutputAssetIDs: Cardano.AssetId[]
) => {
const leftovers = getLeftoverAssets(utxoSelected, uniqueOutputAssetIDs);
// Distribute leftovers to result bundles
const resultBundles = [...requestedAssetChangeBundles];
for (const assetId in leftovers) {
for (const assetId of leftovers.keys()) {
if (resultBundles.length === 0) {
resultBundles.push({ coins: 0n });
}
const quantities = orderBy(leftovers[assetId], (q) => q, 'desc');
const quantities = orderBy(leftovers.get(assetId), (q) => q, 'desc');
while (quantities.length > resultBundles.length) {
// Coalesce the smallest quantities together
const smallestQuantity = quantities.pop()!;
quantities[quantities.length - 1] += smallestQuantity;
}
for (const [idx, quantity] of quantities.entries()) {
const originalBundle = resultBundles[idx];
const originalBundleAssets = originalBundle.assets?.entries() || [];
resultBundles.splice(idx, 1, {
assets: {
...originalBundle.assets,
[assetId]: quantity
},
assets: new Map([...originalBundleAssets, [assetId, quantity]]),
coins: originalBundle.coins
});
}
Expand All @@ -89,24 +88,23 @@ const createBundlePerOutput = (
outputValues: Cardano.Value[],
coinTotalRequested: bigint,
coinChangeTotal: bigint,
assetTotals: Record<string, { selected: bigint; requested: bigint }>
assetTotals: Map<Cardano.AssetId, { selected: bigint; requested: bigint }>
) => {
let totalCoinBundled = 0n;
const totalAssetsBundled: Record<string, bigint> = {};
const totalAssetsBundled: Cardano.TokenMap = new Map();
const bundles = outputValues.map((value) => {
const coins = coinTotalRequested > 0n ? (coinChangeTotal * value.coins) / coinTotalRequested : 0n;
totalCoinBundled += coins;
if (!value.assets) {
return { coins };
}
const assets: Cardano.TokenMap = {};
for (const assetId of Object.keys(value.assets)) {
const outputAmount = value.assets[assetId];
const { selected, requested } = assetTotals[assetId];
const assets: Cardano.TokenMap = new Map();
for (const [assetId, outputAmount] of value.assets.entries()) {
const { selected, requested } = assetTotals.get(assetId)!;
const assetChangeTotal = selected - requested;
const assetChange = (assetChangeTotal * outputAmount) / selected;
totalAssetsBundled[assetId] = (totalAssetsBundled[assetId] || 0n) + assetChange;
assets[assetId] = assetChange;
totalAssetsBundled.set(assetId, (totalAssetsBundled.get(assetId) || 0n) + assetChange);
assets.set(assetId, assetChange);
}
return { assets, coins };
});
Expand All @@ -124,16 +122,16 @@ const createBundlePerOutput = (
const computeRequestedAssetChangeBundles = (
utxoSelected: UtxoWithValue[],
outputValues: Cardano.Value[],
uniqueOutputAssetIDs: string[],
uniqueOutputAssetIDs: Cardano.AssetId[],
implicitCoin: ImplicitCoinBigint,
fee: bigint
): Cardano.Value[] => {
const assetTotals: Record<string, { selected: bigint; requested: bigint }> = {};
const assetTotals: Map<Cardano.AssetId, { selected: bigint; requested: bigint }> = new Map();
for (const assetId of uniqueOutputAssetIDs) {
assetTotals[assetId] = {
assetTotals.set(assetId, {
requested: assetQuantitySelector(assetId)(outputValues),
selected: assetWithValueQuantitySelector(assetId)(utxoSelected)
};
});
}
const coinTotalSelected = getWithValuesCoinQuantity(utxoSelected) + implicitCoin.input;
const coinTotalRequested = getCoinQuantity(outputValues) + fee + implicitCoin.deposit;
Expand All @@ -156,11 +154,11 @@ const computeRequestedAssetChangeBundles = (
}
}
for (const assetId of uniqueOutputAssetIDs) {
const assetTotal = assetTotals[assetId];
const assetLost = assetTotal.selected - assetTotal.requested - totalAssetsBundled[assetId];
const assetTotal = assetTotals.get(assetId)!;
const assetLost = assetTotal.selected - assetTotal.requested - totalAssetsBundled.get(assetId)!;
if (assetLost > 0n) {
const anyBundle = bundles.find(({ assets }) => typeof assets?.[assetId] === 'bigint')!;
anyBundle.assets![assetId] = (anyBundle.assets![assetId] || 0n) + assetLost;
const anyBundle = bundles.find(({ assets }) => assets?.has(assetId))!;
anyBundle.assets?.set(assetId, anyBundle.assets!.get(assetId)! + assetLost);
}
}

Expand Down Expand Up @@ -207,7 +205,7 @@ const coalesceChangeBundlesForMinCoinRequirement = (
return undefined;
}
// Filter empty bundles
return sortedBundles.filter((bundle) => bundle.coins > 0n || Object.keys(bundle.assets || {}).length > 0);
return sortedBundles.filter((bundle) => bundle.coins > 0n || (bundle.assets?.size || 0) > 0);
};

const computeChangeBundles = ({
Expand All @@ -220,7 +218,7 @@ const computeChangeBundles = ({
}: {
utxoSelection: UtxoSelection;
outputValues: Cardano.Value[];
uniqueOutputAssetIDs: string[];
uniqueOutputAssetIDs: Cardano.AssetId[];
implicitCoin: ImplicitCoinBigint;
computeMinimumCoinQuantity: ComputeMinimumCoinQuantity;
fee?: bigint;
Expand Down
6 changes: 3 additions & 3 deletions packages/cip2/src/RoundRobinRandomImprove/roundRobin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigIntMath } from '@cardano-sdk/core';
import { BigIntMath, Cardano } from '@cardano-sdk/core';
import {
ImplicitCoinBigint,
OutputWithValue,
Expand Down Expand Up @@ -36,14 +36,14 @@ const improvesSelection = (
};

const listTokensWithin = (
uniqueOutputAssetIDs: string[],
uniqueOutputAssetIDs: Cardano.AssetId[],
outputs: OutputWithValue[],
implicitCoin: ImplicitCoinBigint
) => [
...uniqueOutputAssetIDs.map((id) => {
const getQuantity = assetWithValueQuantitySelector(id);
return {
filterUtxo: (utxo: UtxoWithValue[]) => utxo.filter(({ value: { assets } }) => assets?.[id]),
filterUtxo: (utxo: UtxoWithValue[]) => utxo.filter(({ value: { assets } }) => assets?.get(id)),
getTotalSelectedQuantity: (utxo: UtxoWithValue[]) => getQuantity(utxo),
minimumTarget: getQuantity(outputs)
};
Expand Down
14 changes: 6 additions & 8 deletions packages/cip2/src/RoundRobinRandomImprove/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface ImplicitCoinBigint {
export interface RoundRobinRandomImproveArgs {
utxosWithValue: UtxoWithValue[];
outputsWithValue: OutputWithValue[];
uniqueOutputAssetIDs: string[];
uniqueOutputAssetIDs: Cardano.AssetId[];
implicitCoin: ImplicitCoinBigint;
}

Expand All @@ -49,9 +49,7 @@ export const preprocessArgs = (
output,
value: cslToCore.value(output.amount())
}));
const uniqueOutputAssetIDs = uniq(
outputsWithValue.flatMap(({ value: { assets } }) => (assets && Object.keys(assets)) || [])
);
const uniqueOutputAssetIDs = uniq(outputsWithValue.flatMap(({ value: { assets } }) => [...(assets?.keys() || [])]));
const implicitCoin: ImplicitCoinBigint = {
deposit: partialImplicitCoin.deposit || 0n,
input: partialImplicitCoin.input || 0n
Expand All @@ -61,11 +59,11 @@ export const preprocessArgs = (

export const withValuesToValues = (totals: WithValue[]) => totals.map((t) => t.value);
export const assetQuantitySelector =
(id: string) =>
(id: Cardano.AssetId) =>
(quantities: Cardano.Value[]): bigint =>
BigIntMath.sum(quantities.map(({ assets }) => assets?.[id] || 0n));
BigIntMath.sum(quantities.map(({ assets }) => assets?.get(id) || 0n));
export const assetWithValueQuantitySelector =
(id: string) =>
(id: Cardano.AssetId) =>
(totals: WithValue[]): bigint =>
assetQuantitySelector(id)(withValuesToValues(totals));
export const getCoinQuantity = (quantities: Cardano.Value[]): bigint =>
Expand All @@ -91,7 +89,7 @@ export const assertIsCoinBalanceSufficient = (
* @throws InputSelectionError { UtxoBalanceInsufficient }
*/
export const assertIsBalanceSufficient = (
uniqueOutputAssetIDs: string[],
uniqueOutputAssetIDs: Cardano.AssetId[],
utxoValues: Cardano.Value[],
outputValues: Cardano.Value[],
implicitCoin: ImplicitCoinBigint
Expand Down
Loading

0 comments on commit 9781ae2

Please sign in to comment.