metered-protocol 1.0.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/LICENSE +21 -0
- package/README.md +103 -0
- package/contracts/metered.ag +135 -0
- package/contracts/metered_ag.tests.json +1226 -0
- package/contracts/metered_session.sil +237 -0
- package/contracts/metered_session.tests.json +1113 -0
- package/dist/contracts/metered.ag +135 -0
- package/dist/contracts/metered_ag.tests.json +1226 -0
- package/dist/contracts/metered_session.sil +237 -0
- package/dist/contracts/metered_session.tests.json +1113 -0
- package/dist/src/bias.d.ts +48 -0
- package/dist/src/bias.d.ts.map +1 -0
- package/dist/src/bias.js +43 -0
- package/dist/src/bias.js.map +1 -0
- package/dist/src/checkpoint.d.ts +57 -0
- package/dist/src/checkpoint.d.ts.map +1 -0
- package/dist/src/checkpoint.js +84 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/deadline.d.ts +71 -0
- package/dist/src/deadline.d.ts.map +1 -0
- package/dist/src/deadline.js +77 -0
- package/dist/src/deadline.js.map +1 -0
- package/dist/src/encoding.d.ts +72 -0
- package/dist/src/encoding.d.ts.map +1 -0
- package/dist/src/encoding.js +187 -0
- package/dist/src/encoding.js.map +1 -0
- package/dist/src/history.d.ts +33 -0
- package/dist/src/history.d.ts.map +1 -0
- package/dist/src/history.js +38 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/http/buyer.d.ts +57 -0
- package/dist/src/http/buyer.d.ts.map +1 -0
- package/dist/src/http/buyer.js +151 -0
- package/dist/src/http/buyer.js.map +1 -0
- package/dist/src/http/client.d.ts +55 -0
- package/dist/src/http/client.d.ts.map +1 -0
- package/dist/src/http/client.js +83 -0
- package/dist/src/http/client.js.map +1 -0
- package/dist/src/http/harness.d.ts +18 -0
- package/dist/src/http/harness.d.ts.map +1 -0
- package/dist/src/http/harness.js +16 -0
- package/dist/src/http/harness.js.map +1 -0
- package/dist/src/http/protocol.d.ts +84 -0
- package/dist/src/http/protocol.d.ts.map +1 -0
- package/dist/src/http/protocol.js +22 -0
- package/dist/src/http/protocol.js.map +1 -0
- package/dist/src/http/provider.d.ts +123 -0
- package/dist/src/http/provider.d.ts.map +1 -0
- package/dist/src/http/provider.js +197 -0
- package/dist/src/http/provider.js.map +1 -0
- package/dist/src/http/serve.d.ts +34 -0
- package/dist/src/http/serve.d.ts.map +1 -0
- package/dist/src/http/serve.js +171 -0
- package/dist/src/http/serve.js.map +1 -0
- package/dist/src/http/service.d.ts +86 -0
- package/dist/src/http/service.d.ts.map +1 -0
- package/dist/src/http/service.js +161 -0
- package/dist/src/http/service.js.map +1 -0
- package/dist/src/index.d.ts +68 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +62 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/meter.d.ts +39 -0
- package/dist/src/meter.d.ts.map +1 -0
- package/dist/src/meter.js +100 -0
- package/dist/src/meter.js.map +1 -0
- package/dist/src/offer.d.ts +6 -0
- package/dist/src/offer.d.ts.map +1 -0
- package/dist/src/offer.js +92 -0
- package/dist/src/offer.js.map +1 -0
- package/dist/src/reconcile.d.ts +18 -0
- package/dist/src/reconcile.d.ts.map +1 -0
- package/dist/src/reconcile.js +101 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/reservation.d.ts +51 -0
- package/dist/src/reservation.d.ts.map +1 -0
- package/dist/src/reservation.js +124 -0
- package/dist/src/reservation.js.map +1 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.d.ts.map +1 -0
- package/dist/src/session.js +92 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/signer.d.ts +31 -0
- package/dist/src/signer.d.ts.map +1 -0
- package/dist/src/signer.js +57 -0
- package/dist/src/signer.js.map +1 -0
- package/dist/src/store.d.ts +30 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +120 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/types.d.ts +80 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tools/anchor-cli.d.ts +2 -0
- package/dist/tools/anchor-cli.d.ts.map +1 -0
- package/dist/tools/anchor-cli.js +101 -0
- package/dist/tools/anchor-cli.js.map +1 -0
- package/dist/tools/anchor.d.ts +68 -0
- package/dist/tools/anchor.d.ts.map +1 -0
- package/dist/tools/anchor.js +136 -0
- package/dist/tools/anchor.js.map +1 -0
- package/dist/tools/chain.d.ts +18 -0
- package/dist/tools/chain.d.ts.map +1 -0
- package/dist/tools/chain.js +18 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/conformance.d.ts +2 -0
- package/dist/tools/conformance.d.ts.map +1 -0
- package/dist/tools/conformance.js +220 -0
- package/dist/tools/conformance.js.map +1 -0
- package/dist/tools/contracts.d.ts +2 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +68 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/covenant-binding.d.ts +40 -0
- package/dist/tools/covenant-binding.d.ts.map +1 -0
- package/dist/tools/covenant-binding.js +60 -0
- package/dist/tools/covenant-binding.js.map +1 -0
- package/dist/tools/covenant-profile.d.ts +41 -0
- package/dist/tools/covenant-profile.d.ts.map +1 -0
- package/dist/tools/covenant-profile.js +74 -0
- package/dist/tools/covenant-profile.js.map +1 -0
- package/dist/tools/covenant.d.ts +30 -0
- package/dist/tools/covenant.d.ts.map +1 -0
- package/dist/tools/covenant.js +54 -0
- package/dist/tools/covenant.js.map +1 -0
- package/dist/tools/demo-report.d.ts +24 -0
- package/dist/tools/demo-report.d.ts.map +1 -0
- package/dist/tools/demo-report.js +40 -0
- package/dist/tools/demo-report.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.d.ts.map +1 -0
- package/dist/tools/demo.js +204 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/dust-map.d.ts +2 -0
- package/dist/tools/dust-map.d.ts.map +1 -0
- package/dist/tools/dust-map.js +63 -0
- package/dist/tools/dust-map.js.map +1 -0
- package/dist/tools/expire-cases.d.ts +4 -0
- package/dist/tools/expire-cases.d.ts.map +1 -0
- package/dist/tools/expire-cases.js +211 -0
- package/dist/tools/expire-cases.js.map +1 -0
- package/dist/tools/fee-check.d.ts +2 -0
- package/dist/tools/fee-check.d.ts.map +1 -0
- package/dist/tools/fee-check.js +127 -0
- package/dist/tools/fee-check.js.map +1 -0
- package/dist/tools/fixtures.d.ts +39 -0
- package/dist/tools/fixtures.d.ts.map +1 -0
- package/dist/tools/fixtures.js +31 -0
- package/dist/tools/fixtures.js.map +1 -0
- package/dist/tools/gen-contract-tests.d.ts +2 -0
- package/dist/tools/gen-contract-tests.d.ts.map +1 -0
- package/dist/tools/gen-contract-tests.js +170 -0
- package/dist/tools/gen-contract-tests.js.map +1 -0
- package/dist/tools/kaspa.d.ts +13 -0
- package/dist/tools/kaspa.d.ts.map +1 -0
- package/dist/tools/kaspa.js +76 -0
- package/dist/tools/kaspa.js.map +1 -0
- package/dist/tools/live-deadline.d.ts +2 -0
- package/dist/tools/live-deadline.d.ts.map +1 -0
- package/dist/tools/live-deadline.js +122 -0
- package/dist/tools/live-deadline.js.map +1 -0
- package/dist/tools/live-expire.d.ts +2 -0
- package/dist/tools/live-expire.d.ts.map +1 -0
- package/dist/tools/live-expire.js +127 -0
- package/dist/tools/live-expire.js.map +1 -0
- package/dist/tools/live-settle.d.ts +2 -0
- package/dist/tools/live-settle.d.ts.map +1 -0
- package/dist/tools/live-settle.js +226 -0
- package/dist/tools/live-settle.js.map +1 -0
- package/dist/tools/live-steps.d.ts +63 -0
- package/dist/tools/live-steps.d.ts.map +1 -0
- package/dist/tools/live-steps.js +155 -0
- package/dist/tools/live-steps.js.map +1 -0
- package/dist/tools/model.d.ts +32 -0
- package/dist/tools/model.d.ts.map +1 -0
- package/dist/tools/model.js +99 -0
- package/dist/tools/model.js.map +1 -0
- package/dist/tools/send.d.ts +2 -0
- package/dist/tools/send.d.ts.map +1 -0
- package/dist/tools/send.js +41 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/tools/session-chain.d.ts +69 -0
- package/dist/tools/session-chain.d.ts.map +1 -0
- package/dist/tools/session-chain.js +113 -0
- package/dist/tools/session-chain.js.map +1 -0
- package/dist/tools/sighash.d.ts +9 -0
- package/dist/tools/sighash.d.ts.map +1 -0
- package/dist/tools/sighash.js +68 -0
- package/dist/tools/sighash.js.map +1 -0
- package/dist/tools/sigscript-check.d.ts +2 -0
- package/dist/tools/sigscript-check.d.ts.map +1 -0
- package/dist/tools/sigscript-check.js +113 -0
- package/dist/tools/sigscript-check.js.map +1 -0
- package/dist/tools/sigscript.d.ts +61 -0
- package/dist/tools/sigscript.d.ts.map +1 -0
- package/dist/tools/sigscript.js +70 -0
- package/dist/tools/sigscript.js.map +1 -0
- package/dist/tools/state-layout.d.ts +2 -0
- package/dist/tools/state-layout.d.ts.map +1 -0
- package/dist/tools/state-layout.js +57 -0
- package/dist/tools/state-layout.js.map +1 -0
- package/package.json +59 -0
- package/spec/CONFORMANCE.md +80 -0
- package/spec/SPEC.md +821 -0
- package/spec/conformance-vectors.json +677 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// The metered session covenant. TESTNET ONLY -- it has never been reviewed by anyone but its
|
|
2
|
+
// author, and four fund-losing bugs have been found in it so far.
|
|
3
|
+
//
|
|
4
|
+
// 520 of Kaspa's 520-byte element limit, both entry points. ZERO SPARE -- the next change of any
|
|
5
|
+
// size needs one of the message-shape fallbacks considered during design. Every reduction pass
|
|
6
|
+
// below was measured against the compiler rather than estimated.
|
|
7
|
+
//
|
|
8
|
+
// PROVEN ON CHAIN, not merely compiled: settle, supersede, a stale claim refused by consensus,
|
|
9
|
+
// the two-output split, and the dust fold all run on testnet-10. 21 simulator cases cover the
|
|
10
|
+
// branches, and `npm run demo` drives the whole lifecycle end to end.
|
|
11
|
+
//
|
|
12
|
+
// FIVE BUGS, and not one of them was something a compiler could have found. Two came from reading
|
|
13
|
+
// the contract against the spec; the fee allowance came from a node refusing to accept a
|
|
14
|
+
// transaction; the dust rule came from a real session earning too little to be paid; and the
|
|
15
|
+
// input-group rule came from reading another language's security invariants and asking whether
|
|
16
|
+
// they applied here.
|
|
17
|
+
//
|
|
18
|
+
// UNDEFINED BEHAVIOUR. SilverScript's UB is fail-OPEN: a contract that completes with UB returns
|
|
19
|
+
// true and the spend succeeds, so UB can never stand in for a rejection. Two consequences are
|
|
20
|
+
// visible throughout: every balance test is written as an ADDITION rather than a subtraction, so
|
|
21
|
+
// no underflow is reachable at all; and where a range check was dropped for size, the reason it
|
|
22
|
+
// was already implied is argued at the site rather than left to be re-derived.
|
|
23
|
+
//
|
|
24
|
+
// TWO PHASES, because supersede requires it. `settle` does not pay anyone: it POSTS a claim,
|
|
25
|
+
// carrying (seq, cumulativeSompi) forward as contract state, and starts the response window. A
|
|
26
|
+
// higher-seq settle replaces it. `expire` pays out once the window has passed. SPEC.md 7.2's
|
|
27
|
+
// settle row reads as though settle itself pays; it cannot, or there would be nothing left to
|
|
28
|
+
// supersede.
|
|
29
|
+
//
|
|
30
|
+
// NATIVE STATE, not a bytecode splice. v1.0.0 has real state fields and validateOutputState; the
|
|
31
|
+
// splice-your-own-bytecode pattern in the pre-1.0 examples is no longer needed. Finding
|
|
32
|
+
// B claimed otherwise and was wrong -- see the correction recorded there.
|
|
33
|
+
// THE FEE ALLOWANCE, and why it is 400,000.
|
|
34
|
+
//
|
|
35
|
+
// This was 100,000 and no spend of this covenant could ever have been broadcast. Kaspa requires a
|
|
36
|
+
// minimum fee from the transaction's compute mass, and a v1 transaction with one signature check
|
|
37
|
+
// masses 1,666 -- 166,600 sompi -- while the two-output close masses 2,038 and needs 203,800. A
|
|
38
|
+
// covenant that caps the fee below the network's own floor forbids every honest settlement, and
|
|
39
|
+
// it does so silently: the contract compiles, the address derives, the simulator passes every
|
|
40
|
+
// unit test, and the node simply refuses the only transaction that could ever satisfy it.
|
|
41
|
+
//
|
|
42
|
+
// It was found by attempting a real spend on testnet-10, which is the only place it COULD be
|
|
43
|
+
// found. Both numbers were measured today by having a node reject transactions until it stated
|
|
44
|
+
// the requirement itself.
|
|
45
|
+
//
|
|
46
|
+
// IT MUST COVER THE MOST EXPENSIVE ENTRY, not the cheapest, and getting that wrong is how this
|
|
47
|
+
// was wrong TWICE. `expire` evaluates checkSig(buyer) || checkSig(provider) -- two signature
|
|
48
|
+
// checks, 201,686 script units -- so it needs a compute budget of 21 where `settle` needs 10.
|
|
49
|
+
// Mass rises with the budget (budget*100 + 666, plus ~372 for a second output), so a two-output
|
|
50
|
+
// expire masses ~3,138 and the node demands ~313,800 sompi. A 250,000 allowance sized for settle
|
|
51
|
+
// forbids every expire.
|
|
52
|
+
//
|
|
53
|
+
// The cost of being generous: whoever closes may take up to this much as fee, so a spiteful
|
|
54
|
+
// counterparty can burn 400,000 sompi (0.004 KAS). That is the honest price of being able to
|
|
55
|
+
// close at all, and it is bounded, which the alternative was not.
|
|
56
|
+
contract MeteredSession(
|
|
57
|
+
byte[32] parties,
|
|
58
|
+
byte[16] sessionId,
|
|
59
|
+
int window,
|
|
60
|
+
int initialSeq,
|
|
61
|
+
int initialSompi,
|
|
62
|
+
) {
|
|
63
|
+
// Contract state, carried from one spend to the next. SPEC.md 7.4: integers only.
|
|
64
|
+
// initialSeq is -1 at funding, so the first settle at seq 0 satisfies the strict guard below.
|
|
65
|
+
int pendingSeq = initialSeq;
|
|
66
|
+
int pendingSompi = initialSompi;
|
|
67
|
+
|
|
68
|
+
// SPEC.md 3.1: partiesCommitment = blake3(buyerPubkey || providerPubkey).
|
|
69
|
+
function bindParties(pubkey buyer, pubkey provider) {
|
|
70
|
+
require(blake3(byte[](buyer) + byte[](provider)) == parties);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// POST A CLAIM. Verifies a doubly-signed State over the SPEC.md 3.4.1 preimage and carries it
|
|
74
|
+
// forward as state. Supersede is the seq guard, not a third entry point.
|
|
75
|
+
entry settle(
|
|
76
|
+
pubkey buyer,
|
|
77
|
+
pubkey provider,
|
|
78
|
+
datasig buyerSig,
|
|
79
|
+
datasig providerSig,
|
|
80
|
+
int seq,
|
|
81
|
+
int cumulativeUnits,
|
|
82
|
+
int cumulativeSompi,
|
|
83
|
+
byte[32] prevState,
|
|
84
|
+
) {
|
|
85
|
+
bindParties(buyer, provider);
|
|
86
|
+
|
|
87
|
+
// Supersede, as a guard. Strictly higher, so a stale State cannot replace a fresher one.
|
|
88
|
+
require(seq > pendingSeq);
|
|
89
|
+
|
|
90
|
+
// A range check BEFORE the OpNum2Bin calls below, because an out-of-range conversion is
|
|
91
|
+
// undefined and undefined is fail-open.
|
|
92
|
+
//
|
|
93
|
+
// NO UPPER BOUND is checked on anything. UNDEFINED-BEHAVIOUR.md: "an integer used by a
|
|
94
|
+
// numeric expression must fit in at most eight signed-magnitude bytes" -- so a live
|
|
95
|
+
// run-time int already has that property and OpNum2Bin(x, 8) on one cannot overflow. An
|
|
96
|
+
// upper-bound check would be re-checking what the VM guarantees. SPEC.md 2.3's 2^53-1 cap
|
|
97
|
+
// stays an off-chain rule; nothing on-chain depends on it.
|
|
98
|
+
//
|
|
99
|
+
// Of the three lower bounds, only cumulativeSompi survives. The other two are omissions
|
|
100
|
+
// with proofs:
|
|
101
|
+
// seq -- implied. `seq > pendingSeq` above, and pendingSeq starts at -1 and
|
|
102
|
+
// only ever rises, so seq >= 0 already holds on every reachable path.
|
|
103
|
+
// cumulativeUnits -- not load-bearing on-chain. It enters the digest and nothing else;
|
|
104
|
+
// no output value depends on it. A negative value converts fine under
|
|
105
|
+
// signed-magnitude, so it is not a UB risk either, and if both
|
|
106
|
+
// parties signed a nonsensical unit count that is an off-chain
|
|
107
|
+
// reconciliation failure (SPEC.md 5), not something consensus decides.
|
|
108
|
+
// cumulativeSompi IS load-bearing: it becomes pendingSompi, whose zero value is the
|
|
109
|
+
// no-claim sentinel in expire, and it is the amount actually paid out.
|
|
110
|
+
require(cumulativeSompi >= 0);
|
|
111
|
+
|
|
112
|
+
// SPEC.md 3.4.1: sessionId(16) + seq(8) + cumUnits(8) + cumSompi(8) + prevState(32) = 72.
|
|
113
|
+
byte[32] digest = blake3(
|
|
114
|
+
byte[](sessionId)
|
|
115
|
+
+ OpNum2Bin(seq, 8)
|
|
116
|
+
+ OpNum2Bin(cumulativeUnits, 8)
|
|
117
|
+
+ OpNum2Bin(cumulativeSompi, 8)
|
|
118
|
+
+ byte[](prevState)
|
|
119
|
+
);
|
|
120
|
+
require(checkMsgSig(buyerSig, digest, buyer));
|
|
121
|
+
require(checkMsgSig(providerSig, digest, provider));
|
|
122
|
+
|
|
123
|
+
// The claim must hand the funds back to this covenant, less a fee, carrying the new state.
|
|
124
|
+
// No overflow: the input value is a consensus-supplied UTXO amount bounded by total supply
|
|
125
|
+
// (~2.87e18 sompi), and the subtraction only shrinks it.
|
|
126
|
+
// NO BOUNDS CHECK on activeInputIndex, and that is argued rather than assumed.
|
|
127
|
+
// It NAMES the input being spent, so it is a valid index by construction -- there is no
|
|
128
|
+
// reachable state in which the input currently executing does not exist. SilverScript's
|
|
129
|
+
// own TUTORIAL.md writes `tx.inputs[this.activeInputIndex].value` unguarded throughout,
|
|
130
|
+
// which is the language's authors making the same claim about their own VM.
|
|
131
|
+
require(tx.outputs.length == 1);
|
|
132
|
+
require(tx.outputs[0].value + 400000 >= tx.inputs[this.activeInputIndex].value);
|
|
133
|
+
validateOutputState(0, State { pendingSeq: seq, pendingSompi: cumulativeSompi });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// FINALISE. After the response window, pay the pending claim out: cumulativeSompi to the
|
|
137
|
+
// provider, the remainder to the buyer. SPEC.md 7.3 -- the window is RELATIVE, so ageDaa
|
|
138
|
+
// lowers to OpCheckSequenceVerify and reads the spending input's sequence field.
|
|
139
|
+
entry expire(pubkey buyer, pubkey provider, sig partySig) {
|
|
140
|
+
bindParties(buyer, provider);
|
|
141
|
+
require(this.ageDaa >= window);
|
|
142
|
+
|
|
143
|
+
bool byAParty = checkSig(partySig, buyer) || checkSig(partySig, provider);
|
|
144
|
+
require(byAParty);
|
|
145
|
+
|
|
146
|
+
// THIS COVENANT MUST BE ALONE IN THE TRANSACTION.
|
|
147
|
+
//
|
|
148
|
+
// Argent's leader/delegate invariants put it as Rule 3: a covenant has to account for
|
|
149
|
+
// EVERY input carrying its id. This one accounts for exactly one -- its own -- so without
|
|
150
|
+
// this line a transaction may spend TWO of them and satisfy both scripts with a single
|
|
151
|
+
// pair of outputs. `expire` pays to P2PK(buyer) and P2PK(provider), which are NOT
|
|
152
|
+
// session-specific, so two sessions between the same two parties produce identical output
|
|
153
|
+
// scripts and each input's checks pass against its own `total`.
|
|
154
|
+
//
|
|
155
|
+
// MEASURED before the fix: two inputs of 10,000,000 paying out 9,600,000 was ACCEPTED,
|
|
156
|
+
// destroying 10,400,000 sompi. Either party can sign it, so it is a griefing attack on
|
|
157
|
+
// whoever has the larger balance at stake.
|
|
158
|
+
//
|
|
159
|
+
// `settle` is not exposed the same way -- its output is the continuation P2SH, which
|
|
160
|
+
// embeds the sessionId, so two sessions cannot share one -- but this entry pays to plain
|
|
161
|
+
// keys and has nothing session-specific to bind.
|
|
162
|
+
require(tx.inputs.length == 1);
|
|
163
|
+
int total = tx.inputs[this.activeInputIndex].value;
|
|
164
|
+
|
|
165
|
+
// No guard: the branches below demand exactly 1 or exactly 2, so a
|
|
166
|
+
// zero-output transaction falls into the else and fails there. It was checking what the
|
|
167
|
+
// structure already guarantees.
|
|
168
|
+
byte[36] buyerLock = new ScriptPubKeyP2PK(buyer);
|
|
169
|
+
|
|
170
|
+
// EVERY BALANCE TEST IS WRITTEN AS AN ADDITION, never `total - x >= y`. Subtraction can
|
|
171
|
+
// underflow, underflow is undefined, and undefined is fail-OPEN -- a spend that should
|
|
172
|
+
// have been rejected would succeed. Written this way the comparison is total by
|
|
173
|
+
// construction, and the guards that would have been needed to make subtraction safe
|
|
174
|
+
// (pendingSompi >= 0, pendingSompi <= total) are not needed at all. Cheaper AND safer,
|
|
175
|
+
// which is the rare direction for that trade.
|
|
176
|
+
//
|
|
177
|
+
// No overflow either: both terms are consensus-supplied UTXO amounts or a state field
|
|
178
|
+
// derived from one, so each is bounded by total supply (~2.87e18 sompi) and their sum
|
|
179
|
+
// stays far below the 8-byte signed-magnitude limit (~9.22e18).
|
|
180
|
+
|
|
181
|
+
// DUST, and why this branch exists. KIP-9 prices an output by its reciprocal:
|
|
182
|
+
// mass = 1e12/out_1 + 1e12/out_2 - 1e12/in, capped at 500,000. For a covenant holding
|
|
183
|
+
// 0.5 KAS that leaves 499,791 for the first term, so an output below ~2,000,835 sompi
|
|
184
|
+
// CANNOT BE CREATED AT ALL. Two fund-losing consequences, both closed here:
|
|
185
|
+
//
|
|
186
|
+
// G a session earning less than that could not be closed by anyone, because the
|
|
187
|
+
// two-output path demands outputs[0].value == pendingSompi exactly. The funds
|
|
188
|
+
// locked. Found by settling a real session that earned 108,900 sompi.
|
|
189
|
+
// H a one-output close with a claim constrained only outputs[0], leaving the rest to
|
|
190
|
+
// fee. A spiteful provider could burn the buyer's whole refund -- Bug 2 again,
|
|
191
|
+
// through a door an earlier comment of mine held open by calling that "normal".
|
|
192
|
+
//
|
|
193
|
+
// The rule is symmetric: whichever side's share is dust, the OTHER side takes the lot.
|
|
194
|
+
// Dust cannot be paid to anyone, so folding it costs that party at most 0.02 KAS, and
|
|
195
|
+
// the alternative is locking the entire balance forever.
|
|
196
|
+
// The provider is owed a payable amount, or it is not. That single question decides the
|
|
197
|
+
// whole shape, and asking it first means providerLock is built ONCE -- SilverScript
|
|
198
|
+
// inlines these at every use site, so a second use costs a second copy of the code.
|
|
199
|
+
// DUST (SPEC.md 7.4a). MEASURED, not the asymptotic figure it used to be. KIP-9 prices
|
|
200
|
+
// an output by its reciprocal and subtracts the input's, so what is payable depends on
|
|
201
|
+
// the OTHER output and on the balance -- 2,000,000 is only the limit a large transaction
|
|
202
|
+
// approaches. tools/dust-map.ts walks every claim against the node's own formula and
|
|
203
|
+
// found unclosable windows at every balance, 981 sompi wide at 1 KAS and 600,000 wide at
|
|
204
|
+
// 0.05 KAS. 2,600,000 closes them for every balance the funding floor admits.
|
|
205
|
+
if (pendingSompi < 2600000) {
|
|
206
|
+
// The provider earned dust, which includes earning nothing at all. SPEC.md 7.2:
|
|
207
|
+
// expire "refunds the buyer entirely if none". One output, all of it to the buyer.
|
|
208
|
+
require(tx.outputs.length == 1);
|
|
209
|
+
require(tx.outputs[0].scriptPubKey == byte[](buyerLock));
|
|
210
|
+
require(tx.outputs[0].value + 400000 >= total);
|
|
211
|
+
} else {
|
|
212
|
+
byte[36] providerLock = new ScriptPubKeyP2PK(provider);
|
|
213
|
+
require(tx.outputs[0].value == pendingSompi);
|
|
214
|
+
require(tx.outputs[0].scriptPubKey == byte[](providerLock));
|
|
215
|
+
if (tx.outputs.length == 1) {
|
|
216
|
+
// The buyer's refund is dust and cannot be created, so it goes to fee. Without
|
|
217
|
+
// this line the remainder is unconstrained and a provider could burn a refund of
|
|
218
|
+
// ANY size, which was H.
|
|
219
|
+
//
|
|
220
|
+
// THE THRESHOLD IS 2,400,000, NOT 2,000,000, and the difference is the fee. The
|
|
221
|
+
// refund is total - pendingSompi - fee, so requiring `pendingSompi + 2600000 >=
|
|
222
|
+
// total` only admits refunds up to 2,000,000 - 400,000 = 1,600,000. A refund
|
|
223
|
+
// between 1,600,000 and the 2,000,000 dust floor would then have NO legal shape:
|
|
224
|
+
// too small to pay as its own output, too large to fold. That gap is another way
|
|
225
|
+
// to lock the balance, and adding the fee allowance closes it exactly.
|
|
226
|
+
require(pendingSompi + 3000000 >= total);
|
|
227
|
+
} else {
|
|
228
|
+
// The ordinary close. The REMAINDER'S VALUE is checked, not only its
|
|
229
|
+
// destination -- either party may sign this spend, and a destination check alone
|
|
230
|
+
// lets one pay the other a token amount and burn the rest to fee.
|
|
231
|
+
require(tx.outputs.length == 2);
|
|
232
|
+
require(tx.outputs[1].scriptPubKey == byte[](buyerLock));
|
|
233
|
+
require(tx.outputs[1].value + pendingSompi + 400000 >= total);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|