sememe 0.0.0-stage → 0.4.22
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 +2777 -2
- package/bin/sememe.mjs +556 -0
- package/index.mjs +48 -0
- package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
- package/interface/native-11111111111111111111111111111111.json +226 -0
- package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
- package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
- package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
- package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
- package/interface/token-metadata.json +132 -0
- package/package.json +71 -4
- package/src/account-list.mjs +308 -0
- package/src/account-union.mjs +523 -0
- package/src/account.mjs +199 -0
- package/src/acquire.mjs +145 -0
- package/src/adopt.mjs +206 -0
- package/src/ata-interface.mjs +276 -0
- package/src/ata.mjs +223 -0
- package/src/base58.mjs +96 -0
- package/src/canonical.mjs +24 -0
- package/src/cli-surface.mjs +300 -0
- package/src/codec.mjs +1118 -0
- package/src/conformance.mjs +431 -0
- package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
- package/src/corpus/harvest-system-2026-10-31.json +26090 -0
- package/src/decode.mjs +286 -0
- package/src/dialect.mjs +180 -0
- package/src/discriminators.mjs +192 -0
- package/src/drift.mjs +172 -0
- package/src/gap.mjs +205 -0
- package/src/lighthouse.mjs +168 -0
- package/src/lock.mjs +440 -0
- package/src/native-audit.mjs +56 -0
- package/src/native-corpus.mjs +57 -0
- package/src/native-interface.mjs +252 -0
- package/src/native.mjs +324 -0
- package/src/peer.mjs +95 -0
- package/src/program-id.mjs +74 -0
- package/src/pull.mjs +117 -0
- package/src/rawle.mjs +454 -0
- package/src/raydium-interface.mjs +747 -0
- package/src/raydium.mjs +391 -0
- package/src/receipt-digest.mjs +84 -0
- package/src/receipt-name.mjs +128 -0
- package/src/receipt-order.mjs +56 -0
- package/src/shank.mjs +73 -0
- package/src/short-forms.mjs +209 -0
- package/src/store-read.mjs +253 -0
- package/src/store-write.mjs +46 -0
- package/src/token-interface.mjs +394 -0
- package/src/token-metadata-interface.mjs +371 -0
- package/src/token-metadata.mjs +317 -0
- package/src/token.mjs +443 -0
- package/src/union.mjs +428 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
package/package.json
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sememe",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.22",
|
|
4
|
+
"description": "Local, fingerprint-verified Solana program interfaces: pull an IDL once, then decode instructions, accounts and events offline. Zero dependencies on the decode path. Ships derived wire tables for System, ComputeBudget, SPL Token, Token-2022 (incl. its token-metadata fallback), Raydium AMM v4 and the SPL Associated Token Account, pinned to upstream Rust.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.mjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"sememe": "bin/sememe.mjs"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./index.mjs",
|
|
13
|
+
"./native": "./src/native.mjs",
|
|
14
|
+
"./store": "./src/store-read.mjs",
|
|
15
|
+
"./pull": "./src/pull.mjs",
|
|
16
|
+
"./codec": "./src/codec.mjs",
|
|
17
|
+
"./canonical": "./src/canonical.mjs",
|
|
18
|
+
"./discriminators": "./src/discriminators.mjs",
|
|
19
|
+
"./native-corpus": "./src/native-corpus.mjs",
|
|
20
|
+
"./native-audit": "./src/native-audit.mjs",
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
"./token": "./src/token.mjs",
|
|
23
|
+
"./token-metadata": "./src/token-metadata.mjs",
|
|
24
|
+
"./raydium": "./src/raydium.mjs",
|
|
25
|
+
"./ata": "./src/ata.mjs",
|
|
26
|
+
"./lighthouse": "./src/lighthouse.mjs",
|
|
27
|
+
"./gap": "./src/gap.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"index.mjs",
|
|
31
|
+
"src/",
|
|
32
|
+
"interface/",
|
|
33
|
+
"vendor/",
|
|
34
|
+
"bin/",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node --test",
|
|
40
|
+
"crosscheck": "node tools/crosscheck.mjs",
|
|
41
|
+
"txdiff": "node tools/tx-diff.mjs",
|
|
42
|
+
"harvest": "node tools/harvest.mjs",
|
|
43
|
+
"tail-hypotheses": "node tools/tail-hypotheses.mjs",
|
|
44
|
+
"tail-structure": "node tools/tail-structure.mjs"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@solana/web3.js": "^1.98.4"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@solana/web3.js": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@solana/web3.js": "1.98.4"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/auroraxo/sememe.git"
|
|
63
|
+
},
|
|
64
|
+
"keywords": [
|
|
65
|
+
"solana",
|
|
66
|
+
"anchor",
|
|
67
|
+
"idl",
|
|
68
|
+
"decoder",
|
|
69
|
+
"borsh",
|
|
70
|
+
"offline",
|
|
71
|
+
"instruction-decoding"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Account-list conformance: the DECLARED interface against the accounts a
|
|
2
|
+
// transaction actually passed.
|
|
3
|
+
//
|
|
4
|
+
// Why this file exists: everything else sememe verifies is BYTES. But a
|
|
5
|
+
// Solana instruction is bytes AND an account list, and the account list has
|
|
6
|
+
// its own defect class, measured on live mainnet traffic of a foreign
|
|
7
|
+
// first-party program (jup-ag order_engine, reported as
|
|
8
|
+
// jup-ag/rfq-webhook-toolkit#96): the published interface declared 11
|
|
9
|
+
// accounts while every observed `fill` carried 12 or 13 — and the extra
|
|
10
|
+
// account at the end SIGNS every fill and pays the fee in 31/141. A consumer
|
|
11
|
+
// that builds its signer/authority reasoning from the interface alone is
|
|
12
|
+
// blind to an account that signs.
|
|
13
|
+
//
|
|
14
|
+
// So this module answers one question, offline, from the same verified
|
|
15
|
+
// store the decoder uses:
|
|
16
|
+
//
|
|
17
|
+
// Given an instruction of a stored IDL and the account list one
|
|
18
|
+
// transaction passed to it, does the interface describe that list?
|
|
19
|
+
//
|
|
20
|
+
// Verdicts (ordered by severity, first match wins):
|
|
21
|
+
// missing-declared-accounts the wire carries FEWER accounts than the
|
|
22
|
+
// interface declares non-optional positions
|
|
23
|
+
// for. The program cannot have accepted it.
|
|
24
|
+
// declared-signer-not-signing the interface says position k is a signer;
|
|
25
|
+
// the transaction does not have it signing.
|
|
26
|
+
// For a SUCCESSFUL transaction this is
|
|
27
|
+
// interface/wire disagreement, not a rule the
|
|
28
|
+
// program applied.
|
|
29
|
+
// fixed-address-mismatch the interface pins an address (e.g.
|
|
30
|
+
// system_program); the wire passed another.
|
|
31
|
+
// undeclared-signing-account the wire carries accounts beyond the
|
|
32
|
+
// declared list AND one of them signs: an
|
|
33
|
+
// authority the interface does not name. This
|
|
34
|
+
// is the finding class this file was built
|
|
35
|
+
// for.
|
|
36
|
+
// unknown-instruction the instruction name is not in the IDL.
|
|
37
|
+
//
|
|
38
|
+
// Informational, never a violation on their own:
|
|
39
|
+
// remaining-accounts extras beyond the declared list that do NOT
|
|
40
|
+
// sign — Anchor's remaining_accounts is a
|
|
41
|
+
// legitimate, very common pattern.
|
|
42
|
+
// signer-at-non-signer-position the interface does not declare a position
|
|
43
|
+
// as a signer but the account signed anyway
|
|
44
|
+
// (the fee payer sits in exactly this
|
|
45
|
+
// position often). Signing MORE than declared
|
|
46
|
+
// is harmless; signing LESS is not.
|
|
47
|
+
//
|
|
48
|
+
// Limits, stated with the code because they change what a verdict means:
|
|
49
|
+
// - An observed position whose signer status cannot be determined (no
|
|
50
|
+
// message header, mangled encoding) is recorded as `unknownSigner`
|
|
51
|
+
// and never produces a signer verdict in either direction: absence of
|
|
52
|
+
// evidence must not become evidence of absence.
|
|
53
|
+
// - Optional accounts: Anchor's own wire shape keeps a placeholder for an
|
|
54
|
+
// absent optional account, so a SHORT list whose missing tail is all
|
|
55
|
+
// optional is reported as `short-optional-tail` (informational), not as
|
|
56
|
+
// a missing-account violation. Which Anchor version a program was built
|
|
57
|
+
// with is not in the IDL, so this stays a separate verdict rather than
|
|
58
|
+
// being folded into `conforms`.
|
|
59
|
+
// - Writable-flag mismatches are reported per position (`writableNotes`)
|
|
60
|
+
// but never decide the verdict: the runtime grants/revokes permission
|
|
61
|
+
// from the transaction, not from the IDL, so an IDL that disagrees
|
|
62
|
+
// about writability is documentation drift, not an acceptance bug.
|
|
63
|
+
// - This checks ONE instruction against ONE transaction. It does not
|
|
64
|
+
// prove the program accepted anything (that is `txErr`, carried by the
|
|
65
|
+
// caller), and it does not prove the interface is wrong rather than the
|
|
66
|
+
// transaction: it names the disagreement.
|
|
67
|
+
|
|
68
|
+
/** The declared account list of an instruction, normalized from either IDL
|
|
69
|
+
* spec (new: signer/writable/optional; legacy: isSigner/isMut/isOptional).
|
|
70
|
+
* The optional flag is load-bearing for conformance: a declared-OPTIONAL
|
|
71
|
+
* signer that arrives without a signature is the account being absent, not
|
|
72
|
+
* a rule violation (measured on OpenBook v2's `cancelAllAndPlaceOrders`,
|
|
73
|
+
* whose legacy IDL marks `openOrdersAdmin` isOptional+isSigner and whose
|
|
74
|
+
* live traffic passes it unsigned whenever the admin is not needed). */
|
|
75
|
+
export function declaredAccounts(instruction) {
|
|
76
|
+
if (!instruction || !Array.isArray(instruction.accounts)) return null;
|
|
77
|
+
return instruction.accounts.map((entry, index) => ({
|
|
78
|
+
index,
|
|
79
|
+
name: entry?.name ?? null,
|
|
80
|
+
signer: Boolean(entry?.signer ?? entry?.isSigner),
|
|
81
|
+
writable: Boolean(entry?.writable ?? entry?.isMut),
|
|
82
|
+
optional: Boolean(entry?.optional ?? entry?.isOptional),
|
|
83
|
+
address: typeof entry?.address === 'string' ? entry.address : null,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Match payload bytes to an IDL instruction by its 8-byte discriminator
|
|
88
|
+
* (stored or derived by Anchor's rule). Returns null when no instruction
|
|
89
|
+
* carries these bytes. */
|
|
90
|
+
export function instructionByDiscriminator(idl, discriminatorBytes) {
|
|
91
|
+
if (!idl || !Array.isArray(discriminatorBytes) || discriminatorBytes.length !== 8) return null;
|
|
92
|
+
const instructions = idl.instructions ?? [];
|
|
93
|
+
const match = instructions.find((entry) => {
|
|
94
|
+
const stored = Array.isArray(entry?.discriminator) ? entry.discriminator : null;
|
|
95
|
+
if (!stored || stored.length !== discriminatorBytes.length) return false;
|
|
96
|
+
return stored.every((byte, index) => byte === discriminatorBytes[index]);
|
|
97
|
+
});
|
|
98
|
+
return match ?? null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Compare one instruction's declared account list with one observed list.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} instruction IDL instruction (may be null -> unknown-instruction)
|
|
105
|
+
* @param {Array} observed [{ address, signer?, writable? }] in wire
|
|
106
|
+
* order; `signer` true/false or absent.
|
|
107
|
+
* * @param {object} [options]
|
|
108
|
+
* @param {string} [options.program] program id, echoed into the verdict
|
|
109
|
+
* @param {string} [options.programId] program id of the instruction — when a
|
|
110
|
+
* DECLARED OPTIONAL position carries this
|
|
111
|
+
* address, Anchor's convention marks the
|
|
112
|
+
* account as ABSENT (the None placeholder
|
|
113
|
+
* is the program's own id). An absent
|
|
114
|
+
* optional account is not a signer and not
|
|
115
|
+
* a disagreement.
|
|
116
|
+
* @param {string} [options.name] instruction name if `instruction` is null
|
|
117
|
+
* @param {boolean} [options.executed] whether the transaction succeeded
|
|
118
|
+
* (meta.err == null). A failed transaction
|
|
119
|
+
* may have been rejected FOR one of these
|
|
120
|
+
* reasons; the verdict is tagged, not
|
|
121
|
+
* downgraded.
|
|
122
|
+
* @param {number} [options.depth] instruction call depth (0 = top-level,
|
|
123
|
+
* >0 = CPI). At depth > 0 the signer flag of
|
|
124
|
+
* an account is NOT observable from the
|
|
125
|
+
* top-level message header: the account may
|
|
126
|
+
* be a PDA signed for inside the CPI stack
|
|
127
|
+
* via `invoke_signed`, which never appears in
|
|
128
|
+
* `numRequiredSignatures` (measured on
|
|
129
|
+
* Whirlpool swap_v2 invoked via CPI by the
|
|
130
|
+
* Titan router). Such a position is reported
|
|
131
|
+
* as an informational note, never as a
|
|
132
|
+
* signature violation; at depth 0 the header
|
|
133
|
+
* IS the authority and the check stands.
|
|
134
|
+
*/
|
|
135
|
+
export function checkAccountList(instruction, observed, options = {}) {
|
|
136
|
+
const { program = null, programId = null, name = null, executed = null, depth = 0 } = options;
|
|
137
|
+
const base = { program, instruction: instruction?.name ?? name ?? null, executed, positions: { declared: null, observed: observed?.length ?? 0 } };
|
|
138
|
+
|
|
139
|
+
if (!instruction) {
|
|
140
|
+
return { ...base, verdict: 'unknown-instruction', violations: [], extras: [], notes: [] };
|
|
141
|
+
}
|
|
142
|
+
const declared = declaredAccounts(instruction);
|
|
143
|
+
if (!declared) {
|
|
144
|
+
return { ...base, verdict: 'unknown-instruction', violations: [], extras: [], notes: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const rows = observed ?? [];
|
|
148
|
+
const violations = [];
|
|
149
|
+
const notes = [];
|
|
150
|
+
const unknownSigner = [];
|
|
151
|
+
|
|
152
|
+
base.positions.declared = declared.length;
|
|
153
|
+
base.positions.required = declared.filter((entry) => !entry.optional).length;
|
|
154
|
+
|
|
155
|
+
// 1. Length: short lists, optional tails, extras.
|
|
156
|
+
let extras = [];
|
|
157
|
+
if (rows.length < declared.length) {
|
|
158
|
+
const missing = declared.slice(rows.length);
|
|
159
|
+
const missingNonOptional = missing.filter((entry) => !entry.optional);
|
|
160
|
+
if (missingNonOptional.length) {
|
|
161
|
+
violations.push({
|
|
162
|
+
kind: 'missing-declared-accounts',
|
|
163
|
+
missing: missing.map((entry) => ({ index: entry.index, name: entry.name, optional: entry.optional })),
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
notes.push({
|
|
167
|
+
kind: 'short-optional-tail',
|
|
168
|
+
missing: missing.map((entry) => ({ index: entry.index, name: entry.name })),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
extras = rows.slice(declared.length).map((entry, offset) => ({
|
|
173
|
+
index: declared.length + offset,
|
|
174
|
+
address: entry?.address ?? null,
|
|
175
|
+
signer: entry?.signer ?? null,
|
|
176
|
+
writable: entry?.writable ?? null,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 2. Positional signer comparison over the declared range.
|
|
181
|
+
const limit = Math.min(declared.length, rows.length);
|
|
182
|
+
const writableNotes = [];
|
|
183
|
+
const absentOptional = [];
|
|
184
|
+
for (let index = 0; index < limit; index++) {
|
|
185
|
+
const want = declared[index];
|
|
186
|
+
const got = rows[index] ?? {};
|
|
187
|
+
const signerKnown = got.signer === true || got.signer === false;
|
|
188
|
+
|
|
189
|
+
// Anchor's absent-optional placeholder: the declared optional position
|
|
190
|
+
// carries the program's own id, which means "None" — not "this account".
|
|
191
|
+
// Comparing its signer flag would manufacture a violation out of the
|
|
192
|
+
// convention that avoids one.
|
|
193
|
+
//
|
|
194
|
+
// A declared-OPTIONAL signer that arrives without a signature is ALSO an
|
|
195
|
+
// absent optional account (Anchor lets the caller pass the program id or
|
|
196
|
+
// any non-signing placeholder when the signer is optional and absent;
|
|
197
|
+
// measured on OpenBook v2 `cancelAllAndPlaceOrders.openOrdersAdmin`).
|
|
198
|
+
const placeholder = want.optional && (
|
|
199
|
+
(programId && got.address === programId) || (want.signer && got.signer === false)
|
|
200
|
+
);
|
|
201
|
+
if (placeholder) {
|
|
202
|
+
absentOptional.push({ index, name: want.name, signer: want.signer });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!signerKnown) {
|
|
207
|
+
unknownSigner.push({ index, name: want.name, declaredSigner: want.signer });
|
|
208
|
+
} else if (want.signer && got.signer === false) {
|
|
209
|
+
// At depth > 0 the signer flag cannot be resolved from the top-level
|
|
210
|
+
// header: the declaring position may be a PDA signed inside the CPI
|
|
211
|
+
// stack via `invoke_signed`, which never shows in
|
|
212
|
+
// `numRequiredSignatures`. Treating it as a violation would be a false
|
|
213
|
+
// positive from measuring the wrong boundary.
|
|
214
|
+
if (depth > 0) {
|
|
215
|
+
notes.push({
|
|
216
|
+
kind: 'cpi-signer-not-observable',
|
|
217
|
+
index, name: want.name, depth,
|
|
218
|
+
reason: 'the top-level message header does not carry CPI signer authority (invoke_signed PDAs)',
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
violations.push({
|
|
222
|
+
kind: 'declared-signer-not-signing',
|
|
223
|
+
index, name: want.name,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
} else if (!want.signer && got.signer === true) {
|
|
227
|
+
notes.push({ kind: 'signer-at-non-signer-position', index, name: want.name });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (want.address && got.address && want.address !== got.address) {
|
|
231
|
+
violations.push({
|
|
232
|
+
kind: 'fixed-address-mismatch',
|
|
233
|
+
index, name: want.name, declared: want.address, observed: got.address,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (typeof got.writable === 'boolean' && got.writable !== want.writable) {
|
|
238
|
+
writableNotes.push({ index, name: want.name, declaredWritable: want.writable, observedWritable: got.writable });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 3. The extras: signing extras are an authority the interface does not name.
|
|
243
|
+
const signingExtras = extras.filter((entry) => entry.signer === true);
|
|
244
|
+
if (signingExtras.length) {
|
|
245
|
+
violations.push({
|
|
246
|
+
kind: 'undeclared-signing-account',
|
|
247
|
+
accounts: signingExtras,
|
|
248
|
+
extraCount: extras.length,
|
|
249
|
+
});
|
|
250
|
+
} else if (extras.length) {
|
|
251
|
+
notes.push({ kind: 'remaining-accounts', extraCount: extras.length, addresses: extras.map((entry) => entry.address) });
|
|
252
|
+
}
|
|
253
|
+
if (absentOptional.length) {
|
|
254
|
+
notes.push({ kind: 'absent-optional-accounts', accounts: absentOptional });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const verdict = violations.length
|
|
258
|
+
? violations[0].kind
|
|
259
|
+
: (notes.some((entry) => entry.kind === 'short-optional-tail') ? 'short-optional-tail'
|
|
260
|
+
: (extras.length ? 'remaining-accounts' : 'conforms'));
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
...base,
|
|
264
|
+
verdict,
|
|
265
|
+
violations,
|
|
266
|
+
extras,
|
|
267
|
+
notes: [...notes, ...(writableNotes.length ? [{ kind: 'writable-disagreement', positions: writableNotes }] : [])],
|
|
268
|
+
unknownSigner,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Aggregate a list of per-instruction verdicts into the report shape.
|
|
274
|
+
* Pure: the live tool only collects rows, this derives every number in the
|
|
275
|
+
* summary from them (a hand-edited summary cannot disagree with its rows).
|
|
276
|
+
*/
|
|
277
|
+
export function summarizeConformance(rows) {
|
|
278
|
+
const summary = {
|
|
279
|
+
instructions: rows.length,
|
|
280
|
+
byVerdict: {},
|
|
281
|
+
byProgram: {},
|
|
282
|
+
byInstruction: {},
|
|
283
|
+
executed: { total: 0, disagreements: 0 },
|
|
284
|
+
unknownDiscriminator: 0,
|
|
285
|
+
unknownSignerPositions: 0,
|
|
286
|
+
};
|
|
287
|
+
for (const row of rows) {
|
|
288
|
+
summary.byVerdict[row.verdict] = (summary.byVerdict[row.verdict] ?? 0) + 1;
|
|
289
|
+
if (row.verdict === 'unknown-discriminator') summary.unknownDiscriminator += 1;
|
|
290
|
+
const program = row.program ?? '(unknown)';
|
|
291
|
+
if (!summary.byProgram[program]) {
|
|
292
|
+
summary.byProgram[program] = { instructions: 0, verdicts: {} };
|
|
293
|
+
}
|
|
294
|
+
summary.byProgram[program].instructions += 1;
|
|
295
|
+
summary.byProgram[program].verdicts[row.verdict] = (summary.byProgram[program].verdicts[row.verdict] ?? 0) + 1;
|
|
296
|
+
const name = row.instruction ?? '(none)';
|
|
297
|
+
if (!summary.byInstruction[name]) summary.byInstruction[name] = { total: 0, verdicts: {} };
|
|
298
|
+
summary.byInstruction[name].total += 1;
|
|
299
|
+
summary.byInstruction[name].verdicts[row.verdict] = (summary.byInstruction[name].verdicts[row.verdict] ?? 0) + 1;
|
|
300
|
+
if (row.executed === true || row.executed === false) {
|
|
301
|
+
summary.executed.total += 1;
|
|
302
|
+
const disagreeing = Array.isArray(row.violations) && row.violations.length > 0;
|
|
303
|
+
if (disagreeing) summary.executed.disagreements += 1;
|
|
304
|
+
}
|
|
305
|
+
summary.unknownSignerPositions += Array.isArray(row.unknownSigner) ? row.unknownSigner.length : 0;
|
|
306
|
+
}
|
|
307
|
+
return summary;
|
|
308
|
+
}
|