@@ -60,13 +60,15 @@ impl Governance {
6060 /// * `env` - The Soroban environment
6161 /// * `token` - Address of the governance token (used for voting power)
6262 /// * `admin` - Address with administrative privileges
63- /// * `proposal_threshold` - Minimum token balance to create proposals
64- /// * `quorum` - Minimum total votes required for valid decisions
65- /// * `voting_period` - Duration of voting in seconds
63+ /// * `proposal_threshold` - Minimum token balance to create proposals (must be >= 0)
64+ /// * `quorum` - Minimum total votes required for valid decisions (must be >= 0)
65+ /// * `voting_period` - Duration of voting in seconds (must be > 0)
6666 /// * `execution_delay` - Delay before executing passed proposals in seconds
6767 ///
6868 /// # Panics
6969 /// * If the contract is already initialized
70+ /// * If voting_period is 0
71+ /// * If proposal_threshold or quorum are negative
7072 pub fn initialize (
7173 env : & Env ,
7274 token : Address ,
@@ -76,9 +78,21 @@ impl Governance {
7678 voting_period : u64 ,
7779 execution_delay : u64 ,
7880 ) {
79- if env. storage ( ) . instance ( ) . has ( & CONFIG ) {
80- panic ! ( "Already initialized" ) ;
81- }
81+ assert ! (
82+ !env. storage( ) . instance( ) . has( & CONFIG ) ,
83+ "ERR_ALREADY_INITIALIZED: Contract is already initialized"
84+ ) ;
85+
86+ // Validate configuration parameters
87+ assert ! (
88+ proposal_threshold >= 0 && quorum >= 0 ,
89+ "ERR_INVALID_CONFIG: Governance parameters must not be negative"
90+ ) ;
91+
92+ assert ! (
93+ voting_period != 0 ,
94+ "ERR_INVALID_CONFIG: Voting period must be greater than 0"
95+ ) ;
8296
8397 let config = GovernanceConfig {
8498 token,
@@ -107,7 +121,7 @@ impl Governance {
107121 env. storage ( )
108122 . instance ( )
109123 . get ( & CONFIG )
110- . expect ( "Not initialized" )
124+ . expect ( "ERR_NOT_INITIALIZED: Contract not initialized" )
111125 }
112126
113127 /// Get the admin address.
@@ -140,8 +154,8 @@ impl Governance {
140154 /// # Arguments
141155 /// * `env` - The Soroban environment
142156 /// * `proposer` - Address creating the proposal (must authorize)
143- /// * `title` - Short descriptive title for the proposal
144- /// * `description` - Detailed description of the proposal
157+ /// * `title` - Short descriptive title for the proposal (must not be empty)
158+ /// * `description` - Detailed description of the proposal (must not be empty)
145159 /// * `proposal_type` - Category of the proposal
146160 /// * `execution_data` - Optional data for proposal execution
147161 ///
@@ -152,7 +166,8 @@ impl Governance {
152166 /// Requires authorization from `proposer`.
153167 ///
154168 /// # Panics
155- /// * If proposer's token balance is below `proposal_threshold`
169+ /// * If proposer has insufficient token balance
170+ /// * If title or description is empty
156171 ///
157172 /// # Events
158173 /// Emits a `proposal_created` event.
@@ -166,14 +181,26 @@ impl Governance {
166181 ) -> u64 {
167182 proposer. require_auth ( ) ;
168183
184+ // Validate input parameters
185+ assert ! (
186+ !title. is_empty( ) ,
187+ "ERR_EMPTY_TITLE: Proposal title cannot be empty"
188+ ) ;
189+
190+ assert ! (
191+ !description. is_empty( ) ,
192+ "ERR_EMPTY_DESCRIPTION: Proposal description cannot be empty"
193+ ) ;
194+
169195 let config = Self :: get_config ( env) ;
170196
171197 // Check proposer has enough tokens
172198 let token_client = token:: Client :: new ( env, & config. token ) ;
173199 let balance = token_client. balance ( & proposer) ;
174- if balance < config. proposal_threshold {
175- panic ! ( "Insufficient token balance to create proposal" ) ;
176- }
200+ assert ! (
201+ balance >= config. proposal_threshold,
202+ "ERR_INSUFFICIENT_BALANCE: Proposer balance below threshold"
203+ ) ;
177204
178205 // Generate proposal ID
179206 let mut proposal_count: u64 = env. storage ( ) . instance ( ) . get ( & PROPOSAL_COUNT ) . unwrap_or ( 0 ) ;
@@ -254,34 +281,38 @@ impl Governance {
254281 . storage ( )
255282 . persistent ( )
256283 . get ( & ( PROPOSALS , proposal_id) )
257- . expect ( "Proposal not found " ) ;
284+ . expect ( "ERR_PROPOSAL_NOT_FOUND: Proposal does not exist " ) ;
258285
259286 // Check proposal is active
260- if proposal. status != ProposalStatus :: Active {
261- panic ! ( "Proposal is not active" ) ;
262- }
287+ assert ! (
288+ proposal. status == ProposalStatus :: Active ,
289+ "ERR_INVALID_STATUS: Proposal is not in active status"
290+ ) ;
263291
264292 // Check voting period
265293 let now = env. ledger ( ) . timestamp ( ) ;
266- if now < proposal. voting_start || now > proposal. voting_end {
267- panic ! ( "Voting period not active" ) ;
268- }
294+ assert ! (
295+ now >= proposal. voting_start && now <= proposal. voting_end,
296+ "ERR_VOTING_PERIOD_INACTIVE: Voting period is not active"
297+ ) ;
269298
270299 // Check if already voted
271300 let vote_key = VoteKey {
272301 proposal_id,
273302 voter : voter. clone ( ) ,
274303 } ;
275- if env. storage ( ) . persistent ( ) . has ( & ( VOTES , vote_key. clone ( ) ) ) {
276- panic ! ( "Already voted on this proposal" ) ;
277- }
304+ assert ! (
305+ !env. storage( ) . persistent( ) . has( & ( VOTES , vote_key. clone( ) ) ) ,
306+ "ERR_ALREADY_VOTED: Address has already voted on this proposal"
307+ ) ;
278308
279309 // Get voting power (token balance)
280310 let token_client = token:: Client :: new ( env, & config. token ) ;
281311 let power = token_client. balance ( & voter) ;
282- if power <= 0 {
283- panic ! ( "No voting power" ) ;
284- }
312+ assert ! (
313+ power > 0 ,
314+ "ERR_NO_VOTING_POWER: Address has no voting power"
315+ ) ;
285316
286317 // Record vote
287318 let vote = Vote {
@@ -333,18 +364,20 @@ impl Governance {
333364 . storage ( )
334365 . persistent ( )
335366 . get ( & ( PROPOSALS , proposal_id) )
336- . expect ( "Proposal not found " ) ;
367+ . expect ( "ERR_PROPOSAL_NOT_FOUND: Proposal does not exist " ) ;
337368
338369 // Check proposal is still active
339- if proposal. status != ProposalStatus :: Active {
340- panic ! ( "Proposal is not active" ) ;
341- }
370+ assert ! (
371+ proposal. status == ProposalStatus :: Active ,
372+ "ERR_INVALID_STATUS: Proposal is not in active status"
373+ ) ;
342374
343375 // Check voting period has ended
344376 let now = env. ledger ( ) . timestamp ( ) ;
345- if now <= proposal. voting_end {
346- panic ! ( "Voting period not ended" ) ;
347- }
377+ assert ! (
378+ now > proposal. voting_end,
379+ "ERR_VOTING_PERIOD_ACTIVE: Voting period has not ended yet"
380+ ) ;
348381
349382 let old_status = proposal. status . clone ( ) ;
350383
@@ -394,18 +427,20 @@ impl Governance {
394427 . storage ( )
395428 . persistent ( )
396429 . get ( & ( PROPOSALS , proposal_id) )
397- . expect ( "Proposal not found " ) ;
430+ . expect ( "ERR_PROPOSAL_NOT_FOUND: Proposal does not exist " ) ;
398431
399432 // Check proposal has passed
400- if proposal. status != ProposalStatus :: Passed {
401- panic ! ( "Proposal has not passed" ) ;
402- }
433+ assert ! (
434+ proposal. status == ProposalStatus :: Passed ,
435+ "ERR_INVALID_STATUS: Proposal has not passed"
436+ ) ;
403437
404438 // Check execution delay has passed
405439 let now = env. ledger ( ) . timestamp ( ) ;
406- if now < proposal. voting_end + config. execution_delay {
407- panic ! ( "Execution delay not met" ) ;
408- }
440+ assert ! (
441+ now >= proposal. voting_end + config. execution_delay,
442+ "ERR_EXECUTION_DELAY_NOT_MET: Execution delay period has not passed"
443+ ) ;
409444
410445 let old_status = proposal. status . clone ( ) ;
411446 proposal. status = ProposalStatus :: Executed ;
@@ -448,26 +483,29 @@ impl Governance {
448483 . storage ( )
449484 . persistent ( )
450485 . get ( & ( PROPOSALS , proposal_id) )
451- . expect ( "Proposal not found " ) ;
486+ . expect ( "ERR_PROPOSAL_NOT_FOUND: Proposal does not exist " ) ;
452487
453488 // Check if cancellable
454489 let is_admin = caller == config. admin ;
455490 let is_proposer = caller == proposal. proposer ;
456491 let now = env. ledger ( ) . timestamp ( ) ;
457492 let voting_ended = now > proposal. voting_end ;
458493
459- if !is_admin && !is_proposer {
460- panic ! ( "Only proposer or admin can cancel" ) ;
461- }
494+ assert ! (
495+ is_admin || is_proposer,
496+ "ERR_UNAUTHORIZED: Only proposer or admin can cancel"
497+ ) ;
462498
463- if !is_admin && voting_ended {
464- panic ! ( "Proposer can only cancel during voting period" ) ;
465- }
499+ assert ! (
500+ is_admin || !voting_ended,
501+ "ERR_VOTING_ENDED: Proposer can only cancel during voting period"
502+ ) ;
466503
467504 // Cannot cancel executed proposals
468- if proposal. status == ProposalStatus :: Executed {
469- panic ! ( "Cannot cancel executed proposal" ) ;
470- }
505+ assert ! (
506+ proposal. status != ProposalStatus :: Executed ,
507+ "ERR_INVALID_STATUS: Cannot cancel executed proposal"
508+ ) ;
471509
472510 let old_status = proposal. status . clone ( ) ;
473511 proposal. status = ProposalStatus :: Cancelled ;
@@ -487,14 +525,17 @@ impl Governance {
487525 ///
488526 /// # Arguments
489527 /// * `env` - The Soroban environment
490- /// * `new_proposal_threshold` - New minimum tokens for proposals (optional)
491- /// * `new_quorum` - New quorum requirement (optional)
492- /// * `new_voting_period` - New voting duration in seconds (optional)
528+ /// * `new_proposal_threshold` - New minimum tokens for proposals (optional, must be >= 0 )
529+ /// * `new_quorum` - New quorum requirement (optional, must be >= 0 )
530+ /// * `new_voting_period` - New voting duration in seconds (optional, must be > 0 )
493531 /// * `new_execution_delay` - New execution delay in seconds (optional)
494532 ///
495533 /// # Authorization
496534 /// Requires authorization from the admin address.
497535 ///
536+ /// # Panics
537+ /// * If invalid configuration parameters are provided
538+ ///
498539 /// # Events
499540 /// Emits a `config_updated` event.
500541 pub fn update_config (
@@ -507,15 +548,31 @@ impl Governance {
507548 let mut config = Self :: get_config ( env) ;
508549 config. admin . require_auth ( ) ;
509550
551+ // Validate parameters if provided
510552 if let Some ( threshold) = new_proposal_threshold {
553+ assert ! (
554+ threshold >= 0 ,
555+ "ERR_INVALID_CONFIG: Proposal threshold must not be negative"
556+ ) ;
511557 config. proposal_threshold = threshold;
512558 }
559+
513560 if let Some ( quorum) = new_quorum {
561+ assert ! (
562+ quorum >= 0 ,
563+ "ERR_INVALID_CONFIG: Quorum must not be negative"
564+ ) ;
514565 config. quorum = quorum;
515566 }
567+
516568 if let Some ( period) = new_voting_period {
569+ assert ! (
570+ period != 0 ,
571+ "ERR_INVALID_CONFIG: Voting period must be greater than 0"
572+ ) ;
517573 config. voting_period = period;
518574 }
575+
519576 if let Some ( delay) = new_execution_delay {
520577 config. execution_delay = delay;
521578 }
0 commit comments