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
2 changes: 2 additions & 0 deletions contracts/cntr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[package]
name = "cntr"
version = "0.0.0"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
crate-type = ["lib"]
path = "src/lib.rs"

[dependencies]
Expand Down
43 changes: 43 additions & 0 deletions contracts/cntr/src/credit_deduction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
pub fn validate_deduction(current_balance: i128, deduction_amount: i128) -> Result<i128, &'static str> {
if deduction_amount <= 0 {
return Err("Deduction amount must be positive");
}
if current_balance < deduction_amount {
return Err("Insufficient credits");
}
Ok(current_balance - deduction_amount)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn success() {
assert_eq!(validate_deduction(100, 40), Ok(60));
}

#[test]
fn exact_balance() {
assert_eq!(validate_deduction(50, 50), Ok(0));
}

#[test]
fn insufficient() {
assert_eq!(validate_deduction(10, 20), Err("Insufficient credits"));
}

#[test]
fn zero_deduction() {
assert_eq!(validate_deduction(100, 0), Err("Deduction amount must be positive"));
}

#[test]
fn negative_deduction() {
assert_eq!(validate_deduction(100, -5), Err("Deduction amount must be positive"));
}

#[test]
fn zero_balance_positive_deduction() {
assert_eq!(validate_deduction(0, 1), Err("Insufficient credits"));
}
/// Error types for credit deduction operations.
#[derive(Debug, Clone, PartialEq)]
pub enum DeductionError {
Expand Down
3 changes: 3 additions & 0 deletions contracts/cntr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod credit_deduction;
pub mod referral_reward;
pub mod role_checker;
pub mod withdrawal_validator;
pub mod credit_topup;
pub mod token_validator;
pub mod grace_period;
Expand Down
52 changes: 52 additions & 0 deletions contracts/cntr/src/referral_reward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
pub fn calculate_referral_split(
total_reward_stroops: i128,
referrer_percent: u32,
) -> Result<(i128, i128), &'static str> {
if referrer_percent > 100 {
return Err("Referrer percent cannot exceed 100");
}
let referrer_amount = total_reward_stroops * referrer_percent as i128 / 100;
let referee_amount = total_reward_stroops - referrer_amount;
Ok((referrer_amount, referee_amount))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn split_50_50() {
assert_eq!(calculate_referral_split(100, 50), Ok((50, 50)));
}

#[test]
fn split_100_0() {
assert_eq!(calculate_referral_split(100, 100), Ok((100, 0)));
}

#[test]
fn split_0_100() {
assert_eq!(calculate_referral_split(100, 0), Ok((0, 100)));
}

#[test]
fn split_70_30() {
assert_eq!(calculate_referral_split(100, 70), Ok((70, 30)));
}

#[test]
fn odd_total_remainder_goes_to_referrer() {
// 7 * 70 / 100 = 4 (integer), referee = 7 - 4 = 3, sum = 7
let (r, e) = calculate_referral_split(7, 70).unwrap();
assert_eq!(r + e, 7);
assert_eq!(r, 4);
}

#[test]
fn percent_over_100_errors() {
assert_eq!(
calculate_referral_split(100, 101),
Err("Referrer percent cannot exceed 100")
);
}
}
51 changes: 51 additions & 0 deletions contracts/cntr/src/role_checker.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
pub fn has_role(roles: &[(String, String)], address: &str, required_role: &str) -> bool {
roles.iter().any(|(addr, role)| addr == address && role == required_role)
}

pub fn get_roles_for_address(roles: &[(String, String)], address: &str) -> Vec<String> {
roles.iter().filter(|(addr, _)| addr == address).map(|(_, role)| role.clone()).collect()
}

#[cfg(test)]
mod tests {
use super::*;

fn pair(a: &str, r: &str) -> (String, String) {
(a.to_string(), r.to_string())
}

#[test]
fn has_role_empty() {
assert!(!has_role(&[], "alice", "admin"));
}

#[test]
fn has_role_match() {
let roles = vec![pair("alice", "admin")];
assert!(has_role(&roles, "alice", "admin"));
}

#[test]
fn has_role_case_sensitive() {
let roles = vec![pair("alice", "Admin")];
assert!(!has_role(&roles, "alice", "admin"));
}

#[test]
fn has_role_wrong_address() {
let roles = vec![pair("alice", "admin")];
assert!(!has_role(&roles, "Alice", "admin"));
}

#[test]
fn get_roles_multiple() {
let roles = vec![pair("alice", "admin"), pair("alice", "member"), pair("bob", "member")];
let mut result = get_roles_for_address(&roles, "alice");
result.sort();
assert_eq!(result, vec!["admin", "member"]);
}

#[test]
fn get_roles_unknown_address() {
let roles = vec![pair("alice", "admin")];
assert!(get_roles_for_address(&roles, "unknown").is_empty());
use std::collections::HashMap;

/// Simple in-memory role registry for access control.
Expand Down
64 changes: 64 additions & 0 deletions contracts/cntr/src/withdrawal_validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
pub fn validate_withdrawal(
owner_address: &str,
caller_address: &str,
available_balance: i128,
requested_amount: i128,
) -> Result<(), &'static str> {
if caller_address != owner_address {
return Err("Unauthorized: caller is not the owner");
}
if requested_amount <= 0 {
return Err("Withdrawal amount must be positive");
}
if requested_amount > available_balance {
return Err("Insufficient balance");
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn success() {
assert_eq!(validate_withdrawal("alice", "alice", 100, 50), Ok(()));
}

#[test]
fn exact_balance() {
assert_eq!(validate_withdrawal("alice", "alice", 100, 100), Ok(()));
}

#[test]
fn unauthorized() {
assert_eq!(
validate_withdrawal("alice", "bob", 100, 50),
Err("Unauthorized: caller is not the owner")
);
}

#[test]
fn zero_amount() {
assert_eq!(
validate_withdrawal("alice", "alice", 100, 0),
Err("Withdrawal amount must be positive")
);
}

#[test]
fn negative_amount() {
assert_eq!(
validate_withdrawal("alice", "alice", 100, -1),
Err("Withdrawal amount must be positive")
);
}

#[test]
fn insufficient_balance() {
assert_eq!(
validate_withdrawal("alice", "alice", 10, 20),
Err("Insufficient balance")
);
}
}
Loading