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
42 changes: 39 additions & 3 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,44 @@ pub const MIN_DISPUTE_WINDOW_OVERRIDE: u64 = 60;
/// Maximum allowed per-bounty dispute window override (30 days in seconds).
pub const MAX_DISPUTE_WINDOW_OVERRIDE: u64 = 2_592_000;

// ─── Contract Errors ───────────────────────────────────────────────────
#[contracterror]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContractError {
InvalidAmount,
AmountTooSmall,
DeadlineMustBeInTheFuture,
ContractIsPaused,
FeeRecipientNotSet,
TokenNotAllowed,
DisputeWindowOverrideTooSmall,
DisputeWindowOverrideTooLarge,
BountyNotOpen,
MaintainerMismatch,
BountyMustBeReserved,
MissingContributor,
ContributorMismatch,
BountyMustBeSubmitted,
BountyAlreadyFinalized,
BountyNotExpiredYet,
CannotExtendFinalizedBounty,
DeadlineMustAdvance,
BountyExpired,
ArbiterNotSet,
NotArbiter,
DisputeWindowNotMet,
NotAdmin,
NoPendingArbiter,
TimelockNotElapsed,
BountyNotFound,
AlreadyInitialized,
StringTooLong,
}

fn panic_error(err: ContractError) -> ! {
panic!("{:?}", err);
}

#[contracttype]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BountyStatus {
Expand Down Expand Up @@ -187,12 +225,10 @@ impl StellarBountyBoardContract {
String::from_str(&_env, CONTRACT_VERSION)
}

pub fn initialize(env: Env, fee_recipient: Address, arbiter: Address, dispute_window: u64) {

pub fn initialize(env: Env, admin: Address, fee_recipient: Address, arbiter: Address, dispute_window: u64) {
// Prevent re-initialization
if env.storage().persistent().has(&DataKey::FeeRecipient) {
panic!("already initialized");
panic_error(ContractError::AlreadyInitialized);
}
env.storage()
.persistent()
Expand Down
103 changes: 103 additions & 0 deletions contracts/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,111 @@ fn test_get_version_matches_cargo_toml() {
);
}

#[test]
#[should_panic(expected = "AlreadyInitialized")]
fn test_initialize_twice_returns_already_initialized() {
let env = Env::default();
let contract_id = env.register_contract(None, StellarBountyBoardContract);
let client = StellarBountyBoardContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let fee_recipient = Address::generate(&env);
let arbiter = Address::generate(&env);

client.initialize(&admin, &fee_recipient, &arbiter, &600);
// Second call should fail with typed AlreadyInitialized error
client.initialize(&admin, &fee_recipient, &arbiter, &600);
}

#[test]
fn test_contract_version_constant() {

#[test]
fn test_create_bounty_repo_title_at_limit_succeeds() {
let env = Env::default();
env.mock_all_auths();
let (client, _admin, maintainer, _contributor, token_id, _fee_recipient, _arbiter) = setup_test(&env);
let token_admin = soroban_sdk::token::StellarAssetClient::new(&env, &token_id);
token_admin.mint(&maintainer, &1000);

let rust_repo: alloc::string::String = core::iter::repeat('r').take(MAX_REPO_LEN as usize).collect();
let rust_title: alloc::string::String = core::iter::repeat('t').take(MAX_TITLE_LEN as usize).collect();

let repo = String::from_str(&env, &rust_repo);
let title = String::from_str(&env, &rust_title);
let deadline = env.ledger().timestamp() + 1000;

let bounty_id = client.create_bounty(
&maintainer,
&token_id,
&500,
&repo,
&1,
&title,
&deadline,
&0u32,
&None,
);

assert_eq!(bounty_id, 1);
}

#[test]
#[should_panic(expected = "StringTooLong")]
fn test_create_bounty_repo_over_limit_fails() {
let env = Env::default();
env.mock_all_auths();
let (client, _admin, maintainer, _contributor, token_id, _fee_recipient, _arbiter) = setup_test(&env);
let token_admin = soroban_sdk::token::StellarAssetClient::new(&env, &token_id);
token_admin.mint(&maintainer, &1000);

// repo is one char longer than allowed
let rust_repo: alloc::string::String = core::iter::repeat('r').take((MAX_REPO_LEN as usize) + 1).collect();
let title: alloc::string::String = core::iter::repeat('t').take(10).collect();

let repo = String::from_str(&env, &rust_repo);
let title = String::from_str(&env, &title);
let deadline = env.ledger().timestamp() + 1000;

client.create_bounty(
&maintainer,
&token_id,
&500,
&repo,
&1,
&title,
&deadline,
&0u32,
&None,
);
}

#[test]
fn test_create_bounty_empty_repo_title_succeeds() {
let env = Env::default();
env.mock_all_auths();
let (client, _admin, maintainer, _contributor, token_id, _fee_recipient, _arbiter) = setup_test(&env);
let token_admin = soroban_sdk::token::StellarAssetClient::new(&env, &token_id);
token_admin.mint(&maintainer, &1000);

let repo = String::from_str(&env, "");
let title = String::from_str(&env, "");
let deadline = env.ledger().timestamp() + 1000;

let bounty_id = client.create_bounty(
&maintainer,
&token_id,
&500,
&repo,
&1,
&title,
&deadline,
&0u32,
&None,
);

assert_eq!(bounty_id, 1);
}
// Direct assertion on the compile-time constant
assert_eq!(CONTRACT_VERSION, env!("CARGO_PKG_VERSION"));
assert!(!CONTRACT_VERSION.is_empty());
Expand Down
Loading