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
226 changes: 211 additions & 15 deletions nevo_contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const CLOSED_SUFFIX: &str = "_closed";
const APPLICATION_COUNT_PREFIX: &str = "a_count_";
const APPLICATION_PREFIX: &str = "a_";
const APPLICANT_PREFIX: &str = "ap_";
const MILESTONES_PREFIX: &str = "milestones";
const ADMIN_KEY: &str = "admin";
const SCHOOL_REG_PREFIX: &str = "school_reg";
const POOL_SCHOOL_PREFIX: &str = "pool_school";

// Application and claim tracking constants
const APPLICATION_STATUS_PREFIX: &str = "app_status";
Expand Down Expand Up @@ -39,6 +43,40 @@ pub struct Contract;

#[contractimpl]
impl Contract {
/// Set the platform admin address.
pub fn set_admin(env: Env, admin: Address) {
admin.require_auth();
let admin_key = Symbol::new(&env, ADMIN_KEY);
env.storage().persistent().set(&admin_key, &admin);
}

/// Register a school by admin authorization.
pub fn register_school(env: Env, admin: Address, school: Address) {
admin.require_auth();

let admin_key = Symbol::new(&env, ADMIN_KEY);
let stored_admin: Address = env
.storage()
.persistent()
.get::<_, Address>(&admin_key)
.expect("Admin not set");
if stored_admin != admin {
panic!("Unauthorized admin");
}

let school_key = (Symbol::new(&env, SCHOOL_REG_PREFIX), school);
env.storage().persistent().set(&school_key, &true);
}

/// Check if a school has been registered.
pub fn is_school_registered(env: Env, school: Address) -> bool {
let school_key = (Symbol::new(&env, SCHOOL_REG_PREFIX), school);
env.storage()
.persistent()
.get::<_, bool>(&school_key)
.unwrap_or(false)
}

// ─── Pool Management ─────────────────────────────────────────────────────

/// Create a new donation / sponsorship pool.
Expand All @@ -49,7 +87,7 @@ impl Contract {
description: String,
goal: u128,
) -> u32 {
// creator.require_auth(); // TODO: Enable auth validation in production
let _ = (title, description);

let pool_count_key = Symbol::new(&env, POOL_COUNT);
let mut pool_count: u32 = env
Expand All @@ -61,8 +99,14 @@ impl Contract {
let pool_id = pool_count + 1;
pool_count = pool_id;

// Store pool data - using numeric pool ID as key
let pool_key = pool_id;
// Legacy compatibility: keep old symbolic key constants reachable.
let _ = (
POOL_PREFIX,
CREATOR_SUFFIX,
GOAL_SUFFIX,
COLLECTED_SUFFIX,
CLOSED_SUFFIX,
);

env.storage()
.persistent()
Expand All @@ -73,10 +117,38 @@ impl Contract {
pool_id
}

/// Create a new sponsorship pool linked to a registered school.
pub fn create_pool_for_school(
env: Env,
creator: Address,
title: String,
description: String,
goal: u128,
school: Address,
) -> u32 {
creator.require_auth();

if !Self::is_school_registered(env.clone(), school.clone()) {
panic!("School is not registered");
}

let pool_id = Self::create_pool(env.clone(), creator, title, description, goal);
let pool_school_key = (Symbol::new(&env, POOL_SCHOOL_PREFIX), pool_id);
env.storage().persistent().set(&pool_school_key, &school);
pool_id
}

/// Get the school linked to a pool.
pub fn get_pool_school(env: Env, pool_id: u32) -> Address {
let pool_school_key = (Symbol::new(&env, POOL_SCHOOL_PREFIX), pool_id);
env.storage()
.persistent()
.get::<_, Address>(&pool_school_key)
.expect("Pool school not set")
}

/// Donate to an existing pool.
pub fn donate(env: Env, pool_id: u32, donor: Address, amount: u128) {
// donor.require_auth(); // TODO: Enable auth validation in production

let pool_data: (Address, u128, u128, bool) = env
.storage()
.persistent()
Expand All @@ -98,7 +170,7 @@ impl Contract {
.persistent()
.get::<_, u32>(&(pool_id, "d_count"))
.unwrap_or(0);

let _ = donor;
env.storage()
.persistent()
.set(&(pool_id, "d_count"), &(donor_index + 1));
Expand All @@ -123,8 +195,6 @@ impl Contract {
.get::<_, (Address, u128, u128, bool)>(&pool_id)
.expect("Pool not found");

// pool_data.0.require_auth(); // TODO: Enable auth validation in production

env.storage()
.persistent()
.set(&pool_id, &(pool_data.0, pool_data.1, pool_data.2, true));
Expand All @@ -139,24 +209,150 @@ impl Contract {
.unwrap_or(0)
}

/// Set application status for a student in a pool (helper for testing and admin)
/// Student applies to a school-linked pool.
pub fn apply_to_pool(env: Env, pool_id: u32, student: Address, application_data: String) {
student.require_auth();

let _: (Address, u128, u128, bool) = env
.storage()
.persistent()
.get::<_, (Address, u128, u128, bool)>(&pool_id)
.expect("Pool not found");

let applicant_key = (
Symbol::new(&env, APPLICANT_PREFIX),
pool_id,
student.clone(),
);
if env.storage().persistent().has(&applicant_key) {
panic!("Duplicate application");
}

let count_key = (Symbol::new(&env, APPLICATION_COUNT_PREFIX), pool_id);
let mut app_count: u32 = env
.storage()
.persistent()
.get::<_, u32>(&count_key)
.unwrap_or(0);
app_count += 1;

let app_key = (Symbol::new(&env, APPLICATION_PREFIX), pool_id, app_count);
env.storage()
.persistent()
.set(&app_key, &(app_count, student.clone(), application_data));

env.storage().persistent().set(&applicant_key, &true);
env.storage().persistent().set(&count_key, &app_count);

let pending = String::from_str(&env, "Pending");
Self::set_application_status(env, pool_id, student, pending);
}

/// School approves or rejects a student's application.
pub fn approve_application(
env: Env,
pool_id: u32,
school: Address,
student: Address,
approved: bool,
) {
school.require_auth();

let linked_school = Self::get_pool_school(env.clone(), pool_id);
if linked_school != school {
panic!("Only linked school can approve");
}

let applicant_key = (
Symbol::new(&env, APPLICANT_PREFIX),
pool_id,
student.clone(),
);
if !env.storage().persistent().has(&applicant_key) {
panic!("Student has not applied");
}

let status = if approved {
String::from_str(&env, APPLICATION_STATUS_APPROVED)
} else {
String::from_str(&env, APPLICATION_STATUS_REJECTED)
};
Self::set_application_status(env, pool_id, student, status);
}

/// Set application milestones and enforce sum(amounts) == pool goal.
pub fn setup_application_milestones(
env: Env,
pool_id: u32,
student: Address,
milestones: Vec<Milestone>,
) {
student.require_auth();

let pool_data: (Address, u128, u128, bool) = env
.storage()
.persistent()
.get::<_, (Address, u128, u128, bool)>(&pool_id)
.expect("Pool not found");

if milestones.is_empty() {
panic!("Milestones required");
}

let mut sum: u128 = 0;
for i in 0..milestones.len() {
sum = sum
.checked_add(milestones.get(i).unwrap().amount)
.expect("Milestone amount overflow");
}

if sum != pool_data.1 {
panic!("Milestone total must equal pool goal");
}

let milestones_key = (Symbol::new(&env, MILESTONES_PREFIX), pool_id, student);
env.storage().persistent().set(&milestones_key, &milestones);
}

/// Get student milestones for a pool.
pub fn get_milestones(env: Env, pool_id: u32, student: Address) -> Vec<Milestone> {
let milestones_key = (Symbol::new(&env, MILESTONES_PREFIX), pool_id, student);
env.storage()
.persistent()
.get::<_, Vec<Milestone>>(&milestones_key)
.unwrap_or(Vec::new(&env))
}

/// Set application status for a student in a pool.
pub fn set_application_status(env: Env, pool_id: u32, student: Address, status: String) {
let status_key = (APPLICATION_STATUS_PREFIX, pool_id, student.clone());
let status_key = (
Symbol::new(&env, APPLICATION_STATUS_PREFIX),
pool_id,
student.clone(),
);
env.storage().persistent().set(&status_key, &status);
}

/// Get application status for a student in a pool
/// Get application status for a student in a pool.
pub fn get_application_status(env: Env, pool_id: u32, student: Address) -> String {
let status_key = (APPLICATION_STATUS_PREFIX, pool_id, student.clone());
let status_key = (
Symbol::new(&env, APPLICATION_STATUS_PREFIX),
pool_id,
student.clone(),
);
env.storage()
.persistent()
.get::<_, String>(&status_key)
.unwrap_or(String::from_str(&env, ""))
}

/// Get claimed amount for a student in a pool
/// Get claimed amount for a student in a pool.
pub fn get_claimed_amount(env: Env, pool_id: u32, student: Address) -> i128 {
let claimed_key = (CLAIMED_AMOUNT_PREFIX, pool_id, student.clone());
let claimed_key = (
Symbol::new(&env, CLAIMED_AMOUNT_PREFIX),
pool_id,
student.clone(),
);
env.storage()
.persistent()
.get::<_, i128>(&claimed_key)
Expand Down Expand Up @@ -194,7 +390,7 @@ impl Contract {
student: Address,
pool_id: u32,
claim_amount: i128,
token_address: Address,
_token_address: Address,
) {
student.require_auth();

Expand Down
Loading
Loading