@@ -13,9 +13,23 @@ pub enum DataKey {
1313 TotalShares ,
1414 TotalAssets ,
1515 Admin ,
16+ DaoThreshold ,
17+ ProposalNonce ,
18+ BenjiStrategy ,
19+ Proposal ( u32 ) ,
20+ Vote ( u32 , Address ) ,
1621 ShareBalance ( Address ) ,
1722}
1823
24+ #[ contracttype]
25+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
26+ pub struct StrategyProposal {
27+ pub strategy : Address ,
28+ pub yes_votes : i128 ,
29+ pub no_votes : i128 ,
30+ pub executed : bool ,
31+ }
32+
1933#[ contract]
2034pub struct YieldVault ;
2135
@@ -31,6 +45,8 @@ impl YieldVault {
3145 env. storage ( ) . instance ( ) . set ( & DataKey :: Token , & token) ;
3246 env. storage ( ) . instance ( ) . set ( & DataKey :: TotalShares , & 0i128 ) ;
3347 env. storage ( ) . instance ( ) . set ( & DataKey :: TotalAssets , & 0i128 ) ;
48+ env. storage ( ) . instance ( ) . set ( & DataKey :: DaoThreshold , & 1i128 ) ;
49+ env. storage ( ) . instance ( ) . set ( & DataKey :: ProposalNonce , & 0u32 ) ;
3450 }
3551
3652 /// Read the underlying token address.
@@ -53,6 +69,83 @@ impl YieldVault {
5369 env. storage ( ) . instance ( ) . get ( & DataKey :: ShareBalance ( user) ) . unwrap_or ( 0 )
5470 }
5571
72+ /// Read configured BENJI strategy contract address.
73+ pub fn benji_strategy ( env : Env ) -> Address {
74+ env. storage ( ) . instance ( ) . get ( & DataKey :: BenjiStrategy ) . unwrap ( )
75+ }
76+
77+ /// Configure DAO quorum threshold. Only admin can update this parameter.
78+ pub fn set_dao_threshold ( env : Env , threshold : i128 ) {
79+ let admin: Address = env. storage ( ) . instance ( ) . get ( & DataKey :: Admin ) . unwrap ( ) ;
80+ admin. require_auth ( ) ;
81+ if threshold <= 0 {
82+ panic ! ( "threshold must be > 0" ) ;
83+ }
84+ env. storage ( ) . instance ( ) . set ( & DataKey :: DaoThreshold , & threshold) ;
85+ }
86+
87+ /// Create a proposal to update the BENJI strategy connector address.
88+ pub fn create_strategy_proposal ( env : Env , proposer : Address , strategy : Address ) -> u32 {
89+ proposer. require_auth ( ) ;
90+ let mut next_nonce: u32 = env. storage ( ) . instance ( ) . get ( & DataKey :: ProposalNonce ) . unwrap_or ( 0 ) ;
91+ next_nonce += 1 ;
92+ env. storage ( ) . instance ( ) . set ( & DataKey :: ProposalNonce , & next_nonce) ;
93+
94+ let proposal = StrategyProposal {
95+ strategy,
96+ yes_votes : 0 ,
97+ no_votes : 0 ,
98+ executed : false ,
99+ } ;
100+ env. storage ( ) . instance ( ) . set ( & DataKey :: Proposal ( next_nonce) , & proposal) ;
101+ next_nonce
102+ }
103+
104+ /// Vote on a proposal with a given voting weight.
105+ pub fn vote_on_proposal ( env : Env , voter : Address , proposal_id : u32 , support : bool , weight : i128 ) {
106+ voter. require_auth ( ) ;
107+ if weight <= 0 {
108+ panic ! ( "weight must be > 0" ) ;
109+ }
110+ if env. storage ( ) . instance ( ) . has ( & DataKey :: Vote ( proposal_id, voter. clone ( ) ) ) {
111+ panic ! ( "duplicate vote" ) ;
112+ }
113+
114+ let mut proposal: StrategyProposal = env. storage ( ) . instance ( ) . get ( & DataKey :: Proposal ( proposal_id) ) . unwrap ( ) ;
115+ if proposal. executed {
116+ panic ! ( "proposal already executed" ) ;
117+ }
118+
119+ if support {
120+ proposal. yes_votes += weight;
121+ } else {
122+ proposal. no_votes += weight;
123+ }
124+
125+ env. storage ( ) . instance ( ) . set ( & DataKey :: Proposal ( proposal_id) , & proposal) ;
126+ env. storage ( ) . instance ( ) . set ( & DataKey :: Vote ( proposal_id, voter) , & true ) ;
127+ }
128+
129+ /// Execute a strategy proposal once it reaches threshold and has majority support.
130+ pub fn execute_strategy_proposal ( env : Env , proposal_id : u32 ) {
131+ let mut proposal: StrategyProposal = env. storage ( ) . instance ( ) . get ( & DataKey :: Proposal ( proposal_id) ) . unwrap ( ) ;
132+ if proposal. executed {
133+ panic ! ( "proposal already executed" ) ;
134+ }
135+
136+ let threshold: i128 = env. storage ( ) . instance ( ) . get ( & DataKey :: DaoThreshold ) . unwrap_or ( 1 ) ;
137+ if proposal. yes_votes < threshold {
138+ panic ! ( "quorum not reached" ) ;
139+ }
140+ if proposal. yes_votes <= proposal. no_votes {
141+ panic ! ( "proposal rejected" ) ;
142+ }
143+
144+ env. storage ( ) . instance ( ) . set ( & DataKey :: BenjiStrategy , & proposal. strategy ) ;
145+ proposal. executed = true ;
146+ env. storage ( ) . instance ( ) . set ( & DataKey :: Proposal ( proposal_id) , & proposal) ;
147+ }
148+
56149 /// Calculates the number of shares given an asset amount based on the current exchange rate.
57150 pub fn calculate_shares ( env : Env , assets : i128 ) -> i128 {
58151 let ts = Self :: total_shares ( env. clone ( ) ) ;
@@ -145,4 +238,23 @@ impl YieldVault {
145238 let ta = Self :: total_assets ( env. clone ( ) ) ;
146239 env. storage ( ) . instance ( ) . set ( & DataKey :: TotalAssets , & ( ta + amount) ) ;
147240 }
241+
242+ /// BENJI strategy connector callback for reporting harvested yield into the vault.
243+ pub fn report_benji_yield ( env : Env , strategy : Address , amount : i128 ) {
244+ strategy. require_auth ( ) ;
245+ if amount <= 0 {
246+ panic ! ( "yield amount must be > 0" ) ;
247+ }
248+ let configured: Address = env. storage ( ) . instance ( ) . get ( & DataKey :: BenjiStrategy ) . unwrap ( ) ;
249+ if strategy != configured {
250+ panic ! ( "unauthorized strategy" ) ;
251+ }
252+
253+ let token_addr = Self :: token ( env. clone ( ) ) ;
254+ let token_client = token:: Client :: new ( & env, & token_addr) ;
255+ token_client. transfer ( & strategy, & env. current_contract_address ( ) , & amount) ;
256+
257+ let ta = Self :: total_assets ( env. clone ( ) ) ;
258+ env. storage ( ) . instance ( ) . set ( & DataKey :: TotalAssets , & ( ta + amount) ) ;
259+ }
148260}
0 commit comments