11#![ no_std]
2- use soroban_sdk:: { contract, contracterror, contractimpl, contracttype, token, Address , Env } ;
2+ use soroban_sdk:: {
3+ contract, contractclient, contracterror, contractimpl, contracttype, token, Address , Env ,
4+ String ,
5+ } ;
6+
7+ #[ contracttype]
8+ #[ derive( Clone , Debug , PartialEq ) ]
9+ pub struct QuestInfo {
10+ pub id : u32 ,
11+ pub owner : Address ,
12+ pub name : String ,
13+ pub description : String ,
14+ pub token_addr : Address ,
15+ pub created_at : u64 ,
16+ }
17+
18+ #[ contractclient( name = "QuestClient" ) ]
19+ pub trait QuestContractTrait {
20+ fn get_quest ( env : Env , quest_id : u32 ) -> Result < QuestInfo , soroban_sdk:: Val > ;
21+ }
322
423// Rewards contract: holds token pools per quest and distributes rewards.
524//
625// Flow:
726// 1. Quest owner calls fund_quest() to deposit tokens into the pool
827// 2. When owner verifies a milestone completion, frontend calls distribute_reward()
928// 3. Tokens transfer from the contract's pool to the enrollee
10- //
11- // Auth model: whoever funds a quest becomes its authority.
12- // Only the authority can distribute from that quest's pool.
1329
1430#[ contracttype]
1531#[ derive( Clone ) ]
1632pub enum DataKey {
1733 TokenAddr ,
34+ QuestContractAddr ,
1835 // Who funded / controls a quest's pool
1936 QuestAuthority ( u32 ) ,
2037 // Token balance allocated to a quest
@@ -45,14 +62,22 @@ pub struct RewardsContract;
4562
4663#[ contractimpl]
4764impl RewardsContract {
48- /// Initialize with the token contract address (SAC for the reward token).
49- pub fn initialize ( env : Env , token_addr : Address ) -> Result < ( ) , Error > {
65+ /// Initialize with the token contract address (SAC for the reward token)
66+ /// and the quest contract address for ownership verification.
67+ pub fn initialize (
68+ env : Env ,
69+ token_addr : Address ,
70+ quest_contract_addr : Address ,
71+ ) -> Result < ( ) , Error > {
5072 if env. storage ( ) . instance ( ) . has ( & DataKey :: TokenAddr ) {
5173 return Err ( Error :: AlreadyInitialized ) ;
5274 }
5375 env. storage ( )
5476 . instance ( )
5577 . set ( & DataKey :: TokenAddr , & token_addr) ;
78+ env. storage ( )
79+ . instance ( )
80+ . set ( & DataKey :: QuestContractAddr , & quest_contract_addr) ;
5681 env. storage ( )
5782 . instance ( )
5883 . set ( & DataKey :: TotalDistributed , & 0_i128 ) ;
@@ -69,11 +94,30 @@ impl RewardsContract {
6994 return Err ( Error :: InvalidAmount ) ;
7095 }
7196
97+ // Security Fix: Verify that the funder is the quest owner
98+ let quest_contract_addr = env
99+ . storage ( )
100+ . instance ( )
101+ . get :: < DataKey , Address > ( & DataKey :: QuestContractAddr )
102+ . ok_or ( Error :: NotInitialized ) ?;
103+
104+ // Using QuestClient trait-based client to avoid WASM requirement in CI
105+ let quest_client = QuestClient :: new ( & env, & quest_contract_addr) ;
106+ let quest_info = quest_client. get_quest ( & quest_id) ;
107+
108+ if quest_info. owner != funder {
109+ return Err ( Error :: Unauthorized ) ;
110+ }
111+
72112 let token_addr = Self :: get_token ( & env) ?;
73113
74114 // If quest already has an authority, only they can add more funds
75115 let auth_key = DataKey :: QuestAuthority ( quest_id) ;
76- if let Some ( existing) = env. storage ( ) . persistent ( ) . get :: < _ , Address > ( & auth_key) {
116+ if let Some ( existing) = env
117+ . storage ( )
118+ . persistent ( )
119+ . get :: < DataKey , Address > ( & auth_key)
120+ {
77121 if existing != funder {
78122 return Err ( Error :: Unauthorized ) ;
79123 }
@@ -121,7 +165,7 @@ impl RewardsContract {
121165 let stored: Address = env
122166 . storage ( )
123167 . persistent ( )
124- . get ( & auth_key)
168+ . get :: < DataKey , Address > ( & auth_key)
125169 . ok_or ( Error :: QuestNotFunded ) ?;
126170 if stored != authority {
127171 return Err ( Error :: Unauthorized ) ;
@@ -197,7 +241,7 @@ impl RewardsContract {
197241 pub fn get_token ( env : & Env ) -> Result < Address , Error > {
198242 env. storage ( )
199243 . instance ( )
200- . get ( & DataKey :: TokenAddr )
244+ . get :: < DataKey , Address > ( & DataKey :: TokenAddr )
201245 . ok_or ( Error :: NotInitialized )
202246 }
203247}
0 commit comments