@crediolabs/policy-synth 0.1.18 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/install/authority-overlap.d.ts +134 -0
- package/dist/install/authority-overlap.js +0 -0
- package/dist/install/build-add-context-rule.d.ts +8 -0
- package/dist/install/build-add-context-rule.js +1 -1
- package/dist/install/build-merge-policy.d.ts +70 -0
- package/dist/install/build-merge-policy.js +130 -0
- package/dist/install/index.d.ts +2 -0
- package/dist/install/index.js +7 -0
- package/dist/install/plan-merge-policy.d.ts +49 -0
- package/dist/install/plan-merge-policy.js +86 -0
- package/dist/install/read-account-rules.d.ts +100 -0
- package/dist/install/read-account-rules.js +283 -0
- package/dist/run/index.d.ts +93 -8
- package/dist/run/index.js +282 -11
- package/dist/run/schemas.d.ts +290 -11
- package/dist/run/schemas.js +77 -11
- package/dist-cjs/install/authority-overlap.d.ts +134 -0
- package/dist-cjs/install/authority-overlap.js +0 -0
- package/dist-cjs/install/build-add-context-rule.d.ts +8 -0
- package/dist-cjs/install/build-add-context-rule.js +1 -0
- package/dist-cjs/install/build-merge-policy.d.ts +70 -0
- package/dist-cjs/install/build-merge-policy.js +134 -0
- package/dist-cjs/install/index.d.ts +2 -0
- package/dist-cjs/install/index.js +23 -2
- package/dist-cjs/install/plan-merge-policy.d.ts +49 -0
- package/dist-cjs/install/plan-merge-policy.js +90 -0
- package/dist-cjs/install/read-account-rules.d.ts +100 -0
- package/dist-cjs/install/read-account-rules.js +296 -0
- package/dist-cjs/run/index.d.ts +93 -8
- package/dist-cjs/run/index.js +283 -10
- package/dist-cjs/run/schemas.d.ts +290 -11
- package/dist-cjs/run/schemas.js +78 -12
- package/package.json +1 -1
- package/src/install/authority-overlap.ts +0 -0
- package/src/install/build-add-context-rule.ts +12 -1
- package/src/install/build-merge-policy.ts +219 -0
- package/src/install/index.ts +34 -0
- package/src/install/plan-merge-policy.ts +133 -0
- package/src/install/read-account-rules.ts +376 -0
- package/src/run/index.ts +386 -14
- package/src/run/schemas.ts +84 -11
package/dist-cjs/run/index.js
CHANGED
|
@@ -18,20 +18,28 @@
|
|
|
18
18
|
// No business logic. No retries. No session state. The same call shape can
|
|
19
19
|
// drive the CLI (which calls into the same core directly without MCP).
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.VerifyPolicyInputSchema = exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.SimulatePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.
|
|
21
|
+
exports.VerifyPolicyInputSchema = exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.SimulatePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.OzAdapterConfigSchema = exports.OraclePriceFixtureSchema = exports.NetworkSchema = exports.MandateSpecSchema = exports.MAINNET_RPC_URL = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.ComposeUserResponsesSchema = void 0;
|
|
22
22
|
exports.runRecordTransaction = runRecordTransaction;
|
|
23
23
|
exports.runSynthesizePolicy = runSynthesizePolicy;
|
|
24
24
|
exports.runSimulatePolicy = runSimulatePolicy;
|
|
25
25
|
exports.runVerifyPolicy = runVerifyPolicy;
|
|
26
26
|
exports.runInstallPolicy = runInstallPolicy;
|
|
27
|
+
exports.enforceAuthorityScan = enforceAuthorityScan;
|
|
28
|
+
exports.runMergePolicy = runMergePolicy;
|
|
27
29
|
exports.runRevokePolicy = runRevokePolicy;
|
|
28
30
|
exports.runGetInterpreterInfo = runGetInterpreterInfo;
|
|
29
31
|
exports.caughtError = caughtError;
|
|
30
32
|
const node_crypto_1 = require("node:crypto");
|
|
31
33
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
32
34
|
const index_ts_1 = require("../index.js");
|
|
35
|
+
const authority_overlap_ts_1 = require("../install/authority-overlap.js");
|
|
33
36
|
const build_install_policy_ts_1 = require("../install/build-install-policy.js");
|
|
37
|
+
const build_merge_policy_ts_1 = require("../install/build-merge-policy.js");
|
|
34
38
|
const get_interpreter_info_ts_1 = require("../install/get-interpreter-info.js");
|
|
39
|
+
const plan_merge_policy_ts_1 = require("../install/plan-merge-policy.js");
|
|
40
|
+
const read_account_rules_ts_1 = require("../install/read-account-rules.js");
|
|
41
|
+
const decode_ts_1 = require("../predicate/decode.js");
|
|
42
|
+
const encode_ts_1 = require("../predicate/encode.js");
|
|
35
43
|
const index_ts_2 = require("../verify/index.js");
|
|
36
44
|
const schemas_ts_1 = require("./schemas.js");
|
|
37
45
|
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
@@ -52,7 +60,7 @@ Object.defineProperty(exports, "PINNED_INTERPRETER_ADDRESS_BY_NETWORK", { enumer
|
|
|
52
60
|
Object.defineProperty(exports, "PINNED_INTERPRETER_GRAMMAR_VERSION", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_GRAMMAR_VERSION; } });
|
|
53
61
|
Object.defineProperty(exports, "PINNED_INTERPRETER_MAINNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_MAINNET_ADDRESS; } });
|
|
54
62
|
Object.defineProperty(exports, "PINNED_INTERPRETER_TESTNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_TESTNET_ADDRESS; } });
|
|
55
|
-
Object.defineProperty(exports, "
|
|
63
|
+
Object.defineProperty(exports, "PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK; } });
|
|
56
64
|
Object.defineProperty(exports, "PredicateLeafSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateLeafSchema; } });
|
|
57
65
|
Object.defineProperty(exports, "PredicateNodeSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateNodeSchema; } });
|
|
58
66
|
Object.defineProperty(exports, "RecordedTransactionSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordedTransactionSchema; } });
|
|
@@ -290,6 +298,25 @@ async function runInstallPolicy(raw) {
|
|
|
290
298
|
const predicateHash = (0, node_crypto_1.createHash)('sha256')
|
|
291
299
|
.update(Buffer.from(encodedPredicate, 'base64'))
|
|
292
300
|
.digest('hex');
|
|
301
|
+
// ---- Cross-rule authority scan ----
|
|
302
|
+
// OZ enforces only the policies of the rule the caller names, so a signer
|
|
303
|
+
// who also sits in a wider rule keeps that wider authority no matter what
|
|
304
|
+
// this predicate says. Refuse by default when the wider rule has no policy
|
|
305
|
+
// at all, because that makes this install decorative.
|
|
306
|
+
const authorityScan = input.skipAuthorityScan
|
|
307
|
+
? { ran: false, skipped: true, reason: 'skipped at caller request', overlaps: [] }
|
|
308
|
+
: await scanAuthorityOverlap({
|
|
309
|
+
smartAccount: input.smartAccount,
|
|
310
|
+
interpreterAddress: expectedInterpreter,
|
|
311
|
+
rule: input.rule,
|
|
312
|
+
encodedPredicate,
|
|
313
|
+
rpcUrl: input.rpcUrl ?? expectedRpc,
|
|
314
|
+
network,
|
|
315
|
+
});
|
|
316
|
+
const overlapError = enforceAuthorityScan(authorityScan, input.allowAuthorityOverlap);
|
|
317
|
+
if (overlapError) {
|
|
318
|
+
return { ok: false, error: overlapError };
|
|
319
|
+
}
|
|
293
320
|
const result = await (0, build_install_policy_ts_1.buildInstallPolicyXdr)({
|
|
294
321
|
smartAccount: input.smartAccount,
|
|
295
322
|
sourceAccount: input.sourceAccount,
|
|
@@ -301,7 +328,7 @@ async function runInstallPolicy(raw) {
|
|
|
301
328
|
rpc: rpcClient,
|
|
302
329
|
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
303
330
|
});
|
|
304
|
-
return { ok: true, data: result };
|
|
331
|
+
return { ok: true, data: authorityScan ? { ...result, authorityScan } : result };
|
|
305
332
|
}
|
|
306
333
|
catch (e) {
|
|
307
334
|
return {
|
|
@@ -310,6 +337,252 @@ async function runInstallPolicy(raw) {
|
|
|
310
337
|
};
|
|
311
338
|
}
|
|
312
339
|
}
|
|
340
|
+
/** Default-deny on the cross-rule scan.
|
|
341
|
+
*
|
|
342
|
+
* Refuses whenever the scan cannot establish that this policy binds the calls
|
|
343
|
+
* it names. An unpoliced neighbour provably does not constrain them. An
|
|
344
|
+
* opaque one, policed by a contract this tool cannot decode, is not KNOWN to,
|
|
345
|
+
* and "not known to" is not "safe" - the same posture as the interpreter and
|
|
346
|
+
* RPC pins. An incomplete scan is refused for the same reason: the overlap
|
|
347
|
+
* list is then a subset of the account, so an empty list proves nothing.
|
|
348
|
+
*
|
|
349
|
+
* `not-restricting` is reported but does NOT block. Both rules are ours and
|
|
350
|
+
* both constrain the calls, and the conjunction remedy is offered; refusing
|
|
351
|
+
* there would also block the legitimate act of adding a separate capability,
|
|
352
|
+
* which OZ composes correctly as a union.
|
|
353
|
+
*
|
|
354
|
+
* Returns a ToolError or null, matching `enforceInterpreterPin`. */
|
|
355
|
+
function enforceAuthorityScan(scan, allowOverlap) {
|
|
356
|
+
if (!scan || allowOverlap === true)
|
|
357
|
+
return null;
|
|
358
|
+
// Default-deny by exclusion rather than by enumeration: anything that is not
|
|
359
|
+
// the one known-safe severity blocks. Listing the blocking severities
|
|
360
|
+
// instead would mean a severity added later silently passes until someone
|
|
361
|
+
// remembers to add it here, and the safe direction is the opposite.
|
|
362
|
+
const blocking = scan.overlaps.filter((o) => o.severity !== 'not-restricting');
|
|
363
|
+
const unpoliced = blocking.filter((o) => o.severity === 'bypass').map((o) => o.ruleId);
|
|
364
|
+
const opaque = blocking.filter((o) => o.severity === 'unknown').map((o) => o.ruleId);
|
|
365
|
+
const unrecognised = blocking
|
|
366
|
+
.filter((o) => o.severity !== 'bypass' && o.severity !== 'unknown')
|
|
367
|
+
.map((o) => o.ruleId);
|
|
368
|
+
if (blocking.length > 0) {
|
|
369
|
+
const parts = [
|
|
370
|
+
unpoliced.length > 0 ? `rule ${unpoliced.join(', ')} has no policy attached` : '',
|
|
371
|
+
opaque.length > 0
|
|
372
|
+
? `rule ${opaque.join(', ')} is policed by a contract this tool cannot decode`
|
|
373
|
+
: '',
|
|
374
|
+
unrecognised.length > 0
|
|
375
|
+
? `rule ${unrecognised.join(', ')} carries an overlap this build does not recognise`
|
|
376
|
+
: '',
|
|
377
|
+
].filter(Boolean);
|
|
378
|
+
return {
|
|
379
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
380
|
+
message: `install_policy: a signer of this rule can already make the same calls through another context rule, so this policy is not established to restrict them: ${parts.join('; ')}. Remove the shared signer from that rule, attach a policy this tool can read, or set allowAuthorityOverlap: true to install anyway`,
|
|
381
|
+
severity: 'error',
|
|
382
|
+
retryable: false,
|
|
383
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
// A scan that was meant to run and threw tells us nothing. Reporting it in
|
|
387
|
+
// a string field and proceeding is the same fail-open shape as an incomplete
|
|
388
|
+
// scan, and a caller that does not read `ran` cannot tell it from a clean
|
|
389
|
+
// result.
|
|
390
|
+
if (scan.ran === false && scan.skipped !== true) {
|
|
391
|
+
return {
|
|
392
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
393
|
+
message: `install_policy: the cross-rule authority scan could not run (${scan.reason ?? 'unknown error'}), so it cannot establish that this policy restricts anything; retry, or set allowAuthorityOverlap: true to install without that assurance`,
|
|
394
|
+
severity: 'error',
|
|
395
|
+
retryable: true,
|
|
396
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (scan.incomplete === true) {
|
|
400
|
+
return {
|
|
401
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
402
|
+
message: 'install_policy: the account has more context rules than the scan could account for, so the overlap result is incomplete and cannot establish that this policy restricts anything; set allowAuthorityOverlap: true to install without that assurance',
|
|
403
|
+
severity: 'error',
|
|
404
|
+
retryable: false,
|
|
405
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
/** Read the account's other context rules and report where this install's
|
|
411
|
+
* signers already hold authority over the same calls.
|
|
412
|
+
*
|
|
413
|
+
* A failure to read is reported rather than thrown, and the caller-facing
|
|
414
|
+
* decision is made by `enforceAuthorityScan`: a scan that tried and failed
|
|
415
|
+
* tells us nothing, so it refuses rather than passing as a clean account.
|
|
416
|
+
*
|
|
417
|
+
* The account data this trusts comes from whichever RPC answered, so a
|
|
418
|
+
* hostile RPC could describe an account with no overlapping rules. That is
|
|
419
|
+
* bounded by the pin already enforced above: `enforceRpcPin` returns before
|
|
420
|
+
* this runs, so the URL is the pinned one for the network unless the caller
|
|
421
|
+
* explicitly set `allowUnpinnedRpcUrl`. This scan deliberately does not add a
|
|
422
|
+
* second pin check, because two places deciding the same thing drift. */
|
|
423
|
+
async function scanAuthorityOverlap(args) {
|
|
424
|
+
try {
|
|
425
|
+
if (!args.encodedPredicate) {
|
|
426
|
+
// Not applicable rather than failed: this rule installs no interpreter
|
|
427
|
+
// predicate, so there is nothing of ours for another rule to undercut.
|
|
428
|
+
// Returning undefined keeps it out of the refusal path, which is
|
|
429
|
+
// reserved for scans that were meant to run and could not.
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
const server = new stellar_sdk_1.rpc.Server(args.rpcUrl, { allowHttp: false });
|
|
433
|
+
const reader = (0, read_account_rules_ts_1.accountRuleReaderFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[args.network]);
|
|
434
|
+
const collected = await (0, read_account_rules_ts_1.collectObservedRules)({
|
|
435
|
+
reader,
|
|
436
|
+
smartAccount: args.smartAccount,
|
|
437
|
+
interpreterAddress: args.interpreterAddress,
|
|
438
|
+
});
|
|
439
|
+
const overlaps = (0, authority_overlap_ts_1.findAuthorityOverlaps)({
|
|
440
|
+
intended: {
|
|
441
|
+
// `add_context_rule` always creates a NEW rule, so there is no id to
|
|
442
|
+
// exclude; -1 matches nothing on the account.
|
|
443
|
+
ruleId: -1,
|
|
444
|
+
contextType: toOverlapContextType(args.rule.contextRuleType),
|
|
445
|
+
signers: args.rule.signers,
|
|
446
|
+
predicate: (0, decode_ts_1.decodePredicate)(args.encodedPredicate),
|
|
447
|
+
},
|
|
448
|
+
existing: collected.rules,
|
|
449
|
+
});
|
|
450
|
+
return { ran: true, incomplete: collected.incomplete, overlaps };
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
return {
|
|
454
|
+
ran: false,
|
|
455
|
+
reason: e instanceof Error ? e.message : String(e),
|
|
456
|
+
overlaps: [],
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/** The rule draft names the callee `contract`; the analyser calls it
|
|
461
|
+
* `address`. Same value, two vocabularies. */
|
|
462
|
+
function toOverlapContextType(ct) {
|
|
463
|
+
switch (ct.kind) {
|
|
464
|
+
case 'call_contract':
|
|
465
|
+
return { kind: 'call_contract', address: ct.contract };
|
|
466
|
+
case 'create_contract':
|
|
467
|
+
return { kind: 'create_contract', wasmHash: ct.wasmHash };
|
|
468
|
+
default:
|
|
469
|
+
return { kind: 'default' };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/** `merge_policy` body - the tightening remedy for a cross-rule overlap.
|
|
473
|
+
*
|
|
474
|
+
* Replaces a rule's predicate with the conjunction of it and a new one. This
|
|
475
|
+
* is the action `install_policy` recommends when it reports an overlap
|
|
476
|
+
* between two rules our interpreter polices, and it is deliberately NOT
|
|
477
|
+
* something `install_policy` does on its own: it detaches a live policy, so
|
|
478
|
+
* the operator has to ask for it.
|
|
479
|
+
*
|
|
480
|
+
* Two transactions in order. `add_policy` refuses a policy already on the
|
|
481
|
+
* rule, so the old attachment goes first, and the second transaction cannot
|
|
482
|
+
* be simulated until the first confirms. */
|
|
483
|
+
async function runMergePolicy(raw) {
|
|
484
|
+
const parsed = schemas_ts_1.MergePolicyInputSchema.safeParse(raw);
|
|
485
|
+
if (!parsed.success) {
|
|
486
|
+
return { ok: false, error: validationError('install_policy', parsed.error.issues) };
|
|
487
|
+
}
|
|
488
|
+
const input = parsed.data;
|
|
489
|
+
const network = input.network ?? 'testnet';
|
|
490
|
+
const expectedInterpreter = schemas_ts_1.PINNED_INTERPRETER_ADDRESS_BY_NETWORK[network];
|
|
491
|
+
const expectedRpc = schemas_ts_1.RPC_URL_BY_NETWORK[network];
|
|
492
|
+
const rpcPinningError = enforceRpcPin('install_policy', input.rpcUrl, input.allowUnpinnedRpcUrl, expectedRpc, network);
|
|
493
|
+
if (rpcPinningError)
|
|
494
|
+
return { ok: false, error: rpcPinningError };
|
|
495
|
+
try {
|
|
496
|
+
const rpcUrl = input.rpcUrl ?? expectedRpc;
|
|
497
|
+
const server = new stellar_sdk_1.rpc.Server(rpcUrl, { allowHttp: false });
|
|
498
|
+
const reader = (0, read_account_rules_ts_1.accountRuleReaderFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[network]);
|
|
499
|
+
const collected = await (0, read_account_rules_ts_1.collectObservedRules)({
|
|
500
|
+
reader,
|
|
501
|
+
smartAccount: input.smartAccount,
|
|
502
|
+
interpreterAddress: expectedInterpreter,
|
|
503
|
+
});
|
|
504
|
+
const rule = collected.rules.find((r) => r.id === input.ruleId);
|
|
505
|
+
if (!rule) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
error: {
|
|
509
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
510
|
+
message: `merge_policy: rule ${input.ruleId} was not found on ${input.smartAccount}${collected.incomplete
|
|
511
|
+
? ' (the rule scan was incomplete, so it may exist but was not reached)'
|
|
512
|
+
: ''}`,
|
|
513
|
+
severity: 'error',
|
|
514
|
+
retryable: false,
|
|
515
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const plan = (0, plan_merge_policy_ts_1.planMergePolicy)({
|
|
520
|
+
rule,
|
|
521
|
+
interpreterAddress: expectedInterpreter,
|
|
522
|
+
incoming: (0, decode_ts_1.decodePredicate)(input.incomingPredicateBlobBase64),
|
|
523
|
+
step: input.step,
|
|
524
|
+
});
|
|
525
|
+
if (!plan.ok) {
|
|
526
|
+
return {
|
|
527
|
+
ok: false,
|
|
528
|
+
error: {
|
|
529
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
530
|
+
message: `merge_policy: ${plan.reason}`,
|
|
531
|
+
severity: 'error',
|
|
532
|
+
retryable: false,
|
|
533
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const encoded = (0, encode_ts_1.encodePredicate)(plan.predicate);
|
|
538
|
+
// The nonce is read, not assumed. OZ's `remove_policy` discards the result
|
|
539
|
+
// of `try_uninstall`, so a detach whose uninstall panicked - our
|
|
540
|
+
// `uninstall` panics MissingState when the master set has been archived -
|
|
541
|
+
// detaches the policy while leaving our nonce behind. Re-installing at 1
|
|
542
|
+
// would then be refused as a replay and the rule would sit unpoliced.
|
|
543
|
+
let installNonce = 1;
|
|
544
|
+
const nonceWarnings = [];
|
|
545
|
+
if (input.step === 'reinstall') {
|
|
546
|
+
const entries = await server.getLedgerEntries((0, read_account_rules_ts_1.nonceLedgerKey)(expectedInterpreter, input.smartAccount, input.ruleId));
|
|
547
|
+
const raw = entries.entries?.[0]?.val;
|
|
548
|
+
const stored = raw && raw.switch() === stellar_sdk_1.xdr.LedgerEntryType.contractData()
|
|
549
|
+
? raw.contractData().val()
|
|
550
|
+
: undefined;
|
|
551
|
+
if (stored && stored.switch() === stellar_sdk_1.xdr.ScValType.scvU32()) {
|
|
552
|
+
installNonce = stored.u32() + 1;
|
|
553
|
+
nonceWarnings.push(`the previous uninstall did not complete: rule ${input.ruleId} still holds interpreter state at nonce ${stored.u32()}, so this reinstalls at ${installNonce} rather than 1. The rule's counters were NOT reset.`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const built = await (0, build_merge_policy_ts_1.buildMergePolicyXdr)({
|
|
557
|
+
smartAccount: input.smartAccount,
|
|
558
|
+
sourceAccount: input.sourceAccount,
|
|
559
|
+
networkPassphrase: schemas_ts_1.NETWORK_PASSPHRASES[network],
|
|
560
|
+
ruleId: input.ruleId,
|
|
561
|
+
policyId: plan.policyId,
|
|
562
|
+
interpreterAddress: expectedInterpreter,
|
|
563
|
+
step: input.step,
|
|
564
|
+
encodedPredicate: encoded.encodedPredicate,
|
|
565
|
+
predicateHash: encoded.predicateHash,
|
|
566
|
+
installNonce,
|
|
567
|
+
...(plan.oracleParams ? { oracleParams: plan.oracleParams } : {}),
|
|
568
|
+
rpc: (0, build_install_policy_ts_1.rpcClientFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[network]),
|
|
569
|
+
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
ok: true,
|
|
573
|
+
data: {
|
|
574
|
+
...built,
|
|
575
|
+
mergedPredicateHash: encoded.predicateHash,
|
|
576
|
+
mergedPredicateBlobBase64: encoded.encodedPredicate,
|
|
577
|
+
warnings: [...plan.warnings, ...nonceWarnings],
|
|
578
|
+
followUp: plan.followUp,
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
return { ok: false, error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e) };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
313
586
|
/** `revoke_policy` body - thin wrapper over `buildRevokePolicyXdr`.
|
|
314
587
|
* Emits an unsigned XDR for `account.remove_context_rule(ruleId)`; the
|
|
315
588
|
* smart account itself handles uninstalling each attached policy. Auth
|
|
@@ -369,12 +642,12 @@ async function runRevokePolicy(raw) {
|
|
|
369
642
|
* fabricating it would be a lie on a security surface; the live
|
|
370
643
|
* mismatch check is worth MORE).
|
|
371
644
|
*
|
|
372
|
-
* Network-aware: `input.network` selects
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
645
|
+
* Network-aware: `input.network` selects the interpreter address, the RPC
|
|
646
|
+
* and the wasm hash. The networks run different binaries - testnet carries
|
|
647
|
+
* the selector-leaf minimum and the signer-set cap, mainnet predates both -
|
|
648
|
+
* so the hash is read through
|
|
649
|
+
* `PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK`. UNAUDITED at the time of
|
|
650
|
+
* writing.
|
|
378
651
|
*
|
|
379
652
|
* Same RPC pin as install/revoke: when `verifyLive` triggers an outbound
|
|
380
653
|
* call, the auth-digest + the answer bind to whichever RPC answered, so
|
|
@@ -414,7 +687,7 @@ async function runGetInterpreterInfo(raw) {
|
|
|
414
687
|
const info = (0, get_interpreter_info_ts_1.getInterpreterInfo)({
|
|
415
688
|
pinnedAddress,
|
|
416
689
|
pinnedGrammarVersion: schemas_ts_1.PINNED_INTERPRETER_GRAMMAR_VERSION,
|
|
417
|
-
pinnedWasmHash: schemas_ts_1.
|
|
690
|
+
pinnedWasmHash: schemas_ts_1.PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK[network],
|
|
418
691
|
network,
|
|
419
692
|
...(deployedGrammarVersion !== undefined ? { deployedGrammarVersion } : {}),
|
|
420
693
|
});
|