@hyperscale0/udl 2.0.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +15 -1
- package/conformance/invalid/call-binds-results.expected.json +10 -0
- package/conformance/invalid/call-binds-results.udl +314 -0
- package/conformance/invalid/call-unknown-action.expected.json +10 -0
- package/conformance/invalid/call-unknown-action.udl +314 -0
- package/conformance/invalid/leaf-effect-mismatch.expected.json +10 -0
- package/conformance/invalid/leaf-effect-mismatch.udl +314 -0
- package/conformance/invalid/piece-plan-without-partition.expected.json +10 -0
- package/conformance/invalid/piece-plan-without-partition.udl +305 -0
- package/conformance/invalid/private-action-independent-approval.expected.json +10 -0
- package/conformance/invalid/private-action-independent-approval.udl +314 -0
- package/conformance/invalid/unfund-order-not-reversed.expected.json +10 -0
- package/conformance/invalid/unfund-order-not-reversed.udl +314 -0
- package/conformance/valid/piece-plan-calls.expected.json +6 -0
- package/conformance/valid/piece-plan-calls.udl +314 -0
- package/dist/diagnostics.d.ts +36 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +36 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/effects.d.ts +26 -3
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +962 -8
- package/dist/effects.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/schema.d.ts +594 -11
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +111 -1
- package/dist/schema.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +289 -4
- package/dist/validation.js.map +1 -1
- package/docs/assets/brand/manifest.json +30 -0
- package/docs/assets/brand/udl-horizontal-white.svg +1 -0
- package/docs/assets/brand/udl-horizontal.svg +1 -0
- package/docs/assets/brand/udl-stacked-white.svg +1 -0
- package/docs/assets/brand/udl-stacked.svg +1 -0
- package/docs/assets/udl.svg +7 -14
- package/docs/llms-full.txt +199 -32
- package/docs/llms.txt +1 -1
- package/docs/reference/clauses.md +162 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/diagnostics.md +38 -32
- package/package.json +2 -2
- package/spec/udl.schema.json +321 -1
- package/src/diagnostics.ts +36 -0
- package/src/effects.ts +1672 -9
- package/src/index.ts +15 -0
- package/src/limits.ts +2 -0
- package/src/schema.ts +149 -2
- package/src/validation.ts +470 -5
package/dist/validation.js
CHANGED
|
@@ -4,7 +4,7 @@ import { udlClauseVocabulary, quoteExpiresAtRefKey, quoteFrozenRefKey, quoteSeed
|
|
|
4
4
|
import { fixedIsoDurationMs } from "./duration.js";
|
|
5
5
|
import { analyzeInstrumentFinance, financeAdmissionProblem, } from "./finance.js";
|
|
6
6
|
import { UDL_LIMITS } from "./limits.js";
|
|
7
|
-
import { deriveUdlActionEffects, udlEffectKinds } from "./effects.js";
|
|
7
|
+
import { deriveUdlActionEffects, resolveUdlActionPlans, udlEffectKinds, } from "./effects.js";
|
|
8
8
|
import { issue } from "./diagnostics.js";
|
|
9
9
|
export class UdlError extends Error {
|
|
10
10
|
issues;
|
|
@@ -224,6 +224,7 @@ function semanticIssues(document, references, options) {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
for (const [instrumentIndex, instrument] of document.instruments.entries()) {
|
|
227
|
+
const resolvedPlansResult = resolveUdlActionPlans(instrument);
|
|
227
228
|
for (const [actionName, action] of Object.entries(instrument.actions)) {
|
|
228
229
|
if (options.requireDecisionPartyBindings) {
|
|
229
230
|
for (const [index, role] of (action.port?.allowedParties ?? []).entries()) {
|
|
@@ -242,7 +243,14 @@ function semanticIssues(document, references, options) {
|
|
|
242
243
|
}
|
|
243
244
|
if (!action.effects)
|
|
244
245
|
continue;
|
|
245
|
-
|
|
246
|
+
let expected;
|
|
247
|
+
if (action.calls && action.calls.length > 0) {
|
|
248
|
+
const plan = resolvedPlansResult.plans.find((p) => p.action === actionName);
|
|
249
|
+
expected = plan ? plan.effects : {};
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
expected = deriveUdlActionEffects(action, udlClauseVocabulary);
|
|
253
|
+
}
|
|
246
254
|
for (const kind of udlEffectKinds) {
|
|
247
255
|
const actualRows = action.effects[kind] ?? [];
|
|
248
256
|
const expectedRows = expected[kind] ?? [];
|
|
@@ -885,11 +893,288 @@ function validateInstrument(instrument, instrumentIndex, instruments, subjects,
|
|
|
885
893
|
validateSetsAt(instrument, base, add);
|
|
886
894
|
validateActions(instrument, base, instruments, references, add);
|
|
887
895
|
validateQuoteCommit(instrument, base, references, add);
|
|
888
|
-
|
|
889
|
-
|
|
896
|
+
validatePiecePlan(instrument, base, references, add);
|
|
897
|
+
const planResolution = resolveUdlActionPlans(instrument);
|
|
898
|
+
for (const planIssue of planResolution.issues) {
|
|
899
|
+
const rawPath = planIssue.path.startsWith("$.")
|
|
900
|
+
? planIssue.path.slice(2).split(".")
|
|
901
|
+
: [planIssue.path];
|
|
902
|
+
add([...base, ...rawPath], planIssue.message, planIssue.code);
|
|
903
|
+
}
|
|
904
|
+
// Validate leaf steps
|
|
905
|
+
const validatedLeaves = new Set();
|
|
906
|
+
for (const plan of planResolution.plans) {
|
|
907
|
+
const actionDef = instrument.actions[plan.action];
|
|
908
|
+
if (!actionDef)
|
|
909
|
+
continue;
|
|
910
|
+
for (const leaf of plan.leaves) {
|
|
911
|
+
const leafKey = `${plan.pieceId ?? ""}:${leaf.originPath.join(".")}`;
|
|
912
|
+
if (!validatedLeaves.has(leafKey)) {
|
|
913
|
+
validatedLeaves.add(leafKey);
|
|
914
|
+
validateStep(instrument, actionDef, leaf.step, [...base, "actions", ...leaf.originPath], add);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// Multi-variant finance oracle check across action plan combinations
|
|
919
|
+
const actionsWithPlans = Object.keys(instrument.actions).filter((aName) => planResolution.plans.some((p) => p.action === aName));
|
|
920
|
+
let totalCombinations = 1;
|
|
921
|
+
const actionPlanMap = {};
|
|
922
|
+
for (const aName of actionsWithPlans) {
|
|
923
|
+
const actionPlans = planResolution.plans.filter((p) => p.action === aName);
|
|
924
|
+
actionPlanMap[aName] = actionPlans;
|
|
925
|
+
totalCombinations *= actionPlans.length;
|
|
926
|
+
}
|
|
927
|
+
if (actionsWithPlans.length > 0 &&
|
|
928
|
+
totalCombinations > UDL_LIMITS.maxActionExpansion) {
|
|
929
|
+
add([...base, "actions"], `variant expansion exceeds combination bound of ${UDL_LIMITS.maxActionExpansion} (${totalCombinations} combinations)`, "UDL2010");
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
const generateCombos = (keys) => {
|
|
933
|
+
if (keys.length === 0)
|
|
934
|
+
return [{}];
|
|
935
|
+
const [first, ...rest] = keys;
|
|
936
|
+
const restCombos = generateCombos(rest);
|
|
937
|
+
const result = [];
|
|
938
|
+
for (const plan of actionPlanMap[first]) {
|
|
939
|
+
for (const combo of restCombos) {
|
|
940
|
+
result.push({ ...combo, [first]: plan });
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return result;
|
|
944
|
+
};
|
|
945
|
+
const combinations = actionsWithPlans.length > 0 ? generateCombos(actionsWithPlans) : [{}];
|
|
946
|
+
const seenFinanceIssues = new Set();
|
|
947
|
+
for (const combo of combinations) {
|
|
948
|
+
const expandedActions = {};
|
|
949
|
+
for (const [aName, aDef] of Object.entries(instrument.actions)) {
|
|
950
|
+
const plan = combo[aName];
|
|
951
|
+
// Only an action that moves money through calls is replaced by its
|
|
952
|
+
// expanded leaves. Authored moves and steps always reach the oracle.
|
|
953
|
+
if (plan && (aDef.calls?.length ?? 0) > 0) {
|
|
954
|
+
const steps = [];
|
|
955
|
+
const moves = [];
|
|
956
|
+
for (const leaf of plan.leaves) {
|
|
957
|
+
if ("key" in leaf.step) {
|
|
958
|
+
moves.push(leaf.step);
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
steps.push(leaf.step);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
expandedActions[aName] = {
|
|
965
|
+
...aDef,
|
|
966
|
+
moves,
|
|
967
|
+
steps,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
expandedActions[aName] = aDef;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const financeInstrument = {
|
|
975
|
+
...instrument,
|
|
976
|
+
actions: expandedActions,
|
|
977
|
+
};
|
|
978
|
+
for (const finIssue of analyzeInstrumentFinance(financeInstrument)) {
|
|
979
|
+
const issueKey = `${finIssue.code}:${finIssue.path.join(".")}:${finIssue.message}`;
|
|
980
|
+
if (!seenFinanceIssues.has(issueKey)) {
|
|
981
|
+
seenFinanceIssues.add(issueKey);
|
|
982
|
+
add([...base, ...finIssue.path], finIssue.message, finIssue.code);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
890
986
|
}
|
|
891
987
|
validateAggregates(instrument, base, instruments, references, add);
|
|
892
988
|
}
|
|
989
|
+
function validatePiecePlan(instrument, base, references, add) {
|
|
990
|
+
const plan = instrument.piecePlan;
|
|
991
|
+
if (!plan)
|
|
992
|
+
return;
|
|
993
|
+
const planBase = [...base, "piecePlan"];
|
|
994
|
+
const mutableFields = new Set(instrument.update?.fields ?? []);
|
|
995
|
+
const allUpdatedFields = new Set(Object.values(instrument.actions).flatMap((a) => a.updates ?? []));
|
|
996
|
+
// Validate total field
|
|
997
|
+
const totalSchema = instrument.fields[plan.total];
|
|
998
|
+
if (!totalSchema ||
|
|
999
|
+
totalSchema.type !== "string" ||
|
|
1000
|
+
!isMoneySchema(totalSchema)) {
|
|
1001
|
+
add([...planBase, "total"], `piece plan total ${plan.total} must be a declared money field`, "UDL4002");
|
|
1002
|
+
}
|
|
1003
|
+
if (!instrument.required.includes(plan.total)) {
|
|
1004
|
+
add([...planBase, "total"], `piece plan total ${plan.total} must be required`, "UDL4002");
|
|
1005
|
+
}
|
|
1006
|
+
if (mutableFields.has(plan.total) || allUpdatedFields.has(plan.total)) {
|
|
1007
|
+
add([...planBase, "total"], `piece plan total ${plan.total} cannot be updated`, "UDL4002");
|
|
1008
|
+
}
|
|
1009
|
+
const pieceIds = new Set();
|
|
1010
|
+
const pieceAmountFields = [];
|
|
1011
|
+
plan.pieces.forEach((piece, index) => {
|
|
1012
|
+
const pieceBase = [...planBase, "pieces", index];
|
|
1013
|
+
if (pieceIds.has(piece.id)) {
|
|
1014
|
+
add([...pieceBase, "id"], `duplicate piece id ${piece.id} in piece plan`, "UDL4002");
|
|
1015
|
+
}
|
|
1016
|
+
pieceIds.add(piece.id);
|
|
1017
|
+
pieceAmountFields.push(piece.amount);
|
|
1018
|
+
const amountSchema = instrument.fields[piece.amount];
|
|
1019
|
+
if (!amountSchema ||
|
|
1020
|
+
amountSchema.type !== "string" ||
|
|
1021
|
+
!isMoneySchema(amountSchema)) {
|
|
1022
|
+
add([...pieceBase, "amount"], `piece amount ${piece.amount} must be a declared money field`, "UDL4002");
|
|
1023
|
+
}
|
|
1024
|
+
if (!instrument.required.includes(piece.amount)) {
|
|
1025
|
+
add([...pieceBase, "amount"], `piece amount ${piece.amount} must be required`, "UDL4002");
|
|
1026
|
+
}
|
|
1027
|
+
if (mutableFields.has(piece.amount) || allUpdatedFields.has(piece.amount)) {
|
|
1028
|
+
add([...pieceBase, "amount"], `piece amount ${piece.amount} cannot be updated`, "UDL4002");
|
|
1029
|
+
}
|
|
1030
|
+
const releaseSchema = instrument.fields[piece.release_to];
|
|
1031
|
+
if (!releaseSchema || !references.accepts(releaseSchema, "acct")) {
|
|
1032
|
+
add([...pieceBase, "release_to"], `piece release_to ${piece.release_to} must be a declared account field`, "UDL4002");
|
|
1033
|
+
}
|
|
1034
|
+
if (!instrument.required.includes(piece.release_to)) {
|
|
1035
|
+
add([...pieceBase, "release_to"], `piece release_to ${piece.release_to} must be required`, "UDL4002");
|
|
1036
|
+
}
|
|
1037
|
+
if (mutableFields.has(piece.release_to) ||
|
|
1038
|
+
allUpdatedFields.has(piece.release_to)) {
|
|
1039
|
+
add([...pieceBase, "release_to"], `piece release_to ${piece.release_to} cannot be updated`, "UDL4002");
|
|
1040
|
+
}
|
|
1041
|
+
const refundSchema = instrument.fields[piece.refund_to];
|
|
1042
|
+
if (!refundSchema || !references.accepts(refundSchema, "acct")) {
|
|
1043
|
+
add([...pieceBase, "refund_to"], `piece refund_to ${piece.refund_to} must be a declared account field`, "UDL4002");
|
|
1044
|
+
}
|
|
1045
|
+
if (!instrument.required.includes(piece.refund_to)) {
|
|
1046
|
+
add([...pieceBase, "refund_to"], `piece refund_to ${piece.refund_to} must be required`, "UDL4002");
|
|
1047
|
+
}
|
|
1048
|
+
if (mutableFields.has(piece.refund_to) ||
|
|
1049
|
+
allUpdatedFields.has(piece.refund_to)) {
|
|
1050
|
+
add([...pieceBase, "refund_to"], `piece refund_to ${piece.refund_to} cannot be updated`, "UDL4002");
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
// Check currency agreement
|
|
1054
|
+
const totalCurrency = totalSchema && typeof totalSchema["x-hyperscale-currency"] === "string"
|
|
1055
|
+
? totalSchema["x-hyperscale-currency"]
|
|
1056
|
+
: undefined;
|
|
1057
|
+
const pieceCurrencies = plan.pieces.map((p) => {
|
|
1058
|
+
const s = instrument.fields[p.amount];
|
|
1059
|
+
return s && typeof s["x-hyperscale-currency"] === "string"
|
|
1060
|
+
? s["x-hyperscale-currency"]
|
|
1061
|
+
: undefined;
|
|
1062
|
+
});
|
|
1063
|
+
const allCurrencies = [totalCurrency, ...pieceCurrencies];
|
|
1064
|
+
const definedCurrencies = allCurrencies.filter((c) => c !== undefined);
|
|
1065
|
+
if (definedCurrencies.length > 0) {
|
|
1066
|
+
const firstCurrency = definedCurrencies[0];
|
|
1067
|
+
if (definedCurrencies.length !== allCurrencies.length ||
|
|
1068
|
+
definedCurrencies.some((c) => c !== firstCurrency)) {
|
|
1069
|
+
add(planBase, `piece plan money fields must agree on one declared currency`, "UDL4002");
|
|
1070
|
+
}
|
|
1071
|
+
else if (!/^[A-Z]{3}$/.test(firstCurrency)) {
|
|
1072
|
+
add(planBase, `piece plan currency ${firstCurrency} must be a concrete ISO currency`, "UDL4002");
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
const concreteCurrency = (() => {
|
|
1077
|
+
const currencyFields = Object.entries(instrument.fields).filter(([, schema]) => isCurrencySchema(schema));
|
|
1078
|
+
if (currencyFields.length === 1) {
|
|
1079
|
+
const [fName, fSchema] = currencyFields[0];
|
|
1080
|
+
if (mutableFields.has(fName) || allUpdatedFields.has(fName)) {
|
|
1081
|
+
return undefined;
|
|
1082
|
+
}
|
|
1083
|
+
if (typeof fSchema.const === "string" &&
|
|
1084
|
+
/^[A-Z]{3}$/.test(fSchema.const)) {
|
|
1085
|
+
return fSchema.const;
|
|
1086
|
+
}
|
|
1087
|
+
if (Array.isArray(fSchema.enum) &&
|
|
1088
|
+
fSchema.enum.length === 1 &&
|
|
1089
|
+
typeof fSchema.enum[0] === "string" &&
|
|
1090
|
+
/^[A-Z]{3}$/.test(fSchema.enum[0])) {
|
|
1091
|
+
return fSchema.enum[0];
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return undefined;
|
|
1095
|
+
})();
|
|
1096
|
+
if (!concreteCurrency) {
|
|
1097
|
+
add(planBase, `piece plan requires an actual single concrete currency via x-hyperscale-currency tags or an immutable concrete instrument currency declaration`, "UDL4002");
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// Disallow updates of the instance currency field
|
|
1101
|
+
const instanceCurrencyFields = Object.entries(instrument.fields)
|
|
1102
|
+
.filter(([, schema]) => isCurrencySchema(schema))
|
|
1103
|
+
.map(([field]) => field);
|
|
1104
|
+
for (const cField of instanceCurrencyFields) {
|
|
1105
|
+
if (mutableFields.has(cField) || allUpdatedFields.has(cField)) {
|
|
1106
|
+
add([...planBase, "currency"], `piece plan instance currency field ${cField} cannot be updated`, "UDL4002");
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
// Check fixed amounts against total
|
|
1110
|
+
const totalVal = getFixedMoneyValue(totalSchema);
|
|
1111
|
+
const pieceVals = plan.pieces.map((p) => getFixedMoneyValue(instrument.fields[p.amount]));
|
|
1112
|
+
if (totalVal !== undefined &&
|
|
1113
|
+
pieceVals.every((v) => v !== undefined)) {
|
|
1114
|
+
const sum = pieceVals.reduce((acc, v) => acc + v, 0n);
|
|
1115
|
+
if (sum !== totalVal) {
|
|
1116
|
+
add([...planBase, "total"], `sum of fixed piece amounts (${sum}) does not equal plan total (${totalVal})`, "UDL4002");
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
// Check partition
|
|
1120
|
+
const isSingletonTotal = plan.pieces.length === 1 && plan.pieces[0].amount === plan.total;
|
|
1121
|
+
if (new Set(pieceAmountFields).size !== pieceAmountFields.length) {
|
|
1122
|
+
add([...planBase, "pieces"], `piece amount fields must be distinct: [${pieceAmountFields.join(", ")}]`, "UDL4002");
|
|
1123
|
+
}
|
|
1124
|
+
else if (!isSingletonTotal) {
|
|
1125
|
+
const planAmounts = [...pieceAmountFields].sort();
|
|
1126
|
+
const matchingPartition = (instrument.partitions ?? []).find((p) => {
|
|
1127
|
+
if (p.totalField !== plan.total)
|
|
1128
|
+
return false;
|
|
1129
|
+
const partAmounts = [...p.pieceFields].sort();
|
|
1130
|
+
return (partAmounts.length === planAmounts.length &&
|
|
1131
|
+
partAmounts.every((f, i) => f === planAmounts[i]));
|
|
1132
|
+
});
|
|
1133
|
+
if (!matchingPartition) {
|
|
1134
|
+
add(planBase, `piece plan must match a declared partition with totalField ${plan.total} and pieceFields [${pieceAmountFields.join(", ")}]`, "UDL4002");
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
// Check orders (UDL5013)
|
|
1138
|
+
const validateOrder = (order, orderName, exactSet) => {
|
|
1139
|
+
const orderPath = [...planBase, orderName];
|
|
1140
|
+
if (new Set(order).size !== order.length) {
|
|
1141
|
+
add(orderPath, `${orderName} contains duplicate piece ids`, "UDL5013");
|
|
1142
|
+
}
|
|
1143
|
+
for (const id of order) {
|
|
1144
|
+
if (!pieceIds.has(id)) {
|
|
1145
|
+
add(orderPath, `${orderName} contains undeclared piece id ${id}`, "UDL5013");
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (exactSet) {
|
|
1149
|
+
if (order.length !== pieceIds.size) {
|
|
1150
|
+
add(orderPath, `${orderName} must cover every declared piece id`, "UDL5013");
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
validateOrder(plan.fund_order, "fund_order", true);
|
|
1155
|
+
validateOrder(plan.release_order, "release_order", false);
|
|
1156
|
+
validateOrder(plan.refund_order, "refund_order", false);
|
|
1157
|
+
validateOrder(plan.unfund_order, "unfund_order", true);
|
|
1158
|
+
if (plan.fund_order.length === plan.unfund_order.length &&
|
|
1159
|
+
!plan.unfund_order.every((id, idx) => id === plan.fund_order[plan.fund_order.length - 1 - idx])) {
|
|
1160
|
+
add([...planBase, "unfund_order"], `unfund_order must be the reverse of fund_order`, "UDL5013");
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function getFixedMoneyValue(schema) {
|
|
1164
|
+
if (!schema || typeof schema !== "object")
|
|
1165
|
+
return undefined;
|
|
1166
|
+
const s = schema;
|
|
1167
|
+
if (typeof s.const === "string" && /^[0-9]+$/.test(s.const)) {
|
|
1168
|
+
return BigInt(s.const);
|
|
1169
|
+
}
|
|
1170
|
+
if (Array.isArray(s.enum) &&
|
|
1171
|
+
s.enum.length === 1 &&
|
|
1172
|
+
typeof s.enum[0] === "string" &&
|
|
1173
|
+
/^[0-9]+$/.test(s.enum[0])) {
|
|
1174
|
+
return BigInt(s.enum[0]);
|
|
1175
|
+
}
|
|
1176
|
+
return undefined;
|
|
1177
|
+
}
|
|
893
1178
|
function validateFeeRules(instrument, base, references, add) {
|
|
894
1179
|
const feeRules = instrument.feeRules ?? [];
|
|
895
1180
|
if (feeRules.length === 0)
|