Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 1x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 54x 24x 84x 84x 84x 78x 54x 84x 8x 76x 30x 54x 36x 30x 18x 18x 12x 12x 46x 42x 42x 42x 132x 64x 68x | import { add, equal, lessThanOrEqual } from "dinero.js";
import { autoInjectable } from "tsyringe";
import {
LedgerEvent,
LedgerEventAbsRefTx,
LedgerEventRelRefTx,
} from "../../domain/ledger-event.js";
import { atd } from "../../domain/utils/math-low-level.js";
import { Amount } from "../../domain/value.js";
import { LogicalValidationRule, ValidationResult } from "../validator.js";
@autoInjectable()
class AllocationPolicyRule implements LogicalValidationRule {
validate(event: LedgerEvent): ValidationResult {
const { txs } = event;
let isValid = true;
const errors: string[] = [];
for (const tx of txs) {
const result = this.validateAmountsInTx(tx);
isValid &&= result.isValid;
errors.push(...result.errors);
}
return {
isValid,
errors,
};
}
private validateAmountsInTx(
tx: LedgerEventAbsRefTx | LedgerEventRelRefTx,
): ValidationResult {
const { sources, destinations } = tx;
const refValue = atd(this.getAmount(tx.refValue.amount));
return [
sources.map((s) => s.allocationPolicy),
destinations.map((d) => d.allocationPolicy),
]
.map((policies, idx) => {
const name = idx === 0 ? "source" : "destination";
const policyTypes = new Set(policies.map((item) => item.policy));
const allPositiveAmounts = policies
.filter((item) => item.policy === "split" || item.policy === "max")
.every((p) => this.getAmount(p.amount).amount > 0n);
if (!allPositiveAmounts) {
return {
isValid: false,
errors: [
`All split/max amounts for ${name} must be greater than 0.`,
],
};
}
if (policyTypes.has("split")) {
const splitMaxSums = policies
.filter((p) => p.policy === "split" || p.policy === "max")
.map((s) => atd(this.getAmount(s.amount)))
.reduce(add, atd({ amount: 0n, scale: 0n }));
if (policyTypes.has("remaining")) {
const isValid = lessThanOrEqual(splitMaxSums, refValue);
return {
isValid,
errors: isValid
? []
: [
`The sum of split and/or max amounts for ${name} cannot be more than the ref value amount.`,
],
};
}
const isValid = equal(splitMaxSums, refValue);
return {
isValid,
errors: isValid
? []
: [
`The sum of split amounts for ${name} must be equal to the ref value amount.`,
],
};
}
return {
isValid: true,
errors: [],
};
})
.reduce((acc, curr) => {
acc.isValid &&= curr.isValid;
acc.errors.push(...curr.errors);
return acc;
});
}
/**
* Helper method to extract the amount from:
* Amount (in absolute ref)
* or
* [Amount | Amount] (in relative ref)
*/
private getAmount(amount: Amount | [Amount, Amount]): Amount {
// Since denominator of ref value and all amounts are assumed to be the same,
// we just need to use the numerator in performing logical validation.
if (Array.isArray(amount)) {
return amount[0];
}
return amount;
}
}
export { AllocationPolicyRule };
|