Skip to content

Commit 82a601f

Browse files
Space_WlkaWilfred007Smartdevs17
authored
Feat add protocol governance (#108)
* implemented oracle contract updater * implemented protocol governance * latest implementation --------- Co-authored-by: Wilfred007 <adzerwilfred007@gmail.com> Co-authored-by: Stephen Joseph <81881979+Smartdevs17@users.noreply.github.com>
1 parent 670dd38 commit 82a601f

11 files changed

Lines changed: 520 additions & 36 deletions

File tree

stellar-lend/contracts/hello-world/src/errors.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ pub enum GovernanceError {
2828
ExecutionFailed = 122,
2929
InvalidMultisigConfig = 123,
3030
InsufficientApprovals = 124,
31-
RecoveryInProgress = 125,
32-
NoRecoveryInProgress = 126,
33-
InvalidGuardianConfig = 127,
34-
GuardianAlreadyExists = 128,
35-
GuardianNotFound = 129,
36-
MathOverflow = 130,
31+
InvalidProposalType = 125,
32+
GuardianAlreadyExists = 126,
33+
GuardianNotFound = 127,
34+
InvalidGuardianConfig = 128,
35+
RecoveryInProgress = 129,
36+
NoRecoveryInProgress = 130,
3737
Unauthorized = 131,
3838
AlreadyInitialized = 132,
3939
NotInitialized = 133,

stellar-lend/contracts/hello-world/src/governance.rs

Lines changed: 252 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ pub use crate::types::{
1414

1515
use crate::events::{
1616
GovernanceInitializedEvent, GuardianAddedEvent, GuardianRemovedEvent, ProposalApprovedEvent,
17-
ProposalCancelledEvent, ProposalCreatedEvent, ProposalExecutedEvent, ProposalFailedEvent,
18-
ProposalQueuedEvent, RecoveryApprovedEvent, RecoveryExecutedEvent, RecoveryStartedEvent,
19-
VoteCastEvent,
17+
ProposalCancelledEvent, ProposalQueuedEvent, RecoveryApprovedEvent, RecoveryExecutedEvent, RecoveryStartedEvent,
18+
VoteCastEvent, emit_proposal_approved,
2019
};
2120

2221
// ========================================================================
@@ -425,14 +424,172 @@ pub fn execute_proposal(
425424
Ok(())
426425
}
427426

428-
fn execute_proposal_type(_env: &Env, proposal_type: &ProposalType) -> Result<(), GovernanceError> {
427+
fn execute_proposal_type(env: &Env, proposal_type: &ProposalType) -> Result<(), GovernanceError> {
429428
match proposal_type {
430-
ProposalType::MinCollateralRatio(_)
431-
| ProposalType::RiskParams(_, _, _, _)
432-
| ProposalType::PauseSwitch(_, _)
433-
| ProposalType::EmergencyPause(_)
434-
| ProposalType::GenericAction(_) => Ok(()),
429+
ProposalType::MinCollateralRatio(ratio) => {
430+
risk_params::set_risk_params(env, Some(*ratio), None, None, None)
431+
.map_err(|_| GovernanceError::ExecutionFailed)?;
432+
}
433+
ProposalType::RiskParams(mcr, lt, cf, li) => {
434+
risk_params::set_risk_params(env, *mcr, *lt, *cf, *li)
435+
.map_err(|_| GovernanceError::ExecutionFailed)?;
436+
}
437+
ProposalType::InterestRateConfig(params) => {
438+
let admin = get_admin(env).ok_or(GovernanceError::NotInitialized)?;
439+
interest_rate::update_interest_rate_config(
440+
env,
441+
admin,
442+
params.base_rate_bps,
443+
params.kink_utilization_bps,
444+
params.multiplier_bps,
445+
params.jump_multiplier_bps,
446+
params.rate_floor_bps,
447+
params.rate_ceiling_bps,
448+
params.spread_bps,
449+
)
450+
.map_err(|_| GovernanceError::ExecutionFailed)?;
451+
}
452+
ProposalType::PauseSwitch(op, paused) => {
453+
let admin = get_admin(env).ok_or(GovernanceError::NotInitialized)?;
454+
risk_management::set_pause_switch(env, admin, op.clone(), *paused)
455+
.map_err(|_| GovernanceError::ExecutionFailed)?;
456+
}
457+
ProposalType::EmergencyPause(paused) => {
458+
let admin = get_admin(env).ok_or(GovernanceError::NotInitialized)?;
459+
risk_management::set_emergency_pause(env, admin, *paused)
460+
.map_err(|_| GovernanceError::ExecutionFailed)?;
461+
}
462+
ProposalType::GenericAction(_) => {
463+
return Err(GovernanceError::InvalidProposalType);
464+
}
435465
}
466+
Ok(())
467+
}
468+
469+
pub fn create_admin_proposal(
470+
env: &Env,
471+
admin: Address,
472+
proposal_type: ProposalType,
473+
description: String,
474+
) -> Result<u64, GovernanceError> {
475+
admin.require_auth();
476+
477+
let stored_admin: Address = env
478+
.storage()
479+
.instance()
480+
.get(&GovernanceDataKey::Admin)
481+
.ok_or(GovernanceError::NotInitialized)?;
482+
483+
if admin != stored_admin {
484+
return Err(GovernanceError::Unauthorized);
485+
}
486+
487+
let config: GovernanceConfig = env
488+
.storage()
489+
.instance()
490+
.get(&GovernanceDataKey::Config)
491+
.ok_or(GovernanceError::NotInitialized)?;
492+
493+
let now = env.ledger().timestamp();
494+
let proposal_id: u64 = env
495+
.storage()
496+
.instance()
497+
.get(&GovernanceDataKey::NextProposalId)
498+
.unwrap_or(0);
499+
500+
let execution_time = now + config.execution_delay.max(MIN_TIMELOCK_DELAY);
501+
502+
let proposal = Proposal {
503+
id: proposal_id,
504+
proposer: admin.clone(),
505+
proposal_type,
506+
description,
507+
status: ProposalStatus::Queued,
508+
start_time: now,
509+
end_time: now,
510+
execution_time: Some(execution_time),
511+
voting_threshold: 0,
512+
for_votes: 0,
513+
against_votes: 0,
514+
abstain_votes: 0,
515+
total_voting_power: 0,
516+
created_at: now,
517+
};
518+
519+
env.storage()
520+
.persistent()
521+
.set(&GovernanceDataKey::Proposal(proposal_id), &proposal);
522+
523+
env.storage()
524+
.instance()
525+
.set(&GovernanceDataKey::NextProposalId, &(proposal_id + 1));
526+
527+
emit_proposal_created_event(env, &proposal_id, &admin);
528+
529+
let topics = (Symbol::new(env, "proposal_queued"), proposal_id);
530+
env.events().publish(topics, execution_time);
531+
532+
Ok(proposal_id)
533+
}
534+
535+
pub fn create_emergency_proposal(
536+
env: &Env,
537+
caller: Address,
538+
proposal_type: ProposalType,
539+
description: String,
540+
) -> Result<u64, GovernanceError> {
541+
caller.require_auth();
542+
543+
// Verification of multisig auth happens via approvals in multisig module,
544+
// but for "emergency bypass" we can allow direct execution if called by a valid multisig admin
545+
// assuming it's correctly authorized by the multisig threshold.
546+
// In this simplified version, we'll check against multisig admins.
547+
548+
let multisig_config: MultisigConfig = env
549+
.storage()
550+
.instance()
551+
.get(&GovernanceDataKey::MultisigConfig)
552+
.ok_or(GovernanceError::NotInitialized)?;
553+
554+
if !multisig_config.admins.contains(&caller) {
555+
return Err(GovernanceError::Unauthorized);
556+
}
557+
558+
let now = env.ledger().timestamp();
559+
let proposal_id: u64 = env
560+
.storage()
561+
.instance()
562+
.get(&GovernanceDataKey::NextProposalId)
563+
.unwrap_or(0);
564+
565+
let proposal = Proposal {
566+
id: proposal_id,
567+
proposer: caller.clone(),
568+
proposal_type,
569+
description,
570+
status: ProposalStatus::Queued,
571+
start_time: now,
572+
end_time: now,
573+
execution_time: Some(now), // No delay for emergency
574+
voting_threshold: 0,
575+
for_votes: 0,
576+
against_votes: 0,
577+
abstain_votes: 0,
578+
total_voting_power: 0,
579+
created_at: now,
580+
};
581+
582+
env.storage()
583+
.persistent()
584+
.set(&GovernanceDataKey::Proposal(proposal_id), &proposal);
585+
586+
env.storage()
587+
.instance()
588+
.set(&GovernanceDataKey::NextProposalId, &(proposal_id + 1));
589+
590+
emit_proposal_created_event(env, &proposal_id, &caller);
591+
592+
Ok(proposal_id)
436593
}
437594

438595
// ========================================================================
@@ -570,6 +727,91 @@ pub fn get_proposal_approvals(env: &Env, proposal_id: u64) -> Option<Vec<Address
570727
env.storage().persistent().get(&approvals_key)
571728
}
572729

730+
pub fn get_multisig_config(env: &Env) -> Option<MultisigConfig> {
731+
env.storage()
732+
.instance()
733+
.get(&GovernanceDataKey::MultisigConfig)
734+
}
735+
736+
pub fn get_multisig_admins(env: &Env) -> Option<Vec<Address>> {
737+
get_multisig_config(env).map(|c| c.admins)
738+
}
739+
740+
pub fn get_multisig_threshold(env: &Env) -> u32 {
741+
get_multisig_config(env).map(|c| c.threshold).unwrap_or(1)
742+
}
743+
744+
pub fn set_multisig_admins(
745+
env: &Env,
746+
caller: Address,
747+
admins: Vec<Address>,
748+
) -> Result<(), GovernanceError> {
749+
let config = get_multisig_config(env).ok_or(GovernanceError::NotInitialized)?;
750+
set_multisig_config(env, caller, admins, config.threshold)
751+
}
752+
753+
pub fn set_multisig_threshold(
754+
env: &Env,
755+
caller: Address,
756+
threshold: u32,
757+
) -> Result<(), GovernanceError> {
758+
let config = get_multisig_config(env).ok_or(GovernanceError::NotInitialized)?;
759+
set_multisig_config(env, caller, config.admins, threshold)
760+
}
761+
762+
pub fn propose_set_min_collateral_ratio(
763+
env: &Env,
764+
proposer: Address,
765+
new_ratio: i128,
766+
) -> Result<u64, GovernanceError> {
767+
create_proposal(
768+
env,
769+
proposer,
770+
ProposalType::MinCollateralRatio(new_ratio),
771+
String::from_str(env, "Update min collateral ratio"),
772+
None,
773+
)
774+
}
775+
776+
pub fn execute_multisig_proposal(
777+
env: &Env,
778+
executor: Address,
779+
proposal_id: u64,
780+
) -> Result<(), GovernanceError> {
781+
executor.require_auth();
782+
783+
let multisig_config = get_multisig_config(env).ok_or(GovernanceError::NotInitialized)?;
784+
if !multisig_config.admins.contains(&executor) {
785+
return Err(GovernanceError::Unauthorized);
786+
}
787+
788+
let mut proposal: Proposal = env
789+
.storage()
790+
.persistent()
791+
.get(&GovernanceDataKey::Proposal(proposal_id))
792+
.ok_or(GovernanceError::ProposalNotFound)?;
793+
794+
if proposal.status != ProposalStatus::Pending {
795+
return Err(GovernanceError::InvalidProposalStatus);
796+
}
797+
798+
let approvals = get_proposal_approvals(env, proposal_id).unwrap_or_else(|| Vec::new(env));
799+
if approvals.len() < multisig_config.threshold {
800+
return Err(GovernanceError::InsufficientApprovals);
801+
}
802+
803+
execute_proposal_type(env, &proposal.proposal_type)?;
804+
805+
proposal.status = ProposalStatus::Executed;
806+
env.storage()
807+
.persistent()
808+
.set(&GovernanceDataKey::Proposal(proposal_id), &proposal);
809+
810+
emit_proposal_executed_event(env, &proposal_id, &executor);
811+
812+
Ok(())
813+
}
814+
573815
// ============================================================================
574816
// Events
575817
// ============================================================================
@@ -608,14 +850,7 @@ fn emit_proposal_failed_event(env: &Env, proposal_id: &u64) {
608850
env.events().publish(topics, ());
609851
}
610852

611-
pub fn emit_approval_event(env: &Env, proposal_id: &u64, approver: &Address) {
612-
let topics = (
613-
Symbol::new(env, "proposal_approved"),
614-
*proposal_id,
615-
approver.clone(),
616-
);
617-
env.events().publish(topics, ());
618-
}
853+
619854

620855
pub fn add_guardian(env: &Env, caller: Address, guardian: Address) -> Result<(), GovernanceError> {
621856
caller.require_auth();
@@ -935,12 +1170,6 @@ pub fn get_admin(env: &Env) -> Option<Address> {
9351170
env.storage().instance().get(&GovernanceDataKey::Admin)
9361171
}
9371172

938-
pub fn get_multisig_config(env: &Env) -> Option<MultisigConfig> {
939-
env.storage()
940-
.instance()
941-
.get(&GovernanceDataKey::MultisigConfig)
942-
}
943-
9441173
pub fn emit_guardian_added_event(env: &Env, guardian: &Address) {
9451174
let topics = (Symbol::new(env, "guardian_added"), guardian.clone());
9461175
env.events().publish(topics, ());

stellar-lend/contracts/hello-world/src/lib.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ pub mod risk_params;
2424
pub mod storage;
2525
pub mod types;
2626
pub mod withdraw;
27+
pub mod recovery;
28+
pub mod multisig;
29+
pub mod types;
30+
pub mod storage;
31+
pub mod reentrancy;
32+
33+
mod admin;
34+
mod errors;
35+
mod reserve;
36+
mod risk_params;
37+
mod config;
38+
mod bridge;
2739

2840
#[cfg(test)]
2941
// mod tests;
@@ -32,7 +44,9 @@ use crate::deposit::DepositDataKey;
3244
use crate::risk_management::RiskManagementError;
3345
use crate::interest_rate::InterestRateError;
3446

35-
/// Helper function to require admin authorization
47+
// ─── Admin helper ─────────────────────────────────────────────────────────────
48+
49+
/// Require that `caller` is the stored admin; panics via `?` on failure.
3650
fn require_admin(env: &Env, caller: &Address) -> Result<(), RiskManagementError> {
3751
caller.require_auth();
3852
let admin_key = DepositDataKey::Admin;
@@ -55,7 +69,31 @@ pub struct HelloContract;
5569
#[contractimpl]
5670
impl HelloContract {
5771
pub fn hello(env: Env) -> String {
58-
String::from_str(&env, "Hello")
72+
String::from_str(env, "Hello")
73+
}
74+
75+
pub fn gov_initialize(
76+
env: Env,
77+
admin: Address,
78+
vote_token: Address,
79+
voting_period: Option<u64>,
80+
execution_delay: Option<u64>,
81+
quorum_bps: Option<u32>,
82+
proposal_threshold: Option<i128>,
83+
timelock_duration: Option<u64>,
84+
default_voting_threshold: Option<i128>,
85+
) -> Result<(), GovernanceError> {
86+
governance::initialize(
87+
&env,
88+
admin,
89+
vote_token,
90+
voting_period,
91+
execution_delay,
92+
quorum_bps,
93+
proposal_threshold,
94+
timelock_duration,
95+
default_voting_threshold,
96+
)
5997
}
6098

6199
pub fn initialize(env: Env, admin: Address) -> Result<(), RiskManagementError> {

0 commit comments

Comments
 (0)