@hyperscale0/hsx 1.0.265 → 1.0.284
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 +36 -5
- package/README.md +2 -1
- package/SECURITY.md +10 -21
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +108 -14
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +3 -3
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +7 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +40 -31
- package/examples/library.hsx +7 -1
- package/examples/rental-deposit.hsx +5 -12
- package/examples/serviced.hsx +7 -1
- package/package.json +4 -4
- package/src/compile.ts +138 -19
- package/src/examples-bundle.ts +3 -3
- package/src/std-bundle.ts +7 -6
- package/std/booking.hsx +8 -3
- package/std/collections.hsx +1 -1
- package/std/escrow.hsx +16 -29
- package/std/financing.hsx +21 -48
- package/std/money.hsx +6 -8
- package/std/purchase.hsx +89 -0
- package/std/reporting.hsx +1 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Since 1.0.123
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Releases after 1.0.123 shipped without per-release entries. This entry
|
|
6
|
+
summarises what changed in the language, the compiler and the bundled programs
|
|
7
|
+
since then.
|
|
8
|
+
|
|
9
|
+
Added:
|
|
10
|
+
|
|
11
|
+
- Eleven neutral archetype programs in `bases/` and the browser export
|
|
6
12
|
`@hyperscale0/hsx/bases`. Generation and preparation bundle their source and
|
|
7
13
|
metadata. Package checks enforce compilation, coverage and neutral content.
|
|
14
|
+
- The `booking` std header, with deposit, balance and cancellation windows.
|
|
15
|
+
- `financing.installments` prices a plan as `amortizing` or `flat_total`, and
|
|
16
|
+
`refund_within` (default 30d) bounds refunds after a plan is paid.
|
|
17
|
+
- `wallet.balance` takes a `topup_fee`; `escrow.hold` takes `deliver_within`.
|
|
18
|
+
- Selections accept `overlaps: { start, end, from, until }` to match rows whose
|
|
19
|
+
date period meets a range.
|
|
20
|
+
- Program instruments take party parameters, and a `fee` on a move of an action
|
|
21
|
+
input is calculated when that action runs.
|
|
22
|
+
- Every agreement reference is an optional creation input on an exposed create.
|
|
23
|
+
- Customers can start the std agreements whose first party can be a customer on
|
|
24
|
+
a record they do not own, through the object's `entryActions`.
|
|
25
|
+
- `economics` can name a party reached through a reference.
|
|
26
|
+
|
|
27
|
+
Changed:
|
|
8
28
|
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
29
|
+
- All 15 examples are Saudi businesses with SAR prices, explicit fees and VAT
|
|
30
|
+
treatment, named business counterparties, list columns, and payment, refund,
|
|
31
|
+
dispute and overdue paths.
|
|
32
|
+
- A required instrument value with no fixed value becomes create `input` on the
|
|
33
|
+
agreement. Only an explicit `rename` shares it as record metadata.
|
|
34
|
+
- Record actions stay private until the program exposes them.
|
|
35
|
+
- Std actions name an accountable actor, and std money pulled from a customer
|
|
36
|
+
needs that customer's authority.
|
|
37
|
+
- A split binds declared business and subject-role recipients itself, and a
|
|
38
|
+
share the payer keeps stays in the source account with no self-transfer.
|
|
39
|
+
- Late charges and recovery costs are bounded across the whole plan, and an
|
|
40
|
+
insurance cover's claim limit is shared across every claim attachment.
|
|
41
|
+
- Financing and escrow plans have exits for returned, undelivered and paid-off
|
|
42
|
+
goods, and a paid-off plan closes once its refund window passes.
|
|
12
43
|
|
|
13
44
|
## 1.0.123
|
|
14
45
|
|
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ npx hsx cost rental.hsx
|
|
|
20
20
|
|
|
21
21
|
Start with the [first program and rental guide](docs/README.md). Save the
|
|
22
22
|
[rental source](examples/rental-deposit.hsx) as `rental.hsx` for these commands.
|
|
23
|
-
It holds 1,000 SAR, charges
|
|
23
|
+
It holds 1,000 SAR, charges one 50 SAR late fee plus 7.50 SAR VAT, and returns
|
|
24
|
+
the remainder.
|
|
24
25
|
The guide explains the fixed business binding and the host needed to execute it.
|
|
25
26
|
|
|
26
27
|
`npx hsx headers --json` prints every std instrument and its tunables from the
|
package/SECURITY.md
CHANGED
|
@@ -9,9 +9,8 @@ security-sensitive belongs in an issue, a pull request, a discussion, or a
|
|
|
9
9
|
commit message.
|
|
10
10
|
|
|
11
11
|
A report we can act on names the affected version and gives us something to
|
|
12
|
-
run: the `.hsx` source, and what you expected the compiler to do with it.
|
|
13
|
-
|
|
14
|
-
into the fix.
|
|
12
|
+
run: the `.hsx` source, and what you expected the compiler to do with it. A
|
|
13
|
+
failing spec is the most useful shape; it goes straight into the fix.
|
|
15
14
|
|
|
16
15
|
## What counts
|
|
17
16
|
|
|
@@ -23,22 +22,12 @@ executes, so the interesting failures are the ones a source file can cause:
|
|
|
23
22
|
whose fees move money the source did not authorise. A `valid` verdict on an
|
|
24
23
|
unsound program is the worst bug this package can have.
|
|
25
24
|
- **A program that makes the lexer, parser, checker, or lowering burn
|
|
26
|
-
unbounded time or memory.** The
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
unterminated strings, unclosed comments, single-character invalid runs, one
|
|
33
|
-
quarter-megabyte invalid run, digit and percent soup, and interleaved open
|
|
34
|
-
delimiters. Across a 64x range of input size its cost per byte grew at most
|
|
35
|
-
1.44x, where a quadratic scanner would grow 64x. The worst admitted file
|
|
36
|
-
costs 9.8 ms to lex and 21.1 ms to compile. A payload that makes any stage
|
|
37
|
-
cost meaningfully more than that per byte is a finding worth reporting.
|
|
38
|
-
|
|
39
|
-
- **A crash.** Parsing and checking are total by design: they return
|
|
40
|
-
diagnostics, they never throw. A source file that throws out of `compile()`
|
|
41
|
-
is a bug even when the program is nonsense.
|
|
25
|
+
unbounded time or memory.** The parser refuses source over 256 KiB with
|
|
26
|
+
`HSX1004`, and block or expression nesting deeper than 32. A way around
|
|
27
|
+
either, or a stage whose cost grows faster than its input, is a finding
|
|
28
|
+
worth reporting.
|
|
29
|
+
- **A crash.** `compile()` reports invalid source as diagnostics. A source
|
|
30
|
+
file that makes it throw is a bug even when the program is nonsense.
|
|
42
31
|
- **A diagnostic that reports the wrong source coordinates**, which sends an
|
|
43
32
|
author to fix the wrong line.
|
|
44
33
|
|
|
@@ -47,8 +36,8 @@ anything that requires already controlling the machine running it.
|
|
|
47
36
|
|
|
48
37
|
## Supported versions
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
Fixes land in the newest published `1.0.N` release. Older releases get no
|
|
40
|
+
backports.
|
|
52
41
|
|
|
53
42
|
## Disclosure
|
|
54
43
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAiBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAiBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAmVD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CA8hIf"}
|
package/dist/src/compile.js
CHANGED
|
@@ -135,6 +135,24 @@ const camel = (name) => name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
|
135
135
|
const automatic = (actor) => actor === "clock" ||
|
|
136
136
|
(actor !== null && typeof actor === "object" && "parent" in actor);
|
|
137
137
|
const title = (name) => name[0].toUpperCase() + name.slice(1).replaceAll("_", " ");
|
|
138
|
+
/** Parties an instrument names itself: account owners, actors, move endpoints. */
|
|
139
|
+
function namedParties(instrument) {
|
|
140
|
+
const names = instrument.fields.flatMap((field) => field.type === "account" && typeof field.owner === "string"
|
|
141
|
+
? [field.owner]
|
|
142
|
+
: []);
|
|
143
|
+
for (const action of Object.values(instrument.actions)) {
|
|
144
|
+
if (typeof action.actor === "object" && "party" in action.actor)
|
|
145
|
+
names.push(action.actor.party);
|
|
146
|
+
for (const move of action.moves) {
|
|
147
|
+
if (move.economics)
|
|
148
|
+
names.push(move.economics.sourceParty);
|
|
149
|
+
for (const endpoint of "from" in move ? [move.from, move.to] : [])
|
|
150
|
+
if (endpoint.startsWith("party."))
|
|
151
|
+
names.push(endpoint.slice(6));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return names;
|
|
155
|
+
}
|
|
138
156
|
function entries(block) {
|
|
139
157
|
const map = new Map();
|
|
140
158
|
for (const row of block.entries) {
|
|
@@ -174,6 +192,8 @@ function text(expr) {
|
|
|
174
192
|
return fail(expr, "expected a name or literal", "write a single name or literal");
|
|
175
193
|
}
|
|
176
194
|
function decimal(raw, scale, expr) {
|
|
195
|
+
if (raw.startsWith("-"))
|
|
196
|
+
fail(expr, "literal cannot be negative", "write a positive value; a move's from and to set its direction");
|
|
177
197
|
const [whole = "", fraction = ""] = raw.split(".");
|
|
178
198
|
if (!/^\d+$/.test(whole) || fraction.length > scale)
|
|
179
199
|
fail(expr, `literal has more than ${scale} decimal places`, `write at most ${scale} decimal places`);
|
|
@@ -243,6 +263,8 @@ function literal(expr, expectedType) {
|
|
|
243
263
|
return expr.value;
|
|
244
264
|
if (expr.kind === "name" && ["true", "false"].includes(expr.value))
|
|
245
265
|
return expr.value === "true";
|
|
266
|
+
if (expr.kind === "name" && expr.value === "runtime")
|
|
267
|
+
fail(expr, "this value is fixed when the program compiles, so it cannot be runtime", "write a literal value");
|
|
246
268
|
return fail(expr, "expected a literal", "write a typed constant");
|
|
247
269
|
}
|
|
248
270
|
export function compile(source, options = {}) {
|
|
@@ -990,7 +1012,7 @@ export function compile(source, options = {}) {
|
|
|
990
1012
|
if (bounds &&
|
|
991
1013
|
(BigInt(String(value)) < BigInt(bounds.minimum) ||
|
|
992
1014
|
BigInt(String(value)) > BigInt(bounds.maximum)))
|
|
993
|
-
fail(v,
|
|
1015
|
+
fail(v, `value is outside ${bounds.minimum}..${bounds.maximum}`, "choose a value inside the tunable's bounds");
|
|
994
1016
|
}
|
|
995
1017
|
catch (error) {
|
|
996
1018
|
if (error instanceof CompileFailure)
|
|
@@ -1039,7 +1061,7 @@ export function compile(source, options = {}) {
|
|
|
1039
1061
|
const left = environment.get(constraint.key), right = environment.get(text(rule.args[0]));
|
|
1040
1062
|
if (!left || !right)
|
|
1041
1063
|
fail(rule, "constraint needs two declared tunables", "name the compared tunables");
|
|
1042
|
-
const numericKind = (expr) => expr.kind === "name" && /^P/.test(expr.value)
|
|
1064
|
+
const numericKind = (expr) => expr.kind === "name" && /^P(?:\d|T)/.test(expr.value)
|
|
1043
1065
|
? "duration"
|
|
1044
1066
|
: expr.kind;
|
|
1045
1067
|
if (!["money", "number", "percent", "duration"].includes(numericKind(left)) ||
|
|
@@ -1106,6 +1128,7 @@ export function compile(source, options = {}) {
|
|
|
1106
1128
|
return lowered;
|
|
1107
1129
|
};
|
|
1108
1130
|
const fields = [];
|
|
1131
|
+
const boundaryReceipts = new Map();
|
|
1109
1132
|
const calculations = [];
|
|
1110
1133
|
let currentAction;
|
|
1111
1134
|
let currentActionName;
|
|
@@ -1486,6 +1509,38 @@ export function compile(source, options = {}) {
|
|
|
1486
1509
|
actions: Object.create(null),
|
|
1487
1510
|
actionOrder: [],
|
|
1488
1511
|
};
|
|
1512
|
+
const actionTargets = new Set();
|
|
1513
|
+
const collectTargets = (block) => {
|
|
1514
|
+
for (const entry of block.entries)
|
|
1515
|
+
if (entry.key.startsWith("when "))
|
|
1516
|
+
collectTargets(asBlock(entry.value));
|
|
1517
|
+
else if (entry.key === "calculate")
|
|
1518
|
+
for (const item of entry.value.kind === "list"
|
|
1519
|
+
? entry.value.items
|
|
1520
|
+
: [entry.value]) {
|
|
1521
|
+
const target = item.kind === "block" ? entries(item).get("target") : undefined;
|
|
1522
|
+
if (target)
|
|
1523
|
+
actionTargets.add(String(data(target)));
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
for (const row of decl.body.entries)
|
|
1527
|
+
if (row.key.startsWith("action "))
|
|
1528
|
+
collectTargets(asBlock(row.value));
|
|
1529
|
+
// Instrument calculations run before every action, create included, so
|
|
1530
|
+
// a fee or split calculates there only on an amount held from create.
|
|
1531
|
+
const heldFromCreate = (amount) => {
|
|
1532
|
+
if (!("field" in amount))
|
|
1533
|
+
return true;
|
|
1534
|
+
const [root, name, ...rest] = amount.field.split(".");
|
|
1535
|
+
const field = fields.find((item) => item.name === name);
|
|
1536
|
+
return (root === "self" &&
|
|
1537
|
+
!rest.length &&
|
|
1538
|
+
!!field &&
|
|
1539
|
+
!actionTargets.has(name) &&
|
|
1540
|
+
(!field.optional ||
|
|
1541
|
+
"value" in field ||
|
|
1542
|
+
calculations.some((node) => node.target === name)));
|
|
1543
|
+
};
|
|
1489
1544
|
for (const row of decl.body.entries.filter((e) => e.key.startsWith("action "))) {
|
|
1490
1545
|
const name = row.key.slice(7);
|
|
1491
1546
|
const selected = (block) => ({
|
|
@@ -1744,7 +1799,7 @@ export function compile(source, options = {}) {
|
|
|
1744
1799
|
...(actionSubject ? { subject: actionSubject } : {}),
|
|
1745
1800
|
};
|
|
1746
1801
|
authored.set(a, row.value);
|
|
1747
|
-
const
|
|
1802
|
+
const moveCalculations = [];
|
|
1748
1803
|
for (const binding of boundaryBindings)
|
|
1749
1804
|
if (!a.subject?.adapters.find((entry) => entry.binding === binding)
|
|
1750
1805
|
?.snapshot)
|
|
@@ -1775,8 +1830,20 @@ export function compile(source, options = {}) {
|
|
|
1775
1830
|
const op = parts.has("operation")
|
|
1776
1831
|
? String(data(parts.get("operation")))
|
|
1777
1832
|
: "internal_transfer.create";
|
|
1778
|
-
if (op === "internal_transfer.reserve" && !parts.has("capture"))
|
|
1779
|
-
|
|
1833
|
+
if (op === "internal_transfer.reserve" && !parts.has("capture")) {
|
|
1834
|
+
if (!parts.has("boundary"))
|
|
1835
|
+
fail(move, "reserve needs a receipt field", "add capture: receipt_name");
|
|
1836
|
+
const capture = `${name}Receipt`;
|
|
1837
|
+
if (fields.some((field) => field.name === capture))
|
|
1838
|
+
fail(move, "boundary receipt name is already used", "name each reservation with an explicit capture");
|
|
1839
|
+
fields.push({ name: capture, type: "text", optional: true });
|
|
1840
|
+
boundaryReceipts.set(name, capture);
|
|
1841
|
+
parts.set("capture", {
|
|
1842
|
+
kind: "text",
|
|
1843
|
+
value: capture,
|
|
1844
|
+
span: move.span,
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1780
1847
|
if ((op === "internal_transfer.post" ||
|
|
1781
1848
|
op === "internal_transfer.void") &&
|
|
1782
1849
|
!parts.has("transfer"))
|
|
@@ -1890,6 +1957,7 @@ export function compile(source, options = {}) {
|
|
|
1890
1957
|
fail(move, "split needs 1 to 64 recipients", "name the receiving parties and percentages");
|
|
1891
1958
|
let total = 0;
|
|
1892
1959
|
const pieces = [];
|
|
1960
|
+
const perAction = !heldFromCreate(val(amount));
|
|
1893
1961
|
for (const [index, [party, rate]] of shares.entries()) {
|
|
1894
1962
|
const recipient = resolve({
|
|
1895
1963
|
kind: "name",
|
|
@@ -1920,8 +1988,12 @@ export function compile(source, options = {}) {
|
|
|
1920
1988
|
const target = `${name}_${key}_share${index + 1}`;
|
|
1921
1989
|
if (fields.some((f) => f.name === target))
|
|
1922
1990
|
fail(move, "split field name conflicts", "rename the move key");
|
|
1923
|
-
fields.push({
|
|
1924
|
-
|
|
1991
|
+
fields.push({
|
|
1992
|
+
name: target,
|
|
1993
|
+
type: "money",
|
|
1994
|
+
...(perAction ? { optional: true } : {}),
|
|
1995
|
+
});
|
|
1996
|
+
(perAction ? moveCalculations : calculations).push(index === shares.length - 1 && pieces.length
|
|
1925
1997
|
? {
|
|
1926
1998
|
target,
|
|
1927
1999
|
op: "subtract",
|
|
@@ -1993,14 +2065,18 @@ export function compile(source, options = {}) {
|
|
|
1993
2065
|
if (terms.has("seller") === terms.has("buyer"))
|
|
1994
2066
|
fail(fee, "fee needs exactly one paying party", "write seller: 1% or buyer: 1%");
|
|
1995
2067
|
const quoted = terms.get(paidBy);
|
|
2068
|
+
// An account the agreement owns holds only what was moved
|
|
2069
|
+
// into it, so a charge on top has nothing to come from.
|
|
2070
|
+
if (paidBy === "buyer" &&
|
|
2071
|
+
fields.some((field) => `self.${field.name}` === transfer.from &&
|
|
2072
|
+
field.type === "account" &&
|
|
2073
|
+
field.owner === "self"))
|
|
2074
|
+
fail(quoted, `a buyer fee cannot come from \`${transfer.from.slice("self.".length)}\`, which holds only the moved amount`, "use a seller fee, which comes out of the held amount");
|
|
1996
2075
|
const rate = quoted.kind === "capped" ? quoted.rate : quoted;
|
|
1997
2076
|
if (rate.kind !== "percent")
|
|
1998
2077
|
fail(rate, "fee rate needs a percentage", "write a percentage such as 1%");
|
|
1999
2078
|
const prefix = `${name}_${key}`;
|
|
2000
|
-
|
|
2001
|
-
// input exists, so a fee on an input amount runs with its action.
|
|
2002
|
-
const perAction = "field" in transfer.amount &&
|
|
2003
|
-
transfer.amount.field.startsWith("input.");
|
|
2079
|
+
const perAction = !heldFromCreate(transfer.amount);
|
|
2004
2080
|
const derived = (suffix, label, calculation) => {
|
|
2005
2081
|
const target = prefix + "_" + suffix;
|
|
2006
2082
|
if (fields.some((f) => f.name === target))
|
|
@@ -2011,7 +2087,7 @@ export function compile(source, options = {}) {
|
|
|
2011
2087
|
label,
|
|
2012
2088
|
...(perAction ? { optional: true } : {}),
|
|
2013
2089
|
});
|
|
2014
|
-
(perAction ?
|
|
2090
|
+
(perAction ? moveCalculations : calculations).push({
|
|
2015
2091
|
...calculation,
|
|
2016
2092
|
target,
|
|
2017
2093
|
});
|
|
@@ -2120,8 +2196,8 @@ export function compile(source, options = {}) {
|
|
|
2120
2196
|
else
|
|
2121
2197
|
a[key] = data(expr);
|
|
2122
2198
|
}
|
|
2123
|
-
if (
|
|
2124
|
-
a.calculate = [...(a.calculate ?? []), ...
|
|
2199
|
+
if (moveCalculations.length)
|
|
2200
|
+
a.calculate = [...(a.calculate ?? []), ...moveCalculations];
|
|
2125
2201
|
if (attachmentInfo) {
|
|
2126
2202
|
if (attachmentInfo.exposed.has(name)) {
|
|
2127
2203
|
if (automatic(a.actor))
|
|
@@ -2199,6 +2275,13 @@ export function compile(source, options = {}) {
|
|
|
2199
2275
|
inst.actions[name] = a;
|
|
2200
2276
|
inst.actionOrder.push(name);
|
|
2201
2277
|
}
|
|
2278
|
+
for (const action of Object.values(inst.actions))
|
|
2279
|
+
for (const move of action.moves)
|
|
2280
|
+
if ("transfer" in move) {
|
|
2281
|
+
const receipt = boundaryReceipts.get(move.transfer.replace(/^self\./, ""));
|
|
2282
|
+
if (receipt)
|
|
2283
|
+
move.transfer = `self.${receipt}`;
|
|
2284
|
+
}
|
|
2202
2285
|
if (attachmentInfo && !attachmentInfo.child)
|
|
2203
2286
|
applyAttachmentEconomics(document, inst, attachmentInfo.parties, attachmentInfo.economics, data);
|
|
2204
2287
|
for (const key of [
|
|
@@ -2403,6 +2486,17 @@ export function compile(source, options = {}) {
|
|
|
2403
2486
|
bindingDiagnostics.push(error.diagnostic);
|
|
2404
2487
|
continue;
|
|
2405
2488
|
}
|
|
2489
|
+
// A business the instrument names itself binds like a parameter, so
|
|
2490
|
+
// Build asks for it and the engine resolves its account and principal.
|
|
2491
|
+
// programOperator and programTax are the company's own accounts.
|
|
2492
|
+
for (const party of document.instruments
|
|
2493
|
+
.slice(firstAttachedInstrument)
|
|
2494
|
+
.flatMap(namedParties))
|
|
2495
|
+
if (document.parties[party]?.kind === "business" &&
|
|
2496
|
+
party !== "programOperator" &&
|
|
2497
|
+
party !== "programTax" &&
|
|
2498
|
+
!Object.values(parties).some((binding) => "party" in binding && binding.party === party))
|
|
2499
|
+
parties[party] ??= { party };
|
|
2406
2500
|
const attachedInst = document.instruments.find((i) => i.id === instId);
|
|
2407
2501
|
for (const createdInst of document.instruments
|
|
2408
2502
|
.slice(firstAttachedInstrument)
|