feat(provenance): v0.0.18 mock runtime integration tests E2E — 9 scenarios canonical own fixtures
Phase 2.1 PROVENANCE pallet — 9 integration tests E2E via FRAME mock runtime
composition (`construct_runtime!` macro + derive_impl + sp_io::TestExternalities).
Cierra honest disclosure sesión 2 ("real chain runtime aún NO arranca"). Pallet
end-to-end validado canonical sin esperar Slice 2e WASM resolution structural.
## 9 integration tests canonical PROV-3 flow
5 scenarios:
- scenario_1_passed_info_anchored_canonical (PROV-2/3/7) ✅
- scenario_2_warning_anchored_canonical (PROV-2/3/6/7) ✅
- scenario_3_failed_critical_rejected_canonical (PROV-6) ❌ NotAnchorable
- scenario_4_passed_block_security_rejected_canonical (PROV-6) ❌ NotAnchorable
- scenario_5_anonymous_level_0_rejected_canonical (PROV-5) ❌ ValidatorTierInsufficient
4 additional:
- additional_idempotency_already_attested_rejected_canonical (PROV-2) ❌ AlreadyAttested
- additional_tampered_signature_rejected_canonical (PROV-4) ❌ InvalidSignature
- additional_tampered_payload_rejected_canonical (PROV-4) ❌ InvalidSignature
- additional_6_step_prov_3_walkthrough_canonical (PROV-3 full) ✅ Pre/post assertions
## Own fixtures canonical
Per audit canonical sesión 3+ (docs/L7_roadmaps/AUDIT_CHARON_SESION_3_STATE_V1.md),
Ergon E2E fixture diary claim aspirational sin shipped commit verified — filesystem
+ git history confirma no source file canonical. Charon construye own fixtures
basados en vendor copy ValidationAttestation real existente. Pattern análogo
Slice 3 mock-WASM + v0.0.16 Ed25519 interim encoding.
## Files canonical
- NEW: crates/origin-chain-provenance/src/mock.rs (75 LOC mock runtime)
- NEW: crates/origin-chain-provenance/src/tests_integration.rs (270 LOC, 9 tests)
- EDIT: crates/origin-chain-provenance/Cargo.toml (sp-core+sp-io+sp-runtime dev-deps)
- EDIT: crates/origin-chain-provenance/src/lib.rs (mod mock + mod tests_integration + docstring v0.0.18)
- EDIT: CHANGELOG.md (v0.0.18 entry canonical pattern Phase 2.1)
## Verification
- cargo test -p origin-chain-provenance — 17 tests verde, 0 failures
(9 integration + 6 standalone Ed25519 + 2 auto-generated)
- Workspace tests no regressions
- Cero warnings críticos
🎉 Phase 2.1 PROVENANCE pallet validated E2E via mock runtime canonical.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f928935989
commit
654cffdf31
6 files changed
+523
-4
No files matched your search
@@ -6,6 +6,102 @@ by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.0.18] — 2026-05-24 — Phase 2.1 mock runtime integration tests E2E (own fixtures, sesión 3+)
|
||||||
|
|
||||||
|
**Mock runtime canonical + 9 integration tests E2E PROV-3 flow** — Cierra honest disclosure
|
||||||
|
sesión 2 ("real chain runtime aún NO arranca por Slice 2e WASM blocker structural").
|
||||||
|
Pallet end-to-end validado via FRAME mock runtime composition (`construct_runtime!` macro
|
||||||
|
+ derive_impl + `sp_io::TestExternalities`), demostrando dispatchable + storage + events
|
||||||
|
funcionan correctamente sin esperar Slice 2e WASM resolution.
|
||||||
|
|
||||||
|
### Deliverables v0.0.18
|
||||||
|
|
||||||
|
**Mock runtime canonical (NEW file)**:
|
||||||
|
- `crates/origin-chain-provenance/src/mock.rs` — 75 LOC canonical
|
||||||
|
- `construct_runtime!` macro composition: `System` + `Provenance` (mínimo testing)
|
||||||
|
- `frame_system::Config` impl via `derive_impl(TestDefaultConfig)` + `AccountId32` canonical
|
||||||
|
- `new_test_ext()` helper canonical (`sp_io::TestExternalities` con `block_number=1` set)
|
||||||
|
- `alice_origin()` + `bob_origin()` helpers para testing dispatchables
|
||||||
|
- `TEST_SIGNING_SEED = [7u8; 32]` deterministic Ed25519 keypair canonical
|
||||||
|
|
||||||
|
**Integration tests canonical E2E (NEW file)**:
|
||||||
|
- `crates/origin-chain-provenance/src/tests_integration.rs` — 270 LOC canonical, 9 tests verde
|
||||||
|
|
||||||
|
**9 integration tests canonical PROV-3 flow** (5 scenarios + 4 additional):
|
||||||
|
|
||||||
|
| # | Test canonical | PROV | Expected outcome |
|
||||||
|
|---|----------------|------|------------------|
|
||||||
|
| 1 | `scenario_1_passed_info_anchored_canonical` | PROV-2/3/7 | ✅ AssetAttested event + storage insert |
|
||||||
|
| 2 | `scenario_2_warning_anchored_canonical` | PROV-2/3/6/7 | ✅ Warning verdict canonical anchorable |
|
||||||
|
| 3 | `scenario_3_failed_critical_rejected_canonical` | PROV-6 | ❌ NotAnchorable error |
|
||||||
|
| 4 | `scenario_4_passed_block_security_rejected_canonical` | PROV-6 | ❌ NotAnchorable (Block severity override) |
|
||||||
|
| 5 | `scenario_5_anonymous_level_0_rejected_canonical` | PROV-5 | ❌ ValidatorTierInsufficient |
|
||||||
|
| 6 | `additional_idempotency_already_attested_rejected_canonical` | PROV-2 | ❌ AlreadyAttested (idempotency) |
|
||||||
|
| 7 | `additional_tampered_signature_rejected_canonical` | PROV-4 | ❌ InvalidSignature (bit flip) |
|
||||||
|
| 8 | `additional_tampered_payload_rejected_canonical` | PROV-4 | ❌ InvalidSignature (payload mismatch) |
|
||||||
|
| 9 | `additional_6_step_prov_3_walkthrough_canonical` | PROV-3 full | ✅ Pre/post flight assertions |
|
||||||
|
|
||||||
|
**Cargo.toml dev-deps canonical (NEW)**:
|
||||||
|
- `sp-core` workspace default-features (AccountId32 + crypto canonical)
|
||||||
|
- `sp-io` workspace default-features (TestExternalities canonical)
|
||||||
|
- `sp-runtime` workspace default-features (BuildStorage + traits canonical)
|
||||||
|
|
||||||
|
**lib.rs canonical updates**:
|
||||||
|
- Docstring header updated v0.0.14 → v0.0.18 (status + v0.0.18 section added)
|
||||||
|
- `#[cfg(test)] pub mod mock;` declaration canonical
|
||||||
|
- `#[cfg(test)] mod tests_integration;` declaration canonical
|
||||||
|
|
||||||
|
### Own fixtures pattern canonical
|
||||||
|
|
||||||
|
Per audit canonical sesión 3+ (`docs/L7_roadmaps/AUDIT_CHARON_SESION_3_STATE_V1.md`),
|
||||||
|
Ergon E2E fixture diary claim 2026-05-24T14:11Z (`slice_7_0r_attestation_e2e_fixture.rs`
|
||||||
|
~520 LOC + JSON 90 LOC) **aspirational sin shipped commit verified** — filesystem +
|
||||||
|
git history + cross-branch search confirma no source file canonical existe.
|
||||||
|
|
||||||
|
Charon construye own fixtures canonical basados en vendor copy `ValidationAttestation`
|
||||||
|
real existente en `origin-chain-types/src/attestation.rs`. Builder canonical:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn build_signed_attestation(asset_seed, verdict, severity, oit_level) -> ValidationAttestation {
|
||||||
|
let signing_key = SigningKey::from_bytes(&TEST_SIGNING_SEED);
|
||||||
|
let mut att = ValidationAttestation { ... };
|
||||||
|
let signature = signing_key.sign(&att.signing_payload());
|
||||||
|
att.signature = signature.to_bytes();
|
||||||
|
att
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pattern análogo Slice 3 mock-WASM canonical + v0.0.16 Ed25519 con interim encoding —
|
||||||
|
"avanza work conceptual mientras real (Ergon sub-crate Cap 42+) waits".
|
||||||
|
|
||||||
|
### Verification end-to-end canonical
|
||||||
|
|
||||||
|
- ✅ `cargo test -p origin-chain-provenance` — **17 tests verde, 0 failures**:
|
||||||
|
- 9 integration tests (`tests_integration` module)
|
||||||
|
- 6 standalone Ed25519 tests (`tests` module)
|
||||||
|
- 2 auto-generated (mock genesis + runtime integrity)
|
||||||
|
- ✅ Workspace tests no regressions
|
||||||
|
- ✅ Cero new warnings críticos (solo cosmetic missing_docs en construct_runtime macro-generated items)
|
||||||
|
|
||||||
|
### Roadmap canonical post-v0.0.18
|
||||||
|
|
||||||
|
| Version | Trigger |
|
||||||
|
|---------|---------|
|
||||||
|
| v0.0.19 | Swap canonical SCALE → postcard encoding cuando Ergon arkhe-noesis-attestation-types Cap 42+ ship (2026-05-30 → 06-05) |
|
||||||
|
| **v0.0.20** | **Phase 2.4 pallet-origin-fees scaffold (sesión 3+ shipping ahora)** |
|
||||||
|
| v0.0.21 | Phase 2.4 fees pallet wired runtime composition Index 8 |
|
||||||
|
| v0.0.22 | Phase 2.4 fees pallet impl complete + mock runtime integration tests E2E |
|
||||||
|
| v0.0.23 | Phase 2.4 Runtime API FeesApi canonical → Phase 2.4 COMPLETE |
|
||||||
|
| v0.1.0 | Slice 2e WASM resolved + full testnet Q3 2026 launch |
|
||||||
|
|
||||||
|
### Filosofía canonical sesión 3+ aplicada
|
||||||
|
|
||||||
|
David canon ratificado 2026-05-14 + lessons sesión 2 internalized: pattern audit-first
|
||||||
|
comparative (verify Ergon fixture claim → aspirational confirmed → build own) + notify-and-continue
|
||||||
|
(diary Ergon notify sin requires_ack, sin esperar response) + completeness over speed
|
||||||
|
(9 scenarios canonical exhaustivos vs minimal demo). "El barquero estudia el río antes
|
||||||
|
de cruzar — y avanza paralelo cuando el cauce principal espera."
|
||||||
|
|
||||||
## [0.0.17] — 2026-05-24 — Phase 2.1 COMPLETE — Runtime API verify_provenance (PROV-8 + 8/8 sub-decisions)
|
## [0.0.17] — 2026-05-24 — Phase 2.1 COMPLETE — Runtime API verify_provenance (PROV-8 + 8/8 sub-decisions)
|
||||||
|
|
||||||
**🎉 Phase 2.1 PROVENANCE pallet COMPLETE end-to-end** — 8/8 PROV sub-decisions
|
**🎉 Phase 2.1 PROVENANCE pallet COMPLETE end-to-end** — 8/8 PROV sub-decisions
|
||||||
|
|||||||
Generated
+3
@@ -2207,6 +2207,9 @@ dependencies = [
|
|||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"sp-api",
|
"sp-api",
|
||||||
|
"sp-core",
|
||||||
|
"sp-io",
|
||||||
|
"sp-runtime",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ ed25519-dalek = { workspace = true }
|
|||||||
parity-scale-codec = { workspace = true }
|
parity-scale-codec = { workspace = true }
|
||||||
scale-info = { workspace = true }
|
scale-info = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Mock runtime canonical para integration tests v0.0.18 — PROV-3 E2E flow.
|
||||||
|
# Per pallet Substrate convention: sp-io TestExternalities + sp-runtime
|
||||||
|
# AccountId32 + frame_support construct_runtime macro.
|
||||||
|
sp-core = { workspace = true, default-features = true }
|
||||||
|
sp-io = { workspace = true, default-features = true }
|
||||||
|
sp-runtime = { workspace = true, default-features = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
//! # pallet-origin-provenance — Phase 2.1 scaffold v0.0.14
|
//! # 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
|
//! Asset attestation pallet canonical per CHN-7 + DC PROVENANCE_PALLET_V1
|
||||||
//! (5/10 firmas hoy 2026-05-24, deadline ratificación 2026-05-29).
|
//! (6/10 firmas sesión 3+ 2026-05-24, deadline ratificación 2026-05-29).
|
||||||
//!
|
//!
|
||||||
//! ## Status canonical: v0.0.16 — Real Ed25519 verification implementada
|
//! ## Status canonical: v0.0.18 — Mock runtime integration tests E2E own fixtures
|
||||||
//!
|
//!
|
||||||
//! Este crate provee:
|
//! Este crate provee:
|
||||||
//! - Storage layout canonical `StorageMap<AssetHash, AssetAttestation>` (PROV-2) ✅
|
//! - Storage layout canonical `StorageMap<AssetHash, AssetAttestation>` (PROV-2) ✅
|
||||||
//! - Dispatchable canonical `attest_asset(origin, ValidationAttestation)` (PROV-3) ✅
|
//! - Dispatchable canonical `attest_asset(origin, ValidationAttestation)` (PROV-3) ✅
|
||||||
//! - **Real Ed25519 verify canonical via ed25519-dalek (PROV-4) ✅ v0.0.16**
|
//! - Real Ed25519 verify canonical via ed25519-dalek (PROV-4) ✅ v0.0.16
|
||||||
//! - OIT Level >= 3 enforcement canonical (PROV-5) ✅
|
//! - OIT Level >= 3 enforcement canonical (PROV-5) ✅
|
||||||
//! - is_anchorable check canonical (PROV-6) ✅
|
//! - is_anchorable check canonical (PROV-6) ✅
|
||||||
//! - Events canonical `AssetAttested` + `AttestationRejected` (PROV-7) ✅
|
//! - Events canonical `AssetAttested` + `AttestationRejected` (PROV-7) ✅
|
||||||
//! - Errors canonical (PROV-4/5/6) ✅
|
//! - Errors canonical (PROV-4/5/6) ✅
|
||||||
//! - Pallet Config trait canonical ✅
|
//! - 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+)
|
//! ## Lo que NO funciona aún (post-Ergon sub-crate Cap 42+)
|
||||||
//!
|
//!
|
||||||
@@ -287,6 +302,16 @@ pub mod pallet {
|
|||||||
|
|
||||||
extern crate alloc;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
//! # Mock runtime canonical para integration tests v0.0.18
|
||||||
|
//!
|
||||||
|
//! Provee mock minimal `Test` runtime composition (`System` + `Provenance` solo)
|
||||||
|
//! para E2E tests dispatchable + storage + event assertions per PROV-3 flow
|
||||||
|
//! 6-step canonical:
|
||||||
|
//!
|
||||||
|
//! 1. PROV-4 Ed25519 signature verification
|
||||||
|
//! 2. PROV-5 validator OIT level >= 3 institutional
|
||||||
|
//! 3. PROV-6 is_anchorable check (verdict + severity)
|
||||||
|
//! 4. PROV-2 ValidationAttestation → AssetAttestation conversion
|
||||||
|
//! 5. PROV-2 StorageMap insert
|
||||||
|
//! 6. PROV-7 AssetAttested event emission
|
||||||
|
//!
|
||||||
|
//! ## Cross-reference canonical
|
||||||
|
//!
|
||||||
|
//! - DC `ORIGIN_PROVENANCE_PALLET_V1` PROV-1..8 sub-decisions
|
||||||
|
//! - `tests_integration.rs` 5 scenarios canonical E2E
|
||||||
|
//! - `lib.rs::pallet::attest_asset` dispatchable impl real
|
||||||
|
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use crate as origin_chain_provenance;
|
||||||
|
use frame_support::derive_impl;
|
||||||
|
use sp_runtime::BuildStorage;
|
||||||
|
|
||||||
|
/// Mock block type canonical para tests.
|
||||||
|
type Block = frame_system::mocking::MockBlock<Test>;
|
||||||
|
|
||||||
|
frame_support::construct_runtime!(
|
||||||
|
pub enum Test {
|
||||||
|
System: frame_system,
|
||||||
|
Provenance: origin_chain_provenance,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||||
|
impl frame_system::Config for Test {
|
||||||
|
type Block = Block;
|
||||||
|
type AccountId = sp_runtime::AccountId32;
|
||||||
|
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl origin_chain_provenance::Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test externalities builder canonical.
|
||||||
|
///
|
||||||
|
/// Returns new `sp_io::TestExternalities` con frame_system genesis configurado
|
||||||
|
/// y block_number set to 1 (PROV-7 events requieren block_number > 0 para
|
||||||
|
/// proper testing canonical).
|
||||||
|
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||||
|
let mut ext: sp_io::TestExternalities = frame_system::GenesisConfig::<Test>::default()
|
||||||
|
.build_storage()
|
||||||
|
.expect("genesis canonical builds")
|
||||||
|
.into();
|
||||||
|
ext.execute_with(|| {
|
||||||
|
System::set_block_number(1);
|
||||||
|
});
|
||||||
|
ext
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test signing key seed canonical (deterministic Ed25519 keypair).
|
||||||
|
///
|
||||||
|
/// Per ed25519-dalek pattern: seed [7u8; 32] produces deterministic verifying_key.
|
||||||
|
/// Used across integration tests para reproducible signing.
|
||||||
|
pub const TEST_SIGNING_SEED: [u8; 32] = [7u8; 32];
|
||||||
|
|
||||||
|
/// Test origin account canonical (32-byte zero pattern).
|
||||||
|
pub fn alice_origin() -> RuntimeOrigin {
|
||||||
|
RuntimeOrigin::signed(sp_runtime::AccountId32::new([1u8; 32]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test origin account canonical (BOB pattern).
|
||||||
|
pub fn bob_origin() -> RuntimeOrigin {
|
||||||
|
RuntimeOrigin::signed(sp_runtime::AccountId32::new([2u8; 32]))
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
//! # Integration tests canonical v0.0.18 — Mock runtime E2E
|
||||||
|
//!
|
||||||
|
//! 5 scenarios canonical PROV-3 attest_asset flow end-to-end via mock runtime:
|
||||||
|
//!
|
||||||
|
//! | Scenario | Verdict | Severity | OIT Level | Expected outcome |
|
||||||
|
//! |----------|---------|----------|-----------|------------------|
|
||||||
|
//! | passed_info_anchored | Passed | Info | 3 | ✅ AssetAttested event + storage insert |
|
||||||
|
//! | warning_anchored | Warning | Warning | 3 | ✅ AssetAttested event + storage insert |
|
||||||
|
//! | failed_critical_rejected | Failed | Critical | 3 | ❌ NotAnchorable error |
|
||||||
|
//! | passed_block_security_rejected | Passed | Block | 3 | ❌ NotAnchorable error |
|
||||||
|
//! | anonymous_level_0_rejected | Passed | Info | 0 | ❌ ValidatorTierInsufficient error |
|
||||||
|
//!
|
||||||
|
//! Plus 4 additional canonical:
|
||||||
|
//! - idempotency: already attested asset rejected con `AlreadyAttested` error
|
||||||
|
//! - tampered_signature: Ed25519 verify fails con `InvalidSignature` error
|
||||||
|
//! - tampered_payload: signing_payload mismatch con `InvalidSignature` error
|
||||||
|
//! - 6_step_walkthrough: explicit PROV-3 flow assertions in sequence
|
||||||
|
//!
|
||||||
|
//! Total: 9 integration tests canonical.
|
||||||
|
//!
|
||||||
|
//! ## Own fixtures pattern
|
||||||
|
//!
|
||||||
|
//! 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 en `origin-chain-types/src/attestation.rs`. Cuando Ergon
|
||||||
|
//! ship `arkhe-noesis-attestation-types` sub-crate Cap 42+, fixtures can swap.
|
||||||
|
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
use crate::mock::{alice_origin, bob_origin, new_test_ext, Provenance, RuntimeEvent, System, Test, TEST_SIGNING_SEED};
|
||||||
|
use crate::{Attestations, Error, Event};
|
||||||
|
use ed25519_dalek::{Signer, SigningKey};
|
||||||
|
use frame_support::{assert_noop, assert_ok};
|
||||||
|
use origin_chain_types::{
|
||||||
|
AssetHash, OitIdentity, SeverityTier, ValidationAttestation, ValidationVerdict,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Fixture builders canonical ──────────────────────────────────────
|
||||||
|
|
||||||
|
/// Build canonical signed ValidationAttestation con (verdict, severity, oit_level)
|
||||||
|
/// parameters. Deterministic Ed25519 signature.
|
||||||
|
///
|
||||||
|
/// asset_id es derivado de seed para uniqueness per scenario.
|
||||||
|
fn build_signed_attestation(
|
||||||
|
asset_seed: u8,
|
||||||
|
verdict: ValidationVerdict,
|
||||||
|
severity: SeverityTier,
|
||||||
|
oit_level: u8,
|
||||||
|
) -> ValidationAttestation {
|
||||||
|
let signing_key = SigningKey::from_bytes(&TEST_SIGNING_SEED);
|
||||||
|
let verifying_key = signing_key.verifying_key();
|
||||||
|
|
||||||
|
let mut att = ValidationAttestation {
|
||||||
|
schema_version: *b"1.0.0 ",
|
||||||
|
asset_id: AssetHash::new([asset_seed; 32]),
|
||||||
|
validator_oit: OitIdentity {
|
||||||
|
public_key: verifying_key.to_bytes(),
|
||||||
|
level: oit_level,
|
||||||
|
},
|
||||||
|
validated_at_us: 1_234_567_890,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build canonical fixture passed_info (most common anchorable scenario).
|
||||||
|
fn fixture_passed_info(asset_seed: u8) -> ValidationAttestation {
|
||||||
|
build_signed_attestation(asset_seed, ValidationVerdict::Passed, SeverityTier::Info, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario 1: passed_info anchored canonical ──────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scenario_1_passed_info_anchored_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let attestation = fixture_passed_info(1);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// PROV-3 attest_asset extrinsic canonical
|
||||||
|
assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone()));
|
||||||
|
|
||||||
|
// PROV-2 storage insert verified
|
||||||
|
let stored = Attestations::<Test>::get(asset_hash);
|
||||||
|
assert!(stored.is_some(), "PROV-2 canonical: AssetAttestation inserted in StorageMap");
|
||||||
|
let stored_unwrap = stored.unwrap();
|
||||||
|
assert_eq!(stored_unwrap.hash, asset_hash);
|
||||||
|
assert_eq!(stored_unwrap.signature.as_bytes(), &attestation.signature);
|
||||||
|
|
||||||
|
// PROV-7 event emitted canonical
|
||||||
|
System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested {
|
||||||
|
asset_hash,
|
||||||
|
block_number: 1,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario 2: warning anchored canonical (non-blocking) ────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scenario_2_warning_anchored_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let attestation = build_signed_attestation(
|
||||||
|
2,
|
||||||
|
ValidationVerdict::Warning,
|
||||||
|
SeverityTier::Warning,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
assert_ok!(Provenance::attest_asset(alice_origin(), attestation));
|
||||||
|
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_some());
|
||||||
|
System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested {
|
||||||
|
asset_hash,
|
||||||
|
block_number: 1,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario 3: failed_critical rejected canonical (PROV-6) ──────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scenario_3_failed_critical_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let attestation = build_signed_attestation(
|
||||||
|
3,
|
||||||
|
ValidationVerdict::Failed,
|
||||||
|
SeverityTier::Critical,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// PROV-6 is_anchorable canonical check fails (Failed verdict + Critical severity)
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(alice_origin(), attestation),
|
||||||
|
Error::<Test>::NotAnchorable
|
||||||
|
);
|
||||||
|
|
||||||
|
// Storage NOT inserted
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario 4: passed_block security rejected canonical (PROV-6) ────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scenario_4_passed_block_security_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// Passed verdict pero Block severity (security violation override)
|
||||||
|
let attestation = build_signed_attestation(
|
||||||
|
4,
|
||||||
|
ValidationVerdict::Passed,
|
||||||
|
SeverityTier::Block,
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// PROV-6 is_anchorable rejects Block severity even with Passed verdict
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(alice_origin(), attestation),
|
||||||
|
Error::<Test>::NotAnchorable
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario 5: anonymous_level_0 rejected canonical (PROV-5) ────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scenario_5_anonymous_level_0_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// Valid verdict + severity, pero OIT level 0 (anonymous) — PROV-5 reject
|
||||||
|
let attestation = build_signed_attestation(
|
||||||
|
5,
|
||||||
|
ValidationVerdict::Passed,
|
||||||
|
SeverityTier::Info,
|
||||||
|
0, // ANONYMOUS — institutional level >= 3 required
|
||||||
|
);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// PROV-5 validator OIT tier check canonical fails
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(alice_origin(), attestation),
|
||||||
|
Error::<Test>::ValidatorTierInsufficient
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Additional canonical: idempotency ────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn additional_idempotency_already_attested_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let attestation = fixture_passed_info(6);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// First attest_asset succeeds canonical
|
||||||
|
assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone()));
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_some());
|
||||||
|
|
||||||
|
// Second attest_asset (same hash) — idempotency canonical rejects
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(bob_origin(), attestation),
|
||||||
|
Error::<Test>::AlreadyAttested
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Additional canonical: tampered signature (PROV-4) ────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn additional_tampered_signature_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let mut attestation = fixture_passed_info(7);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// Flip bit en signature canonical post-sign
|
||||||
|
attestation.signature[0] ^= 0xFF;
|
||||||
|
|
||||||
|
// PROV-4 Ed25519 verify canonical fails
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(alice_origin(), attestation),
|
||||||
|
Error::<Test>::InvalidSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(Attestations::<Test>::get(asset_hash).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Additional canonical: tampered payload (PROV-4) ──────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn additional_tampered_payload_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let mut attestation = fixture_passed_info(8);
|
||||||
|
let original_asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// Tamper asset_id post-sign — cambia signing_payload pero mantiene signature original
|
||||||
|
attestation.asset_id = AssetHash::new([99u8; 32]);
|
||||||
|
let new_asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// PROV-4 signing_payload mismatch canonical, Ed25519 verify fails
|
||||||
|
assert_noop!(
|
||||||
|
Provenance::attest_asset(alice_origin(), attestation),
|
||||||
|
Error::<Test>::InvalidSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ninguno de los dos hashes está en storage
|
||||||
|
assert!(Attestations::<Test>::get(original_asset_hash).is_none());
|
||||||
|
assert!(Attestations::<Test>::get(new_asset_hash).is_none());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Additional canonical: 6-step PROV-3 walkthrough explicit ─────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn additional_6_step_prov_3_walkthrough_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let attestation = fixture_passed_info(9);
|
||||||
|
let asset_hash = attestation.asset_id;
|
||||||
|
|
||||||
|
// Pre-flight assertions canonical
|
||||||
|
assert!(
|
||||||
|
attestation.is_anchorable(),
|
||||||
|
"PRE-flight PROV-6: fixture anchorable canonical"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
attestation.validator_oit.is_institutional(),
|
||||||
|
"PRE-flight PROV-5: validator institutional canonical"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
Attestations::<Test>::get(asset_hash).is_none(),
|
||||||
|
"PRE-flight PROV-2: storage empty para asset_hash canonical"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 1: PROV-4 canonical Ed25519 signature verification (implicit via dispatchable)
|
||||||
|
// Step 2: PROV-5 canonical validate validator_oit.level >= 3 institutional
|
||||||
|
// Step 3: PROV-6 canonical check is_anchorable (verdict + severity)
|
||||||
|
// Step 4: PROV-2 canonical convert ValidationAttestation → AssetAttestation
|
||||||
|
// Step 5: PROV-2 canonical StorageMap insert
|
||||||
|
// Step 6: PROV-7 canonical emit AssetAttested event
|
||||||
|
assert_ok!(Provenance::attest_asset(alice_origin(), attestation.clone()));
|
||||||
|
|
||||||
|
// Post-flight assertions canonical
|
||||||
|
let stored = Attestations::<Test>::get(asset_hash)
|
||||||
|
.expect("POST-flight PROV-2: AssetAttestation persisted canonical");
|
||||||
|
assert_eq!(stored.hash, asset_hash, "POST-flight: hash preserved");
|
||||||
|
assert_eq!(
|
||||||
|
stored.signature.as_bytes(),
|
||||||
|
&attestation.signature,
|
||||||
|
"POST-flight: signature preserved canonical"
|
||||||
|
);
|
||||||
|
|
||||||
|
System::assert_last_event(RuntimeEvent::Provenance(Event::AssetAttested {
|
||||||
|
asset_hash,
|
||||||
|
block_number: 1,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in new issue
Block a user