feat(governance): v0.0.33 Phase 5.2 COMPLETE — Council veto 2/3 threshold + GovernanceApi extension 🎉
Phase 5.2 TECHNICAL COUNCIL COMPLETE end-to-end canonical. veto_proposal dispatchable + 2/3 ceiling threshold + 3 nuevos Runtime API methods (is_council_member + current_council_size + veto_votes_count). CHN-4 5.2+5.5 materializado compile-detect. ## veto_proposal canonical 7-step CHN-4 5.5 1. ensure_signed 2. Validate council_member (NotCouncilMember) 3. Get proposal (must be Voting) 4. Validate council_size > 0 (InsufficientCouncilSize) 5. !VetoVotes::contains_key (AlreadyVetoed) 6. Insert vote + increment VetoCount 7. Check threshold: count >= council_veto_threshold(size) → Rejected + ProposalVetoed event ## Storage + events + errors canonical nuevo - VetoVotes StorageDoubleMap presence-based + VetoCount cached u32 - Event::CouncilVetoVoteCast + ProposalVetoed - Error::AlreadyVetoed + InsufficientCouncilSize ## Runtime API GovernanceApi extension canonical - is_council_member(account) -> bool - current_council_size() -> u32 - veto_votes_count(proposal_id) -> u32 ## Verification - cargo build --release 4.82s clean - cargo test -p origin-chain-governance — 29 tests verde, 0 failures (10 lifecycle + 7 Council mgmt + 5 Council veto NEW + 5 standalone + 2 auto) - cargo test --workspace — 189 tests verde, 0 failures 🎉 Phase 5.2 TECHNICAL COUNCIL COMPLETE canonical. Hijos de Arkhē somos. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
afefcb2c04
commit
b3fc5da6ca
5 files changed
+381
-10
No files matched your search
@@ -6,6 +6,97 @@ by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.0.33] — 2026-05-25 — Phase 5.2 COMPLETE — Council veto 2/3 threshold + GovernanceApi extension 🎉
|
||||||
|
|
||||||
|
**🎉 Phase 5.2 TECHNICAL COUNCIL COMPLETE end-to-end** — `veto_proposal` dispatchable
|
||||||
|
canonical con 2/3 ceiling threshold + 3 nuevos Runtime API methods extension
|
||||||
|
GovernanceApi (`is_council_member` + `current_council_size` + `veto_votes_count`).
|
||||||
|
CHN-4 5.2+5.5 (Technical Council veto authority) materializado compile-detect.
|
||||||
|
|
||||||
|
### Deliverables v0.0.33
|
||||||
|
|
||||||
|
**veto_proposal dispatchable canonical 7-step CHN-4 5.5 flow**:
|
||||||
|
1. ensure_signed canonical
|
||||||
|
2. Validate council member canonical (Error::NotCouncilMember)
|
||||||
|
3. Get proposal (must be Voting status)
|
||||||
|
4. Validate council_size > 0 (Error::InsufficientCouncilSize)
|
||||||
|
5. !VetoVotes::contains_key (Error::AlreadyVetoed)
|
||||||
|
6. Insert vote + increment VetoCount
|
||||||
|
7. Check threshold: count >= council_veto_threshold(size) → flip to Rejected + emit ProposalVetoed; else emit CouncilVetoVoteCast
|
||||||
|
|
||||||
|
**Storage canonical nuevo**:
|
||||||
|
- `VetoVotes<StorageDoubleMap<ProposalId, AccountId, ()>>` presence-based tracking
|
||||||
|
- `VetoCount<StorageMap<ProposalId, u32>>` cached count para O(1) threshold check
|
||||||
|
|
||||||
|
**Events canonical nuevo**:
|
||||||
|
- `Event::CouncilVetoVoteCast` (vote no-final, progress canonical)
|
||||||
|
- `Event::ProposalVetoed` (threshold reached, status flipped)
|
||||||
|
|
||||||
|
**Errors canonical nuevo**:
|
||||||
|
- `Error::AlreadyVetoed` + `InsufficientCouncilSize`
|
||||||
|
|
||||||
|
**Runtime API GovernanceApi extension canonical**:
|
||||||
|
- `is_council_member(account) -> bool` canonical
|
||||||
|
- `current_council_size() -> u32` canonical
|
||||||
|
- `veto_votes_count(proposal_id) -> u32` canonical
|
||||||
|
- Runtime impl wires via `Governance::is_council_member` + `current_council_size` + `veto_count_of` helpers
|
||||||
|
|
||||||
|
### Phase 5.2 sub-decisions COMPLETE canonical post-v0.0.33 🎉
|
||||||
|
|
||||||
|
| Sub | Description | Status |
|
||||||
|
|-----|-------------|--------|
|
||||||
|
| 5.2.1 | CouncilMember + CouncilRole types canonical | ✅ v0.0.32 |
|
||||||
|
| 5.2.2 | Council StorageMap + CouncilSize cached | ✅ v0.0.32 |
|
||||||
|
| 5.2.3 | add_council_member + remove_council_member dispatchables | ✅ v0.0.32 |
|
||||||
|
| 5.2.4 | MAX_COUNCIL_SIZE 7 enforcement | ✅ v0.0.32 |
|
||||||
|
| 5.2.5 | council_veto_threshold 2/3 ceiling const fn | ✅ v0.0.32 |
|
||||||
|
| 5.2.6 | veto_proposal dispatchable canonical | ✅ **v0.0.33** |
|
||||||
|
| 5.2.7 | Auto-Reject on threshold reach | ✅ **v0.0.33** |
|
||||||
|
| 5.2.8 | Runtime API GovernanceApi extension | ✅ **v0.0.33** |
|
||||||
|
|
||||||
|
**PHASE 5.2 TECHNICAL COUNCIL = COMPLETE END-TO-END canonical** 🎉
|
||||||
|
|
||||||
|
### Verification end-to-end canonical
|
||||||
|
|
||||||
|
- ✅ `cargo build --release -p origin-chain-node` 4.82s clean
|
||||||
|
- ✅ `cargo test -p origin-chain-governance` — **29 tests verde, 0 failures**:
|
||||||
|
- 10 lifecycle integration tests
|
||||||
|
- 7 Council management scenarios (v0.0.32)
|
||||||
|
- 5 Council veto scenarios (v0.0.33 NEW)
|
||||||
|
- 5 standalone tests
|
||||||
|
- 2 auto-generated
|
||||||
|
- ✅ `cargo test --workspace` — **189 tests passing, 0 failures**
|
||||||
|
- ✅ `origin-chain-node info` muestra 11 pallets + 15 Runtime APIs canonical (3 new API methods en GovernanceApi)
|
||||||
|
|
||||||
|
### Cross-pillar consumers status post v0.0.33 — Phase 5.2 COMPLETE
|
||||||
|
|
||||||
|
- ✅ David Foundation governance frontend: canonical proposals + votes + Council veto via RPC
|
||||||
|
- ✅ 5 AI advisor agents: query is_council_member para verify human-only voters
|
||||||
|
- ✅ Origin Metaverse Foundation legal entity: council member registry on-chain audit
|
||||||
|
- ✅ Cross-pillar consumers (Atelier + Codex + Ethos): governance decisions canonical
|
||||||
|
|
||||||
|
### Roadmap canonical post-v0.0.33 — Phase 5.1+5.2 COMPLETE 🎉
|
||||||
|
|
||||||
|
| Version | Trigger |
|
||||||
|
|---------|---------|
|
||||||
|
| v0.0.34+ | Phase 5.3-5.8 governance refinements (Treasury disbursement + Emergency referendum + 5 AI advisors AdvisorMayNotVote enforcement) |
|
||||||
|
| v0.0.40+ | Phase 3 `pallet-revive` smart contracts (BLOQUEADO Slice 2e WASM) |
|
||||||
|
| v0.1.0 | Slice 2e WASM resolved + full testnet Q3 2026 launch |
|
||||||
|
| v1.0.0 | Mainnet Q1 2027 |
|
||||||
|
|
||||||
|
### Filosofía canonical Phase 5.2 cierre
|
||||||
|
|
||||||
|
CHN-4 5.5 ratificado canonical materializado: *"Technical Council veto + override
|
||||||
|
canonical (2/3 voters)"*. Ceiling division canonical via `(size × 2 + 2) / 3`
|
||||||
|
asegura mathematical purity canonical: 1→1, 2→2, 3→2, 4→3, 5→4, 6→4, 7→5.
|
||||||
|
|
||||||
|
Lobo blanco invariant compile-detect canonical (test scenario_7): 8 verifications
|
||||||
|
de threshold canonical estricto para council sizes 0-7. Inmutable: AGI advisor
|
||||||
|
roles canonical eventually (Phase 5.6) bloquear voting permanece pero Council
|
||||||
|
veto authority canonical preservada.
|
||||||
|
|
||||||
|
🐺💎⚓ Hijos de Arkhē somos. Phase 5.2 COMPLETE end-to-end canonical.
|
||||||
|
|
||||||
## [0.0.32] — 2026-05-25 — Phase 5.2 Council scaffold + member management (CHN-4 5.2 Technical Council)
|
## [0.0.32] — 2026-05-25 — Phase 5.2 Council scaffold + member management (CHN-4 5.2 Technical Council)
|
||||||
|
|
||||||
**🏛️ Phase 5.2 TECHNICAL COUNCIL scaffold + management canonical** — Extension del
|
**🏛️ Phase 5.2 TECHNICAL COUNCIL scaffold + management canonical** — Extension del
|
||||||
|
|||||||
@@ -79,23 +79,25 @@ pub fn derive_proposal_id(
|
|||||||
/// - Origin Metaverse Foundation legal entity governance audit
|
/// - Origin Metaverse Foundation legal entity governance audit
|
||||||
/// - Cross-pillar consumers (Atelier + Codex + Ethos) para OIP rationale display
|
/// - Cross-pillar consumers (Atelier + Codex + Ethos) para OIP rationale display
|
||||||
sp_api::decl_runtime_apis! {
|
sp_api::decl_runtime_apis! {
|
||||||
/// Governance runtime API canonical (Phase 5.1 v0.0.31).
|
/// Governance runtime API canonical (Phase 5.1 v0.0.31 + Phase 5.2 v0.0.33).
|
||||||
pub trait GovernanceApi {
|
pub trait GovernanceApi {
|
||||||
/// Query proposal canonical por ProposalId.
|
/// Query proposal canonical por ProposalId.
|
||||||
///
|
|
||||||
/// Returns `Some(Proposal)` si registered previamente on-chain,
|
|
||||||
/// `None` si nunca submitted canonical. Read-only canonical sin
|
|
||||||
/// transaction fee canonical (off-chain via RPC).
|
|
||||||
fn query_proposal(id: ProposalId) -> Option<Proposal>;
|
fn query_proposal(id: ProposalId) -> Option<Proposal>;
|
||||||
|
|
||||||
/// Query vote stance canonical por (proposal_id, voter).
|
/// Query vote stance canonical por (proposal_id, voter).
|
||||||
///
|
|
||||||
/// Returns `Some(VoteStance)` si voter cast vote on proposal,
|
|
||||||
/// `None` si nunca voted canonical o proposal NO existe.
|
|
||||||
fn query_vote(
|
fn query_vote(
|
||||||
proposal_id: ProposalId,
|
proposal_id: ProposalId,
|
||||||
voter: origin_chain_types::AccountId,
|
voter: origin_chain_types::AccountId,
|
||||||
) -> Option<VoteStance>;
|
) -> Option<VoteStance>;
|
||||||
|
|
||||||
|
/// **Phase 5.2 v0.0.33**: Query si account es Technical Council member canonical.
|
||||||
|
fn is_council_member(account: origin_chain_types::AccountId) -> bool;
|
||||||
|
|
||||||
|
/// **Phase 5.2 v0.0.33**: Query current Technical Council size canonical.
|
||||||
|
fn current_council_size() -> u32;
|
||||||
|
|
||||||
|
/// **Phase 5.2 v0.0.33**: Query veto votes count para proposal canonical.
|
||||||
|
fn veto_votes_count(proposal_id: ProposalId) -> u32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +240,31 @@ pub mod pallet {
|
|||||||
#[pallet::getter(fn council_size)]
|
#[pallet::getter(fn council_size)]
|
||||||
pub type CouncilSize<T: Config> = StorageValue<_, u32, ValueQuery>;
|
pub type CouncilSize<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||||
|
|
||||||
|
/// CHN-4 5.2 canonical: `VetoVotes<StorageDoubleMap<ProposalId, AccountId, ()>>` canonical.
|
||||||
|
///
|
||||||
|
/// Presence-based tracking canonical de council vetoes (no value stored, solo key
|
||||||
|
/// presence indica voted). Tally canonical computed via iterator count en finalize.
|
||||||
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn veto_vote_of)]
|
||||||
|
pub type VetoVotes<T: Config> = StorageDoubleMap<
|
||||||
|
_,
|
||||||
|
Blake2_128Concat,
|
||||||
|
ProposalId,
|
||||||
|
Blake2_128Concat,
|
||||||
|
T::AccountId,
|
||||||
|
(),
|
||||||
|
OptionQuery,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// CHN-4 5.2 canonical: `VetoCount<StorageMap<ProposalId, u32>>` cached veto count.
|
||||||
|
///
|
||||||
|
/// Cached canonical para O(1) threshold check sin iterar DoubleMap. Incrementado
|
||||||
|
/// canonical en cada veto_proposal call.
|
||||||
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn veto_count_of)]
|
||||||
|
pub type VetoCount<T: Config> =
|
||||||
|
StorageMap<_, Blake2_128Concat, ProposalId, u32, ValueQuery>;
|
||||||
|
|
||||||
/// Events emisión canonical (CHN-4 governance lifecycle).
|
/// Events emisión canonical (CHN-4 governance lifecycle).
|
||||||
#[pallet::event]
|
#[pallet::event]
|
||||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||||
@@ -289,6 +316,29 @@ pub mod pallet {
|
|||||||
/// New council size canonical post-removal.
|
/// New council size canonical post-removal.
|
||||||
new_size: u32,
|
new_size: u32,
|
||||||
},
|
},
|
||||||
|
/// Council vote cast canonical sobre proposal veto (Phase 5.2 v0.0.33).
|
||||||
|
CouncilVetoVoteCast {
|
||||||
|
/// ProposalId canonical being voted on.
|
||||||
|
id: ProposalId,
|
||||||
|
/// Council member voting canonical.
|
||||||
|
voter: T::AccountId,
|
||||||
|
/// Current veto count canonical post-vote.
|
||||||
|
current_veto_count: u32,
|
||||||
|
/// Required threshold canonical para final veto (2/3).
|
||||||
|
required_threshold: u32,
|
||||||
|
},
|
||||||
|
/// Proposal vetoed canonical por Technical Council (Phase 5.2 v0.0.33).
|
||||||
|
///
|
||||||
|
/// Emitted canonical cuando council reach 2/3 threshold canonical canonical.
|
||||||
|
/// Proposal status canonical flipped to Rejected (council veto override).
|
||||||
|
ProposalVetoed {
|
||||||
|
/// ProposalId canonical vetoed.
|
||||||
|
id: ProposalId,
|
||||||
|
/// Veto count canonical at moment of veto.
|
||||||
|
veto_count: u32,
|
||||||
|
/// Council size canonical at moment of veto.
|
||||||
|
council_size: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pallet errors canonical.
|
/// Pallet errors canonical.
|
||||||
@@ -316,6 +366,10 @@ pub mod pallet {
|
|||||||
AlreadyCouncilMember,
|
AlreadyCouncilMember,
|
||||||
/// CHN-4 5.2 canonical: council size canonical exceeds MAX_COUNCIL_SIZE.
|
/// CHN-4 5.2 canonical: council size canonical exceeds MAX_COUNCIL_SIZE.
|
||||||
CouncilSizeExceeded,
|
CouncilSizeExceeded,
|
||||||
|
/// CHN-4 5.2 v0.0.33 canonical: council member ya vetó este proposal.
|
||||||
|
AlreadyVetoed,
|
||||||
|
/// CHN-4 5.2 v0.0.33 canonical: council size canonical insuficiente para veto threshold.
|
||||||
|
InsufficientCouncilSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Minimum stake canonical proposal submission per CHN-4 5.3.
|
/// Minimum stake canonical proposal submission per CHN-4 5.3.
|
||||||
@@ -550,6 +604,80 @@ pub mod pallet {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CHN-4 5.2 v0.0.33 canonical: `veto_proposal` Phase 5.2 dispatchable canonical.
|
||||||
|
///
|
||||||
|
/// 7-step canonical flow:
|
||||||
|
/// 1. ensure_signed canonical
|
||||||
|
/// 2. Validate council member canonical (`Error::NotCouncilMember`)
|
||||||
|
/// 3. Get proposal canonical (must exist + Voting status)
|
||||||
|
/// 4. Validate council size > 0 (`Error::InsufficientCouncilSize`)
|
||||||
|
/// 5. Validate !VetoVotes::contains_key (Error::AlreadyVetoed)
|
||||||
|
/// 6. Insert vote + increment count
|
||||||
|
/// 7. Check threshold canonical: if count >= required → flip status to Rejected
|
||||||
|
/// + emit ProposalVetoed canonical; else emit CouncilVetoVoteCast canonical
|
||||||
|
#[pallet::call_index(5)]
|
||||||
|
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
||||||
|
pub fn veto_proposal(
|
||||||
|
origin: OriginFor<T>,
|
||||||
|
proposal_id: ProposalId,
|
||||||
|
) -> DispatchResult {
|
||||||
|
// Step 1: ensure signed canonical
|
||||||
|
let who = ensure_signed(origin)?;
|
||||||
|
|
||||||
|
// Step 2: validate council member canonical
|
||||||
|
ensure!(
|
||||||
|
Council::<T>::contains_key(&who),
|
||||||
|
Error::<T>::NotCouncilMember
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 3: get proposal canonical
|
||||||
|
let mut proposal = Proposals::<T>::get(proposal_id)
|
||||||
|
.ok_or(Error::<T>::ProposalNotFound)?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
matches!(proposal.status, ProposalStatus::Voting),
|
||||||
|
Error::<T>::ProposalAlreadyFinalized
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 4: council size canonical > 0
|
||||||
|
let council_size = CouncilSize::<T>::get();
|
||||||
|
ensure!(council_size > 0, Error::<T>::InsufficientCouncilSize);
|
||||||
|
|
||||||
|
// Step 5: AlreadyVetoed canonical
|
||||||
|
ensure!(
|
||||||
|
!VetoVotes::<T>::contains_key(proposal_id, &who),
|
||||||
|
Error::<T>::AlreadyVetoed
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 6: insert vote + increment count
|
||||||
|
VetoVotes::<T>::insert(proposal_id, &who, ());
|
||||||
|
let new_count = VetoCount::<T>::get(proposal_id).saturating_add(1);
|
||||||
|
VetoCount::<T>::insert(proposal_id, new_count);
|
||||||
|
|
||||||
|
// Step 7: check threshold canonical
|
||||||
|
let required = council_veto_threshold(council_size);
|
||||||
|
if new_count >= required {
|
||||||
|
// Veto threshold reached canonical → flip status canonical
|
||||||
|
proposal.status = ProposalStatus::Rejected;
|
||||||
|
Proposals::<T>::insert(proposal_id, proposal);
|
||||||
|
|
||||||
|
Self::deposit_event(Event::ProposalVetoed {
|
||||||
|
id: proposal_id,
|
||||||
|
veto_count: new_count,
|
||||||
|
council_size,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Self::deposit_event(Event::CouncilVetoVoteCast {
|
||||||
|
id: proposal_id,
|
||||||
|
voter: who,
|
||||||
|
current_veto_count: new_count,
|
||||||
|
required_threshold: required,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper canonical para convert T::AccountId → 32-byte canonical.
|
/// Helper canonical para convert T::AccountId → 32-byte canonical.
|
||||||
|
|||||||
@@ -383,6 +383,144 @@ fn council_scenario_6_max_size_enforcement_canonical() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Scenario Council Veto 1: 1 vetoer / 1 council → reject canonical ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn council_veto_scenario_1_solo_council_veto_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
use crate::pallet::CouncilRole;
|
||||||
|
use frame_support::dispatch::RawOrigin;
|
||||||
|
|
||||||
|
// Setup: 1 council member (alice) + active proposal
|
||||||
|
assert_ok!(Governance::add_council_member(
|
||||||
|
RawOrigin::Root.into(),
|
||||||
|
alice_account(),
|
||||||
|
CouncilRole::Chair,
|
||||||
|
));
|
||||||
|
let id = submit_default(alice_origin());
|
||||||
|
|
||||||
|
// Alice vetoes → council size 1, threshold 1, veto count 1 → Rejected
|
||||||
|
assert_ok!(Governance::veto_proposal(alice_origin(), id));
|
||||||
|
|
||||||
|
let proposal = Proposals::<Test>::get(id).unwrap();
|
||||||
|
assert!(matches!(proposal.status, ProposalStatus::Rejected));
|
||||||
|
|
||||||
|
System::assert_last_event(RuntimeEvent::Governance(Event::ProposalVetoed {
|
||||||
|
id,
|
||||||
|
veto_count: 1,
|
||||||
|
council_size: 1,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario Council Veto 2: 2/3 council vote canonical ────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn council_veto_scenario_2_two_of_three_council_threshold_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
use crate::pallet::CouncilRole;
|
||||||
|
use frame_support::dispatch::RawOrigin;
|
||||||
|
|
||||||
|
// Setup: 3 council members (alice, bob, charlie)
|
||||||
|
for acc in [alice_account(), bob_account(), charlie_account()] {
|
||||||
|
assert_ok!(Governance::add_council_member(
|
||||||
|
RawOrigin::Root.into(),
|
||||||
|
acc,
|
||||||
|
CouncilRole::Member,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
assert_eq!(Governance::current_council_size(), 3);
|
||||||
|
assert_eq!(Governance::required_veto_votes(), 2); // ceiling(6/3) = 2
|
||||||
|
|
||||||
|
// Submit proposal canonical
|
||||||
|
let id = submit_default(alice_origin());
|
||||||
|
|
||||||
|
// 1st veto → CouncilVetoVoteCast event canonical (not yet threshold)
|
||||||
|
assert_ok!(Governance::veto_proposal(bob_origin(), id));
|
||||||
|
let proposal_after_1 = Proposals::<Test>::get(id).unwrap();
|
||||||
|
assert!(matches!(proposal_after_1.status, ProposalStatus::Voting));
|
||||||
|
|
||||||
|
// 2nd veto → threshold reached → Rejected
|
||||||
|
assert_ok!(Governance::veto_proposal(charlie_origin(), id));
|
||||||
|
let proposal_after_2 = Proposals::<Test>::get(id).unwrap();
|
||||||
|
assert!(matches!(proposal_after_2.status, ProposalStatus::Rejected));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario Council Veto 3: non-council member rejected canonical ──
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn council_veto_scenario_3_non_council_member_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
let id = submit_default(alice_origin());
|
||||||
|
|
||||||
|
// Bob NOT council member → NotCouncilMember
|
||||||
|
assert_noop!(
|
||||||
|
Governance::veto_proposal(bob_origin(), id),
|
||||||
|
Error::<Test>::NotCouncilMember
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario Council Veto 4: double veto same member rejected ──────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn council_veto_scenario_4_double_veto_rejected_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
use crate::pallet::CouncilRole;
|
||||||
|
use frame_support::dispatch::RawOrigin;
|
||||||
|
|
||||||
|
// Setup: 3 council members (so 1 veto doesn't auto-reject)
|
||||||
|
for acc in [alice_account(), bob_account(), charlie_account()] {
|
||||||
|
assert_ok!(Governance::add_council_member(
|
||||||
|
RawOrigin::Root.into(),
|
||||||
|
acc,
|
||||||
|
CouncilRole::Member,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let id = submit_default(alice_origin());
|
||||||
|
|
||||||
|
// First veto canonical OK
|
||||||
|
assert_ok!(Governance::veto_proposal(bob_origin(), id));
|
||||||
|
|
||||||
|
// Second veto same account → AlreadyVetoed
|
||||||
|
assert_noop!(
|
||||||
|
Governance::veto_proposal(bob_origin(), id),
|
||||||
|
Error::<Test>::AlreadyVetoed
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Scenario Council Veto 5: query API methods canonical ───────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn council_veto_scenario_5_query_apis_canonical() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
use crate::pallet::CouncilRole;
|
||||||
|
use frame_support::dispatch::RawOrigin;
|
||||||
|
|
||||||
|
// Empty initial state
|
||||||
|
assert_eq!(Governance::current_council_size(), 0);
|
||||||
|
assert!(!Governance::is_council_member(&alice_account()));
|
||||||
|
|
||||||
|
// Add member
|
||||||
|
assert_ok!(Governance::add_council_member(
|
||||||
|
RawOrigin::Root.into(),
|
||||||
|
alice_account(),
|
||||||
|
CouncilRole::Chair,
|
||||||
|
));
|
||||||
|
assert_eq!(Governance::current_council_size(), 1);
|
||||||
|
assert!(Governance::is_council_member(&alice_account()));
|
||||||
|
|
||||||
|
// Submit + veto
|
||||||
|
let id = submit_default(alice_origin());
|
||||||
|
assert_eq!(crate::VetoCount::<Test>::get(id), 0);
|
||||||
|
|
||||||
|
assert_ok!(Governance::veto_proposal(alice_origin(), id));
|
||||||
|
assert_eq!(crate::VetoCount::<Test>::get(id), 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Scenario Council 7: 2/3 threshold canonical compile-detect ─────
|
// ─── Scenario Council 7: 2/3 threshold canonical compile-detect ─────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ fn print_info() {
|
|||||||
println!(" • Index 7 Provenance (pallet_origin_provenance asset attestation CHN-7) [Phase 2.1 COMPLETE v0.0.17]");
|
println!(" • Index 7 Provenance (pallet_origin_provenance asset attestation CHN-7) [Phase 2.1 COMPLETE v0.0.17]");
|
||||||
println!(" • Index 8 Fees (pallet_origin_fees cross-pillar economy OCAFB-1..6) [Phase 2.4 COMPLETE v0.0.23]");
|
println!(" • Index 8 Fees (pallet_origin_fees cross-pillar economy OCAFB-1..6) [Phase 2.4 COMPLETE v0.0.23]");
|
||||||
println!(" • Index 9 AgentIdentity (pallet_agent_identity AGI DIDs Inmutable #41) [Phase 2.7 COMPLETE v0.0.27]");
|
println!(" • Index 9 AgentIdentity (pallet_agent_identity AGI DIDs Inmutable #41) [Phase 2.7 COMPLETE v0.0.27]");
|
||||||
println!(" • Index 10 Governance (origin_chain_governance Foundation Treasury CHN-4) [Phase 5.1 FOUNDATION COMPLETE v0.0.31]");
|
println!(" • Index 10 Governance (origin_chain_governance Foundation Treasury + Council CHN-4) [Phase 5.1+5.2 COMPLETE v0.0.33]");
|
||||||
println!();
|
println!();
|
||||||
println!("Runtime APIs canonical (15):");
|
println!("Runtime APIs canonical (15):");
|
||||||
println!(" • sp_api::Core (version + execute_block)");
|
println!(" • sp_api::Core (version + execute_block)");
|
||||||
@@ -219,7 +219,7 @@ fn print_info() {
|
|||||||
println!(" • origin_chain_provenance::ProvenanceApi (verify_provenance RPC) [Phase 2.1 v0.0.17]");
|
println!(" • origin_chain_provenance::ProvenanceApi (verify_provenance RPC) [Phase 2.1 v0.0.17]");
|
||||||
println!(" • origin_chain_fees::FeesApi (query_fee_for_action RPC) [Phase 2.4 COMPLETE v0.0.23]");
|
println!(" • origin_chain_fees::FeesApi (query_fee_for_action RPC) [Phase 2.4 COMPLETE v0.0.23]");
|
||||||
println!(" • origin_chain_agent_identity::AgentIdentityApi (query_identity RPC) [Phase 2.7 COMPLETE v0.0.27]");
|
println!(" • origin_chain_agent_identity::AgentIdentityApi (query_identity RPC) [Phase 2.7 COMPLETE v0.0.27]");
|
||||||
println!(" • origin_chain_governance::GovernanceApi (query_proposal + query_vote RPC) [Phase 5.1 FOUNDATION COMPLETE v0.0.31]");
|
println!(" • origin_chain_governance::GovernanceApi (query_proposal + query_vote + Council RPCs) [Phase 5.1+5.2 COMPLETE v0.0.33]");
|
||||||
println!();
|
println!();
|
||||||
println!("Genesis presets canonical (3):");
|
println!("Genesis presets canonical (3):");
|
||||||
println!(" • development (Alice + 100M OGC dev)");
|
println!(" • development (Alice + 100M OGC dev)");
|
||||||
|
|||||||
@@ -625,6 +625,20 @@ impl_runtime_apis! {
|
|||||||
let runtime_voter = ChainAccountId::from(voter.0);
|
let runtime_voter = ChainAccountId::from(voter.0);
|
||||||
Governance::query_vote_of(proposal_id, &runtime_voter)
|
Governance::query_vote_of(proposal_id, &runtime_voter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 5.2 v0.0.33 canonical Council query APIs
|
||||||
|
fn is_council_member(account: origin_chain_types::AccountId) -> bool {
|
||||||
|
let runtime_account = ChainAccountId::from(account.0);
|
||||||
|
Governance::is_council_member(&runtime_account)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_council_size() -> u32 {
|
||||||
|
Governance::current_council_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn veto_votes_count(proposal_id: origin_chain_types::ProposalId) -> u32 {
|
||||||
|
Governance::veto_count_of(proposal_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user