Skip to content
Merged
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
26 changes: 24 additions & 2 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::base::errors::Error;
use crate::base::events::{
AdminTransferred, AutoshareCreated, AutoshareUpdated, ContractPaused, ContractUnpaused,
GroupActivated, GroupDeactivated, NotificationCategory, Withdrawal,
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated,
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated,
NotificationCategory, Withdrawal,
};
use crate::base::types::{AutoShareDetails, GroupMember, PaymentHistory};
use soroban_sdk::{contracttype, token, Address, BytesN, Env, String, Vec};
Expand Down Expand Up @@ -183,9 +184,12 @@ pub fn get_group_members(env: Env, id: BytesN<32>) -> Result<Vec<GroupMember>, E
pub fn add_group_member(
env: Env,
id: BytesN<32>,
caller: Address,
address: Address,
percentage: u32,
) -> Result<(), Error> {
caller.require_auth();

// Check if contract is paused
if get_paused_status(&env) {
return Err(Error::ContractPaused);
Expand All @@ -198,6 +202,11 @@ pub fn add_group_member(
.get(&key)
.ok_or(Error::NotFound)?;

if details.creator != caller {
publish_authorization_failure(&env, &caller, "add_group_member");
return Err(Error::Unauthorized);
}

// Check if already a member
for member in details.members.iter() {
if member.address == address {
Expand Down Expand Up @@ -247,6 +256,15 @@ pub fn initialize_admin(env: Env, admin: Address) {
}
}

fn publish_authorization_failure(env: &Env, caller: &Address, action: &str) {
AuthorizationFailure {
caller: caller.clone(),
category: NotificationCategory::Admin,
action: String::from_str(env, action),
}
.publish(env);
}

fn require_admin(env: &Env, caller: &Address) -> Result<(), Error> {
let admin_key = DataKey::Admin;
let admin: Address = env
Expand All @@ -256,6 +274,7 @@ fn require_admin(env: &Env, caller: &Address) -> Result<(), Error> {
.ok_or(Error::Unauthorized)?;

if admin != *caller {
publish_authorization_failure(env, caller, "require_admin");
return Err(Error::Unauthorized);
}

Expand Down Expand Up @@ -610,6 +629,7 @@ pub fn update_members(
.ok_or(Error::NotFound)?;

if details.creator != caller {
publish_authorization_failure(&env, &caller, "update_members");
return Err(Error::Unauthorized);
}

Expand Down Expand Up @@ -678,6 +698,7 @@ pub fn deactivate_group(env: Env, id: BytesN<32>, caller: Address) -> Result<(),
.ok_or(Error::NotFound)?;

if details.creator != caller {
publish_authorization_failure(&env, &caller, "deactivate_group");
return Err(Error::Unauthorized);
}

Expand Down Expand Up @@ -713,6 +734,7 @@ pub fn activate_group(env: Env, id: BytesN<32>, caller: Address) -> Result<(), E
.ok_or(Error::NotFound)?;

if details.creator != caller {
publish_authorization_failure(&env, &caller, "activate_group");
return Err(Error::Unauthorized);
}

Expand Down
13 changes: 12 additions & 1 deletion contract/contracts/hello-world/src/base/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_sdk::{contractevent, contracttype, Address, BytesN};
use soroban_sdk::{contractevent, contracttype, Address, BytesN, String};

/// High-level notification category attached to every emitted event.
///
Expand Down Expand Up @@ -108,3 +108,14 @@ pub struct Withdrawal {
pub category: NotificationCategory,
pub amount: i128,
}

/// Emitted when an authorization failure is detected by the contract.
#[contractevent(data_format = "single-value")]
#[derive(Clone)]
pub struct AuthorizationFailure {
#[topic]
pub caller: Address,
#[topic]
pub category: NotificationCategory,
pub action: String,
}
8 changes: 7 additions & 1 deletion contract/contracts/hello-world/src/interfaces/autoshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ pub trait AutoShareTrait {
fn get_group_members(env: Env, id: BytesN<32>) -> Vec<GroupMember>;

/// Adds a member to a group with specified percentage.
fn add_group_member(env: Env, id: BytesN<32>, address: Address, percentage: u32);
fn add_group_member(
env: Env,
id: BytesN<32>,
caller: Address,
address: Address,
percentage: u32,
);

/// Deactivates a group. Only the creator can deactivate.
fn deactivate_group(env: Env, id: BytesN<32>, caller: Address);
Expand Down
10 changes: 8 additions & 2 deletions contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,14 @@ impl AutoShareContract {
}

/// Adds a member to a group with specified percentage.
pub fn add_group_member(env: Env, id: BytesN<32>, address: Address, percentage: u32) {
autoshare_logic::add_group_member(env, id, address, percentage).unwrap();
pub fn add_group_member(
env: Env,
id: BytesN<32>,
caller: Address,
address: Address,
percentage: u32,
) {
autoshare_logic::add_group_member(env, id, caller, address, percentage).unwrap();
}

/// Deactivates a group. Only the creator can deactivate.
Expand Down
30 changes: 27 additions & 3 deletions contract/contracts/hello-world/src/tests/autoshare_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,29 @@ fn test_add_group_member_success() {
assert_eq!(final_members.get(2).unwrap().percentage, 34);
}

#[test]
#[should_panic] // Unauthorized
fn test_add_group_member_unauthorized() {
let test_env = setup_test_env();
let client = AutoShareContractClient::new(&test_env.env, &test_env.autoshare_contract);

let creator = test_env.users.get(0).unwrap().clone();
let id = BytesN::from_array(&test_env.env, &[1u8; 32]);
let name = String::from_str(&test_env.env, "Unauthorized Add Test");

let mut members = Vec::new(&test_env.env);
members.push_back(GroupMember {
address: Address::generate(&test_env.env),
percentage: 100,
});

create_helper(&client, &id, &name, &creator, &members, &test_env);

let other_user = Address::generate(&test_env.env);
let member_to_add = Address::generate(&test_env.env);
client.add_group_member(&id, &other_user, &member_to_add, &50);
}

#[test]
#[should_panic] // AlreadyExists
fn test_add_duplicate_member() {
Expand All @@ -622,7 +645,7 @@ fn test_add_duplicate_member() {
create_helper(&client, &id, &name, &creator, &members, &test_env);

// Try to add the same member again - should fail
client.add_group_member(&id, &member1, &50);
client.add_group_member(&id, &creator, &member1, &50);
}

#[test]
Expand All @@ -631,10 +654,11 @@ fn test_add_member_to_non_existent_group() {
let test_env = setup_test_env();
let client = AutoShareContractClient::new(&test_env.env, &test_env.autoshare_contract);

let creator = test_env.users.get(0).unwrap().clone();
let id = BytesN::from_array(&test_env.env, &[99u8; 32]);
let member = Address::generate(&test_env.env);

client.add_group_member(&id, &member, &50);
client.add_group_member(&id, &creator, &member, &50);
}

#[test]
Expand All @@ -659,7 +683,7 @@ fn test_add_member_invalid_total_percentage() {

// Try to add another member with 50% (total would be 150%) - should fail
let member2 = Address::generate(&test_env.env);
client.add_group_member(&id, &member2, &50);
client.add_group_member(&id, &creator, &member2, &50);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion contract/contracts/hello-world/src/tests/pause_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ fn test_add_member_fails_when_paused() {
token_admin_client.mint(&creator, &10000000);
client.create(&id, &name, &creator, &100u32, &token_address);
client.pause(&admin);
client.add_group_member(&id, &member, &50u32);
client.add_group_member(&id, &creator, &member, &50u32);
}

#[test]
Expand Down
Loading