Skip to content
Open
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
24 changes: 19 additions & 5 deletions contracts/sorosave/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,32 @@ pub fn emergency_withdraw(env: &Env, admin: Address, group_id: u64) -> Result<()
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);

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);
// Calculate total deposits (these will be forfeited on default)
let deposits = storage::get_deposits(env, group_id);
let mut total_deposits = 0i128;
for member in group.members.iter() {
if let Some(deposit) = deposits.get(member) {
total_deposits += deposit;
}
}

// Distributable balance = total balance - deposits (deposits are forfeited)
let distributable = balance - total_deposits;

if distributable > 0 {
let per_member = distributable / group.members.len() as i128;
if per_member > 0 {
for member in group.members.iter() {
token_client.transfer(&contract_addr, &member, &per_member);
}
}
}
// Note: Deposits remain in contract (forfeited on emergency/default)
}

let mut group = group;
Expand Down
38 changes: 37 additions & 1 deletion contracts/sorosave/src/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub fn create_group(
name: String,
token: Address,
contribution_amount: i128,
deposit_amount: i128,
cycle_length: u64,
max_members: u32,
) -> Result<u64, ContractError> {
Expand All @@ -18,6 +19,9 @@ pub fn create_group(
if contribution_amount <= 0 {
return Err(ContractError::InvalidAmount);
}
if deposit_amount < 0 {
return Err(ContractError::InvalidAmount);
}
if max_members < 2 {
return Err(ContractError::InsufficientMembers);
}
Expand All @@ -32,8 +36,9 @@ pub fn create_group(
id: group_id,
name,
admin: admin.clone(),
token,
token: token.clone(),
contribution_amount,
deposit_amount,
cycle_length,
max_members,
members,
Expand All @@ -47,6 +52,16 @@ pub fn create_group(
storage::set_group(env, &group);
storage::add_member_group(env, &admin, group_id);

// Collect deposit from admin if required
if deposit_amount > 0 {
let token_client = soroban_sdk::token::Client::new(env, &token);
token_client.transfer(&admin, &env.current_contract_address(), &deposit_amount);

let mut deposits = storage::get_deposits(env, group_id);
deposits.set(admin.clone(), deposit_amount);
storage::set_deposits(env, group_id, &deposits);
}

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

Expand All @@ -73,6 +88,16 @@ pub fn join_group(env: &Env, member: Address, group_id: u64) -> Result<(), Contr
}
}

// Collect deposit if required
if group.deposit_amount > 0 {
let token_client = soroban_sdk::token::Client::new(env, &group.token);
token_client.transfer(&member, &env.current_contract_address(), &group.deposit_amount);

let mut deposits = storage::get_deposits(env, group_id);
deposits.set(member.clone(), group.deposit_amount);
storage::set_deposits(env, group_id, &deposits);
}

group.members.push_back(member.clone());
storage::set_group(env, &group);
storage::add_member_group(env, &member, group_id);
Expand Down Expand Up @@ -111,6 +136,17 @@ pub fn leave_group(env: &Env, member: Address, group_id: u64) -> Result<(), Cont
return Err(ContractError::NotMember);
}

// Return deposit if member is leaving during Forming phase
if group.deposit_amount > 0 {
let mut deposits = storage::get_deposits(env, group_id);
if let Some(deposit) = deposits.get(member.clone()) {
let token_client = soroban_sdk::token::Client::new(env, &group.token);
token_client.transfer(&env.current_contract_address(), &member, &deposit);
deposits.remove(member.clone());
storage::set_deposits(env, group_id, &deposits);
}
}

group.members = new_members;
storage::set_group(env, &group);
storage::remove_member_group(env, &member, group_id);
Expand Down
2 changes: 2 additions & 0 deletions contracts/sorosave/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ impl SoroSaveContract {
name: String,
token: Address,
contribution_amount: i128,
deposit_amount: i128,
cycle_length: u64,
max_members: u32,
) -> Result<u64, ContractError> {
Expand All @@ -44,6 +45,7 @@ impl SoroSaveContract {
name,
token,
contribution_amount,
deposit_amount,
cycle_length,
max_members,
)
Expand Down
13 changes: 13 additions & 0 deletions contracts/sorosave/src/payout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ pub fn distribute_payout(env: &Env, group_id: u64) -> Result<(), ContractError>
group.status = GroupStatus::Completed;
storage::set_group(env, &group);

// Return deposits to all members on successful completion
if group.deposit_amount > 0 {
let deposits = storage::get_deposits(env, group_id);
let token_client = soroban_sdk::token::Client::new(env, &group.token);
let contract_addr = env.current_contract_address();

for member in group.members.iter() {
if let Some(deposit) = deposits.get(member.clone()) {
token_client.transfer(&contract_addr, &member, &deposit);
}
}
}

env.events()
.publish((crate::symbol_short!("grp_comp"),), group_id);
} else {
Expand Down
18 changes: 17 additions & 1 deletion contracts/sorosave/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_sdk::{Address, Env, Vec};
use soroban_sdk::{Address, Env, Map, Vec};

use crate::types::{DataKey, Dispute, RoundInfo, SavingsGroup};

Expand Down Expand Up @@ -122,6 +122,22 @@ pub fn remove_dispute(env: &Env, group_id: u64) {
env.storage().persistent().remove(&key);
}

// --- Deposits ---

pub fn get_deposits(env: &Env, group_id: u64) -> Map<Address, i128> {
let key = DataKey::Deposits(group_id);
env.storage()
.persistent()
.get(&key)
.unwrap_or(Map::new(env))
}

pub fn set_deposits(env: &Env, group_id: u64, deposits: &Map<Address, i128>) {
let key = DataKey::Deposits(group_id);
env.storage().persistent().set(&key, deposits);
extend_persistent_ttl(env, &key);
}

// --- TTL Management ---

fn extend_instance_ttl(env: &Env) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/sorosave/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub struct SavingsGroup {
pub admin: Address,
pub token: Address,
pub contribution_amount: i128,
pub deposit_amount: i128,
pub cycle_length: u64,
pub max_members: u32,
pub members: Vec<Address>,
Expand Down Expand Up @@ -61,4 +62,5 @@ pub enum DataKey {
Round(u64, u32),
MemberGroups(Address),
Dispute(u64),
Deposits(u64),
}