1#[derive(Clone, Copy, PartialEq, Eq)]
37#[repr(u8)]
38pub enum Capability {
39 ReadsState = 0,
41 MutatesState = 1,
43 TouchesJournal = 2,
45 ExternalCall = 3,
47 MutatesTreasury = 4,
49 ReallocatesAccount = 5,
51 CreatesAccount = 6,
53 ClosesAccount = 7,
55 ModifiesAuthority = 8,
57 TransitionsState = 9,
59}
60
61impl Capability {
62 #[inline(always)]
64 pub const fn mask(self) -> u32 {
65 1u32 << (self as u8)
66 }
67}
68
69#[derive(Clone, Copy, PartialEq, Eq)]
73pub struct CapabilitySet {
74 bits: u32,
75}
76
77impl CapabilitySet {
78 #[inline(always)]
80 pub const fn new() -> Self {
81 Self { bits: 0 }
82 }
83
84 #[inline(always)]
86 pub const fn with(self, cap: Capability) -> Self {
87 Self {
88 bits: self.bits | cap.mask(),
89 }
90 }
91
92 #[inline(always)]
94 pub const fn has(&self, cap: Capability) -> bool {
95 self.bits & cap.mask() != 0
96 }
97
98 #[inline(always)]
100 pub const fn bits(&self) -> u32 {
101 self.bits
102 }
103
104 #[inline(always)]
106 pub const fn count(&self) -> u32 {
107 self.bits.count_ones()
108 }
109
110 #[inline(always)]
112 pub const fn union(self, other: Self) -> Self {
113 Self {
114 bits: self.bits | other.bits,
115 }
116 }
117
118 #[inline(always)]
120 pub const fn is_subset_of(&self, other: &Self) -> bool {
121 (self.bits & other.bits) == self.bits
122 }
123}
124
125#[derive(Clone, Copy, PartialEq, Eq)]
127#[repr(u8)]
128pub enum PolicyRequirement {
129 Authority = 0,
131 JournalCapacity = 1,
133 PostMutationCheck = 2,
135 CpiGuard = 3,
137 RentExemption = 4,
139 InvariantCheck = 5,
141 StateSnapshot = 6,
143 LamportConservation = 7,
145}
146
147impl PolicyRequirement {
148 #[inline(always)]
150 pub const fn mask(self) -> u32 {
151 1u32 << (self as u8)
152 }
153}
154
155#[derive(Clone, Copy, PartialEq, Eq)]
157pub struct RequirementSet {
158 bits: u32,
159}
160
161impl RequirementSet {
162 #[inline(always)]
164 pub const fn new() -> Self {
165 Self { bits: 0 }
166 }
167
168 #[inline(always)]
170 pub const fn with(self, req: PolicyRequirement) -> Self {
171 Self {
172 bits: self.bits | req.mask(),
173 }
174 }
175
176 #[inline(always)]
178 pub const fn has(&self, req: PolicyRequirement) -> bool {
179 self.bits & req.mask() != 0
180 }
181
182 #[inline(always)]
184 pub const fn bits(&self) -> u32 {
185 self.bits
186 }
187}
188
189#[derive(Clone, Copy)]
191pub struct PolicyRule {
192 pub capability: Capability,
193 pub requirement: PolicyRequirement,
194}
195
196pub struct InstructionPolicy<const N: usize> {
200 rules: [PolicyRule; N],
201 count: usize,
202}
203
204impl<const N: usize> InstructionPolicy<N> {
205 #[inline(always)]
207 pub const fn new() -> Self {
208 Self {
209 rules: [PolicyRule {
210 capability: Capability::ReadsState,
211 requirement: PolicyRequirement::Authority,
212 }; N],
213 count: 0,
214 }
215 }
216
217 #[inline(always)]
219 pub const fn when(mut self, cap: Capability, req: PolicyRequirement) -> Self {
220 assert!(self.count < N, "policy rule overflow");
221 self.rules[self.count] = PolicyRule {
222 capability: cap,
223 requirement: req,
224 };
225 self.count += 1;
226 self
227 }
228
229 #[inline]
233 pub const fn resolve(&self, caps: &CapabilitySet) -> RequirementSet {
234 let mut reqs = RequirementSet::new();
235 let mut i = 0;
236 while i < self.count {
237 if caps.has(self.rules[i].capability) {
238 reqs = reqs.with(self.rules[i].requirement);
239 }
240 i += 1;
241 }
242 reqs
243 }
244
245 #[inline(always)]
247 pub const fn rule_count(&self) -> usize {
248 self.count
249 }
250}
251
252impl Default for CapabilitySet {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258impl Default for RequirementSet {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264impl<const N: usize> Default for InstructionPolicy<N> {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270pub const TREASURY_WRITE_POLICY: InstructionPolicy<4> = InstructionPolicy::new()
282 .when(Capability::MutatesState, PolicyRequirement::Authority)
283 .when(Capability::MutatesState, PolicyRequirement::StateSnapshot)
284 .when(
285 Capability::MutatesTreasury,
286 PolicyRequirement::LamportConservation,
287 )
288 .when(
289 Capability::MutatesTreasury,
290 PolicyRequirement::InvariantCheck,
291 );
292
293pub const TREASURY_WRITE_CAPS: CapabilitySet = CapabilitySet::new()
295 .with(Capability::MutatesState)
296 .with(Capability::MutatesTreasury);
297
298pub const JOURNAL_TOUCH_POLICY: InstructionPolicy<3> = InstructionPolicy::new()
302 .when(Capability::MutatesState, PolicyRequirement::Authority)
303 .when(
304 Capability::TouchesJournal,
305 PolicyRequirement::JournalCapacity,
306 )
307 .when(Capability::TouchesJournal, PolicyRequirement::StateSnapshot);
308
309pub const JOURNAL_TOUCH_CAPS: CapabilitySet = CapabilitySet::new()
311 .with(Capability::MutatesState)
312 .with(Capability::TouchesJournal);
313
314pub const EXTERNAL_CALL_POLICY: InstructionPolicy<3> = InstructionPolicy::new()
318 .when(Capability::ExternalCall, PolicyRequirement::CpiGuard)
319 .when(
320 Capability::ExternalCall,
321 PolicyRequirement::PostMutationCheck,
322 )
323 .when(Capability::ExternalCall, PolicyRequirement::StateSnapshot);
324
325pub const EXTERNAL_CALL_CAPS: CapabilitySet = CapabilitySet::new().with(Capability::ExternalCall);
327
328pub const SHARD_MUTATION_POLICY: InstructionPolicy<3> = InstructionPolicy::new()
332 .when(Capability::MutatesState, PolicyRequirement::Authority)
333 .when(Capability::MutatesState, PolicyRequirement::StateSnapshot)
334 .when(Capability::MutatesState, PolicyRequirement::InvariantCheck);
335
336pub const SHARD_MUTATION_CAPS: CapabilitySet = CapabilitySet::new().with(Capability::MutatesState);
338
339pub const MIGRATION_SENSITIVE_POLICY: InstructionPolicy<4> = InstructionPolicy::new()
343 .when(Capability::ReallocatesAccount, PolicyRequirement::Authority)
344 .when(
345 Capability::ReallocatesAccount,
346 PolicyRequirement::RentExemption,
347 )
348 .when(
349 Capability::ReallocatesAccount,
350 PolicyRequirement::StateSnapshot,
351 )
352 .when(
353 Capability::ReallocatesAccount,
354 PolicyRequirement::InvariantCheck,
355 );
356
357pub const MIGRATION_SENSITIVE_CAPS: CapabilitySet = CapabilitySet::new()
359 .with(Capability::MutatesState)
360 .with(Capability::ReallocatesAccount);
361
362pub const AUTHORITY_CHANGE_POLICY: InstructionPolicy<4> = InstructionPolicy::new()
366 .when(Capability::ModifiesAuthority, PolicyRequirement::Authority)
367 .when(Capability::ModifiesAuthority, PolicyRequirement::CpiGuard)
368 .when(
369 Capability::ModifiesAuthority,
370 PolicyRequirement::PostMutationCheck,
371 )
372 .when(
373 Capability::ModifiesAuthority,
374 PolicyRequirement::InvariantCheck,
375 );
376
377pub const AUTHORITY_CHANGE_CAPS: CapabilitySet = CapabilitySet::new()
379 .with(Capability::MutatesState)
380 .with(Capability::ModifiesAuthority);
381
382pub const READ_ONLY_AUDIT_POLICY: InstructionPolicy<1> =
386 InstructionPolicy::new().when(Capability::ReadsState, PolicyRequirement::StateSnapshot);
387
388pub const READ_ONLY_AUDIT_CAPS: CapabilitySet = CapabilitySet::new().with(Capability::ReadsState);
390
391pub const ACCOUNT_INIT_POLICY: InstructionPolicy<3> = InstructionPolicy::new()
395 .when(Capability::CreatesAccount, PolicyRequirement::Authority)
396 .when(Capability::CreatesAccount, PolicyRequirement::RentExemption)
397 .when(
398 Capability::CreatesAccount,
399 PolicyRequirement::InvariantCheck,
400 );
401
402pub const ACCOUNT_INIT_CAPS: CapabilitySet = CapabilitySet::new().with(Capability::CreatesAccount);
404
405pub const ACCOUNT_CLOSE_POLICY: InstructionPolicy<3> = InstructionPolicy::new()
409 .when(Capability::ClosesAccount, PolicyRequirement::Authority)
410 .when(Capability::ClosesAccount, PolicyRequirement::StateSnapshot)
411 .when(
412 Capability::ClosesAccount,
413 PolicyRequirement::LamportConservation,
414 );
415
416pub const ACCOUNT_CLOSE_CAPS: CapabilitySet = CapabilitySet::new().with(Capability::ClosesAccount);
418
419#[derive(Clone, Copy)]
429pub struct PolicyPackDescriptor {
430 pub name: &'static str,
432 pub description: &'static str,
434 pub capabilities: &'static CapabilitySet,
436 pub requirements: &'static [(&'static str, &'static str)],
438 pub receipt_expected: bool,
440 pub invariant_hints: &'static [&'static str],
442}
443
444pub const NAMED_POLICY_PACKS: &[PolicyPackDescriptor] = &[
446 PolicyPackDescriptor {
447 name: "TreasuryWrite",
448 description: "Vault/treasury balance mutations. Enforces authority, snapshot, lamport conservation, and invariant checks.",
449 capabilities: &TREASURY_WRITE_CAPS,
450 requirements: &[
451 ("MutatesState", "Authority, StateSnapshot"),
452 ("MutatesTreasury", "LamportConservation, InvariantCheck"),
453 ],
454 receipt_expected: true,
455 invariant_hints: &["balance_conservation", "authority_present"],
456 },
457 PolicyPackDescriptor {
458 name: "JournalTouch",
459 description: "Journal segment writes. Enforces authority, capacity guard, and snapshot.",
460 capabilities: &JOURNAL_TOUCH_CAPS,
461 requirements: &[
462 ("MutatesState", "Authority"),
463 ("TouchesJournal", "JournalCapacity, StateSnapshot"),
464 ],
465 receipt_expected: true,
466 invariant_hints: &["journal_not_full"],
467 },
468 PolicyPackDescriptor {
469 name: "ExternalCall",
470 description: "CPI-invoking instructions. Enforces CPI guard, post-mutation check, and snapshot.",
471 capabilities: &EXTERNAL_CALL_CAPS,
472 requirements: &[
473 ("ExternalCall", "CpiGuard, PostMutationCheck, StateSnapshot"),
474 ],
475 receipt_expected: true,
476 invariant_hints: &["cpi_allowlisted"],
477 },
478 PolicyPackDescriptor {
479 name: "ShardMutation",
480 description: "Shard data modifications. Enforces authority, snapshot, and invariant checks.",
481 capabilities: &SHARD_MUTATION_CAPS,
482 requirements: &[
483 ("MutatesState", "Authority, StateSnapshot, InvariantCheck"),
484 ],
485 receipt_expected: true,
486 invariant_hints: &[],
487 },
488 PolicyPackDescriptor {
489 name: "MigrationSensitive",
490 description: "Account reallocation/migration. Enforces authority, rent exemption, snapshot, and invariant checks.",
491 capabilities: &MIGRATION_SENSITIVE_CAPS,
492 requirements: &[
493 ("ReallocatesAccount", "Authority, RentExemption, StateSnapshot, InvariantCheck"),
494 ],
495 receipt_expected: true,
496 invariant_hints: &["rent_exempt_after_realloc"],
497 },
498 PolicyPackDescriptor {
499 name: "AuthorityChange",
500 description: "Authority/permission modifications. Enforces authority, CPI guard, post-mutation, and invariant checks.",
501 capabilities: &AUTHORITY_CHANGE_CAPS,
502 requirements: &[
503 ("ModifiesAuthority", "Authority, CpiGuard, PostMutationCheck, InvariantCheck"),
504 ],
505 receipt_expected: true,
506 invariant_hints: &["new_authority_valid"],
507 },
508 PolicyPackDescriptor {
509 name: "ReadOnlyAudit",
510 description: "Read-only inspection/audit. Only requires snapshot for traceability.",
511 capabilities: &READ_ONLY_AUDIT_CAPS,
512 requirements: &[
513 ("ReadsState", "StateSnapshot"),
514 ],
515 receipt_expected: false,
516 invariant_hints: &[],
517 },
518 PolicyPackDescriptor {
519 name: "AccountInit",
520 description: "Account creation. Enforces authority, rent exemption, and invariant checks.",
521 capabilities: &ACCOUNT_INIT_CAPS,
522 requirements: &[
523 ("CreatesAccount", "Authority, RentExemption, InvariantCheck"),
524 ],
525 receipt_expected: true,
526 invariant_hints: &["header_initialized"],
527 },
528 PolicyPackDescriptor {
529 name: "AccountClose",
530 description: "Account closure. Enforces authority, snapshot, and lamport conservation.",
531 capabilities: &ACCOUNT_CLOSE_CAPS,
532 requirements: &[
533 ("ClosesAccount", "Authority, StateSnapshot, LamportConservation"),
534 ],
535 receipt_expected: true,
536 invariant_hints: &["sentinel_written"],
537 },
538];
539
540impl Capability {
542 #[inline]
544 pub const fn name(self) -> &'static str {
545 match self {
546 Self::ReadsState => "ReadsState",
547 Self::MutatesState => "MutatesState",
548 Self::TouchesJournal => "TouchesJournal",
549 Self::ExternalCall => "ExternalCall",
550 Self::MutatesTreasury => "MutatesTreasury",
551 Self::ReallocatesAccount => "ReallocatesAccount",
552 Self::CreatesAccount => "CreatesAccount",
553 Self::ClosesAccount => "ClosesAccount",
554 Self::ModifiesAuthority => "ModifiesAuthority",
555 Self::TransitionsState => "TransitionsState",
556 }
557 }
558}
559
560impl PolicyRequirement {
562 #[inline]
564 pub const fn name(self) -> &'static str {
565 match self {
566 Self::Authority => "Authority",
567 Self::JournalCapacity => "JournalCapacity",
568 Self::PostMutationCheck => "PostMutationCheck",
569 Self::CpiGuard => "CpiGuard",
570 Self::RentExemption => "RentExemption",
571 Self::InvariantCheck => "InvariantCheck",
572 Self::StateSnapshot => "StateSnapshot",
573 Self::LamportConservation => "LamportConservation",
574 }
575 }
576}
577
578#[derive(Clone, Copy, Debug, PartialEq, Eq)]
587#[repr(u8)]
588pub enum PolicyClass {
589 Read = 0,
591 Write = 1,
593 Financial = 2,
595 Administrative = 3,
597 Lifecycle = 4,
599 CrossProgram = 5,
601 Governance = 6,
603}
604
605impl PolicyClass {
606 pub const fn name(self) -> &'static str {
608 match self {
609 Self::Read => "read",
610 Self::Write => "write",
611 Self::Financial => "financial",
612 Self::Administrative => "administrative",
613 Self::Lifecycle => "lifecycle",
614 Self::CrossProgram => "cross-program",
615 Self::Governance => "governance",
616 }
617 }
618
619 pub const fn is_mutating(self) -> bool {
621 !matches!(self, Self::Read)
622 }
623
624 pub const fn expects_receipt(self) -> bool {
626 !matches!(self, Self::Read)
627 }
628
629 pub const fn from_capabilities(caps: &CapabilitySet) -> Self {
631 if caps.has(Capability::MutatesTreasury) {
632 return Self::Financial;
633 }
634 if caps.has(Capability::ModifiesAuthority) {
635 return Self::Administrative;
636 }
637 if caps.has(Capability::CreatesAccount)
638 || caps.has(Capability::ClosesAccount)
639 || caps.has(Capability::ReallocatesAccount)
640 {
641 return Self::Lifecycle;
642 }
643 if caps.has(Capability::ExternalCall) {
644 return Self::CrossProgram;
645 }
646 if caps.has(Capability::MutatesState)
647 || caps.has(Capability::TouchesJournal)
648 || caps.has(Capability::TransitionsState)
649 {
650 return Self::Write;
651 }
652 Self::Read
653 }
654}
655
656impl core::fmt::Display for PolicyClass {
657 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
658 f.write_str(self.name())
659 }
660}