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
@@ -79,23 +79,25 @@ pub fn derive_proposal_id(
|
||||
/// - Origin Metaverse Foundation legal entity governance audit
|
||||
/// - Cross-pillar consumers (Atelier + Codex + Ethos) para OIP rationale display
|
||||
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 {
|
||||
/// 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>;
|
||||
|
||||
/// 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(
|
||||
proposal_id: ProposalId,
|
||||
voter: origin_chain_types::AccountId,
|
||||
) -> 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)]
|
||||
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).
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
@@ -289,6 +316,29 @@ pub mod pallet {
|
||||
/// New council size canonical post-removal.
|
||||
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.
|
||||
@@ -316,6 +366,10 @@ pub mod pallet {
|
||||
AlreadyCouncilMember,
|
||||
/// CHN-4 5.2 canonical: council size canonical exceeds MAX_COUNCIL_SIZE.
|
||||
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.
|
||||
@@ -550,6 +604,80 @@ pub mod pallet {
|
||||
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user