Skip to content

Commit 75d6875

Browse files
authored
Merge pull request #180 from Spaully/fix/spaully-contract-security
Add contract attempt throttling, hint gating, and tighten CORS
2 parents add0900 + 5bead24 commit 75d6875

3 files changed

Lines changed: 39 additions & 4 deletions

File tree

backend/src/main.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ async function bootstrap(): Promise<void> {
6464
allowedHeaders: configService.get<string[]>(
6565
'appConfig.cors.allowedHeaders',
6666
) ?? [
67-
'Origin',
68-
'X-Requested-With',
6967
'Content-Type',
70-
'Accept',
7168
'Authorization',
7269
],
7370
credentials:

onchain/contracts/stellar_hunts/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub struct LevelProgress {
4545
pub is_completed: bool,
4646
pub attempts: u32,
4747
pub nft_minted: bool,
48+
pub last_attempt_ledger: u32,
4849
}
4950

5051
// ---------------------------------------------------------------------
@@ -82,6 +83,8 @@ pub enum Error {
8283
WrongLevel = 7,
8384
QuestionPerLevelLimit = 8,
8485
MissingNftContract = 9,
86+
AttemptTooSoon = 10,
87+
LevelImmutable = 11,
8588
}
8689

8790
// ---------------------------------------------------------------------
@@ -259,7 +262,14 @@ impl StellarHunts {
259262
is_completed: false,
260263
attempts: 0,
261264
nft_minted: false,
265+
last_attempt_ledger: 0,
262266
});
267+
268+
let current_ledger = env.ledger().sequence();
269+
if lp.last_attempt_ledger == current_ledger {
270+
panic_with_error!(&env, Error::AttemptTooSoon);
271+
}
272+
lp.last_attempt_ledger = current_ledger;
263273
lp.attempts += 1;
264274

265275
let hashed: BytesN<32> = env.crypto().sha256(&answer).into();
@@ -332,6 +342,25 @@ impl StellarHunts {
332342
panic_with_error!(&env, Error::WrongLevel);
333343
}
334344

345+
let lp_key = DataKey::PlayerLevelProgress(caller.clone(), q.level.clone());
346+
let lp: LevelProgress = env
347+
.storage()
348+
.persistent()
349+
.get(&lp_key)
350+
.unwrap_or(LevelProgress {
351+
player: caller.clone(),
352+
level: q.level.clone(),
353+
last_question_index: 0,
354+
is_completed: false,
355+
attempts: 0,
356+
nft_minted: false,
357+
last_attempt_ledger: 0,
358+
});
359+
360+
if lp.attempts == 0 {
361+
panic_with_error!(&env, Error::NotInitialized);
362+
}
363+
335364
env.events().publish(
336365
(Symbol::new(&env, "hint_requested"),),
337366
(caller.clone(), question_id, q.level.clone()),
@@ -457,6 +486,7 @@ impl StellarHunts {
457486
is_completed: false,
458487
attempts: 0,
459488
nft_minted: false,
489+
last_attempt_ledger: 0,
460490
})
461491
}
462492

@@ -485,6 +515,7 @@ impl StellarHunts {
485515
is_completed: false,
486516
attempts: 0,
487517
nft_minted: false,
518+
last_attempt_ledger: 0,
488519
};
489520
env.storage().persistent().set(
490521
&DataKey::PlayerLevelProgress(player.clone(), Levels::Easy),

onchain/contracts/stellar_hunts_nft/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ impl StellarHuntsNft {
9090
/// game contract passes its own contract address as `minter`.
9191
pub fn mint_level_badge(env: Env, minter: Address, recipient: Address, level: Levels) {
9292
minter.require_auth();
93+
9394
if !Self::has_minter_role(env.clone(), minter.clone()) {
9495
panic_with_error!(&env, Error::NotAuthorized);
9596
}
@@ -100,9 +101,15 @@ impl StellarHuntsNft {
100101
}
101102
env.storage().persistent().set(&badge_key, &true);
102103

104+
let admin: Address = env
105+
.storage()
106+
.instance()
107+
.get(&NftDataKey::Admin)
108+
.expect("admin not set");
109+
103110
env.events().publish(
104111
(Symbol::new(&env, "level_badge_minted"),),
105-
(recipient, level, minter),
112+
(recipient, level, minter, admin),
106113
);
107114
}
108115

0 commit comments

Comments
 (0)