From fc40238b6f934e050d88d71299a44addc9e13c2d Mon Sep 17 00:00:00 2001 From: Richiey1 Date: Sun, 22 Feb 2026 03:37:23 +0100 Subject: [PATCH 1/3] feat: implement Advanced Assessment and Testing Platform --- contracts/teachlink/src/assessment.rs | 463 ++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 contracts/teachlink/src/assessment.rs diff --git a/contracts/teachlink/src/assessment.rs b/contracts/teachlink/src/assessment.rs new file mode 100644 index 0000000..3fc5018 --- /dev/null +++ b/contracts/teachlink/src/assessment.rs @@ -0,0 +1,463 @@ +//! Assessment and Testing Platform Module +//! +//! Build a comprehensive assessment system that supports various question types, +//! automated grading, plagiarism detection, and adaptive testing. + +use soroban_sdk::{ + contracterror, contracttype, symbol_short, Address, Bytes, Env, Map, Symbol, Vec, +}; + +use crate::storage::*; +use crate::types::*; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum AssessmentError { + Unauthorized = 1, + AssessmentNotFound = 2, + QuestionNotFound = 3, + InvalidGrading = 4, + AlreadySubmitted = 5, + DeadlinePassed = 6, + PlagiarismDetected = 7, + InvalidInput = 8, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum QuestionType { + MultipleChoice, + TrueFalse, + ShortAnswer, + Coding, + Matching, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Question { + pub id: u64, + pub q_type: QuestionType, + pub content_hash: Bytes, + pub points: u32, + pub difficulty: u32, // 1-10 scale for adaptive testing + pub correct_answer_hash: Bytes, + pub metadata: Map, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssessmentSettings { + pub time_limit: u64, // seconds, 0 for unlimited + pub passing_score: u32, + pub is_adaptive: bool, + pub allow_retakes: bool, + pub proctoring_enabled: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Assessment { + pub id: u64, + pub creator: Address, + pub title: Bytes, + pub description: Bytes, + pub questions: Vec, + pub settings: AssessmentSettings, + pub created_at: u64, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssessmentSubmission { + pub assessment_id: u64, + pub student: Address, + pub answers: Map, // QuestionID -> AnswerHash or Content + pub score: u32, + pub max_score: u32, + pub timestamp: u64, + pub proctor_logs: Vec, + pub is_graded: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssessmentAnalytics { + pub assessment_id: u64, + pub total_submissions: u32, + pub average_score: u32, + pub pass_rate: u32, // basis points + pub difficulty_rating: u32, +} + +// Storage keys +const ASSESSMENT_COUNTER: Symbol = symbol_short!("ASS_C"); +const ASSESSMENTS: Symbol = symbol_short!("ASS_S"); +const QUESTION_COUNTER: Symbol = symbol_short!("QUE_C"); +const QUESTIONS: Symbol = symbol_short!("QUE_S"); +const SUBMISSIONS: Symbol = symbol_short!("SUB_S"); + +pub struct AssessmentManager; + +impl AssessmentManager { + /// Create a new assessment + pub fn create_assessment( + env: &Env, + creator: Address, + title: Bytes, + description: Bytes, + questions: Vec, + settings: AssessmentSettings, + ) -> Result { + creator.require_auth(); + + let mut counter: u64 = env.storage().instance().get(&ASSESSMENT_COUNTER).unwrap_or(0); + counter += 1; + + let assessment = Assessment { + id: counter, + creator, + title, + description, + questions, + settings, + created_at: env.ledger().timestamp(), + }; + + let mut assessments: Map = env + .storage() + .instance() + .get(&ASSESSMENTS) + .unwrap_or(Map::new(env)); + + assessments.set(counter, assessment); + env.storage().instance().set(&ASSESSMENTS, &assessments); + env.storage().instance().set(&ASSESSMENT_COUNTER, &counter); + + Ok(counter) + } + + /// Add a question to the pool + pub fn add_question( + env: &Env, + creator: Address, + q_type: QuestionType, + content_hash: Bytes, + points: u32, + difficulty: u32, + correct_answer_hash: Bytes, + metadata: Map, + ) -> Result { + creator.require_auth(); + + let mut q_counter: u64 = env.storage().instance().get(&QUESTION_COUNTER).unwrap_or(0); + q_counter += 1; + + let question = Question { + id: q_counter, + q_type, + content_hash, + points, + difficulty, + correct_answer_hash, + metadata, + }; + + let mut questions: Map = env + .storage() + .instance() + .get(&QUESTIONS) + .unwrap_or(Map::new(env)); + + questions.set(q_counter, question); + env.storage().instance().set(&QUESTIONS, &questions); + env.storage().instance().set(&QUESTION_COUNTER, &q_counter); + + Ok(q_counter) + } + + /// Submit an assessment for grading + pub fn submit_assessment( + env: &Env, + student: Address, + assessment_id: u64, + answers: Map, + proctor_logs: Vec, + ) -> Result { + student.require_auth(); + + let assessments: Map = env + .storage() + .instance() + .get(&ASSESSMENTS) + .ok_or(AssessmentError::AssessmentNotFound)?; + + let assessment = assessments.get(assessment_id).ok_or(AssessmentError::AssessmentNotFound)?; + + // Check if deadline passed + if assessment.settings.time_limit > 0 { + let deadline = assessment.created_at + assessment.settings.time_limit; + if env.ledger().timestamp() > deadline { + return Err(AssessmentError::DeadlinePassed); + } + } + + // Check if already submitted + let sub_key = (SUBMISSIONS, student.clone(), assessment_id); + if env.storage().persistent().has(&sub_key) && !assessment.settings.allow_retakes { + return Err(AssessmentError::AlreadySubmitted); + } + + // Plagiarism detection (basic cross-check) + if let Some(detected) = Self::detect_plagiarism(env, assessment_id, &answers) { + if detected { + return Err(AssessmentError::PlagiarismDetected); + } + } + + // Automated grading logic + let mut score: u32 = 0; + let mut max_score: u32 = 0; + let questions_map: Map = env + .storage() + .instance() + .get(&QUESTIONS) + .ok_or(AssessmentError::QuestionNotFound)?; + + for q_id in assessment.questions.iter() { + if let Some(question) = questions_map.get(q_id) { + max_score += question.points; + if let Some(user_answer) = answers.get(q_id) { + // Specific grading logic per question type + match question.q_type { + QuestionType::MultipleChoice | QuestionType::TrueFalse => { + if user_answer == question.correct_answer_hash { + score += question.points; + } + } + QuestionType::ShortAnswer | QuestionType::Coding => { + // Support basic pattern matching or exact check for non-hashed answers + // (In real scenario, might use fuzzy match or external prover) + if user_answer == question.correct_answer_hash { + score += question.points; + } + } + QuestionType::Matching => { + // Matching logic: user_answer could be a serialized map/vector + if user_answer == question.correct_answer_hash { + score += question.points; + } + } + } + } + } + } + + let submission = AssessmentSubmission { + assessment_id, + student: student.clone(), + answers, + score, + max_score, + timestamp: env.ledger().timestamp(), + proctor_logs, + is_graded: true, + }; + + env.storage().persistent().set(&sub_key, &submission); + + // Update analytics + Self::update_analytics(env, assessment_id, score, max_score); + + Ok(score) + } + + /// Adaptive question selection logic + pub fn get_next_adaptive_question( + env: &Env, + assessment_id: u64, + previous_scores: Vec, // Results of already answered questions [0 or 1, ...] + answered_ids: Vec, + ) -> Result { + let assessments: Map = env + .storage() + .instance() + .get(&ASSESSMENTS) + .ok_or(AssessmentError::AssessmentNotFound)?; + + let assessment = assessments.get(assessment_id).ok_or(AssessmentError::AssessmentNotFound)?; + + if !assessment.settings.is_adaptive { + return Err(AssessmentError::InvalidInput); + } + + let questions_map: Map = env + .storage() + .instance() + .get(&QUESTIONS) + .ok_or(AssessmentError::QuestionNotFound)?; + + // Calculate current performance + let mut correct_count = 0; + for s in previous_scores.iter() { + if s > 0 { correct_count += 1; } + } + + let performance_ratio = if previous_scores.len() > 0 { + (correct_count * 100) / previous_scores.len() + } else { + 50 // Base difficulty + }; + + // Select next question based on performance + let target_difficulty = if performance_ratio > 70 { + 7 // High performers get harder questions + } else if performance_ratio < 30 { + 3 // Lower performers get easier questions + } else { + 5 + }; + + let mut best_match: Option = None; + let mut min_diff = 100; + + for q_id in assessment.questions.iter() { + if !answered_ids.contains(q_id) { + if let Some(q) = questions_map.get(q_id) { + let d_diff = if q.difficulty > target_difficulty { + q.difficulty - target_difficulty + } else { + target_difficulty - q.difficulty + }; + if d_diff < min_diff { + min_diff = d_diff; + best_match = Some(q_id); + } + } + } + } + + best_match.ok_or(AssessmentError::QuestionNotFound) + } + + /// Basic Plagiarism Detection: Check if too many answers are identical to previous submissions + fn detect_plagiarism(env: &Env, assessment_id: u64, current_answers: &Map) -> Option { + // Implement a window-based or sampling-based check to avoid O(N) storage scan + // For simplicity, we'll check against the "Recent Submissions" list + let recent_subs_key = symbol_short!("REC_SUB"); + let recent_subs: Vec> = env.storage().instance().get(&recent_subs_key).unwrap_or(Vec::new(env)); + + for past_answers in recent_subs.iter() { + let mut match_count = 0; + let total_questions = current_answers.len(); + + for (q_id, ans) in current_answers.iter() { + if let Some(past_ans) = past_answers.get(q_id) { + if ans == past_ans { + match_count += 1; + } + } + } + + // Flag if more than 90% identical + if total_questions > 2 && (match_count * 100) / total_questions > 90 { + return Some(true); + } + } + + // Store current answers in recent list (keep last 5) + let mut new_recent = recent_subs; + new_recent.push_back(current_answers.clone()); + if new_recent.len() > 5 { + new_recent.remove(0); + } + env.storage().instance().set(&recent_subs_key, &new_recent); + + Some(false) + } + + fn update_analytics(env: &Env, assessment_id: u64, score: u32, max_score: u32) { + let analytics_key = symbol_short!("ASS_ANL"); + let mut assessments_analytics: Map = env + .storage() + .instance() + .get(&analytics_key) + .unwrap_or(Map::new(env)); + + let mut analytics = assessments_analytics.get(assessment_id).unwrap_or(AssessmentAnalytics { + assessment_id, + total_submissions: 0, + average_score: 0, + pass_rate: 0, + difficulty_rating: 5, + }); + + let new_total = analytics.total_submissions + 1; + analytics.average_score = ((analytics.average_score * analytics.total_submissions) + score) / new_total; + analytics.total_submissions = new_total; + + // Logic for pass rate... + + assessments_analytics.set(assessment_id, analytics); + env.storage().instance().set(&analytics_key, &assessments_analytics); + } + + pub fn get_assessment(env: &Env, id: u64) -> Option { + let assessments: Map = env.storage().instance().get(&ASSESSMENTS)?; + assessments.get(id) + } + + pub fn get_submission(env: &Env, student: Address, assessment_id: u64) -> Option { + env.storage().persistent().get(&(SUBMISSIONS, student, assessment_id)) + } + + /// Proctoring: Record a violation during the session + pub fn report_proctoring_violation( + env: &Env, + student: Address, + assessment_id: u64, + violation_type: Bytes, + ) -> Result<(), AssessmentError> { + student.require_auth(); + + let sub_key = (SUBMISSIONS, student.clone(), assessment_id); + let mut submission: AssessmentSubmission = env + .storage() + .persistent() + .get(&sub_key) + .ok_or(AssessmentError::AssessmentNotFound)?; + + submission.proctor_logs.push_back(violation_type); + env.storage().persistent().set(&sub_key, &submission); + + Ok(()) + } + + /// Accessibility: Set accommodation for a student (e.g., extra time) + pub fn set_accommodation( + env: &Env, + admin: Address, + student: Address, + extra_time_seconds: u64, + ) -> Result<(), AssessmentError> { + admin.require_auth(); + // Check if admin is authorized (omitted for brevity, assume owner/admin) + + let acc_key = (symbol_short!("ACC_S"), student); + env.storage().persistent().set(&acc_key, &extra_time_seconds); + + Ok(()) + } + + /// Scheduling: Check if assessment is available at current time + pub fn is_assessment_available(env: &Env, assessment_id: u64) -> bool { + if let Some(assessment) = Self::get_assessment(env, assessment_id) { + // Placeholder for start/end dates in settings + let now = env.ledger().timestamp(); + // Assume we add start_time/end_time to AssessmentSettings in future + true + } else { + false + } + } +} From a170c95477cc9f3a12a1ab15d3497e3a19e15585 Mon Sep 17 00:00:00 2001 From: Richiey1 Date: Sun, 22 Feb 2026 03:37:23 +0100 Subject: [PATCH 2/3] fix: resolve lib methods, storage keys, and social_learning build --- contracts/teachlink/src/lib.rs | 113 +++- contracts/teachlink/src/social_learning.rs | 623 +++------------------ 2 files changed, 187 insertions(+), 549 deletions(-) diff --git a/contracts/teachlink/src/lib.rs b/contracts/teachlink/src/lib.rs index 816d867..bbb6ca5 100644 --- a/contracts/teachlink/src/lib.rs +++ b/contracts/teachlink/src/lib.rs @@ -85,10 +85,11 @@ #![allow(clippy::trivially_copy_pass_by_ref)] #![allow(clippy::needless_borrow)] -use soroban_sdk::{contract, contractimpl, Address, Bytes, Env, Map, String, Vec}; +use soroban_sdk::{contract, contractimpl, Address, Bytes, Env, Map, String, Vec, Symbol}; mod analytics; mod arbitration; +mod assessment; mod atomic_swap; mod audit; mod bft_consensus; @@ -110,7 +111,7 @@ mod notification_types; mod rewards; mod slashing; // mod social_events; -// mod social_learning; +mod social_learning; mod storage; mod tokenization; mod types; @@ -128,6 +129,7 @@ pub use types::{ TransferType, UserNotificationSettings, UserReputation, UserReward, ValidatorInfo, ValidatorReward, ValidatorSignature, }; +pub use assessment::{Assessment, AssessmentSettings, AssessmentSubmission, Question, QuestionType}; /// TeachLink main contract. /// @@ -136,7 +138,6 @@ pub use types::{ #[contract] pub struct TeachLinkBridge; -/* #[contractimpl] impl TeachLinkBridge { /// Initialize the bridge contract @@ -759,6 +760,111 @@ impl TeachLinkBridge { rewards::Rewards::get_rewards_admin(&env) } + // ========== Assessment and Testing Platform Functions ========== + + /// Create a new assessment + pub fn create_assessment( + env: Env, + creator: Address, + title: Bytes, + description: Bytes, + questions: Vec, + settings: AssessmentSettings, + ) -> Result { + assessment::AssessmentManager::create_assessment( + &env, + creator, + title, + description, + questions, + settings, + ) + } + + /// Add a question to the pool + pub fn add_assessment_question( + env: Env, + creator: Address, + q_type: QuestionType, + content_hash: Bytes, + points: u32, + difficulty: u32, + correct_answer_hash: Bytes, + metadata: Map, + ) -> Result { + assessment::AssessmentManager::add_question( + &env, + creator, + q_type, + content_hash, + points, + difficulty, + correct_answer_hash, + metadata, + ) + } + + /// Submit an assessment + pub fn submit_assessment( + env: Env, + student: Address, + assessment_id: u64, + answers: Map, + proctor_logs: Vec, + ) -> Result { + assessment::AssessmentManager::submit_assessment( + &env, + student, + assessment_id, + answers, + proctor_logs, + ) + } + + /// Get assessment details + pub fn get_assessment(env: Env, id: u64) -> Option { + assessment::AssessmentManager::get_assessment(&env, id) + } + + /// Get user submission + pub fn get_assessment_submission( + env: Env, + student: Address, + assessment_id: u64, + ) -> Option { + assessment::AssessmentManager::get_submission(&env, student, assessment_id) + } + + /// Report a proctoring violation + pub fn report_proctor_violation( + env: Env, + student: Address, + assessment_id: u64, + violation_type: Bytes, + ) -> Result<(), assessment::AssessmentError> { + assessment::AssessmentManager::report_proctoring_violation( + &env, + student, + assessment_id, + violation_type, + ) + } + + /// Get next adaptive question + pub fn get_next_adaptive_question( + env: Env, + id: u64, + scores: Vec, + answered_ids: Vec, + ) -> Result { + assessment::AssessmentManager::get_next_adaptive_question( + &env, + id, + scores, + answered_ids, + ) + } + // ========== Escrow Functions ========== /// Create a multi-signature escrow @@ -1377,4 +1483,3 @@ impl TeachLinkBridge { // Analytics function removed due to contracttype limitations // Use internal notification manager for analytics } -*/ diff --git a/contracts/teachlink/src/social_learning.rs b/contracts/teachlink/src/social_learning.rs index dea9b0e..11828d3 100644 --- a/contracts/teachlink/src/social_learning.rs +++ b/contracts/teachlink/src/social_learning.rs @@ -15,7 +15,8 @@ use soroban_sdk::{Address, Bytes, Env, Map, Vec, Symbol, String, contracttype, c use crate::storage::*; use crate::types::*; -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct StudyGroup { pub id: u64, pub name: Bytes, @@ -32,7 +33,8 @@ pub struct StudyGroup { pub settings: StudyGroupSettings, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct StudyGroupSettings { pub allow_member_invites: bool, pub require_admin_approval: bool, @@ -42,7 +44,8 @@ pub struct StudyGroupSettings { pub auto_approve_members: bool, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct DiscussionForum { pub id: u64, pub title: Bytes, @@ -58,7 +61,8 @@ pub struct DiscussionForum { pub view_count: u64, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct ForumPost { pub id: u64, pub forum_id: u64, @@ -74,7 +78,8 @@ pub struct ForumPost { pub attachments: Vec, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CollaborationWorkspace { pub id: u64, pub name: Bytes, @@ -90,7 +95,8 @@ pub struct CollaborationWorkspace { pub settings: WorkspaceSettings, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ProjectType { Study, Research, @@ -99,7 +105,8 @@ pub enum ProjectType { Discussion, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum WorkspaceStatus { Active, Completed, @@ -107,7 +114,8 @@ pub enum WorkspaceStatus { Suspended, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct WorkspaceFile { pub id: u64, pub name: Bytes, @@ -119,7 +127,8 @@ pub struct WorkspaceFile { pub version: u32, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct WorkspaceTask { pub id: u64, pub title: Bytes, @@ -133,7 +142,8 @@ pub struct WorkspaceTask { pub completed_at: Option, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum TaskStatus { Todo, InProgress, @@ -142,7 +152,8 @@ pub enum TaskStatus { Cancelled, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum TaskPriority { Low, Medium, @@ -150,7 +161,8 @@ pub enum TaskPriority { Urgent, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct WorkspaceSettings { pub allow_public_view: bool, pub require_approval_to_join: bool, @@ -159,7 +171,8 @@ pub struct WorkspaceSettings { pub auto_save_interval: u64, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct PeerReview { pub id: u64, pub reviewer: Address, @@ -174,7 +187,8 @@ pub struct PeerReview { pub helpful_votes: u32, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ReviewContentType { Submission, Comment, @@ -183,7 +197,8 @@ pub enum ReviewContentType { Resource, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct MentorshipProfile { pub mentor: Address, pub expertise_areas: Vec, @@ -199,7 +214,8 @@ pub struct MentorshipProfile { pub timezone: Bytes, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum ExperienceLevel { Beginner, Intermediate, @@ -207,14 +223,16 @@ pub enum ExperienceLevel { Expert, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum AvailabilityStatus { Available, Busy, Unavailable, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct MentorshipSession { pub id: u64, pub mentor: Address, @@ -230,7 +248,8 @@ pub struct MentorshipSession { pub completed_at: Option, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum SessionStatus { Scheduled, InProgress, @@ -239,7 +258,8 @@ pub enum SessionStatus { NoShow, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SocialAnalytics { pub user: Address, pub study_groups_joined: u32, @@ -254,7 +274,8 @@ pub struct SocialAnalytics { pub last_updated: u64, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum EngagementLevel { Low, Medium, @@ -262,7 +283,8 @@ pub enum EngagementLevel { VeryHigh, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SocialBadge { pub id: u64, pub name: Bytes, @@ -274,7 +296,8 @@ pub struct SocialBadge { pub created_at: u64, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum BadgeCategory { Collaboration, Mentorship, @@ -283,7 +306,8 @@ pub enum BadgeCategory { Learning, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct BadgeRequirements { pub study_groups_joined: Option, pub discussions_participated: Option, @@ -293,7 +317,8 @@ pub struct BadgeRequirements { pub social_score: Option, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum BadgeRarity { Common, Uncommon, @@ -302,7 +327,8 @@ pub enum BadgeRarity { Legendary, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct GamificationSystem { pub points: Map, pub levels: Map, @@ -312,7 +338,8 @@ pub struct GamificationSystem { pub rewards: Vec, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SocialReward { pub id: u64, pub name: Bytes, @@ -323,7 +350,8 @@ pub struct SocialReward { pub created_at: u64, } -#[derive(Clone, Debug, Eq, PartialEq, contracttype)] +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum RewardCategory { Digital, Physical, @@ -450,7 +478,7 @@ impl SocialLearningManager { } // Check if max members reached - if group.members.len() >= group.max_members as usize { + if group.members.len() >= group.max_members { return Err(SocialLearningError::MaxMembersReached); } @@ -485,11 +513,21 @@ impl SocialLearningManager { } // Remove user from members - let new_members = group.members.iter().filter(|&member| member != user).collect::>(); + let mut new_members = Vec::new(env); + for member in group.members.iter() { + if member != user { + new_members.push_back(member); + } + } group.members = new_members; // Remove from admins if applicable - let new_admins = group.admins.iter().filter(|&admin| admin != user).collect::>(); + let mut new_admins = Vec::new(env); + for admin in group.admins.iter() { + if admin != user { + new_admins.push_back(admin); + } + } group.admins = new_admins; group.last_activity = env.ledger().timestamp(); @@ -498,8 +536,13 @@ impl SocialLearningManager { env.storage().instance().set(&STUDY_GROUPS, &groups); // Update user's study groups - let mut user_groups: Vec = env.storage().instance().get(&USER_STUDY_GROUPS).unwrap_or(Vec::new(&env)); - let new_user_groups = user_groups.iter().filter(|&id| id != group_id).collect::>(); + let user_groups: Vec = env.storage().instance().get(&USER_STUDY_GROUPS).unwrap_or(Vec::new(&env)); + let mut new_user_groups = Vec::new(env); + for id in user_groups.iter() { + if id != group_id { + new_user_groups.push_back(id); + } + } env.storage().instance().set(&USER_STUDY_GROUPS, &new_user_groups); Ok(()) @@ -757,10 +800,10 @@ impl SocialLearningManager { availability, hourly_rate, bio, - rating: 0.0, + rating: 0, review_count: 0, mentee_count: 0, - success_rate: 0.0, + success_rate: 0, languages, timezone, }; @@ -801,513 +844,3 @@ impl SocialLearningManager { env.storage().instance().set(&SOCIAL_ANALYTICS, &analytics); } } - -// Soroban trait implementations for contract types -impl TryFromVal for AvailabilityStatus { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - let symbol_str = symbol_short!(symbol); - match symbol_str { - "Available" => Ok(AvailabilityStatus::Available), - "Busy" => Ok(AvailabilityStatus::Busy), - "Unavailable" => Ok(AvailabilityStatus::Unavailable), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for AvailabilityStatus { - fn into_val(&self, env: &Env) -> Val { - match self { - AvailabilityStatus::Available => Symbol::new(env, "Available").into_val(env), - AvailabilityStatus::Busy => Symbol::new(env, "Busy").into_val(env), - AvailabilityStatus::Unavailable => Symbol::new(env, "Unavailable").into_val(env), - } - } -} - -impl TryFromVal for MentorshipProfile { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(MentorshipProfile { - mentor: Address::try_from_val(env, &map.get(Symbol::new(env, "mentor").into_val(env)).unwrap_or_default())?, - expertise_areas: Vec::::try_from_val(env, &map.get(Symbol::new(env, "expertise_areas").into_val(env)).unwrap_or_default())?, - experience_level: ExperienceLevel::try_from_val(env, &map.get(Symbol::new(env, "experience_level").into_val(env)).unwrap_or_default())?, - availability: AvailabilityStatus::try_from_val(env, &map.get(Symbol::new(env, "availability").into_val(env)).unwrap_or_default())?, - hourly_rate: Option::::try_from_val(env, &map.get(Symbol::new(env, "hourly_rate").into_val(env)).unwrap_or_default())?, - bio: Bytes::try_from_val(env, &map.get(Symbol::new(env, "bio").into_val(env)).unwrap_or_default())?, - rating: u64::try_from_val(env, &map.get(Symbol::new(env, "rating").into_val(env)).unwrap_or_default())?, - review_count: u32::try_from_val(env, &map.get(Symbol::new(env, "review_count").into_val(env)).unwrap_or_default())?, - mentee_count: u32::try_from_val(env, &map.get(Symbol::new(env, "mentee_count").into_val(env)).unwrap_or_default())?, - success_rate: u64::try_from_val(env, &map.get(Symbol::new(env, "success_rate").into_val(env)).unwrap_or_default())?, - languages: Vec::::try_from_val(env, &map.get(Symbol::new(env, "languages").into_val(env)).unwrap_or_default())?, - timezone: Bytes::try_from_val(env, &map.get(Symbol::new(env, "timezone").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for MentorshipProfile { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "mentor").into_val(env), self.mentor.into_val(env)); - map.set(Symbol::new(env, "expertise_areas").into_val(env), self.expertise_areas.into_val(env)); - map.set(Symbol::new(env, "experience_level").into_val(env), self.experience_level.into_val(env)); - map.set(Symbol::new(env, "availability").into_val(env), self.availability.into_val(env)); - map.set(Symbol::new(env, "hourly_rate").into_val(env), self.hourly_rate.into_val(env)); - map.set(Symbol::new(env, "bio").into_val(env), self.bio.into_val(env)); - map.set(Symbol::new(env, "rating").into_val(env), self.rating.into_val(env)); - map.set(Symbol::new(env, "review_count").into_val(env), self.review_count.into_val(env)); - map.set(Symbol::new(env, "mentee_count").into_val(env), self.mentee_count.into_val(env)); - map.set(Symbol::new(env, "success_rate").into_val(env), self.success_rate.into_val(env)); - map.set(Symbol::new(env, "languages").into_val(env), self.languages.into_val(env)); - map.set(Symbol::new(env, "timezone").into_val(env), self.timezone.into_val(env)); - map.into_val(env) - } -} - -impl TryFromVal for SocialAnalytics { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(SocialAnalytics { - user: Address::try_from_val(env, &map.get(Symbol::new(env, "user").into_val(env)).unwrap_or_default())?, - study_groups_joined: u32::try_from_val(env, &map.get(Symbol::new(env, "study_groups_joined").into_val(env)).unwrap_or_default())?, - discussions_participated: u32::try_from_val(env, &map.get(Symbol::new(env, "discussions_participated").into_val(env)).unwrap_or_default())?, - posts_created: u32::try_from_val(env, &map.get(Symbol::new(env, "posts_created").into_val(env)).unwrap_or_default())?, - reviews_given: u32::try_from_val(env, &map.get(Symbol::new(env, "reviews_given").into_val(env)).unwrap_or_default())?, - mentorship_hours: u64::try_from_val(env, &map.get(Symbol::new(env, "mentorship_hours").into_val(env)).unwrap_or_default())?, - collaboration_projects: u32::try_from_val(env, &map.get(Symbol::new(env, "collaboration_projects").into_val(env)).unwrap_or_default())?, - social_score: u64::try_from_val(env, &map.get(Symbol::new(env, "social_score").into_val(env)).unwrap_or_default())?, - engagement_level: EngagementLevel::try_from_val(env, &map.get(Symbol::new(env, "engagement_level").into_val(env)).unwrap_or_default())?, - badges: Vec::::try_from_val(env, &map.get(Symbol::new(env, "badges").into_val(env)).unwrap_or_default())?, - last_updated: u64::try_from_val(env, &map.get(Symbol::new(env, "last_updated").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for SocialAnalytics { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "user").into_val(env), self.user.into_val(env)); - map.set(Symbol::new(env, "study_groups_joined").into_val(env), self.study_groups_joined.into_val(env)); - map.set(Symbol::new(env, "discussions_participated").into_val(env), self.discussions_participated.into_val(env)); - map.set(Symbol::new(env, "posts_created").into_val(env), self.posts_created.into_val(env)); - map.set(Symbol::new(env, "reviews_given").into_val(env), self.reviews_given.into_val(env)); - map.set(Symbol::new(env, "mentorship_hours").into_val(env), self.mentorship_hours.into_val(env)); - map.set(Symbol::new(env, "collaboration_projects").into_val(env), self.collaboration_projects.into_val(env)); - map.set(Symbol::new(env, "social_score").into_val(env), self.social_score.into_val(env)); - map.set(Symbol::new(env, "engagement_level").into_val(env), self.engagement_level.into_val(env)); - map.set(Symbol::new(env, "badges").into_val(env), self.badges.into_val(env)); - map.set(Symbol::new(env, "last_updated").into_val(env), self.last_updated.into_val(env)); - map.into_val(env) - } -} - -// Additional enum implementations -impl TryFromVal for ExperienceLevel { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Beginner" => Ok(ExperienceLevel::Beginner), - "Intermediate" => Ok(ExperienceLevel::Intermediate), - "Advanced" => Ok(ExperienceLevel::Advanced), - "Expert" => Ok(ExperienceLevel::Expert), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for ExperienceLevel { - fn into_val(&self, env: &Env) -> Val { - match self { - ExperienceLevel::Beginner => Symbol::new(env, "Beginner").into_val(env), - ExperienceLevel::Intermediate => Symbol::new(env, "Intermediate").into_val(env), - ExperienceLevel::Advanced => Symbol::new(env, "Advanced").into_val(env), - ExperienceLevel::Expert => Symbol::new(env, "Expert").into_val(env), - } - } -} - -impl TryFromVal for SessionStatus { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Scheduled" => Ok(SessionStatus::Scheduled), - "InProgress" => Ok(SessionStatus::InProgress), - "Completed" => Ok(SessionStatus::Completed), - "Cancelled" => Ok(SessionStatus::Cancelled), - "NoShow" => Ok(SessionStatus::NoShow), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for SessionStatus { - fn into_val(&self, env: &Env) -> Val { - match self { - SessionStatus::Scheduled => Symbol::new(env, "Scheduled").into_val(env), - SessionStatus::InProgress => Symbol::new(env, "InProgress").into_val(env), - SessionStatus::Completed => Symbol::new(env, "Completed").into_val(env), - SessionStatus::Cancelled => Symbol::new(env, "Cancelled").into_val(env), - SessionStatus::NoShow => Symbol::new(env, "NoShow").into_val(env), - } - } -} - -impl TryFromVal for EngagementLevel { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Low" => Ok(EngagementLevel::Low), - "Medium" => Ok(EngagementLevel::Medium), - "High" => Ok(EngagementLevel::High), - "VeryHigh" => Ok(EngagementLevel::VeryHigh), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for EngagementLevel { - fn into_val(&self, env: &Env) -> Val { - match self { - EngagementLevel::Low => Symbol::new(env, "Low").into_val(env), - EngagementLevel::Medium => Symbol::new(env, "Medium").into_val(env), - EngagementLevel::High => Symbol::new(env, "High").into_val(env), - EngagementLevel::VeryHigh => Symbol::new(env, "VeryHigh").into_val(env), - } - } -} - -// PeerReview implementations -impl TryFromVal for PeerReview { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(PeerReview { - id: u64::try_from_val(env, &map.get(Symbol::new(env, "id").into_val(env)).unwrap_or_default())?, - reviewer: Address::try_from_val(env, &map.get(Symbol::new(env, "reviewer").into_val(env)).unwrap_or_default())?, - reviewee: Address::try_from_val(env, &map.get(Symbol::new(env, "reviewee").into_val(env)).unwrap_or_default())?, - content_type: ReviewContentType::try_from_val(env, &map.get(Symbol::new(env, "content_type").into_val(env)).unwrap_or_default())?, - content_id: u64::try_from_val(env, &map.get(Symbol::new(env, "content_id").into_val(env)).unwrap_or_default())?, - rating: u32::try_from_val(env, &map.get(Symbol::new(env, "rating").into_val(env)).unwrap_or_default())?, - feedback: Bytes::try_from_val(env, &map.get(Symbol::new(env, "feedback").into_val(env)).unwrap_or_default())?, - criteria: Map::::try_from_val(env, &map.get(Symbol::new(env, "criteria").into_val(env)).unwrap_or_default())?, - created_at: u64::try_from_val(env, &map.get(Symbol::new(env, "created_at").into_val(env)).unwrap_or_default())?, - is_helpful: bool::try_from_val(env, &map.get(Symbol::new(env, "is_helpful").into_val(env)).unwrap_or_default())?, - helpful_votes: u32::try_from_val(env, &map.get(Symbol::new(env, "helpful_votes").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for PeerReview { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "id").into_val(env), self.id.into_val(env)); - map.set(Symbol::new(env, "reviewer").into_val(env), self.reviewer.into_val(env)); - map.set(Symbol::new(env, "reviewee").into_val(env), self.reviewee.into_val(env)); - map.set(Symbol::new(env, "content_type").into_val(env), self.content_type.into_val(env)); - map.set(Symbol::new(env, "content_id").into_val(env), self.content_id.into_val(env)); - map.set(Symbol::new(env, "rating").into_val(env), self.rating.into_val(env)); - map.set(Symbol::new(env, "feedback").into_val(env), self.feedback.into_val(env)); - map.set(Symbol::new(env, "criteria").into_val(env), self.criteria.into_val(env)); - map.set(Symbol::new(env, "created_at").into_val(env), self.created_at.into_val(env)); - map.set(Symbol::new(env, "is_helpful").into_val(env), self.is_helpful.into_val(env)); - map.set(Symbol::new(env, "helpful_votes").into_val(env), self.helpful_votes.into_val(env)); - map.into_val(env) - } -} - -// ReviewContentType implementations -impl TryFromVal for ReviewContentType { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Submission" => Ok(ReviewContentType::Submission), - "Comment" => Ok(ReviewContentType::Comment), - "Project" => Ok(ReviewContentType::Project), - "Tutorial" => Ok(ReviewContentType::Tutorial), - "Resource" => Ok(ReviewContentType::Resource), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for ReviewContentType { - fn into_val(&self, env: &Env) -> Val { - match self { - ReviewContentType::Submission => Symbol::new(env, "Submission").into_val(env), - ReviewContentType::Comment => Symbol::new(env, "Comment").into_val(env), - ReviewContentType::Project => Symbol::new(env, "Project").into_val(env), - ReviewContentType::Tutorial => Symbol::new(env, "Tutorial").into_val(env), - ReviewContentType::Resource => Symbol::new(env, "Resource").into_val(env), - } - } -} - -// WorkspaceSettings implementations -impl TryFromVal for WorkspaceSettings { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(WorkspaceSettings { - allow_public_view: bool::try_from_val(env, &map.get(Symbol::new(env, "allow_public_view").into_val(env)).unwrap_or_default())?, - require_approval_to_join: bool::try_from_val(env, &map.get(Symbol::new(env, "require_approval_to_join").into_val(env)).unwrap_or_default())?, - enable_chat: bool::try_from_val(env, &map.get(Symbol::new(env, "enable_chat").into_val(env)).unwrap_or_default())?, - enable_video_calls: bool::try_from_val(env, &map.get(Symbol::new(env, "enable_video_calls").into_val(env)).unwrap_or_default())?, - auto_save_interval: u64::try_from_val(env, &map.get(Symbol::new(env, "auto_save_interval").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for WorkspaceSettings { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "allow_public_view").into_val(env), self.allow_public_view.into_val(env)); - map.set(Symbol::new(env, "require_approval_to_join").into_val(env), self.require_approval_to_join.into_val(env)); - map.set(Symbol::new(env, "enable_chat").into_val(env), self.enable_chat.into_val(env)); - map.set(Symbol::new(env, "enable_video_calls").into_val(env), self.enable_video_calls.into_val(env)); - map.set(Symbol::new(env, "auto_save_interval").into_val(env), self.auto_save_interval.into_val(env)); - map.into_val(env) - } -} - -// CollaborationWorkspace implementations -impl TryFromVal for CollaborationWorkspace { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(CollaborationWorkspace { - id: u64::try_from_val(env, &map.get(Symbol::new(env, "id").into_val(env)).unwrap_or_default())?, - name: Bytes::try_from_val(env, &map.get(Symbol::new(env, "name").into_val(env)).unwrap_or_default())?, - description: Bytes::try_from_val(env, &map.get(Symbol::new(env, "description").into_val(env)).unwrap_or_default())?, - creator: Address::try_from_val(env, &map.get(Symbol::new(env, "creator").into_val(env)).unwrap_or_default())?, - collaborators: Vec::
::try_from_val(env, &map.get(Symbol::new(env, "collaborators").into_val(env)).unwrap_or_default())?, - project_type: ProjectType::try_from_val(env, &map.get(Symbol::new(env, "project_type").into_val(env)).unwrap_or_default())?, - status: WorkspaceStatus::try_from_val(env, &map.get(Symbol::new(env, "status").into_val(env)).unwrap_or_default())?, - created_at: u64::try_from_val(env, &map.get(Symbol::new(env, "created_at").into_val(env)).unwrap_or_default())?, - last_activity: u64::try_from_val(env, &map.get(Symbol::new(env, "last_activity").into_val(env)).unwrap_or_default())?, - files: Vec::::try_from_val(env, &map.get(Symbol::new(env, "files").into_val(env)).unwrap_or_default())?, - tasks: Vec::::try_from_val(env, &map.get(Symbol::new(env, "tasks").into_val(env)).unwrap_or_default())?, - settings: WorkspaceSettings::try_from_val(env, &map.get(Symbol::new(env, "settings").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for CollaborationWorkspace { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "id").into_val(env), self.id.into_val(env)); - map.set(Symbol::new(env, "name").into_val(env), self.name.into_val(env)); - map.set(Symbol::new(env, "description").into_val(env), self.description.into_val(env)); - map.set(Symbol::new(env, "creator").into_val(env), self.creator.into_val(env)); - map.set(Symbol::new(env, "collaborators").into_val(env), self.collaborators.into_val(env)); - map.set(Symbol::new(env, "project_type").into_val(env), self.project_type.into_val(env)); - map.set(Symbol::new(env, "status").into_val(env), self.status.into_val(env)); - map.set(Symbol::new(env, "created_at").into_val(env), self.created_at.into_val(env)); - map.set(Symbol::new(env, "last_activity").into_val(env), self.last_activity.into_val(env)); - map.set(Symbol::new(env, "files").into_val(env), self.files.into_val(env)); - map.set(Symbol::new(env, "tasks").into_val(env), self.tasks.into_val(env)); - map.set(Symbol::new(env, "settings").into_val(env), self.settings.into_val(env)); - map.into_val(env) - } -} - -// ProjectType implementations -impl TryFromVal for ProjectType { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Study" => Ok(ProjectType::Study), - "Research" => Ok(ProjectType::Research), - "Assignment" => Ok(ProjectType::Assignment), - "Tutorial" => Ok(ProjectType::Tutorial), - "Discussion" => Ok(ProjectType::Discussion), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for ProjectType { - fn into_val(&self, env: &Env) -> Val { - match self { - ProjectType::Study => Symbol::new(env, "Study").into_val(env), - ProjectType::Research => Symbol::new(env, "Research").into_val(env), - ProjectType::Assignment => Symbol::new(env, "Assignment").into_val(env), - ProjectType::Tutorial => Symbol::new(env, "Tutorial").into_val(env), - ProjectType::Discussion => Symbol::new(env, "Discussion").into_val(env), - } - } -} - -// WorkspaceStatus implementations -impl TryFromVal for WorkspaceStatus { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Active" => Ok(WorkspaceStatus::Active), - "Completed" => Ok(WorkspaceStatus::Completed), - "Archived" => Ok(WorkspaceStatus::Archived), - "Suspended" => Ok(WorkspaceStatus::Suspended), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for WorkspaceStatus { - fn into_val(&self, env: &Env) -> Val { - match self { - WorkspaceStatus::Active => Symbol::new(env, "Active").into_val(env), - WorkspaceStatus::Completed => Symbol::new(env, "Completed").into_val(env), - WorkspaceStatus::Archived => Symbol::new(env, "Archived").into_val(env), - WorkspaceStatus::Suspended => Symbol::new(env, "Suspended").into_val(env), - } - } -} - -// WorkspaceFile implementations -impl TryFromVal for WorkspaceFile { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(WorkspaceFile { - id: u64::try_from_val(env, &map.get(Symbol::new(env, "id").into_val(env)).unwrap_or_default())?, - name: Bytes::try_from_val(env, &map.get(Symbol::new(env, "name").into_val(env)).unwrap_or_default())?, - content_hash: Bytes::try_from_val(env, &map.get(Symbol::new(env, "content_hash").into_val(env)).unwrap_or_default())?, - uploader: Address::try_from_val(env, &map.get(Symbol::new(env, "uploader").into_val(env)).unwrap_or_default())?, - uploaded_at: u64::try_from_val(env, &map.get(Symbol::new(env, "uploaded_at").into_val(env)).unwrap_or_default())?, - file_type: Bytes::try_from_val(env, &map.get(Symbol::new(env, "file_type").into_val(env)).unwrap_or_default())?, - size: u64::try_from_val(env, &map.get(Symbol::new(env, "size").into_val(env)).unwrap_or_default())?, - version: u32::try_from_val(env, &map.get(Symbol::new(env, "version").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for WorkspaceFile { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "id").into_val(env), self.id.into_val(env)); - map.set(Symbol::new(env, "name").into_val(env), self.name.into_val(env)); - map.set(Symbol::new(env, "content_hash").into_val(env), self.content_hash.into_val(env)); - map.set(Symbol::new(env, "uploader").into_val(env), self.uploader.into_val(env)); - map.set(Symbol::new(env, "uploaded_at").into_val(env), self.uploaded_at.into_val(env)); - map.set(Symbol::new(env, "file_type").into_val(env), self.file_type.into_val(env)); - map.set(Symbol::new(env, "size").into_val(env), self.size.into_val(env)); - map.set(Symbol::new(env, "version").into_val(env), self.version.into_val(env)); - map.into_val(env) - } -} - -// WorkspaceTask implementations -impl TryFromVal for WorkspaceTask { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let map = Map::::try_from_val(env, val)?; - Ok(WorkspaceTask { - id: u64::try_from_val(env, &map.get(Symbol::new(env, "id").into_val(env)).unwrap_or_default())?, - title: Bytes::try_from_val(env, &map.get(Symbol::new(env, "title").into_val(env)).unwrap_or_default())?, - description: Bytes::try_from_val(env, &map.get(Symbol::new(env, "description").into_val(env)).unwrap_or_default())?, - assignee: Address::try_from_val(env, &map.get(Symbol::new(env, "assignee").into_val(env)).unwrap_or_default())?, - creator: Address::try_from_val(env, &map.get(Symbol::new(env, "creator").into_val(env)).unwrap_or_default())?, - due_date: u64::try_from_val(env, &map.get(Symbol::new(env, "due_date").into_val(env)).unwrap_or_default())?, - status: TaskStatus::try_from_val(env, &map.get(Symbol::new(env, "status").into_val(env)).unwrap_or_default())?, - priority: TaskPriority::try_from_val(env, &map.get(Symbol::new(env, "priority").into_val(env)).unwrap_or_default())?, - created_at: u64::try_from_val(env, &map.get(Symbol::new(env, "created_at").into_val(env)).unwrap_or_default())?, - completed_at: Option::::try_from_val(env, &map.get(Symbol::new(env, "completed_at").into_val(env)).unwrap_or_default())?, - }) - } -} - -impl IntoVal for WorkspaceTask { - fn into_val(&self, env: &Env) -> Val { - let mut map = Map::::new(env); - map.set(Symbol::new(env, "id").into_val(env), self.id.into_val(env)); - map.set(Symbol::new(env, "title").into_val(env), self.title.into_val(env)); - map.set(Symbol::new(env, "description").into_val(env), self.description.into_val(env)); - map.set(Symbol::new(env, "assignee").into_val(env), self.assignee.into_val(env)); - map.set(Symbol::new(env, "creator").into_val(env), self.creator.into_val(env)); - map.set(Symbol::new(env, "due_date").into_val(env), self.due_date.into_val(env)); - map.set(Symbol::new(env, "status").into_val(env), self.status.into_val(env)); - map.set(Symbol::new(env, "priority").into_val(env), self.priority.into_val(env)); - map.set(Symbol::new(env, "created_at").into_val(env), self.created_at.into_val(env)); - map.set(Symbol::new(env, "completed_at").into_val(env), self.completed_at.into_val(env)); - map.into_val(env) - } -} - -// TaskStatus implementations -impl TryFromVal for TaskStatus { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Todo" => Ok(TaskStatus::Todo), - "InProgress" => Ok(TaskStatus::InProgress), - "Review" => Ok(TaskStatus::Review), - "Completed" => Ok(TaskStatus::Completed), - "Cancelled" => Ok(TaskStatus::Cancelled), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for TaskStatus { - fn into_val(&self, env: &Env) -> Val { - match self { - TaskStatus::Todo => Symbol::new(env, "Todo").into_val(env), - TaskStatus::InProgress => Symbol::new(env, "InProgress").into_val(env), - TaskStatus::Review => Symbol::new(env, "Review").into_val(env), - TaskStatus::Completed => Symbol::new(env, "Completed").into_val(env), - TaskStatus::Cancelled => Symbol::new(env, "Cancelled").into_val(env), - } - } -} - -// TaskPriority implementations -impl TryFromVal for TaskPriority { - type Error = soroban_sdk::ConversionError; - - fn try_from_val(env: &Env, val: &Val) -> Result { - let symbol = Symbol::try_from_val(env, val)?; - match symbol.to_string().as_str() { - "Low" => Ok(TaskPriority::Low), - "Medium" => Ok(TaskPriority::Medium), - "High" => Ok(TaskPriority::High), - "Urgent" => Ok(TaskPriority::Urgent), - _ => Err(soroban_sdk::ConversionError {}), - } - } -} - -impl IntoVal for TaskPriority { - fn into_val(&self, env: &Env) -> Val { - match self { - TaskPriority::Low => Symbol::new(env, "Low").into_val(env), - TaskPriority::Medium => Symbol::new(env, "Medium").into_val(env), - TaskPriority::High => Symbol::new(env, "High").into_val(env), - TaskPriority::Urgent => Symbol::new(env, "Urgent").into_val(env), - } - } -} From 331bf2ac69fece4aa3b3560ac45d2c32e1e362b7 Mon Sep 17 00:00:00 2001 From: Richiey1 Date: Sun, 22 Feb 2026 03:37:23 +0100 Subject: [PATCH 3/3] test: add integration tests for assessment platform --- .../test_adaptive_selection.1.json | 704 +++++++++++++ .../test_snapshots/test_add_question.1.json | 208 ++++ .../test_create_assessment.1.json | 277 +++++ .../test_plagiarism_detection.1.json | 992 ++++++++++++++++++ .../test_submit_assessment_grading.1.json | 832 +++++++++++++++ contracts/teachlink/tests/test_assessment.rs | 215 ++++ 6 files changed, 3228 insertions(+) create mode 100644 contracts/teachlink/test_snapshots/test_adaptive_selection.1.json create mode 100644 contracts/teachlink/test_snapshots/test_add_question.1.json create mode 100644 contracts/teachlink/test_snapshots/test_create_assessment.1.json create mode 100644 contracts/teachlink/test_snapshots/test_plagiarism_detection.1.json create mode 100644 contracts/teachlink/test_snapshots/test_submit_assessment_grading.1.json create mode 100644 contracts/teachlink/tests/test_assessment.rs diff --git a/contracts/teachlink/test_snapshots/test_adaptive_selection.1.json b/contracts/teachlink/test_snapshots/test_adaptive_selection.1.json new file mode 100644 index 0000000..05e6a2d --- /dev/null +++ b/contracts/teachlink/test_snapshots/test_adaptive_selection.1.json @@ -0,0 +1,704 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "45617379" + }, + { + "u32": 10 + }, + { + "u32": 1 + }, + { + "bytes": "41" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "4d6564" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "41" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "48617264" + }, + { + "u32": 10 + }, + { + "u32": 9 + }, + { + "bytes": "41" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "4164617074697665205175697a" + }, + { + "bytes": "44657363" + }, + { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + }, + { + "u64": "3" + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ASS_C" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "ASS_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "44657363" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "questions" + }, + "val": { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + }, + { + "u64": "3" + } + ] + } + }, + { + "key": { + "symbol": "settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "4164617074697665205175697a" + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "QUE_C" + }, + "val": { + "u64": "3" + } + }, + { + "key": { + "symbol": "QUE_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "45617379" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 1 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "4d6564" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + }, + { + "key": { + "u64": "3" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "48617264" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 9 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "3" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/test_add_question.1.json b/contracts/teachlink/test_snapshots/test_add_question.1.json new file mode 100644 index 0000000..bdff3e0 --- /dev/null +++ b/contracts/teachlink/test_snapshots/test_add_question.1.json @@ -0,0 +1,208 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "ShortAnswer" + } + ] + }, + { + "bytes": "57686174206973206f776e6572736869703f" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "4d656d6f727920736166657479206d656368616e69736d" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "QUE_C" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "QUE_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "57686174206973206f776e6572736869703f" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "4d656d6f727920736166657479206d656368616e69736d" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "ShortAnswer" + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/test_create_assessment.1.json b/contracts/teachlink/test_snapshots/test_create_assessment.1.json new file mode 100644 index 0000000..7b5b56b --- /dev/null +++ b/contracts/teachlink/test_snapshots/test_create_assessment.1.json @@ -0,0 +1,277 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "52757374204d617374657279205175697a" + }, + { + "bytes": "5465737420796f7572205275737420736b696c6c73" + }, + { + "vec": [] + }, + { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 70 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "3600" + } + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ASS_C" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "ASS_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "5465737420796f7572205275737420736b696c6c73" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "questions" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 70 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "3600" + } + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "52757374204d617374657279205175697a" + } + } + ] + } + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/test_plagiarism_detection.1.json b/contracts/teachlink/test_snapshots/test_plagiarism_detection.1.json new file mode 100644 index 0000000..6f7cda4 --- /dev/null +++ b/contracts/teachlink/test_snapshots/test_plagiarism_detection.1.json @@ -0,0 +1,992 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "5131" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "41" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "5132" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "42" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "5133" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "43" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "5175697a" + }, + { + "bytes": "44657363" + }, + { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + }, + { + "u64": "3" + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": "1" + }, + { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "42" + } + }, + { + "key": { + "u64": "3" + }, + "val": { + "bytes": "43" + } + } + ] + }, + { + "vec": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "SUB_S" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "answers" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "42" + } + }, + { + "key": { + "u64": "3" + }, + "val": { + "bytes": "43" + } + } + ] + } + }, + { + "key": { + "symbol": "assessment_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "is_graded" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "max_score" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "proctor_logs" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "student" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": "0" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ASS_ANL" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "assessment_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "average_score" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "difficulty_rating" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "pass_rate" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "total_submissions" + }, + "val": { + "u32": 1 + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "ASS_C" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "ASS_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "44657363" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "questions" + }, + "val": { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + }, + { + "u64": "3" + } + ] + } + }, + { + "key": { + "symbol": "settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "5175697a" + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "QUE_C" + }, + "val": { + "u64": "3" + } + }, + { + "key": { + "symbol": "QUE_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "5131" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "5132" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "42" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + }, + { + "key": { + "u64": "3" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "5133" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "43" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "3" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "REC_SUB" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "42" + } + }, + { + "key": { + "u64": "3" + }, + "val": { + "bytes": "43" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/test_snapshots/test_submit_assessment_grading.1.json b/contracts/teachlink/test_snapshots/test_submit_assessment_grading.1.json new file mode 100644 index 0000000..4aef5dd --- /dev/null +++ b/contracts/teachlink/test_snapshots/test_submit_assessment_grading.1.json @@ -0,0 +1,832 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "5131" + }, + { + "u32": 10 + }, + { + "u32": 5 + }, + { + "bytes": "41" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "add_assessment_question", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + }, + { + "bytes": "5132" + }, + { + "u32": 20 + }, + { + "u32": 8 + }, + { + "bytes": "42" + }, + { + "map": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "5175697a" + }, + { + "bytes": "44657363" + }, + { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "submit_assessment", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": "1" + }, + { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "57726f6e67" + } + } + ] + }, + { + "vec": [] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "SUB_S" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "u64": "1" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "answers" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "57726f6e67" + } + } + ] + } + }, + { + "key": { + "symbol": "assessment_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "is_graded" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "max_score" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "proctor_logs" + }, + "val": { + "vec": [] + } + }, + { + "key": { + "symbol": "score" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "student" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "timestamp" + }, + "val": { + "u64": "0" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "symbol": "ASS_ANL" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "assessment_id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "average_score" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "difficulty_rating" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "pass_rate" + }, + "val": { + "u32": 0 + } + }, + { + "key": { + "symbol": "total_submissions" + }, + "val": { + "u32": 1 + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "ASS_C" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "ASS_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "created_at" + }, + "val": { + "u64": "0" + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "bytes": "44657363" + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "questions" + }, + "val": { + "vec": [ + { + "u64": "1" + }, + { + "u64": "2" + } + ] + } + }, + { + "key": { + "symbol": "settings" + }, + "val": { + "map": [ + { + "key": { + "symbol": "allow_retakes" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "is_adaptive" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "passing_score" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "proctoring_enabled" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "time_limit" + }, + "val": { + "u64": "0" + } + } + ] + } + }, + { + "key": { + "symbol": "title" + }, + "val": { + "bytes": "5175697a" + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "QUE_C" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "QUE_S" + }, + "val": { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "5131" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 5 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "1" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "map": [ + { + "key": { + "symbol": "content_hash" + }, + "val": { + "bytes": "5132" + } + }, + { + "key": { + "symbol": "correct_answer_hash" + }, + "val": { + "bytes": "42" + } + }, + { + "key": { + "symbol": "difficulty" + }, + "val": { + "u32": 8 + } + }, + { + "key": { + "symbol": "id" + }, + "val": { + "u64": "2" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "map": [] + } + }, + { + "key": { + "symbol": "points" + }, + "val": { + "u32": 20 + } + }, + { + "key": { + "symbol": "q_type" + }, + "val": { + "vec": [ + { + "symbol": "MultipleChoice" + } + ] + } + } + ] + } + } + ] + } + }, + { + "key": { + "symbol": "REC_SUB" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "u64": "1" + }, + "val": { + "bytes": "41" + } + }, + { + "key": { + "u64": "2" + }, + "val": { + "bytes": "57726f6e67" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/teachlink/tests/test_assessment.rs b/contracts/teachlink/tests/test_assessment.rs new file mode 100644 index 0000000..e95507b --- /dev/null +++ b/contracts/teachlink/tests/test_assessment.rs @@ -0,0 +1,215 @@ +#![allow(clippy::needless_pass_by_value)] + +use soroban_sdk::{testutils::Address as _, Address, Bytes, Env, Map, Vec}; +use teachlink_contract::{ + AssessmentSettings, QuestionType, TeachLinkBridge, + TeachLinkBridgeClient, +}; + +fn setup_test(env: &Env) -> (TeachLinkBridgeClient<'_>, Address, Address) { + let contract_id = env.register(TeachLinkBridge, ()); + let client = TeachLinkBridgeClient::new(env, &contract_id); + + let creator = Address::generate(env); + let student = Address::generate(env); + + (client, creator, student) +} + +#[test] +fn test_create_assessment() { + let env = Env::default(); + let (client, creator, _) = setup_test(&env); + env.mock_all_auths(); + + let title = Bytes::from_slice(&env, b"Rust Mastery Quiz"); + let description = Bytes::from_slice(&env, b"Test your Rust skills"); + let questions = Vec::new(&env); + let settings = AssessmentSettings { + time_limit: 3600, + passing_score: 70, + is_adaptive: false, + allow_retakes: true, + proctoring_enabled: true, + }; + + let assessment_id = client.create_assessment( + &creator, + &title, + &description, + &questions, + &settings, + ); + + assert_eq!(assessment_id, 1); + + let assessment = client.get_assessment(&assessment_id).unwrap(); + assert_eq!(assessment.creator, creator); +} + +#[test] +fn test_add_question() { + let env = Env::default(); + let (client, creator, _) = setup_test(&env); + env.mock_all_auths(); + + let content_hash = Bytes::from_slice(&env, b"What is ownership?"); + let correct_hash = Bytes::from_slice(&env, b"Memory safety mechanism"); + let metadata = Map::new(&env); + + let q_id = client.add_assessment_question( + &creator, + &QuestionType::ShortAnswer, + &content_hash, + &10, + &5, + &correct_hash, + &metadata, + ); + + assert_eq!(q_id, 1); +} + +#[test] +fn test_submit_assessment_grading() { + let env = Env::default(); + let (client, creator, student) = setup_test(&env); + env.mock_all_auths(); + + // 1. Add questions + let q1_correct = Bytes::from_slice(&env, b"A"); + let q1_id = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Q1"), &10, &5, &q1_correct, &Map::new(&env) + ); + + let q2_correct = Bytes::from_slice(&env, b"B"); + let q2_id = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Q2"), &20, &8, &q2_correct, &Map::new(&env) + ); + + // 2. Create assessment + let mut questions = Vec::new(&env); + questions.push_back(q1_id); + questions.push_back(q2_id); + + let assessment_id = client.create_assessment( + &creator, &Bytes::from_slice(&env, b"Quiz"), + &Bytes::from_slice(&env, b"Desc"), &questions, + &AssessmentSettings { + time_limit: 0, passing_score: 15, is_adaptive: false, + allow_retakes: false, proctoring_enabled: false + } + ); + + // 3. Submit answers + let mut answers = Map::new(&env); + answers.set(q1_id, q1_correct); // Correct + answers.set(q2_id, Bytes::from_slice(&env, b"Wrong")); // Incorrect + + let score = client.submit_assessment( + &student, &assessment_id, &answers, &Vec::new(&env) + ); + + assert_eq!(score, 10); + + let submission = client.get_assessment_submission(&student, &assessment_id).unwrap(); + assert_eq!(submission.score, 10); + assert_eq!(submission.max_score, 30); +} + +#[test] +fn test_adaptive_selection() { + let env = Env::default(); + let (client, creator, _) = setup_test(&env); + env.mock_all_auths(); + + // Add easy, medium, hard questions + let q_easy = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Easy"), &10, &1, &Bytes::from_slice(&env, b"A"), &Map::new(&env) + ); + let q_med = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Med"), &10, &5, &Bytes::from_slice(&env, b"A"), &Map::new(&env) + ); + let q_hard = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Hard"), &10, &9, &Bytes::from_slice(&env, b"A"), &Map::new(&env) + ); + + let mut questions = Vec::new(&env); + questions.push_back(q_easy); + questions.push_back(q_med); + questions.push_back(q_hard); + + let assessment_id = client.create_assessment( + &creator, &Bytes::from_slice(&env, b"Adaptive Quiz"), + &Bytes::from_slice(&env, b"Desc"), &questions, + &AssessmentSettings { + time_limit: 0, passing_score: 10, is_adaptive: true, + allow_retakes: false, proctoring_enabled: false + } + ); + + // High performance simulation + let mut scores = Vec::new(&env); + scores.push_back(1); // Answered correctly + let mut answered = Vec::new(&env); + answered.push_back(q_med); + + let next_q = client.get_next_adaptive_question( + &assessment_id, &scores, &answered + ); + + // Should select hard question (difficulty 9) over easy (difficulty 1) + assert_eq!(next_q, q_hard); +} + +#[test] +#[should_panic(expected = "Error(Contract, #7)")] // PlagiarismDetected = 7 +fn test_plagiarism_detection() { + let env = Env::default(); + let (client, creator, student1) = setup_test(&env); + let student2 = Address::generate(&env); + env.mock_all_auths(); + + let q1_id = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Q1"), &10, &5, &Bytes::from_slice(&env, b"A"), &Map::new(&env) + ); + let q2_id = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Q2"), &10, &5, &Bytes::from_slice(&env, b"B"), &Map::new(&env) + ); + let q3_id = client.add_assessment_question( + &creator, &QuestionType::MultipleChoice, + &Bytes::from_slice(&env, b"Q3"), &10, &5, &Bytes::from_slice(&env, b"C"), &Map::new(&env) + ); + + let mut questions = Vec::new(&env); + questions.push_back(q1_id); + questions.push_back(q2_id); + questions.push_back(q3_id); + + let assessment_id = client.create_assessment( + &creator, &Bytes::from_slice(&env, b"Quiz"), + &Bytes::from_slice(&env, b"Desc"), &questions, + &AssessmentSettings { + time_limit: 0, passing_score: 5, is_adaptive: false, + allow_retakes: false, proctoring_enabled: false + } + ); + + let mut answers = Map::new(&env); + answers.set(q1_id, Bytes::from_slice(&env, b"A")); + answers.set(q2_id, Bytes::from_slice(&env, b"B")); + answers.set(q3_id, Bytes::from_slice(&env, b"C")); + + // Student 1 submits + client.submit_assessment(&student1, &assessment_id, &answers, &Vec::new(&env)); + + // Student 2 submits identical answers + client.submit_assessment(&student2, &assessment_id, &answers, &Vec::new(&env)); +}