Skip to content

Commit

Permalink
fixup! feat(input-selection): added new greedy input selector
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelCastilloB committed May 30, 2023
1 parent 14391e8 commit dcd67f1
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 31 deletions.
Expand Up @@ -3,13 +3,13 @@ import { Cardano } from '@cardano-sdk/core';
import { InputSelectionError, InputSelectionFailure } from '../InputSelectionError';
import { InputSelectionParameters, InputSelector, SelectionConstraints, SelectionResult } from '../types';
import {
getAssetAddition,
getAssetDifference,
addTokenMaps,
getAssetQuantities,
getCoinQuantity,
hasNegativeAssetValue,
sortByCoins,
splitChange,
subtractTokenMaps,
toValues
} from '../util';

Expand Down Expand Up @@ -113,12 +113,12 @@ export class GreedyInputSelector implements InputSelector {
const implicitAssetInput = implicitValue?.mint || new Map<AssetId, bigint>();
const totalLovelaceInput = totalLovelaceInUtxoSet + implicitCoinInput;
const totalLovelaceOutput = totalLovelaceInOutputSet + implicitCoinOutput;
const totalAssetsInput = getAssetAddition(totalAssetsInUtxoSet, implicitAssetInput);
const totalAssetsInput = addTokenMaps(totalAssetsInUtxoSet, implicitAssetInput);

// Calculate the difference between the given outputs and the UTXO set. This will let us know
// how much we need to return as change.
const changeLovelace = totalLovelaceOutput - totalLovelaceInput;
const changeAssets = getAssetDifference(totalAssetsInOutputSet, totalAssetsInput);
const changeAssets = subtractTokenMaps(totalAssetsInOutputSet, totalAssetsInput);

// If the wallet tries to spend more than we can cover with the current UTXO set + implicit value throw.
if (changeLovelace <= 0n || hasNegativeAssetValue(changeAssets))
Expand Down
36 changes: 16 additions & 20 deletions packages/input-selection/src/util.ts
Expand Up @@ -6,6 +6,8 @@ import { ComputeMinimumCoinQuantity, ImplicitValue, TokenBundleSizeExceedsLimit
import { InputSelectionError, InputSelectionFailure } from './InputSelectionError';
import uniq from 'lodash/uniq';

const PERCENTAGE_TOLERANCE = 0.05;

export const stubMaxSizeAddress = Cardano.PaymentAddress(
'addr_test1qqydn46r6mhge0kfpqmt36m6q43knzsd9ga32n96m89px3nuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475qypp3m9'
);
Expand Down Expand Up @@ -193,7 +195,7 @@ export const sortByCoins = (lhs: Cardano.TxOut, rhs: Cardano.TxOut) => {
* @param rhs the right-hand side of the subtraction operation.
* @returns The difference between both TokenMaps.
*/
export const getAssetDifference = (
export const subtractTokenMaps = (
lhs: Cardano.TokenMap | undefined,
rhs: Cardano.TokenMap | undefined
): Cardano.TokenMap | undefined => {
Expand Down Expand Up @@ -253,7 +255,7 @@ export const getAssetDifference = (
* @param rhs the right-hand side of the addition operation.
* @returns The addition between both TokenMaps.
*/
export const getAssetAddition = (
export const addTokenMaps = (
lhs: Cardano.TokenMap | undefined,
rhs: Cardano.TokenMap | undefined
): Cardano.TokenMap | undefined => {
Expand Down Expand Up @@ -356,16 +358,15 @@ const distributeAssets = (
throw new InputSelectionError(InputSelectionFailure.UtxoBalanceInsufficient);
}

if (!output.value.assets) {
if (!output.value.assets || output.value.assets.size === 0) {
// If this output failed and doesn't contain any assets, it means there is not enough coins to cover
// the min ADA coin pero UTXO. We will throw for now, but it may be possible to fix this by moving some excess
// lovelace from one of the other outputs (ignoring the given distribution).
// the min ADA coin per UTXO even after moving all the assets to the other outputs.
throw new InputSelectionError(InputSelectionFailure.UtxoBalanceInsufficient);
}

const splicedAsset = new Map([...output.value.assets!.entries()].splice(0, 1));
const currentOutputNewAssets = getAssetDifference(output.value.assets, splicedAsset);
const nextOutputNewAssets = getAssetAddition(adjustedOutputs[i + 1].value.assets, splicedAsset);
const currentOutputNewAssets = subtractTokenMaps(output.value.assets, splicedAsset);
const nextOutputNewAssets = addTokenMaps(adjustedOutputs[i + 1].value.assets, splicedAsset);

output.value.assets = currentOutputNewAssets;
adjustedOutputs[i + 1].value.assets = nextOutputNewAssets;
Expand Down Expand Up @@ -405,6 +406,12 @@ export const splitChange = async (
tokenBundleSizeExceedsLimit: TokenBundleSizeExceedsLimit
): Promise<Array<Cardano.TxOut>> => {
const changeAddresses = await getChangeAddresses();
const totalPercentage = [...changeAddresses.values()].reduce((sum, current) => sum + current, 0);

// We are going to enforce that the given % mostly add up to 100% (to account for division errors)
if (Math.abs(1 - totalPercentage) > PERCENTAGE_TOLERANCE)
throw new InputSelectionError(InputSelectionFailure.UtxoBalanceInsufficient); // TODO: We need a new error for this types of failures

const changeOutputs: Array<Cardano.TxOut> = [...changeAddresses.entries()].map((val) => ({
address: val[0],
value: { coins: 0n }
Expand All @@ -424,20 +431,9 @@ export const splitChange = async (
runningTotal > totalChangeLovelace ? coinAllocation - (runningTotal - totalChangeLovelace) : coinAllocation;
}

const totalAllocated = changeOutputs.reduce(
(prev, current) => {
current.value.coins += prev.value.coins;
return current;
},
{
address: '',
value: { coins: 0n }
}
).value.coins;

if (totalAllocated < totalChangeLovelace) {
if (runningTotal < totalChangeLovelace) {
// This may be because the given proportions don't add up to 100%. We will add the missing balance to the last output.
const missingAllocation = totalChangeLovelace - totalAllocated;
const missingAllocation = totalChangeLovelace - runningTotal;
changeOutputs[changeOutputs.length - 1].value.coins += missingAllocation;
}

Expand Down

0 comments on commit dcd67f1

Please sign in to comment.