Skip to content
Open
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
19 changes: 7 additions & 12 deletions Documents/Task Bounty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
//! - Dispute resolution
//! - Multi-token support (XLM and SAC tokens)

mod types;
mod storage;
mod task;
mod submission;
mod dispute;
mod events;
mod storage;
mod submission;
mod task;
mod types;

#[cfg(test)]
mod test;

use soroban_sdk::{contract, contractimpl, Address, Env, String, Vec};
use types::{Task, Submission, TaskStatus, SubmissionStatus};
use types::{Submission, SubmissionStatus, Task, TaskStatus};

#[contract]
pub struct TaskBountyContract;
Expand Down Expand Up @@ -101,18 +101,13 @@ impl TaskBountyContract {
submission::submit_work(&env, task_id, contributor, work_url, description)
}

/// Approve a submission and release payment
/// Approve a submission and release a proportional reward share
///
/// # Arguments
/// * `task_id` - ID of the task
/// * `submission_id` - ID of the submission to approve
/// * `poster` - Address of the task poster (for auth)
pub fn approve_submission(
env: Env,
task_id: u64,
submission_id: u64,
poster: Address,
) {
pub fn approve_submission(env: Env, task_id: u64, submission_id: u64, poster: Address) {
poster.require_auth();

submission::approve_submission(&env, task_id, submission_id, poster)
Expand Down
38 changes: 20 additions & 18 deletions Documents/Task Bounty/src/submission.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use soroban_sdk::{Address, Env, String, token};
use crate::types::{Submission, SubmissionStatus, TaskStatus, Error};
use crate::storage;
use crate::events;
use crate::storage;
use crate::types::{Error, Submission, SubmissionStatus, TaskStatus};
use soroban_sdk::{token, Address, Env, String};

/// Submit work for a task
pub fn submit_work(
Expand Down Expand Up @@ -68,12 +68,7 @@ pub fn submit_work(
}

/// Approve a submission and release payment
pub fn approve_submission(
env: &Env,
task_id: u64,
submission_id: u64,
poster: Address,
) {
pub fn approve_submission(env: &Env, task_id: u64, submission_id: u64, poster: Address) {
// Get task
if !storage::task_exists(env, task_id) {
panic_with_error!(env, Error::TaskNotFound);
Expand Down Expand Up @@ -115,24 +110,31 @@ pub fn approve_submission(

// Update statuses
submission.status = SubmissionStatus::Approved;
task.status = TaskStatus::Completed;
task.approved_count += 1;

if task.approved_count >= task.max_submissions {
task.status = TaskStatus::Completed;
} else {
task.status = TaskStatus::InProgress;
}

storage::set_submission(env, &submission);
storage::set_task(env, &task);

// Transfer reward to contributor
let contract_address = env.current_contract_address();
let token_client = token::Client::new(env, &task.token);
token_client.transfer(&contract_address, &submission.contributor, &task.reward);
let approved_slots = task.max_submissions as i128;
let base_payout = task.reward / approved_slots;
let payout = if task.approved_count >= task.max_submissions {
task.reward - (base_payout * (approved_slots - 1))
} else {
base_payout
};
token_client.transfer(&contract_address, &submission.contributor, &payout);

// Emit event
events::emit_submission_approved(
env,
task_id,
submission_id,
&submission.contributor,
task.reward,
);
events::emit_submission_approved(env, task_id, submission_id, &submission.contributor, payout);
}

/// Reject a submission
Expand Down
7 changes: 4 additions & 3 deletions Documents/Task Bounty/src/task.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use soroban_sdk::{Address, Env, String, token};
use crate::types::{Task, TaskStatus, Error};
use crate::storage;
use crate::events;
use crate::storage;
use crate::types::{Error, Task, TaskStatus};
use soroban_sdk::{token, Address, Env, String};

const MIN_REWARD: i128 = 1_000_000; // 0.1 XLM (7 decimals)
const MAX_DEADLINE: u64 = 31_536_000; // 365 days in seconds
Expand Down Expand Up @@ -52,6 +52,7 @@ pub fn create_task(
deadline,
max_submissions,
submission_count: 0,
approved_count: 0,
status: TaskStatus::Open,
created_at: current_time,
};
Expand Down
80 changes: 77 additions & 3 deletions Documents/Task Bounty/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ use soroban_sdk::{
testutils::{Address as _, Ledger, LedgerInfo},
token, Address, Env, String,
};
use types::{TaskStatus, SubmissionStatus};
use types::{SubmissionStatus, TaskStatus};

fn create_token_contract<'a>(env: &Env, admin: &Address) -> token::Client<'a> {
let token_address = env.register_stellar_asset_contract(admin.clone());
token::Client::new(env, &token_address)
}

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

Expand Down Expand Up @@ -63,6 +70,7 @@ fn test_create_task() {
assert_eq!(task.reward, reward);
assert_eq!(task.deadline, deadline);
assert_eq!(task.max_submissions, 3);
assert_eq!(task.approved_count, 0);
assert_eq!(task.status, TaskStatus::Open);
}

Expand Down Expand Up @@ -231,7 +239,10 @@ fn test_approve_submission() {

// Check payment
let contributor_balance_after = token_client.balance(&contributor);
assert_eq!(contributor_balance_after, contributor_balance_before + reward);
assert_eq!(
contributor_balance_after,
contributor_balance_before + reward
);

// Check statuses
let task = client.get_task(&task_id);
Expand All @@ -241,6 +252,69 @@ fn test_approve_submission() {
assert_eq!(submission.status, SubmissionStatus::Approved);
}

#[test]
fn test_multiple_winners_share_reward() {
let (env, poster, contributor, _, token_client, contract_id) = setup_test();
let client = TaskBountyContractClient::new(&env, &contract_id);

let reward = 9_000_000;

let task_id = client.create_task(
&poster,
&String::from_str(&env, "Task"),
&String::from_str(&env, "Description"),
&token_client.address,
&reward,
&(env.ledger().timestamp() + 86400),
&3,
);

let contributor2 = Address::generate(&env);
let contributor3 = Address::generate(&env);

let sub1 = client.submit_work(
&task_id,
&contributor,
&String::from_str(&env, "ipfs://1"),
&String::from_str(&env, "Work 1"),
);
let sub2 = client.submit_work(
&task_id,
&contributor2,
&String::from_str(&env, "ipfs://2"),
&String::from_str(&env, "Work 2"),
);
let sub3 = client.submit_work(
&task_id,
&contributor3,
&String::from_str(&env, "ipfs://3"),
&String::from_str(&env, "Work 3"),
);

let share = reward / 3;
let first_before = token_client.balance(&contributor);
let second_before = token_client.balance(&contributor2);
let third_before = token_client.balance(&contributor3);

client.approve_submission(&task_id, &sub1, &poster);
let task_after_first = client.get_task(&task_id);
assert_eq!(task_after_first.status, TaskStatus::InProgress);
assert_eq!(task_after_first.approved_count, 1);
assert_eq!(token_client.balance(&contributor), first_before + share);

client.approve_submission(&task_id, &sub2, &poster);
let task_after_second = client.get_task(&task_id);
assert_eq!(task_after_second.status, TaskStatus::InProgress);
assert_eq!(task_after_second.approved_count, 2);
assert_eq!(token_client.balance(&contributor2), second_before + share);

client.approve_submission(&task_id, &sub3, &poster);
let task_after_third = client.get_task(&task_id);
assert_eq!(task_after_third.status, TaskStatus::Completed);
assert_eq!(task_after_third.approved_count, 3);
assert_eq!(token_client.balance(&contributor3), third_before + share);
}

#[test]
fn test_reject_submission() {
let (env, poster, contributor, _, token_client, contract_id) = setup_test();
Expand Down
13 changes: 7 additions & 6 deletions Documents/Task Bounty/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ pub struct Task {
pub poster: Address,
pub title: String,
pub description: String,
pub token: Address, // Token address for reward
pub reward: i128, // Reward amount
pub deadline: u64, // Unix timestamp
pub token: Address, // Token address for reward
pub reward: i128, // Reward amount
pub deadline: u64, // Unix timestamp
pub max_submissions: u32,
pub submission_count: u32,
pub approved_count: u32,
pub status: TaskStatus,
pub created_at: u64, // Unix timestamp
pub created_at: u64, // Unix timestamp
}

/// Submission structure
Expand All @@ -44,9 +45,9 @@ pub struct Submission {
pub id: u64,
pub task_id: u64,
pub contributor: Address,
pub work_url: String, // IPFS, Arweave, GitHub, etc.
pub work_url: String, // IPFS, Arweave, GitHub, etc.
pub description: String,
pub submitted_at: u64, // Unix timestamp
pub submitted_at: u64, // Unix timestamp
pub status: SubmissionStatus,
}

Expand Down