2121//! - `payment`: Total amount paid by the buyer (for buy events, ≥ key price)
2222
2323use crate :: {
24- constants, read_registered_creator_profile , CreatorKeysContract , CreatorKeysContractArgs ,
25- CreatorKeysContractClient ,
24+ constants, read_creator_supply , read_registered_creator_profile , CreatorKeysContract ,
25+ CreatorKeysContractArgs , CreatorKeysContractClient ,
2626} ;
2727use soroban_sdk:: {
2828 contracterror, contractimpl, contracttype, symbol_short, Address , Env , String , Symbol , Vec ,
2929} ;
3030
31+ /// Event name for protocol trade fee collected on a buy or sell.
32+ pub const FEE_COLLECTED_EVENT_NAME : Symbol = symbol_short ! ( "fee_coll" ) ;
33+
34+ /// Event name for a sell rejected by the anti-flash-trade lockup window.
35+ pub const LOCKUP_BLOCKED_EVENT_NAME : Symbol = symbol_short ! ( "lck_blk" ) ;
36+
37+ /// Event name for quorum threshold update.
38+ pub const QUORUM_UPDATED_EVENT_NAME : Symbol = symbol_short ! ( "qrm_upd" ) ;
39+
40+ /// Event name for proposal/poll closed.
41+ pub const POLL_CLOSED_EVENT_NAME : Symbol = symbol_short ! ( "poll_cls" ) ;
42+
3143/// Event name for protocol pause.
3244pub const PAUSE_EVENT_NAME : Symbol = symbol_short ! ( "pause" ) ;
3345
@@ -742,94 +754,66 @@ pub fn keys_burned_topics(key_id: &Address) -> (Symbol, Address) {
742754 ( KEYS_BURNED_EVENT_NAME , key_id. clone ( ) )
743755}
744756
745- // --- Co-creator removal, auction, and staking reward events ---
746-
747- /// Event name for co-creator removal.
748- pub const CO_CREATOR_REMOVED_EVENT_NAME : Symbol = symbol_short ! ( "co_rem" ) ;
749-
750- /// Event name for auction configuration.
751- pub const AUCTION_CONFIGURED_EVENT_NAME : Symbol = symbol_short ! ( "auc_cfg" ) ;
752-
753- /// Event name for auction cancellation.
754- pub const AUCTION_CANCELLED_EVENT_NAME : Symbol = symbol_short ! ( "auc_can" ) ;
755-
756- /// Event name for a purchase made during a creator's auction phase.
757- pub const AUCTION_PURCHASE_EVENT_NAME : Symbol = symbol_short ! ( "auc_buy" ) ;
758-
759- /// Event name for a staking reward claim.
760- pub const STAKE_REWARD_CLAIMED_EVENT_NAME : Symbol = symbol_short ! ( "stk_clm" ) ;
761-
762757#[ derive( Clone , Debug , Eq , PartialEq ) ]
763758#[ contracttype]
764- pub struct CoCreatorRemovedEvent {
765- pub creator_id : Address ,
766- pub co_creator : Address ,
759+ pub struct FeeCollectedEvent {
760+ /// Treasury address that received the fee.
761+ pub treasury : Address ,
762+ /// Fee amount deducted from the trade.
763+ pub amount : i128 ,
764+ /// Ledger sequence number at the time of the trade.
767765 pub ledger : u32 ,
768766}
769767
770- pub fn co_creator_removed_topics ( creator : & Address ) -> ( Symbol , Address ) {
771- ( CO_CREATOR_REMOVED_EVENT_NAME , creator. clone ( ) )
772- }
773-
774- #[ derive( Clone , Debug , Eq , PartialEq ) ]
775- #[ contracttype]
776- pub struct AuctionConfiguredEvent {
777- pub creator_id : Address ,
778- pub auction_price : i128 ,
779- pub auction_supply : u32 ,
780- }
781-
782- pub fn auction_configured_topics ( creator : & Address ) -> ( Symbol , Address ) {
783- ( AUCTION_CONFIGURED_EVENT_NAME , creator. clone ( ) )
768+ /// Shared fee collected event topics tuple.
769+ pub fn fee_collected_topics ( treasury : & Address ) -> ( Symbol , Address ) {
770+ ( FEE_COLLECTED_EVENT_NAME , treasury. clone ( ) )
784771}
785772
786773#[ derive( Clone , Debug , Eq , PartialEq ) ]
787774#[ contracttype]
788- pub struct AuctionCancelledEvent {
775+ pub struct LockupBlockedEvent {
776+ /// Creator whose keys the seller attempted to sell.
789777 pub creator_id : Address ,
790- pub auction_price : i128 ,
791- pub auction_supply : u32 ,
778+ /// Seller whose sale was rejected.
779+ pub seller : Address ,
780+ /// Ledger timestamp of the seller's most recent buy.
781+ pub last_buy_timestamp : u64 ,
782+ /// Timestamp at which the lockup expires (exclusive).
783+ pub unlock_at : u64 ,
784+ /// Ledger timestamp at rejection.
785+ pub current_timestamp : u64 ,
792786}
793787
794- pub fn auction_cancelled_topics ( creator : & Address ) -> ( Symbol , Address ) {
795- ( AUCTION_CANCELLED_EVENT_NAME , creator. clone ( ) )
788+ /// Shared lockup blocked event topics tuple.
789+ pub fn lockup_blocked_topics ( creator : & Address , seller : & Address ) -> ( Symbol , Address , Address ) {
790+ ( LOCKUP_BLOCKED_EVENT_NAME , creator. clone ( ) , seller. clone ( ) )
796791}
797792
798793#[ derive( Clone , Debug , Eq , PartialEq ) ]
799794#[ contracttype]
800- pub struct AuctionPurchaseEvent {
801- pub buyer : Address ,
802- pub creator_id : Address ,
803- pub quantity : u32 ,
804- pub price_paid : i128 ,
805- pub new_supply : u32 ,
806- pub auction_sold : u32 ,
795+ pub struct QuorumUpdatedEvent {
796+ pub creator : Address ,
797+ pub quorum_bps : u32 ,
807798 pub ledger : u32 ,
808799}
809800
810- pub fn auction_purchase_topics ( creator : & Address , buyer : & Address ) -> ( Symbol , Address , Address ) {
811- ( AUCTION_PURCHASE_EVENT_NAME , creator. clone ( ) , buyer . clone ( ) )
801+ pub fn quorum_updated_topics ( creator : & Address ) -> ( Symbol , Address ) {
802+ ( QUORUM_UPDATED_EVENT_NAME , creator. clone ( ) )
812803}
813804
814805#[ derive( Clone , Debug , Eq , PartialEq ) ]
815806#[ contracttype]
816- pub struct StakeRewardClaimedEvent {
817- pub wallet : Address ,
818- pub key_id : Address ,
819- pub quantity_unlocked : u32 ,
820- pub reward_amount : i128 ,
807+ pub struct PollClosedEvent {
808+ pub creator_id : Address ,
809+ pub poll_id : u32 ,
810+ pub total_weight : u32 ,
811+ pub quorum_reached : bool ,
821812 pub ledger : u32 ,
822813}
823814
824- pub fn stake_reward_claimed_topics (
825- creator : & Address ,
826- wallet : & Address ,
827- ) -> ( Symbol , Address , Address ) {
828- (
829- STAKE_REWARD_CLAIMED_EVENT_NAME ,
830- creator. clone ( ) ,
831- wallet. clone ( ) ,
832- )
815+ pub fn poll_closed_topics ( creator : & Address , poll_id : u32 ) -> ( Symbol , Address , u32 ) {
816+ ( POLL_CLOSED_EVENT_NAME , creator. clone ( ) , poll_id)
833817}
834818
835819#[ contracterror]
@@ -845,6 +829,11 @@ pub enum PollError {
845829 PollExpired = 26 ,
846830 NotAHolder = 27 ,
847831 InvalidOption = 28 ,
832+ QuorumNotReached = 29 ,
833+ QuorumTooHigh = 30 ,
834+ QuorumTooLow = 31 ,
835+ Unauthorized = 32 ,
836+ AlreadyClosed = 33 ,
848837}
849838
850839#[ derive( Clone ) ]
@@ -855,31 +844,33 @@ pub enum PollDataKey {
855844 Vote ( Address , u32 , Address ) ,
856845}
857846
858- #[ derive( Clone ) ]
847+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
859848#[ contracttype]
860849pub struct Poll {
861850 pub question : String ,
862851 pub options : Vec < String > ,
863852 pub vote_counts : Vec < u32 > ,
864853 pub total_weight : u32 ,
865854 pub expires_at : u32 ,
855+ pub closed : bool ,
866856}
867857
868- #[ derive( Clone ) ]
858+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
869859#[ contracttype]
870860pub struct PollVote {
871861 pub option_index : u32 ,
872862 pub weight : u32 ,
873863}
874864
875- #[ derive( Clone ) ]
865+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
876866#[ contracttype]
877867pub struct PollResult {
878868 pub question : String ,
879869 pub options : Vec < String > ,
880870 pub vote_counts : Vec < u32 > ,
881871 pub total_weight : u32 ,
882872 pub expired : bool ,
873+ pub closed : bool ,
883874}
884875
885876pub fn poll_storage_key ( creator_id : & Address , poll_id : u32 ) -> PollDataKey {
@@ -962,6 +953,7 @@ impl CreatorKeysContract {
962953 vote_counts,
963954 total_weight : 0 ,
964955 expires_at,
956+ closed : false ,
965957 } ;
966958
967959 env. storage ( )
@@ -991,6 +983,9 @@ impl CreatorKeysContract {
991983 voter. require_auth ( ) ;
992984 let mut poll = read_poll ( & env, & creator_id, poll_id) ?;
993985
986+ if poll. closed {
987+ return Err ( PollError :: AlreadyClosed ) ;
988+ }
994989 if is_poll_expired ( & env, & poll) {
995990 return Err ( PollError :: PollExpired ) ;
996991 }
@@ -1070,8 +1065,81 @@ impl CreatorKeysContract {
10701065 vote_counts : poll. vote_counts ,
10711066 total_weight : poll. total_weight ,
10721067 expired,
1068+ closed : poll. closed ,
1069+ } )
1070+ }
1071+
1072+ /// Closes a creator poll if the configured quorum threshold has been reached.
1073+ ///
1074+ /// Computes participation as `total_voting_weight / circulating_supply` (in basis points)
1075+ /// against the creator's configured `quorum_bps`. If participation is below the quorum
1076+ /// threshold, returns `Err(PollError::QuorumNotReached)`.
1077+ pub fn close_poll (
1078+ env : Env ,
1079+ creator_id : Address ,
1080+ poll_id : u32 ,
1081+ ) -> Result < PollResult , PollError > {
1082+ let mut poll = read_poll ( & env, & creator_id, poll_id) ?;
1083+
1084+ if poll. closed {
1085+ return Err ( PollError :: AlreadyClosed ) ;
1086+ }
1087+
1088+ let circulating_supply = read_creator_supply ( & env, & creator_id) ;
1089+ let quorum_key = constants:: storage:: quorum_bps ( & creator_id) ;
1090+ let quorum_bps: u32 = env. storage ( ) . persistent ( ) . get ( & quorum_key) . unwrap_or ( 0 ) ;
1091+
1092+ if quorum_bps > 0 {
1093+ if circulating_supply == 0 {
1094+ return Err ( PollError :: QuorumNotReached ) ;
1095+ }
1096+ let total_weight_bps = ( poll. total_weight as u128 )
1097+ . checked_mul ( 10_000 )
1098+ . ok_or ( PollError :: Overflow ) ?;
1099+ let required_bps = ( circulating_supply as u128 )
1100+ . checked_mul ( quorum_bps as u128 )
1101+ . ok_or ( PollError :: Overflow ) ?;
1102+
1103+ if total_weight_bps < required_bps {
1104+ return Err ( PollError :: QuorumNotReached ) ;
1105+ }
1106+ }
1107+
1108+ poll. closed = true ;
1109+ env. storage ( )
1110+ . persistent ( )
1111+ . set ( & poll_storage_key ( & creator_id, poll_id) , & poll) ;
1112+
1113+ env. events ( ) . publish (
1114+ poll_closed_topics ( & creator_id, poll_id) ,
1115+ PollClosedEvent {
1116+ creator_id : creator_id. clone ( ) ,
1117+ poll_id,
1118+ total_weight : poll. total_weight ,
1119+ quorum_reached : true ,
1120+ ledger : env. ledger ( ) . sequence ( ) ,
1121+ } ,
1122+ ) ;
1123+
1124+ let expired = is_poll_expired ( & env, & poll) ;
1125+ Ok ( PollResult {
1126+ question : poll. question ,
1127+ options : poll. options ,
1128+ vote_counts : poll. vote_counts ,
1129+ total_weight : poll. total_weight ,
1130+ expired,
1131+ closed : true ,
10731132 } )
10741133 }
1134+
1135+ /// Alias for `close_poll`.
1136+ pub fn close_proposal (
1137+ env : Env ,
1138+ creator_id : Address ,
1139+ poll_id : u32 ,
1140+ ) -> Result < PollResult , PollError > {
1141+ Self :: close_poll ( env, creator_id, poll_id)
1142+ }
10751143}
10761144
10771145/// Event name for batch buy completion.
0 commit comments