@@ -198,6 +198,10 @@ pub mod dex {
198198 competition_scores : Mapping < ( u64 , AccountId ) , u128 > ,
199199 competition_participants : Mapping < u64 , Vec < AccountId > > ,
200200 competition_claimed : Mapping < ( u64 , AccountId ) , bool > ,
201+ /// Native value actually escrowed for each trading competition's reward
202+ /// pool (competition_id -> amount). Claims may only draw from this
203+ /// balance, never from nothing (Issue #1022).
204+ competition_reward_escrow : Mapping < u64 , u128 > ,
201205 admin_timelock_delay : u64 ,
202206 pending_admin_actions : Mapping < u64 , PendingAdminAction > ,
203207 pending_admin_action_counter : u64 ,
@@ -249,6 +253,7 @@ pub mod dex {
249253 competition_scores : Mapping :: default ( ) ,
250254 competition_participants : Mapping :: default ( ) ,
251255 competition_claimed : Mapping :: default ( ) ,
256+ competition_reward_escrow : Mapping :: default ( ) ,
252257 admin_timelock_delay : 0 ,
253258 pending_admin_actions : Mapping :: default ( ) ,
254259 pending_admin_action_counter : 0 ,
@@ -997,8 +1002,17 @@ pub mod dex {
9971002 Ok ( ( ) )
9981003 }
9991004
1000- /// Create a new trading competition. Admin only. Sets start/end timestamps, reward pool, and participant cap.
1001- #[ ink( message) ]
1005+ /// Creates a new trading competition. Only the admin may call.
1006+ ///
1007+ /// # Reward escrow (Issue #1022)
1008+ ///
1009+ /// The competition reward must be **funded up front**: the caller must
1010+ /// attach native value at least equal to `reward_amount`. The attached
1011+ /// value is escrowed in `competition_reward_escrow` and the governance
1012+ /// token supply is minted once, by exactly the escrowed amount, so
1013+ /// `claim_competition_reward` can never credit more than the value
1014+ /// actually held for the competition.
1015+ #[ ink( message, payable) ]
10021016 pub fn create_trading_competition (
10031017 & mut self ,
10041018 pair_id : Option < u64 > ,
@@ -1016,6 +1030,9 @@ pub mod dex {
10161030 if title. is_empty ( ) || start_block >= end_block || reward_amount == 0 {
10171031 return Err ( Error :: InvalidRequest ) ;
10181032 }
1033+ if self . env ( ) . transferred_value ( ) < reward_amount {
1034+ return Err ( Error :: InvalidRequest ) ;
1035+ }
10191036 self . trade_competition_counter = self . trade_competition_counter . saturating_add ( 1 ) ;
10201037 let competition_id = self . trade_competition_counter ;
10211038 let competition = TradingCompetition {
@@ -1034,6 +1051,15 @@ pub mod dex {
10341051 . insert ( competition_id, & competition) ;
10351052 self . competition_participants
10361053 . insert ( competition_id, & Vec :: < AccountId > :: new ( ) ) ;
1054+
1055+ // Escrow the full announced reward and mint the governance supply
1056+ // once, backed by the attached value. Claims later redistribute
1057+ // this escrow without touching `total_supply` again.
1058+ self . competition_reward_escrow
1059+ . insert ( competition_id, & reward_amount) ;
1060+ self . governance_config . total_supply =
1061+ self . governance_config . total_supply . saturating_add ( reward_amount) ;
1062+
10371063 self . env ( ) . emit_event ( TradingCompetitionCreated {
10381064 competition_id,
10391065 pair_id,
@@ -1057,7 +1083,10 @@ pub mod dex {
10571083 . unwrap_or ( 0 )
10581084 }
10591085
1060- /// Claim reward tokens for a completed competition. Requires the competition to be finished.
1086+ /// Claim reward tokens for a completed competition. Requires the
1087+ /// competition to be finished and its reward pool to have been funded
1088+ /// (escrowed) at creation. Claims are paid pro rata from the escrow;
1089+ /// an unfunded competition yields `RewardUnavailable` (Issue #1022).
10611090 #[ ink( message) ]
10621091 pub fn claim_competition_reward ( & mut self , competition_id : u64 ) -> Result < u128 , Error > {
10631092 let competition = self
@@ -1110,13 +1139,27 @@ pub mod dex {
11101139 return Err ( Error :: InvalidRequest ) ;
11111140 }
11121141
1142+ // The reward pool must actually hold value: claims may only draw
1143+ // from the escrow funded at creation (Issue #1022). The pro-rata
1144+ // shares of all participants never exceed the escrowed amount, so
1145+ // the cap is purely defensive against partially-funded legacy
1146+ // competitions.
1147+ let escrowed = self . competition_reward_escrow . get ( competition_id) . unwrap_or ( 0 ) ;
1148+ if escrowed == 0 {
1149+ return Err ( Error :: RewardUnavailable ) ;
1150+ }
1151+ let reward = reward. min ( escrowed) ;
1152+
1153+ self . competition_reward_escrow
1154+ . insert ( competition_id, & escrowed. saturating_sub ( reward) ) ;
11131155 self . competition_claimed
11141156 . insert ( ( competition_id, caller) , & true ) ;
11151157 let balance = self . governance_balances . get ( caller) . unwrap_or ( 0 ) ;
11161158 self . governance_balances
11171159 . insert ( caller, & balance. saturating_add ( reward) ) ;
1118- self . governance_config . total_supply =
1119- self . governance_config . total_supply . saturating_add ( reward) ;
1160+ // `total_supply` is not increased here: it was minted once when
1161+ // the competition was funded, so supply grows only by the escrowed
1162+ // amount across the whole competition lifecycle.
11201163 self . env ( ) . emit_event ( CompetitionRewardClaimed {
11211164 competition_id,
11221165 trader : caller,
@@ -1551,6 +1594,13 @@ pub mod dex {
15511594 self . governance_balances . get ( trader) . unwrap_or ( 0 )
15521595 }
15531596
1597+ /// Return the amount of value still escrowed for a competition's reward
1598+ /// pool (0 if it was never funded or has been fully claimed).
1599+ #[ ink( message) ]
1600+ pub fn get_competition_reward_escrow ( & self , competition_id : u64 ) -> u128 {
1601+ self . competition_reward_escrow . get ( competition_id) . unwrap_or ( 0 )
1602+ }
1603+
15541604 /// Return the configurable settings for a competition.
15551605 #[ ink( message) ]
15561606 pub fn get_competition_settings ( & self , competition_id : u64 ) -> Option < ( u32 , u128 ) > {
0 commit comments