Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions contracts/quest-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,50 @@ pub struct QuestEngineContract;

#[contractimpl]
impl QuestEngineContract {
/// Initializes the QuestEngine contract with the token address.
pub fn initialize(env: Env, token: Address, reward_pool: Address) {
/// Initializes the QuestEngine contract with the token address and admin.
pub fn initialize(env: Env, admin: Address, token: Address, reward_pool: Address) {
if env.storage().instance().has(&DataKey::Token) {
panic!("Already initialized");
}
admin.require_auth();
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Token, &token);
env.storage()
.instance()
.set(&DataKey::RewardPool, &reward_pool);
env.storage().instance().set(&DataKey::QuestCounter, &0u32);
}

/// Toggles the pause state of the contract (emergency circuit breaker).
///
/// # Arguments
/// * `admin` - The admin address (must match stored admin)
/// * `status` - The pause status (true = paused, false = unpaused)
///
/// # Panics
/// * If contract is not initialized
/// * If admin does not match stored admin
/// * If admin authentication fails
pub fn set_pause(env: Env, admin: Address, status: bool) {
// 1. Fetch 'Admin' address from Instance storage
let stored_admin: Address = env
.storage()
.instance()
.get(&DataKey::Admin)
.expect("Not initialized");

// 2. Assert admin == stored_admin
if admin != stored_admin {
panic!("Unauthorized");
}

// 3. admin.require_auth()
admin.require_auth();

// 4. Store pause status in Instance storage
env.storage().instance().set(&DataKey::IsPaused, &status);
}

/// Allows an employer to lock USDC directly in the QuestEngine contract.
/// This acts as an isolated vault specifically for B2B bounties.
pub fn create_build_quest(
Expand Down Expand Up @@ -180,6 +212,14 @@ impl QuestEngineContract {
quest_id: u32,
approve: bool,
) {
// 0. Check if contract is paused
let is_paused: bool = env
.storage()
.instance()
.get(&DataKey::IsPaused)
.unwrap_or(false);
assert!(!is_paused, "Contract is paused");

// 1. employer.require_auth()
employer.require_auth();

Expand Down
59 changes: 33 additions & 26 deletions contracts/quest-engine/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ use crate::{QuestEngineContract, QuestEngineContractClient};

// ── Helpers ───────────────────────────────────────────────────────────────────

fn setup() -> (Env, QuestEngineContractClient<'static>, Address, Address) {
fn setup() -> (
Env,
QuestEngineContractClient<'static>,
Address,
Address,
Address,
) {
let env = Env::default();
env.mock_all_auths();

Expand All @@ -23,11 +29,12 @@ fn setup() -> (Env, QuestEngineContractClient<'static>, Address, Address) {
.register_stellar_asset_contract_v2(token_admin.clone())
.address();

// Initialize the contract with token
// Initialize the contract with admin, token, and reward_pool
let admin = Address::generate(&env);
let reward_pool = Address::generate(&env);
client.initialize(&token_id, &reward_pool);
client.initialize(&admin, &token_id, &reward_pool);

(env, client, token_id, reward_pool)
(env, client, token_id, reward_pool, admin)
}

fn mint_tokens(env: &Env, token_id: &Address, to: &Address, amount: &i128) {
Expand All @@ -44,15 +51,15 @@ fn token_balance(env: &Env, token_id: &Address, of: &Address) -> i128 {
#[test]
#[should_panic(expected = "Already initialized")]
fn test_initialize_twice_panics() {
let (_env, client, token_id, reward_pool) = setup();
client.initialize(&token_id, &reward_pool);
let (_env, client, token_id, reward_pool, admin) = setup();
client.initialize(&admin, &token_id, &reward_pool);
}

// ── create_build_quest Tests ─────────────────────────────────────────────────

#[test]
fn test_create_build_quest_success() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let reward_amount: i128 = 1_000;
let metadata_hash = BytesN::from_array(&env, &[1u8; 32]);
Expand Down Expand Up @@ -85,7 +92,7 @@ fn test_create_build_quest_success() {

#[test]
fn test_create_build_quest_emits_event() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let reward_amount: i128 = 500;
let metadata_hash = BytesN::from_array(&env, &[2u8; 32]);
Expand All @@ -105,7 +112,7 @@ fn test_create_build_quest_emits_event() {

#[test]
fn test_create_build_quest_increments_ids() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let metadata_hash = BytesN::from_array(&env, &[3u8; 32]);

Expand Down Expand Up @@ -148,13 +155,13 @@ fn test_create_quest_without_init_panics() {

#[test]
fn test_get_quest_returns_none_for_nonexistent() {
let (_env, client, _token_id, _reward_pool) = setup();
let (_env, client, _token_id, _reward_pool, _admin) = setup();
assert_eq!(client.get_quest(&999), None);
}

#[test]
fn test_create_build_quest_multiple_employers() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer1 = Address::generate(&env);
let employer2 = Address::generate(&env);
let metadata_hash = BytesN::from_array(&env, &[4u8; 32]);
Expand All @@ -181,7 +188,7 @@ fn test_create_build_quest_multiple_employers() {

#[test]
fn test_submit_proof_success() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand All @@ -203,7 +210,7 @@ fn test_submit_proof_success() {

#[test]
fn test_submit_proof_emits_event() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand All @@ -230,7 +237,7 @@ fn test_submit_proof_emits_event() {
#[test]
#[should_panic(expected = "Quest not found")]
fn test_submit_proof_nonexistent_quest_panics() {
let (_env, client, _token_id, _reward_pool) = setup();
let (_env, client, _token_id, _reward_pool, _admin) = setup();
let learner = Address::generate(&_env);
let proof_hash = BytesN::from_array(&_env, &[9u8; 32]);

Expand All @@ -240,7 +247,7 @@ fn test_submit_proof_nonexistent_quest_panics() {
#[test]
#[should_panic(expected = "Submission already exists")]
fn test_submit_proof_duplicate_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand All @@ -260,7 +267,7 @@ fn test_submit_proof_duplicate_panics() {

#[test]
fn test_get_submission_returns_none_for_nonexistent() {
let (_env, client, _token_id, _reward_pool) = setup();
let (_env, client, _token_id, _reward_pool, _admin) = setup();
let learner = Address::generate(&_env);
assert_eq!(client.get_submission(&learner, &999), None);
}
Expand All @@ -269,7 +276,7 @@ fn test_get_submission_returns_none_for_nonexistent() {

#[test]
fn test_review_submission_approve_success() {
let (env, client, token_id, reward_pool) = setup();
let (env, client, token_id, reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand Down Expand Up @@ -304,7 +311,7 @@ fn test_review_submission_approve_success() {

#[test]
fn test_review_submission_reject_success() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand Down Expand Up @@ -342,7 +349,7 @@ fn test_review_submission_reject_success() {

#[test]
fn test_review_submission_emits_event() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand Down Expand Up @@ -371,7 +378,7 @@ fn test_review_submission_emits_event() {
#[test]
#[should_panic(expected = "Quest not found")]
fn test_review_submission_nonexistent_quest_panics() {
let (_env, client, _token_id, _reward_pool) = setup();
let (_env, client, _token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&_env);
let learner = Address::generate(&_env);

Expand All @@ -381,7 +388,7 @@ fn test_review_submission_nonexistent_quest_panics() {
#[test]
#[should_panic(expected = "Only the quest employer can review submissions")]
fn test_review_submission_wrong_employer_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let wrong_employer = Address::generate(&env);
let learner = Address::generate(&env);
Expand All @@ -403,7 +410,7 @@ fn test_review_submission_wrong_employer_panics() {
#[test]
#[should_panic(expected = "Submission not found")]
fn test_review_submission_nonexistent_submission_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand All @@ -420,7 +427,7 @@ fn test_review_submission_nonexistent_submission_panics() {
#[test]
#[should_panic(expected = "Submission is not pending review")]
fn test_review_submission_already_reviewed_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let learner = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand All @@ -443,7 +450,7 @@ fn test_review_submission_already_reviewed_panics() {

#[test]
fn test_refund_quest_success() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let reward_amount: i128 = 1000;
let metadata_hash = BytesN::from_array(&env, &[30u8; 32]);
Expand All @@ -469,7 +476,7 @@ fn test_refund_quest_success() {
#[test]
#[should_panic(expected = "Quest already inactive")]
fn test_refund_quest_already_inactive_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let reward_amount: i128 = 1000;
let metadata_hash = BytesN::from_array(&env, &[31u8; 32]);
Expand All @@ -485,7 +492,7 @@ fn test_refund_quest_already_inactive_panics() {
#[test]
#[should_panic(expected = "Unauthorized")]
fn test_refund_quest_wrong_employer_panics() {
let (env, client, token_id, _reward_pool) = setup();
let (env, client, token_id, _reward_pool, _admin) = setup();
let employer = Address::generate(&env);
let wrong_employer = Address::generate(&env);
let reward_amount: i128 = 1000;
Expand Down
2 changes: 2 additions & 0 deletions contracts/quest-engine/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ pub struct Submission {
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DataKey {
Admin,
Quest(u32),
Submission(Address, u32), // (Submitter Address, Quest ID)
Token,
QuestCounter,
RewardPool,
IsPaused,
}
Loading
Loading