Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 206 additions & 105 deletions contracts/sorosave/src/admin.rs
Original file line number Diff line number Diff line change
@@ -1,175 +1,276 @@
use soroban_sdk::{Address, Env, String};
use soroban_sdk::{Address, Env, String, Vec};

use crate::errors::ContractError;
use crate::storage;
use crate::types::{Dispute, GroupStatus};
use crate::types::{Dispute, GroupStatus, AdminProposal, ProposalType, ProposalStatus};

pub fn pause_group(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
admin.require_auth();

let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;
pub fn add_admin(env: &Env, current_admin: Address, new_admin: Address) -> Result<(), ContractError> {
current_admin.require_auth();

let proposal_id = storage::get_next_proposal_id(env);
let proposal = AdminProposal {
id: proposal_id,
proposal_type: ProposalType::AddAdmin,
target: new_admin.clone(),
threshold: None,
proposer: current_admin.clone(),
approvals: Vec::from_array(env, [current_admin]),
status: ProposalStatus::Pending,
created_at: env.ledger().timestamp(),
};

storage::set_admin_proposal(env, &proposal);

let admins = storage::get_admins(env);
let threshold = storage::get_admin_threshold(env);

if proposal.approvals.len() >= threshold {
execute_add_admin_proposal(env, proposal_id)?;
}

env.events()
.publish((crate::symbol_short!("add_admn"),), (proposal_id, new_admin));

Ok(())
}

if admin != group.admin && admin != storage::get_admin(env) {
pub fn remove_admin(env: &Env, current_admin: Address, target_admin: Address) -> Result<(), ContractError> {
current_admin.require_auth();

let admins = storage::get_admins(env);
if admins.len() <= 1 {
return Err(ContractError::Unauthorized);
}

if group.status == GroupStatus::Completed {
return Err(ContractError::GroupCompleted);

let proposal_id = storage::get_next_proposal_id(env);
let proposal = AdminProposal {
id: proposal_id,
proposal_type: ProposalType::RemoveAdmin,
target: target_admin.clone(),
threshold: None,
proposer: current_admin.clone(),
approvals: Vec::from_array(env, [current_admin]),
status: ProposalStatus::Pending,
created_at: env.ledger().timestamp(),
};

storage::set_admin_proposal(env, &proposal);

let threshold = storage::get_admin_threshold(env);

if proposal.approvals.len() >= threshold {
execute_remove_admin_proposal(env, proposal_id)?;
}

group.status = GroupStatus::Paused;
storage::set_group(env, &group);


env.events()
.publish((crate::symbol_short!("grp_paus"),), group_id);

.publish((crate::symbol_short!("rem_admn"),), (proposal_id, target_admin));
Ok(())
}

pub fn resume_group(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
pub fn set_threshold(env: &Env, admin: Address, new_threshold: u32) -> Result<(), ContractError> {
admin.require_auth();

let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

if admin != group.admin && admin != storage::get_admin(env) {

let admins = storage::get_admins(env);
if new_threshold == 0 || new_threshold > admins.len() {
return Err(ContractError::Unauthorized);
}

if group.status != GroupStatus::Paused {
return Err(ContractError::GroupNotActive);

let proposal_id = storage::get_next_proposal_id(env);
let proposal = AdminProposal {
id: proposal_id,
proposal_type: ProposalType::SetThreshold,
target: admin.clone(),
threshold: Some(new_threshold),
proposer: admin.clone(),
approvals: Vec::from_array(env, [admin]),
status: ProposalStatus::Pending,
created_at: env.ledger().timestamp(),
};

storage::set_admin_proposal(env, &proposal);

let current_threshold = storage::get_admin_threshold(env);

if proposal.approvals.len() >= current_threshold {
execute_set_threshold_proposal(env, proposal_id)?;
}

group.status = GroupStatus::Active;
storage::set_group(env, &group);


env.events()
.publish((crate::symbol_short!("grp_resm"),), group_id);

.publish((crate::symbol_short!("set_thrs"),), (proposal_id, new_threshold));
Ok(())
}

pub fn raise_dispute(
env: &Env,
member: Address,
group_id: u64,
reason: String,
) -> Result<(), ContractError> {
member.require_auth();

let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

// Verify membership
let mut is_member = false;
for m in group.members.iter() {
if m == member {
is_member = true;
break;
}
pub fn approve_admin_proposal(env: &Env, admin: Address, proposal_id: u64) -> Result<(), ContractError> {
admin.require_auth();

let admins = storage::get_admins(env);
if !admins.contains(&admin) {
return Err(ContractError::Unauthorized);
}
if !is_member {
return Err(ContractError::NotMember);

let mut proposal = storage::get_admin_proposal(env, proposal_id)
.ok_or(ContractError::ProposalNotFound)?;

if proposal.status != ProposalStatus::Pending {
return Err(ContractError::ProposalNotActive);
}

if group.status != GroupStatus::Active {
return Err(ContractError::GroupNotActive);
if proposal.approvals.contains(&admin) {
return Err(ContractError::AlreadyApproved);
}

proposal.approvals.push_back(admin.clone());
storage::set_admin_proposal(env, &proposal);

let threshold = storage::get_admin_threshold(env);

if proposal.approvals.len() >= threshold {
match proposal.proposal_type {
ProposalType::AddAdmin => execute_add_admin_proposal(env, proposal_id)?,
ProposalType::RemoveAdmin => execute_remove_admin_proposal(env, proposal_id)?,
ProposalType::SetThreshold => execute_set_threshold_proposal(env, proposal_id)?,
}
}

env.events()
.publish((crate::symbol_short!("appr_prop"),), (proposal_id, admin));

Ok(())
}

let dispute = Dispute {
raised_by: member.clone(),
reason,
raised_at: env.ledger().timestamp(),
};

group.status = GroupStatus::Disputed;
storage::set_group(env, &group);
storage::set_dispute(env, group_id, &dispute);
fn execute_add_admin_proposal(env: &Env, proposal_id: u64) -> Result<(), ContractError> {
let mut proposal = storage::get_admin_proposal(env, proposal_id)
.ok_or(ContractError::ProposalNotFound)?;

let mut admins = storage::get_admins(env);
admins.push_back(proposal.target.clone());
storage::set_admins(env, &admins);

proposal.status = ProposalStatus::Executed;
storage::set_admin_proposal(env, &proposal);

env.events()
.publish((crate::symbol_short!("exec_add"),), (proposal_id, proposal.target.clone()));

Ok(())
}

fn execute_remove_admin_proposal(env: &Env, proposal_id: u64) -> Result<(), ContractError> {
let mut proposal = storage::get_admin_proposal(env, proposal_id)
.ok_or(ContractError::ProposalNotFound)?;

let mut admins = storage::get_admins(env);
if let Some(index) = admins.iter().position(|admin| admin == proposal.target) {
admins.remove(index as u32);
storage::set_admins(env, &admins);
}

proposal.status = ProposalStatus::Executed;
storage::set_admin_proposal(env, &proposal);

env.events()
.publish((crate::symbol_short!("dispute"),), (group_id, member));
.publish((crate::symbol_short!("exec_rem"),), (proposal_id, proposal.target.clone()));

Ok(())
}

fn execute_set_threshold_proposal(env: &Env, proposal_id: u64) -> Result<(), ContractError> {
let mut proposal = storage::get_admin_proposal(env, proposal_id)
.ok_or(ContractError::ProposalNotFound)?;

if let Some(new_threshold) = proposal.threshold {
storage::set_admin_threshold(env, new_threshold);
}

proposal.status = ProposalStatus::Executed;
storage::set_admin_proposal(env, &proposal);

env.events()
.publish((crate::symbol_short!("exec_thr"),), (proposal_id, proposal.threshold.unwrap_or(0)));

Ok(())
}

pub fn resolve_dispute(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
pub fn pause_group(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
admin.require_auth();

let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

if admin != group.admin && admin != storage::get_admin(env) {
let admins = storage::get_admins(env);
if !admins.contains(&admin) {
return Err(ContractError::Unauthorized);
}

if group.status != GroupStatus::Disputed {
return Err(ContractError::GroupNotActive);
let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

if group.status == GroupStatus::Completed {
return Err(ContractError::GroupCompleted);
}

group.status = GroupStatus::Active;
group.status = GroupStatus::Paused;
storage::set_group(env, &group);
storage::remove_dispute(env, group_id);

env.events()
.publish((crate::symbol_short!("resolved"),), group_id);
.publish((crate::symbol_short!("grp_paus"),), group_id);

Ok(())
}

pub fn emergency_withdraw(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
pub fn resume_group(env: &Env, admin: Address, group_id: u64) -> Result<(), ContractError> {
admin.require_auth();

let group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

// Only protocol admin can trigger emergency withdraw
if admin != storage::get_admin(env) {
let admins = storage::get_admins(env);
if !admins.contains(&admin) {
return Err(ContractError::Unauthorized);
}

if group.status == GroupStatus::Completed {
return Err(ContractError::GroupCompleted);
}

// Calculate remaining balance and distribute equally
let token_client = soroban_sdk::token::Client::new(env, &group.token);
let contract_addr = env.current_contract_address();
let balance = token_client.balance(&contract_addr);
let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

if balance > 0 {
let per_member = balance / group.members.len() as i128;
if per_member > 0 {
for member in group.members.iter() {
token_client.transfer(&contract_addr, &member, &per_member);
}
}
if group.status != GroupStatus::Paused {
return Err(ContractError::GroupNotActive);
}

let mut group = group;
group.status = GroupStatus::Completed;
group.status = GroupStatus::Active;
storage::set_group(env, &group);

env.events()
.publish((crate::symbol_short!("emergenc"),), group_id);
.publish((crate::symbol_short!("grp_resm"),), group_id);

Ok(())
}

pub fn set_group_admin(
pub fn raise_dispute(
env: &Env,
current_admin: Address,
member: Address,
group_id: u64,
new_admin: Address,
reason: String,
) -> Result<(), ContractError> {
current_admin.require_auth();
member.require_auth();

let mut group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;
let group = storage::get_group(env, group_id).ok_or(ContractError::GroupNotFound)?;

if !group.members.contains(&member) {
return Err(ContractError::NotGroupMember);
}

if current_admin != group.admin {
return Err(ContractError::Unauthorized);
if group.status != GroupStatus::Active {
return Err(ContractError::GroupNotActive);
}

group.admin = new_admin.clone();
storage::set_group(env, &group);
let dispute_id = storage::get_next_dispute_id(env);
let dispute = Dispute {
id: dispute_id,
group_id,
member: member.clone(),
reason: reason.clone(),
resolved: false,
created_at: env.ledger().timestamp(),
};

storage::set_dispute(env, &dispute);

env.events()
.publish((crate::symbol_short!("adm_chng"),), (group_id, new_admin));
.publish((crate::symbol_short!("dispute"),), (dispute_id, group_id, member));

Ok(())
}
}
Loading