@easy1staking/cip113-sdk-ts 0.7.0 → 0.10.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/README.md +118 -2
- package/blueprints/standard/v0.5.0-alpha.3/UPSTREAM_PIN.json +35 -0
- package/blueprints/standard/v0.5.0-alpha.3/plutus.json +1126 -0
- package/blueprints/standard/v0.5.0-alpha.4/UPSTREAM_PIN.json +36 -0
- package/blueprints/standard/v0.5.0-alpha.4/plutus.json +1434 -0
- package/dist/core/evo-utils.d.ts +357 -39
- package/dist/core/evo-utils.d.ts.map +1 -1
- package/dist/core/evo-utils.js +513 -27
- package/dist/core/evo-utils.js.map +1 -1
- package/dist/core/ledger-order.d.ts +204 -18
- package/dist/core/ledger-order.d.ts.map +1 -1
- package/dist/core/ledger-order.js +385 -28
- package/dist/core/ledger-order.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/dist/standard/blueprint.d.ts +129 -29
- package/dist/standard/blueprint.d.ts.map +1 -1
- package/dist/standard/blueprint.js +245 -47
- package/dist/standard/blueprint.js.map +1 -1
- package/dist/standard/scripts.d.ts +189 -75
- package/dist/standard/scripts.d.ts.map +1 -1
- package/dist/standard/scripts.js +454 -123
- package/dist/standard/scripts.js.map +1 -1
- package/dist/substandards/dummy/index.d.ts +19 -0
- package/dist/substandards/dummy/index.d.ts.map +1 -1
- package/dist/substandards/dummy/index.js +322 -73
- package/dist/substandards/dummy/index.js.map +1 -1
- package/dist/substandards/freeze-and-seize/index.d.ts.map +1 -1
- package/dist/substandards/freeze-and-seize/index.js +455 -117
- package/dist/substandards/freeze-and-seize/index.js.map +1 -1
- package/dist/types.d.ts +161 -48
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/standard/scripts.js
CHANGED
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Standard script parameterization — CIP-113 0.5.0-alpha.
|
|
2
|
+
* Standard script parameterization — CIP-113 0.5.0-alpha.4
|
|
3
|
+
* (upstream 7e8a63198c5b240135f1aa2f043ce5d7c046b2c4).
|
|
3
4
|
*
|
|
4
5
|
* Uses Evolution SDK directly for UPLC.applyParamsToScript and ScriptHash.
|
|
5
6
|
*
|
|
6
|
-
* Dependency graph.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `params_policy` in parallel instead of in series.
|
|
7
|
+
* Dependency graph. It is NOT a single chain: `programmable_logic_base` hangs
|
|
8
|
+
* off the params-NFT policy, so everything downstream of it fans out in
|
|
9
|
+
* parallel rather than in series.
|
|
10
10
|
*
|
|
11
|
-
* always_fail(nonce) -> hash (issuance side only
|
|
12
|
-
*
|
|
13
|
-
* protocol_params_mint(utxo_ref, coord_hash) -> policy == params_policy
|
|
11
|
+
* always_fail(nonce) -> hash (issuance side only)
|
|
12
|
+
* protocol_params(utxo_ref) -> policy == params_policy
|
|
14
13
|
* |
|
|
15
|
-
* +-- programmable_logic_base(params_policy)
|
|
16
|
-
*
|
|
17
|
-
* +--
|
|
18
|
-
* +--
|
|
19
|
-
* +--
|
|
14
|
+
* +-- programmable_logic_base(params_policy) -> hash == plb
|
|
15
|
+
* | |
|
|
16
|
+
* | +-- transfer(Script(plb), registry_node_cs, max_inline) -> hash
|
|
17
|
+
* | +-- third_party(Script(plb), registry_node_cs, max_inline) -> hash
|
|
18
|
+
* | +-- unfracking(Script(plb), registry_node_cs, max_inline) -> hash
|
|
19
|
+
* | +-- issuance_logic(Script(plb), registry_node_cs,
|
|
20
|
+
* | params_policy, max_inline) -> hash NEW in alpha.4
|
|
21
|
+
* |
|
|
22
|
+
* +-- issuance_mint(Script(minting_logic), params_policy) -> policy
|
|
23
|
+
*
|
|
24
|
+
* programmable_logic_global(transfer_hash, third_party_hash, unfracking_hash)
|
|
25
|
+
* -> hash (built LAST)
|
|
20
26
|
*
|
|
21
|
-
* issuance_cbor_hex_mint(utxo_ref, always_fail_hash)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* upgrade_multisig(
|
|
27
|
+
* issuance_cbor_hex_mint(utxo_ref, always_fail_hash) -> policy
|
|
28
|
+
* registry(utxo_ref, issuance_cbor_hex_cs) -> policy
|
|
29
|
+
*
|
|
30
|
+
* upgrade_multisig(utxo_ref) -> hash (independent one-shot)
|
|
25
31
|
*
|
|
26
32
|
* ⚠ PARAMETER TYPES ARE NOT INTERCHANGEABLE and TypeScript cannot tell them
|
|
27
33
|
* apart — every one of these is a hex string at the call site. `params_policy`
|
|
28
|
-
*
|
|
29
|
-
* Credentials (
|
|
30
|
-
*
|
|
31
|
-
*
|
|
34
|
+
* and `registry_node_cs` are PolicyIds (bare ByteArrays); `minting_logic_cred`
|
|
35
|
+
* and `programmable_logic_base` are Credentials (constructor-wrapped
|
|
36
|
+
* Script/VerificationKey). `scriptCredential()` wraps; `Data.bytearray()` does
|
|
37
|
+
* not. Passing a policy where a credential belongs produces a valid script with
|
|
38
|
+
* the wrong hash.
|
|
39
|
+
*
|
|
40
|
+
* ⛔ alpha.4 makes that sharper: `issuance_logic` takes TWO ADJACENT PolicyId
|
|
41
|
+
* parameters — `registry_node_cs` then `params_policy`. They are the same type,
|
|
42
|
+
* the same length, and both are `string` here. Swapping them yields a script
|
|
43
|
+
* that builds, hashes and deploys, and nothing before the ledger will say so.
|
|
44
|
+
*
|
|
45
|
+
* The types below come from the blueprint's own parameter schemas, not from
|
|
32
46
|
* upstream's prose docs — see the hazard note in blueprint.ts.
|
|
33
47
|
*/
|
|
34
48
|
import { Data } from "@evolution-sdk/evolution";
|
|
@@ -55,14 +69,8 @@ export function createStandardScripts(blueprint, onParameterize) {
|
|
|
55
69
|
alwaysFail(nonce) {
|
|
56
70
|
return parameterize(STANDARD_VALIDATORS.ALWAYS_FAIL, [Data.bytearray(nonce)]);
|
|
57
71
|
},
|
|
58
|
-
|
|
59
|
-
return parameterize(STANDARD_VALIDATORS.
|
|
60
|
-
},
|
|
61
|
-
protocolParamsMint(utxoRef, coordinationHash) {
|
|
62
|
-
return parameterize(STANDARD_VALIDATORS.PROTOCOL_PARAMS_MINT, [
|
|
63
|
-
outputReference(utxoRef),
|
|
64
|
-
Data.bytearray(coordinationHash),
|
|
65
|
-
]);
|
|
72
|
+
protocolParams(utxoRef) {
|
|
73
|
+
return parameterize(STANDARD_VALIDATORS.PROTOCOL_PARAMS, [outputReference(utxoRef)]);
|
|
66
74
|
},
|
|
67
75
|
programmableLogicBase(paramsPolicy) {
|
|
68
76
|
// PolicyId — a bare ByteArray. It was scriptCredential(plgHash) before #110.
|
|
@@ -70,150 +78,464 @@ export function createStandardScripts(blueprint, onParameterize) {
|
|
|
70
78
|
Data.bytearray(paramsPolicy),
|
|
71
79
|
]);
|
|
72
80
|
},
|
|
73
|
-
transfer(
|
|
74
|
-
return parameterize(STANDARD_VALIDATORS.TRANSFER, [
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
transfer(progLogicCred, registryPolicy, maxInlineDatumBytes) {
|
|
82
|
+
return parameterize(STANDARD_VALIDATORS.TRANSFER, [
|
|
83
|
+
scriptCredential(progLogicCred),
|
|
84
|
+
Data.bytearray(registryPolicy),
|
|
85
|
+
Data.int(BigInt(maxInlineDatumBytes)),
|
|
86
|
+
]);
|
|
78
87
|
},
|
|
79
|
-
|
|
80
|
-
return parameterize(STANDARD_VALIDATORS.
|
|
88
|
+
thirdParty(progLogicCred, registryPolicy, maxInlineDatumBytes) {
|
|
89
|
+
return parameterize(STANDARD_VALIDATORS.THIRD_PARTY, [
|
|
90
|
+
scriptCredential(progLogicCred),
|
|
91
|
+
Data.bytearray(registryPolicy),
|
|
92
|
+
Data.int(BigInt(maxInlineDatumBytes)),
|
|
93
|
+
]);
|
|
81
94
|
},
|
|
82
|
-
|
|
83
|
-
return parameterize(STANDARD_VALIDATORS.
|
|
84
|
-
|
|
85
|
-
Data.
|
|
95
|
+
unfracking(progLogicCred, registryPolicy, maxInlineDatumBytes) {
|
|
96
|
+
return parameterize(STANDARD_VALIDATORS.UNFRACKING, [
|
|
97
|
+
scriptCredential(progLogicCred),
|
|
98
|
+
Data.bytearray(registryPolicy),
|
|
99
|
+
Data.int(BigInt(maxInlineDatumBytes)),
|
|
86
100
|
]);
|
|
87
101
|
},
|
|
102
|
+
upgradeMultisig(utxoRef) {
|
|
103
|
+
return parameterize(STANDARD_VALIDATORS.UPGRADE_MULTISIG, [outputReference(utxoRef)]);
|
|
104
|
+
},
|
|
88
105
|
issuanceCborHexMint(utxoRef, alwaysFailHash) {
|
|
89
106
|
return parameterize(STANDARD_VALIDATORS.ISSUANCE_CBOR_HEX_MINT, [
|
|
90
107
|
outputReference(utxoRef),
|
|
91
108
|
Data.bytearray(alwaysFailHash),
|
|
92
109
|
]);
|
|
93
110
|
},
|
|
94
|
-
|
|
95
|
-
return parameterize(STANDARD_VALIDATORS.
|
|
111
|
+
registry(utxoRef, issuanceCborHexPolicy) {
|
|
112
|
+
return parameterize(STANDARD_VALIDATORS.REGISTRY, [
|
|
96
113
|
outputReference(utxoRef),
|
|
97
114
|
Data.bytearray(issuanceCborHexPolicy),
|
|
98
|
-
scriptCredential(registrySpendHash),
|
|
99
115
|
]);
|
|
100
116
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
programmableLogicGlobal(transferHash, thirdPartyHash, unfrackingHash) {
|
|
118
|
+
// Bare ScriptHashes — NOT scriptCredential(). The blueprint declares
|
|
119
|
+
// `aiken/crypto/ScriptHash` here, while issuance_mint declares
|
|
120
|
+
// `cardano/address/Credential` for the same 28 bytes. Wrapping these
|
|
121
|
+
// would produce a different script that still hashes and still deploys.
|
|
122
|
+
return parameterize(STANDARD_VALIDATORS.PROGRAMMABLE_LOGIC_GLOBAL, [
|
|
123
|
+
Data.bytearray(transferHash),
|
|
124
|
+
Data.bytearray(thirdPartyHash),
|
|
125
|
+
Data.bytearray(unfrackingHash),
|
|
104
126
|
]);
|
|
105
127
|
},
|
|
106
|
-
issuanceMint(
|
|
128
|
+
issuanceMint(mintingLogicHash, paramsPolicy) {
|
|
107
129
|
return parameterize(STANDARD_VALIDATORS.ISSUANCE_MINT, [
|
|
108
|
-
scriptCredential(plbHash),
|
|
109
|
-
Data.bytearray(registryNodePolicy),
|
|
110
130
|
scriptCredential(mintingLogicHash),
|
|
111
131
|
Data.bytearray(paramsPolicy),
|
|
112
132
|
]);
|
|
113
133
|
},
|
|
134
|
+
issuanceLogic(progLogicCred, registryPolicy, paramsPolicy, maxInlineDatumBytes) {
|
|
135
|
+
// Credential, then TWO bare PolicyIds in blueprint order
|
|
136
|
+
// (registry_node_cs, params_policy), then the Int. See the interface
|
|
137
|
+
// doc: the two policies are the swap hazard alpha.4 introduced.
|
|
138
|
+
return parameterize(STANDARD_VALIDATORS.ISSUANCE_LOGIC, [
|
|
139
|
+
scriptCredential(progLogicCred),
|
|
140
|
+
Data.bytearray(registryPolicy),
|
|
141
|
+
Data.bytearray(paramsPolicy),
|
|
142
|
+
Data.int(BigInt(maxInlineDatumBytes)),
|
|
143
|
+
]);
|
|
144
|
+
},
|
|
114
145
|
};
|
|
115
146
|
}
|
|
116
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Thrown when a deployment record and the blueprint do not describe the same
|
|
149
|
+
* protocol instance.
|
|
150
|
+
*
|
|
151
|
+
* ⛔ IT IS THE CLASS CONSUMERS CATCH, so every refusal from this module must
|
|
152
|
+
* be one. The documented consumer shape is
|
|
153
|
+
* `catch (e) { if (e instanceof DeploymentMismatchError) … }`; a bare `Error`
|
|
154
|
+
* thrown beside it drops silently into whatever generic handler comes next,
|
|
155
|
+
* which is how a refusal that names a field ends up reported as "something went
|
|
156
|
+
* wrong".
|
|
157
|
+
*
|
|
158
|
+
* ⚠ `mismatches` IS EMPTY FOR REFUSALS THAT ARE NOT HASH COMPARISONS, and the
|
|
159
|
+
* emptiness is the signal rather than an oversight: nothing derived disagreed
|
|
160
|
+
* with anything recorded — the record was malformed, or held a value that was
|
|
161
|
+
* never a legal choice. Those pass their own `detail` message, because the
|
|
162
|
+
* composed "N script hash(es) differ" text would describe a comparison that
|
|
163
|
+
* never happened.
|
|
164
|
+
*/
|
|
117
165
|
export class DeploymentMismatchError extends Error {
|
|
118
166
|
mismatches;
|
|
119
|
-
constructor(mismatches, blueprintTitle) {
|
|
120
|
-
super(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
constructor(mismatches, blueprintTitle, detail) {
|
|
168
|
+
super(detail ??
|
|
169
|
+
`Blueprint "${blueprintTitle}" does not reproduce this deployment. ` +
|
|
170
|
+
`${mismatches.length} script hash(es) differ:\n` +
|
|
171
|
+
mismatches
|
|
172
|
+
.map((m) => ` ${m.name}: derived ${m.derived}, deployment says ${m.deployed}`)
|
|
173
|
+
.join("\n") +
|
|
174
|
+
`\nThe blueprint and the DeploymentParams describe different protocol instances. ` +
|
|
175
|
+
`Transactions built from this pairing would be rejected at submission.` +
|
|
176
|
+
dispatcherCauseHint(mismatches));
|
|
127
177
|
this.name = "DeploymentMismatchError";
|
|
128
178
|
this.mismatches = mismatches;
|
|
129
179
|
}
|
|
130
180
|
}
|
|
181
|
+
/** The name `deriveDeploymentScripts` gives the dispatcher-coherence check. */
|
|
182
|
+
const PLG_CHECK_NAME = "programmable_logic_global (dispatcher coherence)";
|
|
131
183
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* arity and type (protocol_params_mint's `always_fail_hash` became
|
|
139
|
-
* `coordination_addr_hash` in 0.5.0-alpha.1), so a wrong value typechecks,
|
|
140
|
-
* builds, and only fails when the ledger rejects the transaction. This is the
|
|
141
|
-
* check that catches it, and it is why buildDeploymentScripts no longer
|
|
142
|
-
* overwrites derived hashes with deployment values.
|
|
143
|
-
*
|
|
144
|
-
* Not covered: always_fail (its nonce is not carried in DeploymentParams),
|
|
145
|
-
* issuance_mint (parameterized per minting logic), and upgrade_multisig (its
|
|
146
|
-
* signers/threshold are an authority choice, not a derived protocol value).
|
|
147
|
-
* coordination_spend IS covered — DeploymentParams carries its nonce precisely
|
|
148
|
-
* so the lock target can be re-derived rather than trusted.
|
|
149
|
-
*
|
|
150
|
-
* WHERE THIS CHECK HAS VALUE — and where it has none.
|
|
151
|
-
*
|
|
152
|
-
* It is only meaningful when the blueprint and the deployment come from
|
|
153
|
-
* INDEPENDENT sources, so that they can actually disagree: a deployment loaded
|
|
154
|
-
* from disk, a database, or the chain, checked against the blueprint currently
|
|
155
|
-
* bundled. That is the case it catches, and the failure it catches is real —
|
|
156
|
-
* this repo shipped a blueprint swapped in place under an unchanged directory
|
|
157
|
-
* name, with 4 of 8 validator hashes moved.
|
|
158
|
-
*
|
|
159
|
-
* It proves NOTHING at bootstrap time. A deployment script derives the hashes,
|
|
160
|
-
* populates DeploymentParams from those same values, and then asserts against
|
|
161
|
-
* them — a tautology that cannot fail. Do not read a passing assertion inside a
|
|
162
|
-
* bootstrap as evidence that the deployment is correct; assert on LOAD instead.
|
|
184
|
+
* ⚠ THE DISPATCHER CHECK IS REACHABLE FROM THREE CAUSES AND ITS NAME ONLY
|
|
185
|
+
* SUGGESTS ONE. "dispatcher coherence" trains the reader to look for a stale
|
|
186
|
+
* delegate, so a mismatch caused by the RECORDED `unfrackingParameter` sends
|
|
187
|
+
* them to the wrong two fields — and the parameter is the one cause the reader
|
|
188
|
+
* cannot see by comparing the record against itself. The check's name is pinned
|
|
189
|
+
* by `test/deployment-assertion.test.mjs`; the causes are named here instead.
|
|
163
190
|
*/
|
|
164
|
-
|
|
191
|
+
function dispatcherCauseHint(mismatches) {
|
|
192
|
+
if (!mismatches.some((m) => m.name === PLG_CHECK_NAME))
|
|
193
|
+
return "";
|
|
194
|
+
return (`\n\n${PLG_CHECK_NAME} is derived from THREE recorded values, and any one of ` +
|
|
195
|
+
`them can cause it:\n` +
|
|
196
|
+
` transfer.scriptHash\n` +
|
|
197
|
+
` thirdParty.scriptHash\n` +
|
|
198
|
+
` programmableLogicGlobal.unfrackingParameter — what the dispatcher was ` +
|
|
199
|
+
`COMPILED AGAINST, which is NOT necessarily unfracking.scriptHash: a deployment ` +
|
|
200
|
+
`may record UNFRACKING_DISABLED here while deploying the real unfracking script ` +
|
|
201
|
+
`beside it. Check this one before the delegates; it is the only one of the three ` +
|
|
202
|
+
`that cannot be checked against anything else in the record.`);
|
|
203
|
+
}
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// The unfracking parameter — a deployment CHOICE baked into the dispatcher
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
/**
|
|
208
|
+
* The `unfracking_hash` a dispatcher is compiled against when the deployment
|
|
209
|
+
* wants unfracking DEPLOYED BUT UNREACHABLE: 28 zero bytes.
|
|
210
|
+
*
|
|
211
|
+
* ⚠ IT IS BAKED INTO THE DISPATCHER'S HASH, so two deployments that chose
|
|
212
|
+
* differently are DIFFERENT PROTOCOLS. Same category as `maxInlineDatumBytes`
|
|
213
|
+
* (see `types.ts`): a deployment CHOICE, recoverable from no hash, which is why
|
|
214
|
+
* `DeploymentParams.programmableLogicGlobal.unfrackingParameter` records the
|
|
215
|
+
* value itself rather than a flag.
|
|
216
|
+
*
|
|
217
|
+
* ⚠ WHY 28 BYTES AND NOT `#""`. The Aiken parameter is a bare `ScriptHash`
|
|
218
|
+
* — a `ByteArray` with no length constraint, not an `Option`, so there is no
|
|
219
|
+
* "none" to reach for. `#""` is type-legal, but NOBODY HERE CAN READ THE
|
|
220
|
+
* COMPILED VALIDATOR'S BODY: whether it asserts a length, or builds an address
|
|
221
|
+
* out of this parameter, is unknown. A 28-byte value is structurally identical
|
|
222
|
+
* to a real script hash, so every path behaves normally.
|
|
223
|
+
* ⇒ Choose a value that cannot reach code we cannot read, rather than reason
|
|
224
|
+
* about what that code probably does.
|
|
225
|
+
*
|
|
226
|
+
* ⚠ NOTHING HASHES TO IT. At 2^224 that is an impossibility, not a low risk,
|
|
227
|
+
* so the sentinel can never collide with a real `unfracking` deployment. Zeros
|
|
228
|
+
* rather than `FF…FF`, which reads as a mask or a placeholder.
|
|
229
|
+
*
|
|
230
|
+
* ⛔ IT LIVES HERE AND NOWHERE ELSE. The platform must IMPORT it. A
|
|
231
|
+
* platform-side copy is a second place to disagree about a value that
|
|
232
|
+
* determines a script hash.
|
|
233
|
+
*/
|
|
234
|
+
export const UNFRACKING_DISABLED = "00".repeat(28);
|
|
235
|
+
/**
|
|
236
|
+
* Refuse a recorded `unfrackingParameter` that is neither of the two values a
|
|
237
|
+
* deployment may legitimately hold.
|
|
238
|
+
*
|
|
239
|
+
* ⛔ THIS IS A TWO-ELEMENT SET MEMBERSHIP, NOT A "DOESN'T MATCH ⇒ SENTINEL"
|
|
240
|
+
* FALLBACK. `if (recorded !== derived) { assume sentinel }` is the shape that
|
|
241
|
+
* passes everything: it accepts a typo, a truncation, and a stale hash from a
|
|
242
|
+
* different deployment — each of which then silently becomes the value the
|
|
243
|
+
* dispatcher is built from. Both candidates are named explicitly, and so is the
|
|
244
|
+
* value that was actually found; the standing discipline is
|
|
245
|
+
* `test/provenance-artefact.test.mjs` — refused by a NAMED assertion, never by
|
|
246
|
+
* a `??`/`||` guess.
|
|
247
|
+
*
|
|
248
|
+
* ⚠ THE CASE ONLY THIS CATCHES, and the reason it has to exist at all: a
|
|
249
|
+
* bootstrap that compiled the dispatcher against a wrong value and then recorded
|
|
250
|
+
* BOTH. Every one of the ten hash checks then reproduces — the record is
|
|
251
|
+
* SELF-CONSISTENT, so there is nothing for a comparison to disagree with — and
|
|
252
|
+
* only this membership test says the parameter was never a legal choice.
|
|
253
|
+
*/
|
|
254
|
+
const CANONICAL_SCRIPT_HASH = /^[0-9a-f]{56}$/;
|
|
255
|
+
/**
|
|
256
|
+
* Read `programmableLogicGlobal.unfrackingParameter` and refuse anything that
|
|
257
|
+
* is not a WELL-FORMED script hash — BEFORE a single script is derived.
|
|
258
|
+
*
|
|
259
|
+
* ⛔ REFUSAL 1, THE OWN-PROPERTY CHECK, AND IT IS NOT DEFENSIVE PROGRAMMING.
|
|
260
|
+
* With `Object.prototype.unfrackingParameter` set, a record with the field
|
|
261
|
+
* GENUINELY ABSENT reads back a value, passes every one of the ten hash checks,
|
|
262
|
+
* and builds the dispatcher from something nobody recorded. MEASURED, not
|
|
263
|
+
* feared. This repo already treats that exact shape as a defect class —
|
|
264
|
+
* `test/ledger-order.test.mjs`, *"refuses an inherited `Object.prototype` key"*
|
|
265
|
+
* — so the standard exists and this call site has to meet it. `in` and a
|
|
266
|
+
* truthiness test both walk the prototype chain; only `hasOwnProperty` does not.
|
|
267
|
+
*
|
|
268
|
+
* ⛔ REFUSAL 2, THE SHAPE CHECK. A stray space, a newline from a copy-paste, a
|
|
269
|
+
* `0x` prefix off a block explorer, a `Uint8Array`, a `Buffer`, `null` — every
|
|
270
|
+
* one of these used to fall through to Evolution's
|
|
271
|
+
* `ParseError: Data.ByteArray … Expected string`, which names NO field, NO
|
|
272
|
+
* record and NO file, in front of an artefact that cannot be regenerated.
|
|
273
|
+
* Fails closed either way; the whole difference is whether the deployer can act
|
|
274
|
+
* on it.
|
|
275
|
+
*
|
|
276
|
+
* ⚠ BYTE-EXACT, AND DELIBERATELY NOT NORMALISING. An uppercase spelling of a
|
|
277
|
+
* legal hash is REFUSED and told it is a CASE problem — never lowercased and
|
|
278
|
+
* accepted. This value is what the dispatcher was COMPILED AGAINST; accepting a
|
|
279
|
+
* record whose spelling differs from what was hashed would re-open the exact
|
|
280
|
+
* hole this ticket exists to close.
|
|
281
|
+
*
|
|
282
|
+
* ⚠ TWO REFUSALS, TWO MESSAGES, AND THEY ANSWER DIFFERENT QUESTIONS — *is this
|
|
283
|
+
* a well-formed script hash?* and *is it one of the two this deployment may
|
|
284
|
+
* record?* (`refuseOnUnfrackingParameter`, below). A reader who cannot tell
|
|
285
|
+
* which one fired cannot act, so they are never merged into one condition.
|
|
286
|
+
*/
|
|
287
|
+
function readUnfrackingParameter(deployment, blueprintTitle) {
|
|
288
|
+
const plg = deployment.programmableLogicGlobal;
|
|
289
|
+
if (!Object.prototype.hasOwnProperty.call(plg, "unfrackingParameter")) {
|
|
290
|
+
throw new DeploymentMismatchError([], blueprintTitle, `programmableLogicGlobal.unfrackingParameter is ABSENT from this deployment ` +
|
|
291
|
+
`record — it has no OWN property of that name. It is REQUIRED and it has no ` +
|
|
292
|
+
`default: it records what programmable_logic_global was COMPILED AGAINST, ` +
|
|
293
|
+
`either the real unfracking script hash or UNFRACKING_DISABLED ` +
|
|
294
|
+
`(${UNFRACKING_DISABLED}), and NOTHING IN THE RECORD DETERMINES WHICH. ` +
|
|
295
|
+
`unfracking.scriptHash is not it: a deployment may deploy, publish and ` +
|
|
296
|
+
`record the real unfracking script while compiling the dispatcher against ` +
|
|
297
|
+
`the sentinel, so defaulting to it would be right often enough that nobody ` +
|
|
298
|
+
`would ever check. Add the field to the record. ` +
|
|
299
|
+
`⚠ An inherited Object.prototype.unfrackingParameter does NOT satisfy this: ` +
|
|
300
|
+
`it would hand the dispatcher a value no deployment recorded.`);
|
|
301
|
+
}
|
|
302
|
+
const recorded = plg.unfrackingParameter;
|
|
303
|
+
if (typeof recorded !== "string" || !CANONICAL_SCRIPT_HASH.test(recorded)) {
|
|
304
|
+
const shown = typeof recorded === "string" ? JSON.stringify(recorded) : describe(recorded);
|
|
305
|
+
const caseOnly = typeof recorded === "string" &&
|
|
306
|
+
CANONICAL_SCRIPT_HASH.test(recorded.toLowerCase()) &&
|
|
307
|
+
recorded !== recorded.toLowerCase();
|
|
308
|
+
throw new DeploymentMismatchError([], blueprintTitle, `programmableLogicGlobal.unfrackingParameter records ${shown}, which is not a ` +
|
|
309
|
+
`well-formed script hash. It must be EXACTLY 56 LOWERCASE HEX CHARACTERS ` +
|
|
310
|
+
`(28 bytes), with no 0x prefix, no whitespace and no surrounding quotes.` +
|
|
311
|
+
(caseOnly
|
|
312
|
+
? ` ⚠ THIS IS A CASE PROBLEM, AND ONLY A CASE PROBLEM: lowercased, this IS ` +
|
|
313
|
+
`56 hex characters. It is refused rather than normalised because this value ` +
|
|
314
|
+
`is what the dispatcher was COMPILED AGAINST — accepting a spelling that ` +
|
|
315
|
+
`differs from the bytes that were hashed is the ambiguity this field exists ` +
|
|
316
|
+
`to remove. Write it in lowercase in the record.`
|
|
317
|
+
: ``) +
|
|
318
|
+
`\nThis check runs BEFORE any script is derived, so nothing downstream saw ` +
|
|
319
|
+
`this value; without it the failure surfaces inside Evolution's CBOR encoder ` +
|
|
320
|
+
`naming neither the field, the record, nor the file.`);
|
|
321
|
+
}
|
|
322
|
+
return recorded;
|
|
323
|
+
}
|
|
324
|
+
/** A non-string value, described by type rather than by a misleading cast. */
|
|
325
|
+
function describe(value) {
|
|
326
|
+
if (value === null)
|
|
327
|
+
return "null";
|
|
328
|
+
if (value === undefined)
|
|
329
|
+
return "undefined";
|
|
330
|
+
const ctor = value?.constructor?.name;
|
|
331
|
+
return `a ${typeof value}${ctor && ctor !== typeof value ? ` (${ctor})` : ``}: ` +
|
|
332
|
+
`${String(value).slice(0, 80)}`;
|
|
333
|
+
}
|
|
334
|
+
function refuseOnUnfrackingParameter(recorded, derivedUnfrackingHash, blueprintTitle) {
|
|
335
|
+
if (recorded === derivedUnfrackingHash || recorded === UNFRACKING_DISABLED)
|
|
336
|
+
return;
|
|
337
|
+
const shown = JSON.stringify(recorded);
|
|
338
|
+
throw new DeploymentMismatchError([], blueprintTitle, `programmableLogicGlobal.unfrackingParameter records ${shown}, which is neither ` +
|
|
339
|
+
`value a deployment may record. It must be EXACTLY ONE OF:\n` +
|
|
340
|
+
` ${derivedUnfrackingHash} — the derived unfracking script hash ` +
|
|
341
|
+
`(unfracking ENABLED: the dispatcher's unfracking arm dispatches to it)\n` +
|
|
342
|
+
` ${UNFRACKING_DISABLED} — UNFRACKING_DISABLED ` +
|
|
343
|
+
`(unfracking deployed and published, but the dispatcher's unfracking arm made ` +
|
|
344
|
+
`permanently unsatisfiable)\n` +
|
|
345
|
+
`A third value is a typo, a truncation, or a hash from a different deployment. ` +
|
|
346
|
+
`It is not inferable and it is not defaultable: it is what this dispatcher was ` +
|
|
347
|
+
`COMPILED AGAINST, so a wrong value here means every transaction that withdraws ` +
|
|
348
|
+
`through programmable_logic_global was built for a script that is not on chain.`);
|
|
349
|
+
}
|
|
350
|
+
function deriveDeploymentScripts(blueprint, deployment) {
|
|
351
|
+
// ⛔ BEFORE ANY DERIVATION, AND READ EXACTLY ONCE. Two reads of one recorded
|
|
352
|
+
// field are two chances to disagree — this module's own founding lesson — and
|
|
353
|
+
// a guard placed after the first read would be guarding a value the builder
|
|
354
|
+
// had already consumed. `readUnfrackingParameter` refuses an absent or
|
|
355
|
+
// inherited property and a malformed one; `refuseOnUnfrackingParameter` (run
|
|
356
|
+
// after the hash checks, see the call sites) refuses a well-formed value that
|
|
357
|
+
// is not one of the two this deployment may record.
|
|
358
|
+
//
|
|
359
|
+
// ⚠ IT VALIDATES, IT DOES NOT TRANSFORM. The string handed to the builder
|
|
360
|
+
// below is the same string the record holds — no trim, no lowercase, no
|
|
361
|
+
// 0x-strip — so this guard cannot move a single derived hash.
|
|
362
|
+
const recordedUnfrackingParameter = readUnfrackingParameter(deployment, blueprint.preamble.title);
|
|
165
363
|
const builders = createStandardScripts(blueprint);
|
|
364
|
+
const plb = deployment.programmableLogicBase.scriptHash;
|
|
365
|
+
const registryPolicy = deployment.registry.scriptHash;
|
|
366
|
+
const paramsPolicy = deployment.protocolParams.policyId;
|
|
367
|
+
const mid = deployment.maxInlineDatumBytes;
|
|
368
|
+
// ⚑ ONE call per script. Every `derived` field below reads a `.hash` off one
|
|
369
|
+
// of these objects, and `scripts` returns the very same objects — so a wrong
|
|
370
|
+
// argument here is caught by the check beside it rather than surviving into
|
|
371
|
+
// the resolved surface.
|
|
372
|
+
const protocolParams = builders.protocolParams(deployment.protocolParams.txInput);
|
|
373
|
+
const programmableLogicBase = builders.programmableLogicBase(paramsPolicy);
|
|
374
|
+
const transfer = builders.transfer(plb, registryPolicy, mid);
|
|
375
|
+
const thirdParty = builders.thirdParty(plb, registryPolicy, mid);
|
|
376
|
+
const unfracking = builders.unfracking(plb, registryPolicy, mid);
|
|
377
|
+
// ⛔ THE THIRD ARGUMENT IS READ FROM THE RECORD, NOT FROM `unfracking`. A
|
|
378
|
+
// deployment may compile this dispatcher against `UNFRACKING_DISABLED` while
|
|
379
|
+
// deploying, publishing and recording the REAL unfracking script beside it,
|
|
380
|
+
// so `deployment.unfracking.scriptHash` is NOT the value that was hashed —
|
|
381
|
+
// only `programmableLogicGlobal.unfrackingParameter` says what was. Reading
|
|
382
|
+
// the wrong one derives a dispatcher hash for a script nobody deployed, and
|
|
383
|
+
// the failure surfaces at withdrawal time naming neither field.
|
|
384
|
+
//
|
|
385
|
+
// ⚠ It is used unvalidated HERE and refused by `refuseOnUnfrackingParameter`
|
|
386
|
+
// before either public entry point returns — see the call sites for why the
|
|
387
|
+
// refusal is sequenced after the hash mismatches rather than before them.
|
|
388
|
+
const programmableLogicGlobal = builders.programmableLogicGlobal(deployment.transfer.scriptHash, deployment.thirdParty.scriptHash, recordedUnfrackingParameter);
|
|
389
|
+
const issuanceCborHexMint = builders.issuanceCborHexMint(deployment.issuance.txInput, deployment.issuance.alwaysFailScriptHash);
|
|
390
|
+
const registry = builders.registry(deployment.registry.txInput, deployment.registry.issuanceScriptHash);
|
|
391
|
+
const issuanceLogic = builders.issuanceLogic(plb, registryPolicy, paramsPolicy, mid);
|
|
392
|
+
const upgradeMultisig = builders.upgradeMultisig(deployment.upgradeMultisig.txInput);
|
|
166
393
|
const checks = [
|
|
167
394
|
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
derived: builders.protocolParamsMint(deployment.protocolParams.txInput, deployment.protocolParams.coordinationScriptHash).hash,
|
|
395
|
+
// ⚑ ONE derivation for what used to be two. The params NFT policy and the
|
|
396
|
+
// params address's payment credential are the same value in alpha.3, so
|
|
397
|
+
// deriving them separately is not "belt and braces" — it is two chances
|
|
398
|
+
// to disagree about one fact.
|
|
399
|
+
name: "protocol_params (policy == address)",
|
|
400
|
+
derived: protocolParams.hash,
|
|
175
401
|
deployed: deployment.protocolParams.policyId,
|
|
176
402
|
},
|
|
177
403
|
{
|
|
178
404
|
name: "programmable_logic_base",
|
|
179
|
-
derived:
|
|
180
|
-
deployed:
|
|
405
|
+
derived: programmableLogicBase.hash,
|
|
406
|
+
deployed: plb,
|
|
181
407
|
},
|
|
182
408
|
{
|
|
183
409
|
name: "transfer",
|
|
184
|
-
derived:
|
|
410
|
+
derived: transfer.hash,
|
|
185
411
|
deployed: deployment.transfer.scriptHash,
|
|
186
412
|
},
|
|
187
413
|
{
|
|
188
414
|
name: "third_party",
|
|
189
|
-
derived:
|
|
415
|
+
derived: thirdParty.hash,
|
|
190
416
|
deployed: deployment.thirdParty.scriptHash,
|
|
191
417
|
},
|
|
192
418
|
{
|
|
193
419
|
name: "unfracking",
|
|
194
|
-
derived:
|
|
420
|
+
derived: unfracking.hash,
|
|
195
421
|
deployed: deployment.unfracking.scriptHash,
|
|
196
422
|
},
|
|
423
|
+
{
|
|
424
|
+
// ⛔ THE COHERENCE CHECK THE LEDGER CANNOT DO. A script cannot read
|
|
425
|
+
// another script's parameters, so nothing on chain verifies that the
|
|
426
|
+
// dispatcher was compiled against THESE three delegates. Deriving it from
|
|
427
|
+
// the deployment's own delegate hashes is the only place that mismatch
|
|
428
|
+
// can be caught — and a dispatcher naming stale delegates fails at
|
|
429
|
+
// withdrawal time with an index error, never with "wrong dispatcher".
|
|
430
|
+
//
|
|
431
|
+
// ⚠ AND IT IS NOW REACHABLE FROM THREE CAUSES, NOT ONE. Its name says
|
|
432
|
+
// "stale delegates", but the third derivation input is the RECORDED
|
|
433
|
+
// `programmableLogicGlobal.unfrackingParameter` — so a record that
|
|
434
|
+
// compiled against `UNFRACKING_DISABLED` and then wrote the enabled
|
|
435
|
+
// dispatcher's hash (or the reverse) fails HERE, with a name pointing at
|
|
436
|
+
// the two fields that are fine. The parameter is also the only one of the
|
|
437
|
+
// three that cannot be cross-checked against anything else in the record.
|
|
438
|
+
// `dispatcherCauseHint` names all three in the thrown message; do not let
|
|
439
|
+
// this comment and that message drift apart.
|
|
440
|
+
name: PLG_CHECK_NAME,
|
|
441
|
+
derived: programmableLogicGlobal.hash,
|
|
442
|
+
deployed: deployment.programmableLogicGlobal.scriptHash,
|
|
443
|
+
},
|
|
197
444
|
{
|
|
198
445
|
name: "issuance_cbor_hex_mint",
|
|
199
|
-
derived:
|
|
446
|
+
derived: issuanceCborHexMint.hash,
|
|
200
447
|
deployed: deployment.issuance.policyId,
|
|
201
448
|
},
|
|
202
449
|
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
450
|
+
// ⚑ Also one derivation for what used to be two (registry_mint's policy
|
|
451
|
+
// and registry_spend's address).
|
|
452
|
+
name: "registry (policy == address)",
|
|
453
|
+
derived: registry.hash,
|
|
454
|
+
deployed: registryPolicy,
|
|
206
455
|
},
|
|
207
456
|
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
457
|
+
// ⛔ THE FOURTH CONSUMER OF `mid`. transfer, third_party and unfracking
|
|
458
|
+
// were three; alpha.4's issuance_logic is the fourth, and the count is
|
|
459
|
+
// the mechanism: the negative test asserting FOUR mismatches for a wrong
|
|
460
|
+
// `maxInlineDatumBytes` is what proves this call site exists. A comment
|
|
461
|
+
// cannot prove a call site; a count can.
|
|
462
|
+
//
|
|
463
|
+
// ⚠ Argument order is `(plb, registry_node_cs, params_policy, mid)` and
|
|
464
|
+
// the middle two are both bare PolicyIds. Swapping them builds, hashes
|
|
465
|
+
// and deploys — see the adjacent-parameter negative in
|
|
466
|
+
// test/deployment-assertion.test.mjs.
|
|
467
|
+
name: "issuance_logic",
|
|
468
|
+
derived: issuanceLogic.hash,
|
|
469
|
+
deployed: deployment.issuanceLogic.scriptHash,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
// ⛔ CHECKED AGAIN, AND THE REASON IT CAN BE IS THE WHOLE CHANGE. This
|
|
473
|
+
// check was REMOVED in S-11 after it shipped a defect: it derived
|
|
474
|
+
// `upgrade_multisig` from `upgradeAuthority.hash`, a relationship that
|
|
475
|
+
// never existed (a PAYMENT key hash matched against `extra_signatories`
|
|
476
|
+
// versus the STAKE credential named in the params datum). alpha.3 could
|
|
477
|
+
// not derive it at all — its signer set and threshold were a deployment
|
|
478
|
+
// CHOICE that DeploymentParams did not record. alpha.4 replaces both
|
|
479
|
+
// parameters with a single `utxo_ref`, which IS recorded, so the hash is
|
|
480
|
+
// derivable and the check comes back.
|
|
481
|
+
//
|
|
482
|
+
// ⚠ AND THE VACUITY TRAP COMES BACK WITH IT, IN A NEW SHAPE. What hid the
|
|
483
|
+
// S-11 bug was a fixture using ONE value for two fields, so the wrong
|
|
484
|
+
// derivation reproduced perfectly and the check passed vacuously — only a
|
|
485
|
+
// live devnet exposed it. DeploymentParams now holds TWO one-shot
|
|
486
|
+
// `TxInput`s of identical type: `protocolParams.txInput` and
|
|
487
|
+
// `upgradeMultisig.txInput`. A fixture that reuses one for both makes
|
|
488
|
+
// this check pass no matter which one the code reads.
|
|
489
|
+
//
|
|
490
|
+
// ⛔ Do NOT add any check relating `upgradeAuthority` to
|
|
491
|
+
// `upgradeMultisig`. A deployment may legitimately name a key credential,
|
|
492
|
+
// a different script, or the multisig as its authority, and the validator
|
|
493
|
+
// never inspects it. Such a check would reject valid deployments — it is
|
|
494
|
+
// the S-11 defect returning under a new name.
|
|
495
|
+
name: "upgrade_multisig",
|
|
496
|
+
derived: upgradeMultisig.hash,
|
|
497
|
+
deployed: deployment.upgradeMultisig.scriptHash,
|
|
211
498
|
},
|
|
212
499
|
];
|
|
500
|
+
return {
|
|
501
|
+
builders,
|
|
502
|
+
paramsPolicy,
|
|
503
|
+
unfrackingParameter: {
|
|
504
|
+
recorded: recordedUnfrackingParameter,
|
|
505
|
+
derivedUnfrackingHash: unfracking.hash,
|
|
506
|
+
},
|
|
507
|
+
scripts: {
|
|
508
|
+
protocolParams,
|
|
509
|
+
programmableLogicBase,
|
|
510
|
+
transfer,
|
|
511
|
+
thirdParty,
|
|
512
|
+
unfracking,
|
|
513
|
+
programmableLogicGlobal,
|
|
514
|
+
issuanceCborHexMint,
|
|
515
|
+
registry,
|
|
516
|
+
issuanceLogic,
|
|
517
|
+
upgradeMultisig,
|
|
518
|
+
},
|
|
519
|
+
checks,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
/** Throw if any derived hash disagrees with the deployment record. */
|
|
523
|
+
function refuseOnMismatch(checks, blueprintTitle) {
|
|
213
524
|
const mismatches = checks.filter((c) => c.derived !== c.deployed);
|
|
214
525
|
if (mismatches.length > 0) {
|
|
215
|
-
throw new DeploymentMismatchError(mismatches,
|
|
526
|
+
throw new DeploymentMismatchError(mismatches, blueprintTitle);
|
|
216
527
|
}
|
|
528
|
+
}
|
|
529
|
+
export function assertDeploymentScripts(blueprint, deployment) {
|
|
530
|
+
const { checks, unfrackingParameter } = deriveDeploymentScripts(blueprint, deployment);
|
|
531
|
+
refuseOnMismatch(checks, blueprint.preamble.title);
|
|
532
|
+
// ⚠ ORDER IS DELIBERATE, AND IT IS NOT A CONDITIONAL. A wrong
|
|
533
|
+
// `maxInlineDatumBytes` moves the derived `unfracking` hash, which would let a
|
|
534
|
+
// parameter complaint pre-empt — and hide — the four named delegate
|
|
535
|
+
// mismatches that say what actually went wrong. Hash disagreements are
|
|
536
|
+
// reported first, by name; this refusal is what is left when all ten
|
|
537
|
+
// reproduce and the record is nevertheless not a legal deployment.
|
|
538
|
+
refuseOnUnfrackingParameter(unfrackingParameter.recorded, unfrackingParameter.derivedUnfrackingHash, blueprint.preamble.title);
|
|
217
539
|
return checks;
|
|
218
540
|
}
|
|
219
541
|
/**
|
|
@@ -225,21 +547,30 @@ export function assertDeploymentScripts(blueprint, deployment) {
|
|
|
225
547
|
* undetectable until submission.
|
|
226
548
|
*/
|
|
227
549
|
export function buildDeploymentScripts(blueprint, deployment) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
550
|
+
// ⚑ ONE FACT, ONE DERIVATION — and this line is what makes that phrase true
|
|
551
|
+
// rather than aspirational. The scripts returned below are the SAME OBJECTS
|
|
552
|
+
// whose hashes were just checked, not a second build from the same arguments.
|
|
553
|
+
// A second build is a second chance to disagree, and it WAS unguarded here:
|
|
554
|
+
// see the block on `DerivedDeployment`.
|
|
555
|
+
const { builders, paramsPolicy, scripts, checks, unfrackingParameter } = deriveDeploymentScripts(blueprint, deployment);
|
|
556
|
+
refuseOnMismatch(checks, blueprint.preamble.title);
|
|
557
|
+
// Same refusal, same order, as `assertDeploymentScripts` — the resolved
|
|
558
|
+
// surface must never be built from a parameter the assertion would reject.
|
|
559
|
+
refuseOnUnfrackingParameter(unfrackingParameter.recorded, unfrackingParameter.derivedUnfrackingHash, blueprint.preamble.title);
|
|
231
560
|
return {
|
|
232
|
-
|
|
233
|
-
protocolParamsMint: builders.protocolParamsMint(deployment.protocolParams.txInput, deployment.protocolParams.coordinationScriptHash),
|
|
234
|
-
programmableLogicBase: builders.programmableLogicBase(paramsPolicy),
|
|
235
|
-
transfer: builders.transfer(paramsPolicy),
|
|
236
|
-
thirdParty: builders.thirdParty(paramsPolicy),
|
|
237
|
-
unfracking: builders.unfracking(paramsPolicy),
|
|
238
|
-
issuanceCborHexMint: builders.issuanceCborHexMint(deployment.issuance.txInput, deployment.issuance.alwaysFailScriptHash),
|
|
239
|
-
registryMint: builders.registryMint(deployment.directoryMint.txInput, deployment.issuance.policyId, deployment.directorySpend.scriptHash),
|
|
240
|
-
registrySpend: builders.registrySpend(paramsPolicy),
|
|
561
|
+
...scripts,
|
|
241
562
|
buildIssuanceMint(mintingLogicHash) {
|
|
242
|
-
|
|
563
|
+
// ⚑ ITS SIGNATURE IS UNCHANGED AND ITS RESULT IS NOT. alpha.4 dropped
|
|
564
|
+
// `programmable_logic_base` and `registry_node_cs` from issuance_mint's
|
|
565
|
+
// parameters, so THE POLICY ID CHANGES FOR THE SAME MINTING LOGIC. That
|
|
566
|
+
// is correct, not a bug to fix: an alpha.3 token and an alpha.4 token
|
|
567
|
+
// built from identical issuance logic are different assets.
|
|
568
|
+
//
|
|
569
|
+
// ⚠ NOT part of the single-derivation collapse above, and cannot be: it
|
|
570
|
+
// is parameterised per minting logic, so there is no one hash for
|
|
571
|
+
// `assertDeploymentScripts` to check. It stays uncovered, as the
|
|
572
|
+
// assertion's own "Not covered" note says.
|
|
573
|
+
return builders.issuanceMint(mintingLogicHash, paramsPolicy);
|
|
243
574
|
},
|
|
244
575
|
};
|
|
245
576
|
}
|