From b747962c9bd00856858633d2a3e2a3cb0e836a36 Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Thu, 23 Jul 2026 21:09:04 +0100 Subject: [PATCH] feat(events): stop enforcing the submission deadline on-chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The submission deadline was enforced by the contract (reject apply/submit/ withdraw_submission after it, require+future-check at create), but it gates nothing the contract is responsible for: - no money path reads it (since #61 the payout/refund liveness is anchored to select-time via PRIZE_CLAIM_WINDOW_SECS, not the deadline); - winners are the manager's discretion — select_winners never consults submissions, so a late submission row is inert; - permissionless-submit storage abuse is bounded by the #86 count cap, not the deadline. Meanwhile the deadline is immutable on-chain (no set/extend entrypoint), so the contract could not support the extensions organizers do routinely. A submission window — with its extensions, grace periods, and cutoffs — is a backend/product concern; the chain keeps only custody, the count cap, and discretionary winner selection. Remove the enforcement: - delete the deadline checks in submit / withdraw_submission / bounty apply and the create-time DeadlineMustBeFuture check; - drop the required-deadline check from hackathon/crowdfunding validate_create. Keep EventRecord.deadline as advisory metadata (still stored, emitted at create, backend-owned) — no storage-layout change, no ABI break. The now-dead DeadlineRequired/DeadlinePassed/DeadlineMustBeFuture variants are retired, freeing 3 slots against the 50-case error-enum cap. Removed the 5 tests that asserted the deleted behavior. 220 events + 66 profile green; make build OK (events 55,742 B); fmt clean. --- contracts/events/src/bounty.rs | 11 +--- contracts/events/src/crowdfunding.rs | 4 -- contracts/events/src/errors.rs | 5 +- contracts/events/src/event_ops.rs | 16 ------ contracts/events/src/hackathon.rs | 3 - contracts/events/src/tests/bounty_pillar.rs | 18 ------ contracts/events/src/tests/crowdfunding.rs | 21 ------- .../events/src/tests/hackathon_pillar.rs | 57 ------------------- 8 files changed, 5 insertions(+), 130 deletions(-) diff --git a/contracts/events/src/bounty.rs b/contracts/events/src/bounty.rs index 8a155db..26c61a1 100644 --- a/contracts/events/src/bounty.rs +++ b/contracts/events/src/bounty.rs @@ -28,7 +28,7 @@ pub fn apply( admin::require_not_paused(env)?; let event = storage::get_event(env, bounty_id).ok_or(Error::EventNotFound)?; - require_active_bounty(env, &event)?; + require_active_bounty(&event)?; applicant.require_auth(); idempotency::require_unseen(env, &applicant, &op_id)?; @@ -61,7 +61,7 @@ pub fn withdraw_application( admin::require_not_paused(env)?; let event = storage::get_event(env, bounty_id).ok_or(Error::EventNotFound)?; - require_active_bounty(env, &event)?; + require_active_bounty(&event)?; applicant.require_auth(); idempotency::require_unseen(env, &applicant, &op_id)?; @@ -85,17 +85,12 @@ pub fn withdraw_application( // ============================================================ // HELPERS // ============================================================ -fn require_active_bounty(env: &Env, event: &EventRecord) -> Result<(), Error> { +fn require_active_bounty(event: &EventRecord) -> Result<(), Error> { if !matches!(event.pillar, Pillar::Bounty) { return Err(Error::InvalidPillar); } if !matches!(event.status, EventStatus::Active) { return Err(Error::EventNotActive); } - if let Some(deadline) = event.deadline { - if deadline <= env.ledger().timestamp() { - return Err(Error::DeadlinePassed); - } - } Ok(()) } diff --git a/contracts/events/src/crowdfunding.rs b/contracts/events/src/crowdfunding.rs index 0ca78d1..04a98ff 100644 --- a/contracts/events/src/crowdfunding.rs +++ b/contracts/events/src/crowdfunding.rs @@ -11,10 +11,6 @@ pub fn validate_create(_env: &Env, record: &EventRecord, _owner: &Address) -> Re _ => return Err(Error::InvalidReleaseKind), } - if record.deadline.is_none() { - return Err(Error::DeadlineRequired); - } - if record.winner_distribution.len() != 1 { return Err(Error::InvalidDistribution); } diff --git a/contracts/events/src/errors.rs b/contracts/events/src/errors.rs index c3cb278..fd8959c 100644 --- a/contracts/events/src/errors.rs +++ b/contracts/events/src/errors.rs @@ -29,9 +29,8 @@ pub enum Error { InvalidReleaseKind = 33, InvalidDistribution = 34, InvalidBudget = 35, - DeadlineRequired = 36, - DeadlinePassed = 37, - DeadlineMustBeFuture = 38, + // 36-38 retired: deadline enforcement removed (submission windows are an + // off-chain/backend concern; the contract no longer gates on deadline). TitleTooLong = 39, ApplicantAlreadyApplied = 40, diff --git a/contracts/events/src/event_ops.rs b/contracts/events/src/event_ops.rs index d31e3a3..814cf08 100644 --- a/contracts/events/src/event_ops.rs +++ b/contracts/events/src/event_ops.rs @@ -81,12 +81,6 @@ pub fn create_event(env: &Env, params: CreateEventParams, op_id: BytesN<32>) -> return Err(Error::DistributionMismatch); } - if let Some(deadline) = params.deadline { - if deadline <= env.ledger().timestamp() { - return Err(Error::DeadlineMustBeFuture); - } - } - if let Some(bps) = params.fee_bps_override { if bps > MAX_FEE_BPS { return Err(Error::InvalidFeeBps); @@ -544,11 +538,6 @@ pub fn submit( if matches!(event.pillar, Pillar::Crowdfunding) { return Err(Error::InvalidPillar); } - if let Some(deadline) = event.deadline { - if deadline <= env.ledger().timestamp() { - return Err(Error::DeadlinePassed); - } - } applicant.require_auth(); idempotency::require_unseen(env, &applicant, &op_id)?; @@ -612,11 +601,6 @@ pub fn withdraw_submission( if !matches!(event.status, EventStatus::Active) { return Err(Error::EventNotActive); } - if let Some(deadline) = event.deadline { - if deadline <= env.ledger().timestamp() { - return Err(Error::DeadlinePassed); - } - } applicant.require_auth(); idempotency::require_unseen(env, &applicant, &op_id)?; diff --git a/contracts/events/src/hackathon.rs b/contracts/events/src/hackathon.rs index ca738d2..f212590 100644 --- a/contracts/events/src/hackathon.rs +++ b/contracts/events/src/hackathon.rs @@ -9,8 +9,5 @@ pub fn validate_create(_env: &Env, record: &EventRecord, _owner: &Address) -> Re if !matches!(record.release_kind, ReleaseKind::Single) { return Err(Error::InvalidReleaseKind); } - if record.deadline.is_none() { - return Err(Error::DeadlineRequired); - } Ok(()) } diff --git a/contracts/events/src/tests/bounty_pillar.rs b/contracts/events/src/tests/bounty_pillar.rs index a21fe59..f2cf850 100644 --- a/contracts/events/src/tests/bounty_pillar.rs +++ b/contracts/events/src/tests/bounty_pillar.rs @@ -280,24 +280,6 @@ fn apply_on_completed_event_reverts() { assert_eq!(err, Error::EventNotActive); } -#[test] -fn apply_after_deadline_reverts() { - let ctx = setup(); - let deadline = ctx.env.ledger().timestamp() + 100; - let bounty_id = create_bounty_with_deadline(&ctx, deadline); - - ctx.env.ledger().with_mut(|li| { - li.timestamp = deadline; - }); - - let op_id = BytesN::random(&ctx.env); - let err = expect_op_err( - ctx.events - .try_apply_to_bounty(&bounty_id, &ctx.applicant, &op_id), - ); - assert_eq!(err, Error::DeadlinePassed); -} - #[test] fn apply_when_paused_reverts() { let ctx = setup(); diff --git a/contracts/events/src/tests/crowdfunding.rs b/contracts/events/src/tests/crowdfunding.rs index 7c281a3..9ff55de 100644 --- a/contracts/events/src/tests/crowdfunding.rs +++ b/contracts/events/src/tests/crowdfunding.rs @@ -158,27 +158,6 @@ fn create_rejects_single_release_kind() { assert!(res.is_err(), "single release must be rejected"); } -#[test] -fn create_rejects_missing_deadline() { - let ctx = setup(); - let params = CreateEventParams { - pillar: Pillar::Crowdfunding, - owner: ctx.builder.clone(), - token: ctx.token_addr.clone(), - total_budget: FUNDING_GOAL, - release_kind: ReleaseKind::Multi(3), - content_uri: String::from_str(&ctx.env, "uri"), - title: String::from_str(&ctx.env, "Bad CF"), - deadline: None, - winner_distribution: single_dist_100_at_1(&ctx.env), - fee_bps_override: None, - manager: None, - }; - let op = BytesN::random(&ctx.env); - let res = ctx.events.try_create_event(¶ms, &op); - assert!(res.is_err()); -} - #[test] fn create_rejects_distribution_with_multiple_positions() { let ctx = setup(); diff --git a/contracts/events/src/tests/hackathon_pillar.rs b/contracts/events/src/tests/hackathon_pillar.rs index 2e63a1f..a56733d 100644 --- a/contracts/events/src/tests/hackathon_pillar.rs +++ b/contracts/events/src/tests/hackathon_pillar.rs @@ -172,48 +172,6 @@ fn create_rejects_multi_release_kind() { assert!(res.is_err(), "hackathon must use Single release"); } -#[test] -fn create_rejects_missing_deadline() { - let ctx = setup(); - let params = CreateEventParams { - pillar: Pillar::Hackathon, - owner: ctx.owner.clone(), - token: ctx.token_addr.clone(), - total_budget: TOTAL_BUDGET, - release_kind: ReleaseKind::Single, - content_uri: String::from_str(&ctx.env, "uri"), - title: String::from_str(&ctx.env, "Hackathon"), - deadline: None, - winner_distribution: single_winner_dist(&ctx.env), - fee_bps_override: None, - manager: None, - }; - let op = BytesN::random(&ctx.env); - let res = ctx.events.try_create_event(¶ms, &op); - assert!(res.is_err(), "hackathon requires a submission deadline"); -} - -#[test] -fn create_rejects_past_deadline() { - let ctx = setup(); - let params = CreateEventParams { - pillar: Pillar::Hackathon, - owner: ctx.owner.clone(), - token: ctx.token_addr.clone(), - total_budget: TOTAL_BUDGET, - release_kind: ReleaseKind::Single, - content_uri: String::from_str(&ctx.env, "uri"), - title: String::from_str(&ctx.env, "Hackathon"), - deadline: Some(ctx.env.ledger().timestamp()), - winner_distribution: single_winner_dist(&ctx.env), - fee_bps_override: None, - manager: None, - }; - let op = BytesN::random(&ctx.env); - let res = ctx.events.try_create_event(¶ms, &op); - assert!(res.is_err(), "deadline must be in the future"); -} - // ============================================================ // submit (open submission model) // ============================================================ @@ -252,21 +210,6 @@ fn resubmit_keeps_original_timestamp_and_updates_uri() { assert_eq!(second.submitted_at, first_time); } -#[test] -fn submit_after_deadline_reverts() { - let ctx = setup(); - let id = create_hackathon(&ctx); - - ctx.env.ledger().with_mut(|li| { - li.timestamp += 2 * 86_400; - }); - - let uri = String::from_str(&ctx.env, "ipfs://Qm.../late.json"); - let op = BytesN::random(&ctx.env); - let res = ctx.events.try_submit(&id, &ctx.applicant, &uri, &op); - assert!(res.is_err(), "submission after the deadline must revert"); -} - #[test] fn submit_replayed_op_reverts() { let ctx = setup();