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
209 changes: 156 additions & 53 deletions dongle-smartcontract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,184 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, Env, Address, String};
mod project_registry;
mod review_registry;
mod verification_registry;
mod fee_manager;
mod types;
mod errors;
mod constants;
mod events;
mod utils;
mod storage_keys;
mod rating_calculator;

#[cfg(test)]
mod test;

use soroban_sdk::{contract, contractimpl, Env, Address, String, Vec};
use crate::project_registry::ProjectRegistry;
use crate::review_registry::ReviewRegistry;
use crate::verification_registry::VerificationRegistry;
use crate::fee_manager::FeeManager;
use crate::types::{Project, Review, VerificationRecord, FeeConfig, VerificationStatus};
use crate::errors::ContractError;

#[contract]
pub struct DongleContract;

#[contractimpl]
impl DongleContract {
/// Validates project description length (200–1000 chars).
/// Panics with descriptive message on invalid input.
pub fn register_project(_env: Env, _owner: Address, _name: String, description: String /* other params */) {
let desc_len = description.len() as u32;
// --- Project Registry ---

if desc_len == 0 {
panic!("Description cannot be empty");
pub fn register_project(
env: Env,
owner: Address,
name: String,
description: String,
category: String,
website: Option<String>,
logo_cid: Option<String>,
metadata_cid: Option<String>,
) -> u64 {
ProjectRegistry::register_project(
&env,
owner,
name,
description,
category,
website,
logo_cid,
metadata_cid,
)
}
if desc_len < 200 {
panic!("Description must be at least 200 characters long");

pub fn update_project(
env: Env,
project_id: u64,
caller: Address,
name: Option<String>,
description: Option<String>,
category: Option<String>,
website: Option<Option<String>>,
logo_cid: Option<Option<String>>,
metadata_cid: Option<Option<String>>,
) -> Option<Project> {
ProjectRegistry::update_project(
&env,
project_id,
caller,
name,
description,
category,
website,
logo_cid,
metadata_cid,
)
}
if desc_len > 1000 {
panic!("Description exceeds maximum length of 1000 characters");

pub fn get_project(env: Env, project_id: u64) -> Option<Project> {
ProjectRegistry::get_project(&env, project_id)
}
}

// Optional simple getter (for future use or testing)
pub fn get_description(env: Env, /* project_key: Symbol */) -> String {
// Placeholder - return empty for now
String::from_str(&env, "")
pub fn list_projects(env: Env, start_id: u64, limit: u32) -> Vec<Project> {
ProjectRegistry::list_projects(&env, start_id, limit)
}
}

#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{Env, Address, String};
pub fn get_projects_by_owner(env: Env, owner: Address) -> Vec<Project> {
ProjectRegistry::get_projects_by_owner(&env, owner)
}

// --- Review Registry ---

pub fn add_review(
env: Env,
project_id: u64,
reviewer: Address,
rating: u32,
comment_cid: Option<String>,
) {
ReviewRegistry::add_review(env, project_id, reviewer, rating, comment_cid)
}

pub fn update_review(
env: Env,
project_id: u64,
reviewer: Address,
rating: u32,
comment_cid: Option<String>,
) {
ReviewRegistry::update_review(env, project_id, reviewer, rating, comment_cid)
}

#[test]
#[should_panic]
fn test_empty_description_panics() {
let env = Env::default();
let owner = Address::from_string(&String::from_str(&env, "GAEXAMPLEADDRESS1234567890"));
let name = String::from_str(&env, "Test Project");
let empty_desc = String::from_str(&env, "");
pub fn delete_review(env: Env, project_id: u64, reviewer: Address) {
let _ = ReviewRegistry::delete_review(env, project_id, reviewer);
}

pub fn get_review(env: Env, project_id: u64, reviewer: Address) -> Option<Review> {
ReviewRegistry::get_review(env, project_id, reviewer)
}

// --- Verification Registry ---

pub fn request_verification(
env: Env,
project_id: u64,
requester: Address,
evidence_cid: String,
) {
VerificationRegistry::request_verification(&env, project_id, requester, evidence_cid)
}

pub fn approve_verification(env: Env, project_id: u64, admin: Address) {
let _ = VerificationRegistry::approve_verification(&env, project_id, admin);
}

pub fn reject_verification(env: Env, project_id: u64, admin: Address) {
let _ = VerificationRegistry::reject_verification(&env, project_id, admin);
}

let _ = DongleContract::register_project(env.clone(), owner, name, empty_desc);
pub fn get_verification(env: Env, project_id: u64) -> Option<VerificationRecord> {
VerificationRegistry::get_verification(&env, project_id).ok()
}

#[test]
#[should_panic]
fn test_short_description_panics() {
let env = Env::default();
let owner = Address::from_string(&String::from_str(&env, "GAEXAMPLEADDRESS1234567890"));
let name = String::from_str(&env, "Test Project");
let short_desc = String::from_str(&env, "short description"); // < 200 chars
// --- Fee Manager ---

let _ = DongleContract::register_project(env.clone(), owner, name, short_desc);
pub fn set_fee(
env: Env,
admin: Address,
token: Option<Address>,
amount: u128,
treasury: Address,
) {
let _ = FeeManager::set_fee(&env, admin, token, amount, treasury);
}

#[test]
#[should_panic]
fn test_long_description_panics() {
let env = Env::default();
let owner = Address::from_string(&String::from_str(&env, "GAEXAMPLEADDRESS1234567890"));
let name = String::from_str(&env, "Test Project");
let long_desc = String::from_str(&env, &("a".repeat(1001))); // > 1000 chars
pub fn pay_fee(
env: Env,
payer: Address,
project_id: u64,
token: Option<Address>,
) {
let _ = FeeManager::pay_fee(&env, payer, project_id, token);
}

let _ = DongleContract::register_project(env.clone(), owner, name, long_desc);
pub fn get_fee_config(env: Env) -> FeeConfig {
FeeManager::get_fee_config(&env).unwrap_or(FeeConfig {
token: None,
verification_fee: 0,
registration_fee: 0,
})
}

#[test]
fn test_valid_description_does_not_panic() {
let env = Env::default();
let owner = Address::from_string(&String::from_str(&env, "GAEXAMPLEADDRESS1234567890"));
let name = String::from_str(&env, "Test Project");
let valid_desc = String::from_str(&env, &("a".repeat(500))); // 500 chars = valid
pub fn get_owner_project_count(env: Env, owner: Address) -> u32 {
ProjectRegistry::get_projects_by_owner(&env, owner).len()
}

pub fn set_admin(env: Env, admin: Address) {
env.storage().persistent().set(&crate::types::DataKey::Admin(admin), &());
}

let _ = DongleContract::register_project(env.clone(), owner, name, valid_desc);
// No panic → test passes automatically
pub fn initialize(env: Env, admin: Address) {
Self::set_admin(env, admin);
}
}
57 changes: 48 additions & 9 deletions dongle-smartcontract/src/project_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ impl ProjectRegistry {
) -> u64 {
owner.require_auth();

// Validation
if name.len() == 0 {
panic!("InvalidProjectName");
}
if description.len() == 0 {
panic!("InvalidProjectDescription");
}
if category.len() == 0 {
panic!("InvalidProjectCategory");
}

let mut count: u64 = env
.storage()
.persistent()
Expand Down Expand Up @@ -128,11 +139,30 @@ impl ProjectRegistry {
}

pub fn list_projects(
_env: &Env,
_start_id: u64,
_limit: u32,
) -> Result<Vec<Project>, ContractError> {
todo!("Project listing logic not implemented")
env: &Env,
start_id: u64,
limit: u32,
) -> Vec<Project> {
let count: u64 = env
.storage()
.persistent()
.get(&DataKey::ProjectCount)
.unwrap_or(0);

let mut projects = Vec::new(env);
if start_id == 0 || start_id > count {
return projects;
}

let end_id = core::cmp::min(start_id.saturating_add(limit as u64), count + 1);

for id in start_id..end_id {
if let Some(project) = Self::get_project(env, id) {
projects.push_back(project);
}
}

projects
}

pub fn project_exists(env: &Env, project_id: u64) -> bool {
Expand All @@ -142,10 +172,19 @@ impl ProjectRegistry {
}

pub fn validate_project_data(
_name: &String,
_description: &String,
_category: &String,
name: &String,
description: &String,
category: &String,
) -> Result<(), ContractError> {
todo!("Project data validation not implemented")
if name.len() == 0 {
return Err(ContractError::InvalidProjectData);
}
if description.len() == 0 {
return Err(ContractError::ProjectDescriptionTooLong); // Just picking one for now to match ContractError
}
if category.len() == 0 {
return Err(ContractError::InvalidProjectCategory);
}
Ok(())
}
}
Loading
Loading