@@ -109,6 +109,24 @@ impl GovernanceFixture {
109109 . ledger ( )
110110 . with_mut ( |li| li. timestamp = li. timestamp . saturating_add ( seconds) ) ;
111111 }
112+
113+ fn set_delegate ( & self , delegator : Address , delegate : Address ) -> Result < ( ) , GovernanceError > {
114+ self . env . as_contract ( & self . contract_id , || {
115+ GovernanceContract :: set_delegate ( self . env . clone ( ) , delegator, delegate)
116+ } )
117+ }
118+
119+ fn unset_delegate ( & self , delegator : Address ) -> Result < ( ) , GovernanceError > {
120+ self . env . as_contract ( & self . contract_id , || {
121+ GovernanceContract :: unset_delegate ( self . env . clone ( ) , delegator)
122+ } )
123+ }
124+
125+ fn get_delegate ( & self , delegator : Address ) -> Option < Address > {
126+ self . env . as_contract ( & self . contract_id , || {
127+ GovernanceContract :: get_delegate ( self . env . clone ( ) , delegator)
128+ } )
129+ }
112130}
113131
114132#[ test]
@@ -328,3 +346,180 @@ fn governance_set_voting_period_is_applied_to_new_proposals() {
328346
329347 assert_eq ! ( proposal. end_time - proposal. start_time, 250 ) ;
330348}
349+
350+ // ===== DELEGATION TESTS =====
351+
352+ #[ test]
353+ fn delegation_set_and_get_round_trip ( ) {
354+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
355+ let delegator = Address :: generate ( & fixture. env ) ;
356+ let delegate = Address :: generate ( & fixture. env ) ;
357+
358+ assert_eq ! ( fixture. get_delegate( delegator. clone( ) ) , None ) ;
359+
360+ fixture. set_delegate ( delegator. clone ( ) , delegate. clone ( ) ) . unwrap ( ) ;
361+ assert_eq ! ( fixture. get_delegate( delegator. clone( ) ) , Some ( delegate) ) ;
362+ }
363+
364+ #[ test]
365+ fn delegation_unset_removes_entry ( ) {
366+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
367+ let delegator = Address :: generate ( & fixture. env ) ;
368+ let delegate = Address :: generate ( & fixture. env ) ;
369+
370+ fixture. set_delegate ( delegator. clone ( ) , delegate. clone ( ) ) . unwrap ( ) ;
371+ fixture. unset_delegate ( delegator. clone ( ) ) . unwrap ( ) ;
372+
373+ assert_eq ! ( fixture. get_delegate( delegator) , None ) ;
374+ }
375+
376+ #[ test]
377+ fn delegation_rejects_self_delegation ( ) {
378+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
379+ let addr = Address :: generate ( & fixture. env ) ;
380+
381+ let result = fixture. set_delegate ( addr. clone ( ) , addr. clone ( ) ) ;
382+ assert_eq ! ( result, Err ( GovernanceError :: SelfDelegation ) ) ;
383+ }
384+
385+ #[ test]
386+ fn delegation_rejects_cycle ( ) {
387+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
388+ let a = Address :: generate ( & fixture. env ) ;
389+ let b = Address :: generate ( & fixture. env ) ;
390+
391+ // A delegates to B; then B tries to delegate back to A
392+ fixture. set_delegate ( a. clone ( ) , b. clone ( ) ) . unwrap ( ) ;
393+ let result = fixture. set_delegate ( b. clone ( ) , a. clone ( ) ) ;
394+ assert_eq ! ( result, Err ( GovernanceError :: DelegationCycle ) ) ;
395+ }
396+
397+ #[ test]
398+ fn delegation_rejects_double_set_without_unset ( ) {
399+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
400+ let delegator = Address :: generate ( & fixture. env ) ;
401+ let d1 = Address :: generate ( & fixture. env ) ;
402+ let d2 = Address :: generate ( & fixture. env ) ;
403+
404+ fixture. set_delegate ( delegator. clone ( ) , d1. clone ( ) ) . unwrap ( ) ;
405+ let result = fixture. set_delegate ( delegator. clone ( ) , d2. clone ( ) ) ;
406+ assert_eq ! ( result, Err ( GovernanceError :: DelegationAlreadySet ) ) ;
407+ }
408+
409+ #[ test]
410+ fn delegation_allows_reset_after_unset ( ) {
411+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
412+ let delegator = Address :: generate ( & fixture. env ) ;
413+ let d1 = Address :: generate ( & fixture. env ) ;
414+ let d2 = Address :: generate ( & fixture. env ) ;
415+
416+ fixture. set_delegate ( delegator. clone ( ) , d1. clone ( ) ) . unwrap ( ) ;
417+ fixture. unset_delegate ( delegator. clone ( ) ) . unwrap ( ) ;
418+ fixture. set_delegate ( delegator. clone ( ) , d2. clone ( ) ) . unwrap ( ) ;
419+
420+ assert_eq ! ( fixture. get_delegate( delegator) , Some ( d2) ) ;
421+ }
422+
423+ #[ test]
424+ fn delegation_unset_without_active_delegation_errors ( ) {
425+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
426+ let delegator = Address :: generate ( & fixture. env ) ;
427+
428+ let result = fixture. unset_delegate ( delegator) ;
429+ assert_eq ! ( result, Err ( GovernanceError :: NoDelegationSet ) ) ;
430+ }
431+
432+ #[ test]
433+ fn delegation_griefing_guard_rejects_beyond_cap ( ) {
434+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
435+ let delegate = Address :: generate ( & fixture. env ) ;
436+
437+ // Fill delegate up to the cap (MAX_INCOMING_DELEGATIONS = 50)
438+ for _ in 0 ..50 {
439+ let d = Address :: generate ( & fixture. env ) ;
440+ fixture. set_delegate ( d, delegate. clone ( ) ) . unwrap ( ) ;
441+ }
442+
443+ // The 51st should be rejected
444+ let overflow = Address :: generate ( & fixture. env ) ;
445+ let result = fixture. set_delegate ( overflow, delegate. clone ( ) ) ;
446+ assert_eq ! ( result, Err ( GovernanceError :: DelegateLimitReached ) ) ;
447+ }
448+
449+ #[ test]
450+ fn delegation_weight_counted_in_vote ( ) {
451+ // voter_one has two delegators; their single vote should count as 3
452+ let fixture = GovernanceFixture :: new ( 100 , 3 ) ;
453+ let proposal_id = fixture. create_noop_proposal ( "gov_deleg_1" ) ;
454+
455+ let delegator_a = Address :: generate ( & fixture. env ) ;
456+ let delegator_b = Address :: generate ( & fixture. env ) ;
457+
458+ fixture
459+ . set_delegate ( delegator_a. clone ( ) , fixture. voter_one . clone ( ) )
460+ . unwrap ( ) ;
461+ fixture
462+ . set_delegate ( delegator_b. clone ( ) , fixture. voter_one . clone ( ) )
463+ . unwrap ( ) ;
464+
465+ // voter_one votes FOR (weight = 1 own + 2 delegated = 3 → meets quorum of 3)
466+ fixture
467+ . vote ( fixture. voter_one . clone ( ) , proposal_id. clone ( ) , true )
468+ . unwrap ( ) ;
469+
470+ fixture. advance_time ( 100 ) ;
471+ let ( passed, _) = fixture. validate ( proposal_id) . unwrap ( ) ;
472+ assert ! ( passed, "proposal should pass with delegated weight" ) ;
473+ }
474+
475+ #[ test]
476+ fn delegation_against_weight_counted_in_vote ( ) {
477+ // voter_one has 1 delegator; their against vote counts as 2
478+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
479+ let proposal_id = fixture. create_noop_proposal ( "gov_deleg_2" ) ;
480+
481+ let delegator = Address :: generate ( & fixture. env ) ;
482+ fixture
483+ . set_delegate ( delegator. clone ( ) , fixture. voter_one . clone ( ) )
484+ . unwrap ( ) ;
485+
486+ // voter_two: 1 FOR; voter_one: 2 AGAINST → proposal should not pass
487+ fixture
488+ . vote ( fixture. voter_two . clone ( ) , proposal_id. clone ( ) , true )
489+ . unwrap ( ) ;
490+ fixture
491+ . vote ( fixture. voter_one . clone ( ) , proposal_id. clone ( ) , false )
492+ . unwrap ( ) ;
493+
494+ fixture. advance_time ( 100 ) ;
495+ let ( passed, _) = fixture. validate ( proposal_id) . unwrap ( ) ;
496+ assert ! ( !passed, "proposal should fail when against weight exceeds for" ) ;
497+ }
498+
499+ #[ test]
500+ fn unset_delegation_restores_delegate_capacity ( ) {
501+ let fixture = GovernanceFixture :: new ( 100 , 1 ) ;
502+ let delegate = Address :: generate ( & fixture. env ) ;
503+
504+ // Fill to cap
505+ let mut delegators = soroban_sdk:: Vec :: new ( & fixture. env ) ;
506+ for _ in 0 ..50 {
507+ let d = Address :: generate ( & fixture. env ) ;
508+ fixture. set_delegate ( d. clone ( ) , delegate. clone ( ) ) . unwrap ( ) ;
509+ delegators. push_back ( d) ;
510+ }
511+
512+ // Overflow still rejected
513+ let overflow = Address :: generate ( & fixture. env ) ;
514+ assert_eq ! (
515+ fixture. set_delegate( overflow. clone( ) , delegate. clone( ) ) ,
516+ Err ( GovernanceError :: DelegateLimitReached )
517+ ) ;
518+
519+ // Remove one delegation
520+ let first = delegators. get ( 0 ) . unwrap ( ) ;
521+ fixture. unset_delegate ( first) . unwrap ( ) ;
522+
523+ // Now the overflow address can delegate
524+ fixture. set_delegate ( overflow, delegate) . unwrap ( ) ;
525+ }
0 commit comments