From 39593e89d160867dcb335d1906c7786e0d5b1156 Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 17:47:48 +0100 Subject: [PATCH 1/7] fix: sanitize non-numeric pagination parameters using Number() instead of parseInt() --- app/app/api/posts/[id]/comments/route.ts | 6 ++++-- app/app/api/posts/[id]/entries/route.ts | 11 ++++------- app/app/api/posts/route.ts | 6 ++++-- app/app/api/users/[id]/followers/route.ts | 6 ++++-- app/app/api/users/[id]/following/route.ts | 6 ++++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/app/api/posts/[id]/comments/route.ts b/app/app/api/posts/[id]/comments/route.ts index 514fff1..6ff94a9 100644 --- a/app/app/api/posts/[id]/comments/route.ts +++ b/app/app/api/posts/[id]/comments/route.ts @@ -12,8 +12,10 @@ export async function GET( try { const { id } = await params; const { searchParams } = new URL(request.url); - const page = parseInt(searchParams.get("page") || "1"); - const limit = parseInt(searchParams.get("limit") || "50"); + const rawPage = Number(searchParams.get("page")); + const rawLimit = Number(searchParams.get("limit")); + const page = Number.isNaN(rawPage) || rawPage < 1 ? 1 : Math.floor(rawPage); + const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); const skip = (page - 1) * limit; const [comments, total] = await Promise.all([ diff --git a/app/app/api/posts/[id]/entries/route.ts b/app/app/api/posts/[id]/entries/route.ts index 0af1d07..ea139e3 100644 --- a/app/app/api/posts/[id]/entries/route.ts +++ b/app/app/api/posts/[id]/entries/route.ts @@ -303,15 +303,12 @@ export async function GET( const { id: postId } = await params; const { searchParams } = new URL(request.url); - const page = parseInt(searchParams.get("page") || "1"); - const limit = parseInt(searchParams.get("limit") || "10"); + const rawPage = Number(searchParams.get("page")); + const rawLimit = Number(searchParams.get("limit")); + const page = Number.isNaN(rawPage) || rawPage < 1 ? 1 : Math.floor(rawPage); + const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); const skip = (page - 1) * limit; - // Validate pagination params - if (page < 1 || limit < 1 || limit > 100) { - return apiError("Invalid pagination parameters", 400); - } - // Check if post exists const post = await prisma.post.findUnique({ where: { id: postId }, diff --git a/app/app/api/posts/route.ts b/app/app/api/posts/route.ts index 5a69f04..987b2d5 100644 --- a/app/app/api/posts/route.ts +++ b/app/app/api/posts/route.ts @@ -215,8 +215,10 @@ const GET = async (request: NextRequest) => { const status = searchParams.get("status"); const from = searchParams.get("from"); const to = searchParams.get("to"); - const page = parseInt(searchParams.get("page") || "1"); - const limit = parseInt(searchParams.get("limit") || "10"); + const rawPage = Number(searchParams.get("page")); + const rawLimit = Number(searchParams.get("limit")); + const page = Number.isNaN(rawPage) || rawPage < 1 ? 1 : Math.floor(rawPage); + const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); const where: Prisma.PostWhereInput = {}; where.moderationStatus = { notIn: ["suspended", "banned"] }; diff --git a/app/app/api/users/[id]/followers/route.ts b/app/app/api/users/[id]/followers/route.ts index 6ca4d0f..fec0d9e 100644 --- a/app/app/api/users/[id]/followers/route.ts +++ b/app/app/api/users/[id]/followers/route.ts @@ -9,8 +9,10 @@ export async function GET( try { const { id: targetUserId } = await params; const { searchParams } = new URL(request.url); - const limit = parseInt(searchParams.get('limit') || '20'); - const skip = parseInt(searchParams.get('skip') || '0'); + const rawLimit = Number(searchParams.get('limit')); + const rawSkip = Number(searchParams.get('skip')); + const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); + const skip = Number.isNaN(rawSkip) || rawSkip < 0 ? 0 : Math.floor(rawSkip); // Followers are users who follow the target user // i.e., followingId = targetUserId diff --git a/app/app/api/users/[id]/following/route.ts b/app/app/api/users/[id]/following/route.ts index 8e60fe2..f8b368f 100644 --- a/app/app/api/users/[id]/following/route.ts +++ b/app/app/api/users/[id]/following/route.ts @@ -9,8 +9,10 @@ export async function GET( try { const { id: targetUserId } = await params; const { searchParams } = new URL(request.url); - const limit = parseInt(searchParams.get('limit') || '20'); - const skip = parseInt(searchParams.get('skip') || '0'); + const rawLimit = Number(searchParams.get('limit')); + const rawSkip = Number(searchParams.get('skip')); + const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); + const skip = Number.isNaN(rawSkip) || rawSkip < 0 ? 0 : Math.floor(rawSkip); // Following are users the target user follows // i.e., userId = targetUserId, followingId != null From 13480ba692da5e8d18354d3a98aece46cef38b0f Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 17:55:14 +0100 Subject: [PATCH 2/7] fix: entries route pagination must validate and return 400 for invalid params (default limit 10, not 20) --- app/app/api/posts/[id]/entries/route.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/app/api/posts/[id]/entries/route.ts b/app/app/api/posts/[id]/entries/route.ts index ea139e3..3e9587d 100644 --- a/app/app/api/posts/[id]/entries/route.ts +++ b/app/app/api/posts/[id]/entries/route.ts @@ -305,8 +305,13 @@ export async function GET( const rawPage = Number(searchParams.get("page")); const rawLimit = Number(searchParams.get("limit")); - const page = Number.isNaN(rawPage) || rawPage < 1 ? 1 : Math.floor(rawPage); - const limit = Number.isNaN(rawLimit) || rawLimit < 1 ? 20 : Math.min(Math.floor(rawLimit), 100); + const page = searchParams.get("page") === null ? 1 : Math.floor(rawPage); + const limit = searchParams.get("limit") === null ? 10 : Math.floor(rawLimit); + + if (Number.isNaN(page) || page < 1 || Number.isNaN(limit) || limit < 1 || limit > 100) { + return apiError("Invalid pagination parameters", 400); + } + const skip = (page - 1) * limit; // Check if post exists From 93d889c43fe8d1013a1aed10b316bcc98f2eac24 Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 17:59:10 +0100 Subject: [PATCH 3/7] fix: remove duplicate #[allow(clippy::too_many_arguments)] on #[contractimpl] --- contracts/geev-core/src/giveaway.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/geev-core/src/giveaway.rs b/contracts/geev-core/src/giveaway.rs index 75eb136..40d1eea 100644 --- a/contracts/geev-core/src/giveaway.rs +++ b/contracts/geev-core/src/giveaway.rs @@ -29,7 +29,6 @@ pub struct GiveawayWinnerSelected { prize_amount: i128, } -#[allow(clippy::too_many_arguments)] #[contractimpl] impl GiveawayContract { #[allow(clippy::too_many_arguments)] From a6752b9dad6ebc83c1b651859a6d76a2d950bd4b Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 18:04:31 +0100 Subject: [PATCH 4/7] refactor: use parameter struct for create_giveaway to reduce function arguments --- contracts/geev-core/src/giveaway.rs | 54 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/contracts/geev-core/src/giveaway.rs b/contracts/geev-core/src/giveaway.rs index 40d1eea..e85602a 100644 --- a/contracts/geev-core/src/giveaway.rs +++ b/contracts/geev-core/src/giveaway.rs @@ -8,6 +8,17 @@ use soroban_sdk::{ #[contract] pub struct GiveawayContract; +/// Parameters for creating a new giveaway +pub struct CreateGiveawayArgs { + pub creator: Address, + pub token: Address, + pub amount: i128, + pub title: String, + pub duration_seconds: u64, + pub winner_count: u32, + pub verification: Option, +} + #[contractevent] pub struct GiveawayCreated { giveaway_id: u64, @@ -31,60 +42,53 @@ pub struct GiveawayWinnerSelected { #[contractimpl] impl GiveawayContract { - #[allow(clippy::too_many_arguments)] pub fn create_giveaway( env: Env, - creator: Address, - token: Address, - amount: i128, - title: String, - duration_seconds: u64, - winner_count: u32, - verification: Option, + args: CreateGiveawayArgs, ) -> u64 { - creator.require_auth(); + args.creator.require_auth(); - if winner_count == 0 { + if args.winner_count == 0 { panic_with_error!(&env, Error::InvalidWinnerCount); } // Check if token is whitelisted - let token_key = DataKey::AllowedToken(token.clone()); + let token_key = DataKey::AllowedToken(args.token.clone()); let is_allowed: bool = env.storage().instance().get(&token_key).unwrap_or(false); if !is_allowed { panic_with_error!(&env, Error::TokenNotSupported); } - let token_client = token::Client::new(&env, &token); - token_client.transfer(&creator, env.current_contract_address(), &amount); + let token_client = token::Client::new(&env, &args.token); + token_client.transfer(&args.creator, env.current_contract_address(), &args.amount); let giveaway_id = Self::generate_id(&env); - let end_time = env.ledger().timestamp() + duration_seconds; + let end_time = env.ledger().timestamp() + args.duration_seconds; - let verification_type = match &verification { + let verification_type = match &args.verification { Some(v) if v.uses_reputation => 2, Some(_) => 1, None => 0, }; - let min_reputation = verification.as_ref().map(|v| v.min_reputation).unwrap_or(0); + let min_reputation = args.verification.as_ref().map(|v| v.min_reputation).unwrap_or(0); let giveaway = Giveaway { id: giveaway_id, - creator: creator.clone(), - token: token.clone(), - amount, - title, + creator: args.creator.clone(), + token: args.token.clone(), + amount: args.amount, + title: args.title, participant_count: 0, end_time, status: GiveawayStatus::Active, - winner_count, + winner_count: args.winner_count, winners: Vec::new(&env), verification_type, min_reputation, }; - if let Some(verification) = &verification { + if let Some(verification) = &args.verification { if !verification.uses_reputation { for addr in verification.allowlist.iter() { env.storage().persistent().set( @@ -101,9 +105,9 @@ impl GiveawayContract { GiveawayCreated { giveaway_id, - creator, - token_address: token, - total_amount: amount, + creator: args.creator, + token_address: args.token, + total_amount: args.amount, end_time, } .publish(&env); From 03a717223d56e3eb8e3a8afe4f1f1e2ed94209fc Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 18:08:53 +0100 Subject: [PATCH 5/7] fix: pin Rust toolchain version to 1.75.0 and improve cache configuration in CI --- .github/workflows/rust.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index eab63b9..fb4aa2d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,11 +21,14 @@ jobs: - name: Set up Rust uses: dtolnay/rust-toolchain@stable with: + toolchain: 1.75.0 components: rustfmt, clippy targets: wasm32-unknown-unknown - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: true - name: Check formatting run: cargo fmt --all -- --check From c72d727ce20ba1a3842664098439dba5d6491341 Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 18:12:41 +0100 Subject: [PATCH 6/7] fix: revert to original function signature with single allow attribute for formatting compliance --- contracts/geev-core/src/giveaway.rs | 54 +++++++++++++---------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/contracts/geev-core/src/giveaway.rs b/contracts/geev-core/src/giveaway.rs index e85602a..40d1eea 100644 --- a/contracts/geev-core/src/giveaway.rs +++ b/contracts/geev-core/src/giveaway.rs @@ -8,17 +8,6 @@ use soroban_sdk::{ #[contract] pub struct GiveawayContract; -/// Parameters for creating a new giveaway -pub struct CreateGiveawayArgs { - pub creator: Address, - pub token: Address, - pub amount: i128, - pub title: String, - pub duration_seconds: u64, - pub winner_count: u32, - pub verification: Option, -} - #[contractevent] pub struct GiveawayCreated { giveaway_id: u64, @@ -42,53 +31,60 @@ pub struct GiveawayWinnerSelected { #[contractimpl] impl GiveawayContract { + #[allow(clippy::too_many_arguments)] pub fn create_giveaway( env: Env, - args: CreateGiveawayArgs, + creator: Address, + token: Address, + amount: i128, + title: String, + duration_seconds: u64, + winner_count: u32, + verification: Option, ) -> u64 { - args.creator.require_auth(); + creator.require_auth(); - if args.winner_count == 0 { + if winner_count == 0 { panic_with_error!(&env, Error::InvalidWinnerCount); } // Check if token is whitelisted - let token_key = DataKey::AllowedToken(args.token.clone()); + let token_key = DataKey::AllowedToken(token.clone()); let is_allowed: bool = env.storage().instance().get(&token_key).unwrap_or(false); if !is_allowed { panic_with_error!(&env, Error::TokenNotSupported); } - let token_client = token::Client::new(&env, &args.token); - token_client.transfer(&args.creator, env.current_contract_address(), &args.amount); + let token_client = token::Client::new(&env, &token); + token_client.transfer(&creator, env.current_contract_address(), &amount); let giveaway_id = Self::generate_id(&env); - let end_time = env.ledger().timestamp() + args.duration_seconds; + let end_time = env.ledger().timestamp() + duration_seconds; - let verification_type = match &args.verification { + let verification_type = match &verification { Some(v) if v.uses_reputation => 2, Some(_) => 1, None => 0, }; - let min_reputation = args.verification.as_ref().map(|v| v.min_reputation).unwrap_or(0); + let min_reputation = verification.as_ref().map(|v| v.min_reputation).unwrap_or(0); let giveaway = Giveaway { id: giveaway_id, - creator: args.creator.clone(), - token: args.token.clone(), - amount: args.amount, - title: args.title, + creator: creator.clone(), + token: token.clone(), + amount, + title, participant_count: 0, end_time, status: GiveawayStatus::Active, - winner_count: args.winner_count, + winner_count, winners: Vec::new(&env), verification_type, min_reputation, }; - if let Some(verification) = &args.verification { + if let Some(verification) = &verification { if !verification.uses_reputation { for addr in verification.allowlist.iter() { env.storage().persistent().set( @@ -105,9 +101,9 @@ impl GiveawayContract { GiveawayCreated { giveaway_id, - creator: args.creator, - token_address: args.token, - total_amount: args.amount, + creator, + token_address: token, + total_amount: amount, end_time, } .publish(&env); From 0138ee40d951ac980f9acf2b12c8f62b157183a6 Mon Sep 17 00:00:00 2001 From: Alu-card19 Date: Thu, 25 Jun 2026 18:15:52 +0100 Subject: [PATCH 7/7] fix: upgrade Rust toolchain to 1.82.0 to support edition2024 feature in dependencies --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fb4aa2d..56c9971 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.75.0 + toolchain: 1.82.0 components: rustfmt, clippy targets: wasm32-unknown-unknown