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
22 changes: 22 additions & 0 deletions campaign/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,25 @@ pub fn get_campaign_status(env: &Env) -> crate::types::CampaignStatusResponse {
days_remaining,
}
}

/// Issue #45 — Get campaign hours remaining until deadline.
///
/// Returns the hours portion of the remaining time (0 to 23) in the current day before the deadline.
/// If the deadline has passed, returns 0.
/// No auth required (read-only view).
///
/// # Panics
/// - `Error::NotInitialized` if campaign not initialized
#[must_use]
pub fn get_campaign_hours_remaining(env: &Env) -> u32 {
let campaign =
get_campaign(env).unwrap_or_else(|| panic_with_error!(env, Error::NotInitialized));

let now = env.ledger().timestamp();
if now < campaign.end_time {
let delta = campaign.end_time - now;
((delta % 86_400) / 3_600) as u32
} else {
0
}
}
6 changes: 6 additions & 0 deletions campaign/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,12 @@ impl CampaignContract {
contract::get_campaign_status(&env)
}

/// Issue #45 – Get campaign hours remaining until deadline.
/// No auth required (read-only view).
pub fn get_campaign_hours_remaining(env: Env) -> u32 {
contract::get_campaign_hours_remaining(&env)
}

/// Issue #207 – Release a single milestone (all assets proportionally).
///
/// Issue #242 – Reentrancy protection: acquires lock at entry, releases at exit.
Expand Down
76 changes: 75 additions & 1 deletion campaign/src/test/get_campaign_status_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,80 @@ fn calculates_days_remaining() {
with_contract(&env, || {
setup_active_campaign(&env);
let result = CampaignContract::get_campaign_status(env.clone());
assert!(result.days_remaining > 0);
assert_eq!(result.days_remaining, 1); // 100_000 seconds / 86_400 seconds = 1 day
});
}

#[test]
fn calculates_subday_remaining() {
let env = make_env();
env.ledger().set_timestamp(BASE);
with_contract(&env, || {
let creator = Address::generate(&env);
let campaign = CampaignData {
creator: creator.clone(),
goal_amount: 1000,
raised_amount: 0,
end_time: env.ledger().timestamp() + 36_000, // 10 hours from now
status: CampaignStatus::Active,
accepted_assets: {
let mut assets: Vec<StellarAsset> = Vec::new(&env);
assets.push_back(StellarAsset {
asset_code: String::from_str(&env, "XLM"),
issuer: Some(Address::generate(&env)),
});
assets
},
milestone_count: 1,
min_donation_amount: 0,
created_at_ledger: env.ledger().sequence(),
created_at_time: env.ledger().timestamp(),
concluded_at_ledger: None,
};
set_campaign(&env, &campaign);

let status = CampaignContract::get_campaign_status(env.clone());
assert_eq!(status.status, CampaignStatus::Active);
assert_eq!(status.days_remaining, 0);

let hours = CampaignContract::get_campaign_hours_remaining(env.clone());
assert_eq!(hours, 10);
});
}

#[test]
fn calculates_past_subday_remaining() {
let env = make_env();
env.ledger().set_timestamp(BASE);
with_contract(&env, || {
let creator = Address::generate(&env);
let campaign = CampaignData {
creator: creator.clone(),
goal_amount: 1000,
raised_amount: 0,
end_time: env.ledger().timestamp() - 36_000, // 10 hours ago
status: CampaignStatus::Ended,
accepted_assets: {
let mut assets: Vec<StellarAsset> = Vec::new(&env);
assets.push_back(StellarAsset {
asset_code: String::from_str(&env, "XLM"),
issuer: Some(Address::generate(&env)),
});
assets
},
milestone_count: 1,
min_donation_amount: 0,
created_at_ledger: env.ledger().sequence(),
created_at_time: env.ledger().timestamp(),
concluded_at_ledger: None,
};
set_campaign(&env, &campaign);

let status = CampaignContract::get_campaign_status(env.clone());
assert_eq!(status.status, CampaignStatus::Ended);
assert_eq!(status.days_remaining, 0);

let hours = CampaignContract::get_campaign_hours_remaining(env.clone());
assert_eq!(hours, 0);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
{
"generators": {
"address": 3,
"nonce": 0,
"mux_id": 0
},
"auth": [
[],
[]
],
"ledger": {
"protocol_version": 26,
"sequence_number": 0,
"timestamp": 31536000,
"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": "CampaignData"
}
]
},
"durability": "persistent",
"val": {
"map": [
{
"key": {
"symbol": "accepted_assets"
},
"val": {
"vec": [
{
"map": [
{
"key": {
"symbol": "asset_code"
},
"val": {
"string": "XLM"
}
},
{
"key": {
"symbol": "issuer"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
}
]
}
]
}
},
{
"key": {
"symbol": "concluded_at_ledger"
},
"val": "void"
},
{
"key": {
"symbol": "created_at_ledger"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "created_at_time"
},
"val": {
"u64": "31536000"
}
},
{
"key": {
"symbol": "creator"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
}
},
{
"key": {
"symbol": "end_time"
},
"val": {
"u64": "31500000"
}
},
{
"key": {
"symbol": "goal_amount"
},
"val": {
"i128": "1000"
}
},
{
"key": {
"symbol": "milestone_count"
},
"val": {
"u32": 1
}
},
{
"key": {
"symbol": "min_donation_amount"
},
"val": {
"i128": "0"
}
},
{
"key": {
"symbol": "raised_amount"
},
"val": {
"i128": "0"
}
},
{
"key": {
"symbol": "status"
},
"val": {
"vec": [
{
"symbol": "Ended"
}
]
}
}
]
}
}
},
"ext": "v0"
},
"live_until": 1036800
},
{
"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": null
}
}
}
},
"ext": "v0"
},
"live_until": 4095
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
"live_until": 4095
}
]
},
"events": []
}
Loading
Loading