Build + tests verde (exit 0, runtime 27 tests incl. sudo_pallet_index_12). Verificacion adversarial ultracode (5 skeptics): 4 confirmed + 1 partial. FIX-5 — governability (audit V2-CA1): - pallet-sudo 43.0.0 wired Index 12 (workspace + runtime Cargo.toml + std). - impl pallet_sudo::Config (RuntimeEvent/RuntimeCall/WeightInfo) en configs.rs. - genesis: sudo key en los 3 presets — dev/local=Alice, mainnet=foundation_treasury (NO una dev key; el verificador confirmo que no hay backdoor critico). - Da Root origin -> las dispatchables ensure_root de Governance (add/remove council, mark/unmark advisor, treasury_disbursement) dejan de ser no-invocables. Verificado extremo-a-extremo: sudo() despacha RawOrigin::Root, que es exactamente lo que ensure_root acepta. - Sunset documentado (codigo + doc): sudo -> governance real (pallet-collective/ referenda) ANTES de mainnet. Sudo key unica = riesgo de centralizacion aceptable solo como interim/testnet. FIX-2 — lifecycle (audit V1-H2/V2-CA2): - StorageVersion::new(0) + pallet::storage_version en los 4 pallets (provenance/fees/agent-identity/governance). Aditivo, in-code==on-chain==0, sin migracion forzada. Habilita migraciones futuras. CAVEAT honesto (hallazgo high del red-team, NO defecto de FIX-5/FIX-2): treasury_disbursement solo DECREMENTA un contador u128 (Treasury StorageValue), NO mueve OGC real via pallet_balances. FIX-5 restaura la GOBERNABILIDAD (la dispatchable es invocable), pero mover los 250M reales es FIX-6: anadir type Currency al Config + T::Currency::transfer desde una cuenta-tesoreria derivada (PalletId) + financiarla en genesis + test del free_balance. NO se afirma "tesoreria descongelada de verdad" — solo "gobernabilidad restaurada". PENDIENTE: - FIX-2 AGI key recovery (rotate/revoke/active en agent-identity): diferido — necesita un DC de semantica de recovery #41 (single-sig vs multisig threshold, DID estable vs rota, recovery de agent_chain vacio). No se construye contra spec no-ratificada. - pallet-sudo no en feature runtime-benchmarks (inerte hoy: el runtime no impl el Benchmark API). Follow-up con weights benchmarking. - mainnet Foundation sudo key (FNDT placeholder): David ratifica la key real (multisig) pre-launch Q1 2027 (documentado). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
433 lines
19 KiB
Rust
433 lines
19 KiB
Rust
//! # pallet-origin-provenance — Phase 2.1 COMPLETE + v0.0.18 mock runtime integration tests
|
|
//!
|
|
//! Asset attestation pallet canonical per CHN-7 + DC PROVENANCE_PALLET_V1
|
|
//! (6/10 firmas sesión 3+ 2026-05-24, deadline ratificación 2026-05-29).
|
|
//!
|
|
//! ## Status canonical: v0.0.18 — Mock runtime integration tests E2E own fixtures
|
|
//!
|
|
//! Este crate provee:
|
|
//! - Storage layout canonical `StorageMap<AssetHash, AssetAttestation>` (PROV-2) ✅
|
|
//! - Dispatchable canonical `attest_asset(origin, ValidationAttestation)` (PROV-3) ✅
|
|
//! - Real Ed25519 verify canonical via ed25519-dalek (PROV-4) ✅ v0.0.16
|
|
//! - OIT Level >= 3 enforcement canonical (PROV-5) ✅
|
|
//! - is_anchorable check canonical (PROV-6) ✅
|
|
//! - Events canonical `AssetAttested` + `AttestationRejected` (PROV-7) ✅
|
|
//! - Errors canonical (PROV-4/5/6) ✅
|
|
//! - Pallet Config trait canonical ✅
|
|
//! - Runtime API canonical `verify_provenance` (PROV-8) ✅ v0.0.17
|
|
//! - **Mock runtime + integration tests E2E own fixtures (v0.0.18 sesión 3+) ✅**
|
|
//!
|
|
//! ## v0.0.18 sesión 3+ mock runtime integration tests
|
|
//!
|
|
//! Per audit canonical sesión 3+ (`AUDIT_CHARON_SESION_3_STATE_V1.md`),
|
|
//! Ergon E2E fixture diary claim aspirational sin shipped commit verified.
|
|
//! Charon construye own fixtures basados en vendor copy ValidationAttestation
|
|
//! real existente. 9 integration tests canonical:
|
|
//!
|
|
//! - 5 scenarios PROV-3 flow: passed_info anchored + warning anchored +
|
|
//! failed_critical rejected + passed_block_security rejected + anonymous_level_0 rejected
|
|
//! - 4 additional: idempotency + tampered_signature + tampered_payload + 6-step walkthrough
|
|
//!
|
|
//! Mock runtime composition canonical via `construct_runtime!` macro (System + Provenance).
|
|
//!
|
|
//! ## Lo que NO funciona aún (post-Ergon sub-crate Cap 42+)
|
|
//!
|
|
//! - Real `ValidationAttestation` consumption — usa vendor copy interim Option B
|
|
//! (signing_payload usa SCALE encoding canonical; Ergon real sub-crate usará
|
|
//! postcard encoding — swap canonical post Cap 42+ sin breaking pallet logic)
|
|
//! - Runtime API canonical `verify_provenance` (PROV-8) — v0.0.17 next slice
|
|
//!
|
|
//! Per Ergon decision (diary `3f773a03` 2026-05-24): él ships
|
|
//! `arkhe-noesis-attestation-types` standalone canonical Cap 42+ (target
|
|
//! 2026-05-30 → 06-05). Mientras tanto, mi vendor copy en
|
|
//! `origin-chain-types::attestation` permite avanzar canonical compile-only.
|
|
//!
|
|
//! ## Roadmap canonical post-ratificación DC PROVENANCE_PALLET_V1
|
|
//!
|
|
//! 1. **v0.0.14 (ESTA)**: Scaffold skeleton canonical compile-only
|
|
//! 2. **v0.0.15** (post 2026-05-29 DC ratify): Storage + events + errors wired
|
|
//! 3. **v0.0.16** (post 2026-06-05 Ergon sub-crate): Real Ed25519 + OIT verify
|
|
//! 4. **v0.0.17**: Runtime API canonical verify_provenance + wire en runtime
|
|
//! 5. **v0.1.0** (post Q3 2026 testnet): full integration end-to-end
|
|
//!
|
|
//! ## Cross-pillar consumers canonical desbloqueados (post-impl)
|
|
//!
|
|
//! - Trinity (Phanes) asset mesh BLAKE3 → `attest_asset` canonical
|
|
//! - Codex (.odoc) chain_attestation block populated by Vault tier
|
|
//! - Ethos (Sussi) credential NFT mint + chain attestation
|
|
//! - Atelier (Marihan) skill NFT mint via institutional wallet
|
|
//! - Origindocs (David ratificado 2026-05-24): academic IP defensible
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
#![warn(missing_docs)]
|
|
|
|
pub use pallet::*;
|
|
|
|
/// Re-export canonical types desde origin-chain-types para consumers.
|
|
pub use origin_chain_types::{
|
|
AssetAttestation, AssetHash, OitIdentity, SeverityTier, ValidationAttestation,
|
|
ValidationVerdict,
|
|
};
|
|
|
|
/// PROV-8 canonical: Runtime API canonical para light clients + indexers +
|
|
/// cross-pillar consumers.
|
|
///
|
|
/// Permite query canonical de attestation status sin necesidad de full node
|
|
/// access — light client envía hash → recibe `Option<AssetAttestation>` con
|
|
/// signature canonical + creator + block number canonical + agent chain.
|
|
///
|
|
/// Consumers canonical:
|
|
/// - Trinity (Phanes) — asset gallery render con attestation badge
|
|
/// - Codex (.odoc) — Vault tier verify-before-render canonical
|
|
/// - Codex origindocs — academic IP defensible verify
|
|
/// - Ethos (Sussi) — credential verification canonical
|
|
/// - Atelier (Marihan) — Skill Library Zona 6 verify-before-purchase
|
|
///
|
|
/// Future Phase 2.3 expansion canonical:
|
|
/// - `verify_by_creator(creator) -> Vec<AssetAttestation>` (paginated)
|
|
/// - `list_attestations_in_block(block_num) -> Vec<AssetAttestation>`
|
|
/// - `cross_runtime_proof(hash) -> CrossRuntimeProof`
|
|
sp_api::decl_runtime_apis! {
|
|
/// Provenance runtime API canonical canonical (PROV-8).
|
|
pub trait ProvenanceApi {
|
|
/// Verify provenance canonical de un asset por su hash canonical.
|
|
///
|
|
/// Returns `Some(AssetAttestation)` si attested previamente on-chain,
|
|
/// `None` si nunca attested canonical. Read-only query canonical sin
|
|
/// transaction fee canonical (off-chain via RPC).
|
|
fn verify_provenance(hash: AssetHash) -> Option<AssetAttestation>;
|
|
}
|
|
}
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
use origin_chain_types::{AssetAttestation, AssetHash, ValidationAttestation};
|
|
|
|
/// Pallet configuration trait canonical.
|
|
///
|
|
/// Per DC PROVENANCE_PALLET_V1 PROV-3 + ADR-0057 trait-seam discipline.
|
|
#[pallet::config]
|
|
pub trait Config: frame_system::Config {
|
|
/// Runtime event type canonical wired into `RuntimeEvent` aggregate.
|
|
type RuntimeEvent: From<Event<Self>>
|
|
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
|
|
|
// Future canonical (post OCAFB ratify):
|
|
// type OitFeeSchedule: arkhe_amis_server::OitFeeSchedule;
|
|
}
|
|
|
|
/// Pallet definition canonical.
|
|
///
|
|
/// v0.0.15: `AssetAttestation.agent_chain` ahora es `BoundedVec<AccountId,
|
|
/// ConstU32<10>>` canonical → MaxEncodedLen ahora satisfied canonical →
|
|
/// `#[pallet::without_storage_info]` ya NO necesario canonical.
|
|
/// StorageVersion en código (FIX-2 lifecycle, audit V1-H2/V2-CA2): arranca
|
|
/// en 0 (genesis). Habilita migraciones futuras vía on_chain_storage_version().
|
|
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::storage_version(STORAGE_VERSION)]
|
|
pub struct Pallet<T>(_);
|
|
|
|
/// PROV-2 canonical: `StorageMap<AssetHash, AssetAttestation>` canonical.
|
|
///
|
|
/// O(1) lookup canonical via Blake2_128Concat hasher (FRAME-native canonical).
|
|
/// Memory canonical ~140 bytes per attestation canonical.
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn attestation_of)]
|
|
pub type Attestations<T: Config> =
|
|
StorageMap<_, Blake2_128Concat, AssetHash, AssetAttestation, OptionQuery>;
|
|
|
|
/// PROV-7 canonical: Events emisión canonical.
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
/// Asset attestation canonical anchored on-chain (PROV-7).
|
|
AssetAttested {
|
|
/// BLAKE3 content hash canonical del asset attested.
|
|
asset_hash: AssetHash,
|
|
/// Block number canonical at which attestation landed.
|
|
block_number: BlockNumberFor<T>,
|
|
},
|
|
/// Attestation rejected canonical con reason canonical (PROV-7).
|
|
AttestationRejected {
|
|
/// BLAKE3 content hash canonical que falló validation.
|
|
asset_hash: AssetHash,
|
|
/// Reason canonical de rejection per RejectionReason enum.
|
|
reason: RejectionReason,
|
|
},
|
|
}
|
|
|
|
/// Rejection reasons canonical PROV-7.
|
|
///
|
|
/// **Marihan refinement Phase 2.2 noted** (DC PROVENANCE_PALLET_V1 signature
|
|
/// `2e6b8469`): añadir variant `MalformedValidationAttestation` para casos
|
|
/// donde `signing_payload()` falle parse pre-Ed25519-verify. Non-blocking
|
|
/// Phase 2.1 — incluido aquí canonical anticipated.
|
|
#[derive(
|
|
Encode,
|
|
Decode,
|
|
DecodeWithMemTracking,
|
|
MaxEncodedLen,
|
|
TypeInfo,
|
|
Clone,
|
|
Copy,
|
|
PartialEq,
|
|
Eq,
|
|
RuntimeDebug,
|
|
)]
|
|
pub enum RejectionReason {
|
|
/// Ed25519 signature verification canonical failed (PROV-4).
|
|
InvalidSignature,
|
|
/// Validator OIT level canonical < 3 (institutional required PROV-5).
|
|
ValidatorTierInsufficient,
|
|
/// Attestation NOT anchorable canonical per is_anchorable check (PROV-6).
|
|
NotAnchorable,
|
|
/// Asset hash canonical already attested previamente (idempotency canonical).
|
|
AlreadyAttested,
|
|
/// Validation attestation canonical malformed (Marihan refinement Phase 2.2).
|
|
MalformedValidationAttestation,
|
|
}
|
|
|
|
/// Pallet errors canonical (PROV-4/5/6).
|
|
#[pallet::error]
|
|
pub enum Error<T> {
|
|
/// PROV-4 canonical: Ed25519 signature verification canonical failed.
|
|
InvalidSignature,
|
|
/// PROV-5 canonical: validator OIT level canonical insufficient (< 3).
|
|
ValidatorTierInsufficient,
|
|
/// PROV-6 canonical: attestation NOT anchorable canonical (verdict/severity).
|
|
NotAnchorable,
|
|
/// PROV-2 canonical: asset hash canonical already attested previamente.
|
|
AlreadyAttested,
|
|
/// Marihan refinement Phase 2.2 noted canonical.
|
|
MalformedValidationAttestation,
|
|
}
|
|
|
|
/// Dispatchables canonical (PROV-3 canonical: attest_asset).
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {
|
|
/// PROV-3 canonical: `attest_asset` extrinsic canonical.
|
|
///
|
|
/// Submits a `ValidationAttestation` (Ergon Slice 7.0r struct) para
|
|
/// anchoring canonical on-chain. Flow canonical 6-step per DC PROV-3:
|
|
///
|
|
/// 1. PROV-4 canonical: Ed25519 signature verification canonical
|
|
/// 2. PROV-5 canonical: validate `validator_oit.level >= 3` institutional
|
|
/// 3. PROV-6 canonical: check `is_anchorable()` (verdict + severity)
|
|
/// 4. PROV-2 canonical: convert ValidationAttestation → AssetAttestation
|
|
/// 5. PROV-2 canonical: insert StorageMap canonical
|
|
/// 6. PROV-7 canonical: emit AssetAttested event canonical
|
|
///
|
|
/// **SCAFFOLD v0.0.14 canonical**: real impl deferred post Ergon Cap 42+
|
|
/// sub-crate ship + Ed25519 verification wire-up. Current canonical solo
|
|
/// validates basic state (origin signed + not already attested) + emite
|
|
/// placeholder event canonical.
|
|
#[pallet::call_index(0)]
|
|
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
|
pub fn attest_asset(
|
|
origin: OriginFor<T>,
|
|
validation_attestation: ValidationAttestation,
|
|
) -> DispatchResult {
|
|
// Verify origin canonical signed.
|
|
let _signer = ensure_signed(origin)?;
|
|
|
|
let asset_hash = validation_attestation.asset_id;
|
|
|
|
// PROV-2 idempotency check canonical.
|
|
ensure!(
|
|
!Attestations::<T>::contains_key(asset_hash),
|
|
Error::<T>::AlreadyAttested
|
|
);
|
|
|
|
// PROV-5 OIT level >= 3 enforcement canonical.
|
|
ensure!(
|
|
validation_attestation.validator_oit.is_institutional(),
|
|
Error::<T>::ValidatorTierInsufficient
|
|
);
|
|
|
|
// PROV-6 is_anchorable check canonical.
|
|
ensure!(
|
|
validation_attestation.is_anchorable(),
|
|
Error::<T>::NotAnchorable
|
|
);
|
|
|
|
// PROV-4 canonical: Real Ed25519 verification via ed25519-dalek (v0.0.16).
|
|
//
|
|
// Pattern canonical:
|
|
// 1. Reconstruct VerifyingKey desde validator_oit.public_key (32 bytes)
|
|
// 2. Reconstruct Signature desde attestation.signature (64 bytes)
|
|
// 3. Compute signing_payload canonical (SCALE encoding interim Option B)
|
|
// 4. verify_strict canonical (rejects malleable signatures + zero-keys)
|
|
//
|
|
// Cuando Ergon arkhe-noesis-attestation-types ship Cap 42+, swap canonical
|
|
// a postcard encoding canonical sin breaking pallet logic — verify call site
|
|
// remains identical canonical.
|
|
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
|
&validation_attestation.validator_oit.public_key,
|
|
)
|
|
.map_err(|_| Error::<T>::InvalidSignature)?;
|
|
|
|
let signature = ed25519_dalek::Signature::from_bytes(
|
|
&validation_attestation.signature,
|
|
);
|
|
|
|
let payload = validation_attestation.signing_payload();
|
|
|
|
pubkey
|
|
.verify_strict(&payload, &signature)
|
|
.map_err(|_| Error::<T>::InvalidSignature)?;
|
|
|
|
// PROV-2 canonical: convert + insert canonical.
|
|
// SCAFFOLD: minimal AssetAttestation construction (without creator/sig
|
|
// mapping canonical — that's Ergon sub-crate concern post Cap 42+).
|
|
let attestation = AssetAttestation {
|
|
hash: asset_hash,
|
|
creator: origin_chain_types::AccountId::new([0u8; 32]), // SCAFFOLD placeholder
|
|
signature: origin_chain_types::Signature::new(validation_attestation.signature),
|
|
anchored_at_block: 0, // SCAFFOLD — block_number wire post v0.0.16
|
|
agent_chain: origin_chain_types::AgentChain::default(), // BoundedVec empty
|
|
};
|
|
|
|
Attestations::<T>::insert(asset_hash, attestation);
|
|
|
|
// PROV-7 emit event canonical.
|
|
let block_number = <frame_system::Pallet<T>>::block_number();
|
|
Self::deposit_event(Event::AssetAttested {
|
|
asset_hash,
|
|
block_number,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
extern crate alloc;
|
|
|
|
// v0.0.18 — Mock runtime canonical + integration tests E2E PROV-3 flow.
|
|
// Per audit canonical sesión 3+ (`AUDIT_CHARON_SESION_3_STATE_V1.md`),
|
|
// own fixtures basados en vendor copy ValidationAttestation real existente
|
|
// (no depender de Ergon E2E fixture diary claim aspirational).
|
|
#[cfg(test)]
|
|
pub mod mock;
|
|
|
|
#[cfg(test)]
|
|
mod tests_integration;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use ed25519_dalek::{Signer, SigningKey};
|
|
use origin_chain_types::{OitIdentity, SeverityTier, ValidationAttestation, ValidationVerdict};
|
|
|
|
// Compile-time + Ed25519 verify standalone tests canonical.
|
|
// Mock runtime integration tests canonical = Phase 2.1 v0.0.17 next slice.
|
|
|
|
fn signed_attestation(verdict: ValidationVerdict, severity: SeverityTier) -> ValidationAttestation {
|
|
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
|
|
let verifying_key = signing_key.verifying_key();
|
|
|
|
let mut att = ValidationAttestation {
|
|
schema_version: *b"1.0.0 ",
|
|
asset_id: origin_chain_types::AssetHash::new([1u8; 32]),
|
|
validator_oit: OitIdentity {
|
|
public_key: verifying_key.to_bytes(),
|
|
level: 3,
|
|
},
|
|
validated_at_us: 1234567890,
|
|
verdict,
|
|
severity,
|
|
constraints_checked: 42,
|
|
signature: [0u8; 64], // placeholder, replaced post sign
|
|
};
|
|
|
|
let payload = att.signing_payload();
|
|
let signature = signing_key.sign(&payload);
|
|
att.signature = signature.to_bytes();
|
|
att
|
|
}
|
|
|
|
#[test]
|
|
fn ed25519_verify_real_valid_signature_canonical() {
|
|
// PROV-4 canonical: signed attestation canonical verifies correctamente.
|
|
let att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
|
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
|
&att.validator_oit.public_key,
|
|
)
|
|
.expect("valid pubkey canonical");
|
|
let sig = ed25519_dalek::Signature::from_bytes(&att.signature);
|
|
let payload = att.signing_payload();
|
|
assert!(pubkey.verify_strict(&payload, &sig).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn ed25519_verify_tampered_signature_rejected_canonical() {
|
|
// PROV-4 canonical: tampered signature canonical rejected.
|
|
let mut att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
|
// Flip a bit en signature canonical.
|
|
att.signature[0] ^= 0xFF;
|
|
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
|
&att.validator_oit.public_key,
|
|
)
|
|
.expect("valid pubkey canonical");
|
|
let sig = ed25519_dalek::Signature::from_bytes(&att.signature);
|
|
let payload = att.signing_payload();
|
|
assert!(pubkey.verify_strict(&payload, &sig).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn ed25519_verify_tampered_payload_rejected_canonical() {
|
|
// PROV-4 canonical: tampered payload canonical (signing_payload mismatch) rejected.
|
|
let mut att = signed_attestation(ValidationVerdict::Passed, SeverityTier::Info);
|
|
let original_sig = att.signature;
|
|
// Flip a bit en asset_id (cambia signing_payload pero mantiene signature original).
|
|
att.asset_id = origin_chain_types::AssetHash::new([99u8; 32]);
|
|
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
|
&att.validator_oit.public_key,
|
|
)
|
|
.expect("valid pubkey canonical");
|
|
let sig = ed25519_dalek::Signature::from_bytes(&original_sig);
|
|
let payload = att.signing_payload(); // payload canonical NEW (con asset_id tampered)
|
|
assert!(pubkey.verify_strict(&payload, &sig).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn ed25519_zero_signature_fails_verify_canonical() {
|
|
// PROV-4 canonical: all-zero signature canonical rejected por verify_strict.
|
|
// (Note: ed25519-dalek `from_bytes` acepta all-zero pubkey/sig — son
|
|
// valid Edwards points; el rejection canonical pasa en verify_strict.)
|
|
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
|
|
let verifying_key = signing_key.verifying_key();
|
|
let payload = b"any payload canonical";
|
|
let zero_sig = ed25519_dalek::Signature::from_bytes(&[0u8; 64]);
|
|
assert!(verifying_key.verify_strict(payload, &zero_sig).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn pallet_scaffold_compiles_canonical() {
|
|
// Scaffold compiles — verified by cargo check.
|
|
// Real mock runtime integration tests = Phase 2.1 v0.0.17.
|
|
}
|
|
|
|
#[test]
|
|
fn rejection_reason_variants_canonical() {
|
|
// Verify enum variants canonical exist + Marihan refinement included.
|
|
let r1 = RejectionReason::InvalidSignature;
|
|
let r2 = RejectionReason::ValidatorTierInsufficient;
|
|
let _r3 = RejectionReason::NotAnchorable;
|
|
let r4 = RejectionReason::AlreadyAttested;
|
|
let r5 = RejectionReason::MalformedValidationAttestation;
|
|
assert_ne!(
|
|
core::mem::discriminant(&r1),
|
|
core::mem::discriminant(&r2)
|
|
);
|
|
assert_ne!(
|
|
core::mem::discriminant(&r4),
|
|
core::mem::discriminant(&r5)
|
|
);
|
|
}
|
|
}
|