@@ -3,12 +3,12 @@ use soroban_sdk::{contract, contractimpl, Address, Env, Map};
33
44mod portfolio;
55mod reflector;
6- mod types;
76#[ cfg( test) ]
87mod test;
8+ mod types;
99
10- pub use types:: * ;
1110pub use reflector:: * ;
11+ pub use types:: * ;
1212
1313#[ contract]
1414pub struct PortfolioRebalancer ;
@@ -20,7 +20,9 @@ impl PortfolioRebalancer {
2020 return Err ( Error :: AlreadyInitialized ) ;
2121 }
2222 env. storage ( ) . instance ( ) . set ( & DataKey :: Admin , & admin) ;
23- env. storage ( ) . instance ( ) . set ( & DataKey :: ReflectorAddress , & reflector_address) ;
23+ env. storage ( )
24+ . instance ( )
25+ . set ( & DataKey :: ReflectorAddress , & reflector_address) ;
2426 env. storage ( ) . instance ( ) . set ( & DataKey :: Initialized , & true ) ;
2527 Ok ( ( ) )
2628 }
@@ -37,12 +39,18 @@ impl PortfolioRebalancer {
3739 return Err ( Error :: InvalidAllocation ) ;
3840 }
3941
40- if rebalance_threshold < 1 || rebalance_threshold > 50 {
42+ if ! ( 1 ..= 50 ) . contains ( & rebalance_threshold ) {
4143 return Err ( Error :: InvalidThreshold ) ;
4244 }
43-
44- let portfolio_id: u64 = env. storage ( ) . persistent ( ) . get ( & DataKey :: NextPortfolioId ) . unwrap_or ( 1 ) ;
45- env. storage ( ) . persistent ( ) . set ( & DataKey :: NextPortfolioId , & ( portfolio_id + 1 ) ) ;
45+
46+ let portfolio_id: u64 = env
47+ . storage ( )
48+ . persistent ( )
49+ . get ( & DataKey :: NextPortfolioId )
50+ . unwrap_or ( 1 ) ;
51+ env. storage ( )
52+ . persistent ( )
53+ . set ( & DataKey :: NextPortfolioId , & ( portfolio_id + 1 ) ) ;
4654
4755 let portfolio = Portfolio {
4856 user : user. clone ( ) ,
@@ -53,17 +61,18 @@ impl PortfolioRebalancer {
5361 total_value : 0 ,
5462 is_active : true ,
5563 } ;
56-
57- env. storage ( ) . persistent ( ) . set ( & DataKey :: Portfolio ( portfolio_id ) , & portfolio ) ;
58- env . events ( ) . publish (
59- ( "portfolio" , "created" ) ,
60- ( portfolio_id , user )
61- ) ;
64+
65+ env. storage ( )
66+ . persistent ( )
67+ . set ( & DataKey :: Portfolio ( portfolio_id ) , & portfolio ) ;
68+ env . events ( )
69+ . publish ( ( "portfolio" , "created" ) , ( portfolio_id , user ) ) ;
6270 Ok ( portfolio_id)
6371 }
6472
6573 pub fn get_portfolio ( env : Env , portfolio_id : u64 ) -> Portfolio {
66- env. storage ( ) . persistent ( )
74+ env. storage ( )
75+ . persistent ( )
6776 . get ( & DataKey :: Portfolio ( portfolio_id) )
6877 . unwrap ( )
6978 }
@@ -72,99 +81,123 @@ impl PortfolioRebalancer {
7281 if amount <= 0 {
7382 panic ! ( "Amount must be positive" ) ;
7483 }
75-
84+
7685 // Check for emergency stop
7786 if let Some ( true ) = env. storage ( ) . instance ( ) . get ( & DataKey :: EmergencyStop ) {
7887 panic ! ( "Emergency stop active" ) ;
7988 }
8089
81- let mut portfolio: Portfolio = env. storage ( ) . persistent ( )
90+ let mut portfolio: Portfolio = env
91+ . storage ( )
92+ . persistent ( )
8293 . get ( & DataKey :: Portfolio ( portfolio_id) )
8394 . unwrap ( ) ;
84-
95+
8596 portfolio. user . require_auth ( ) ;
86-
97+
8798 // Verify asset is in portfolio (optional based on requirements, but good practice)
8899 if !portfolio. target_allocations . contains_key ( asset. clone ( ) ) {
89- // For now, allow depositing any asset, as users might deposit first then rebalance
90- // or maybe we should restrict? The issue says "valid and invalid inputs".
91- // Let's assume valid input means positive amount and valid asset.
100+ // For now, allow depositing any asset, as users might deposit first then rebalance
101+ // or maybe we should restrict? The issue says "valid and invalid inputs".
102+ // Let's assume valid input means positive amount and valid asset.
92103 }
93104
94105 let current_balance = portfolio. current_balances . get ( asset. clone ( ) ) . unwrap_or ( 0 ) ;
95- portfolio. current_balances . set ( asset. clone ( ) , current_balance + amount) ;
96-
97- env. storage ( ) . persistent ( ) . set ( & DataKey :: Portfolio ( portfolio_id) , & portfolio) ;
98- env. events ( ) . publish (
99- ( "portfolio" , "deposit" ) ,
100- ( portfolio_id, asset, amount)
101- ) ;
106+ portfolio
107+ . current_balances
108+ . set ( asset. clone ( ) , current_balance + amount) ;
109+
110+ env. storage ( )
111+ . persistent ( )
112+ . set ( & DataKey :: Portfolio ( portfolio_id) , & portfolio) ;
113+ env. events ( )
114+ . publish ( ( "portfolio" , "deposit" ) , ( portfolio_id, asset, amount) ) ;
102115 }
103116
104117 pub fn check_rebalance_needed ( env : Env , portfolio_id : u64 ) -> bool {
105- let portfolio: Portfolio = env. storage ( ) . persistent ( )
118+ let portfolio: Portfolio = env
119+ . storage ( )
120+ . persistent ( )
106121 . get ( & DataKey :: Portfolio ( portfolio_id) )
107122 . unwrap ( ) ;
108123
109- let reflector_address: Address = env. storage ( ) . instance ( ) . get ( & DataKey :: ReflectorAddress ) . unwrap ( ) ;
124+ let reflector_address: Address = env
125+ . storage ( )
126+ . instance ( )
127+ . get ( & DataKey :: ReflectorAddress )
128+ . unwrap ( ) ;
110129 let reflector_client = ReflectorClient :: new ( & env, & reflector_address) ;
111130
112131 // Calculate total current value
113- let total_value = portfolio:: calculate_portfolio_value ( & env, & portfolio. current_balances , & reflector_client) ;
114-
132+ let total_value = portfolio:: calculate_portfolio_value (
133+ & env,
134+ & portfolio. current_balances ,
135+ & reflector_client,
136+ ) ;
137+
115138 if total_value == 0 {
116139 return false ;
117140 }
118141
119142 // Check drift for each asset
120143 for ( asset, target_percent) in portfolio. target_allocations . iter ( ) {
121144 let current_balance = portfolio. current_balances . get ( asset. clone ( ) ) . unwrap_or ( 0 ) ;
122-
145+
123146 // Get price from reflector
124147 // Note: In a real app we'd need to handle potential failures/missing prices gracefully
125148 // For this check, if price missing, we can't calculate drift, so maybe skip or fail?
126149 // Let's skip for simplicity in this check
127- if let Some ( price_data) = reflector_client. lastprice ( & crate :: reflector:: Asset :: Stellar ( asset. clone ( ) ) ) {
128- let current_asset_value = ( current_balance * price_data. price ) / 10i128 . pow ( 14 ) ;
129- let current_percent = ( current_asset_value * 100 ) / total_value;
130-
131- let drift = ( current_percent as i128 - target_percent as i128 ) . abs ( ) ;
132- if drift > portfolio. rebalance_threshold as i128 {
133- return true ;
134- }
150+ if let Some ( price_data) =
151+ reflector_client. lastprice ( & crate :: reflector:: Asset :: Stellar ( asset. clone ( ) ) )
152+ {
153+ let current_asset_value = ( current_balance * price_data. price ) / 10i128 . pow ( 14 ) ;
154+ let current_percent = ( current_asset_value * 100 ) / total_value;
155+
156+ let drift = ( current_percent - target_percent as i128 ) . abs ( ) ;
157+ if drift > portfolio. rebalance_threshold as i128 {
158+ return true ;
159+ }
135160 }
136161 }
137-
162+
138163 false
139164 }
140165
141166 pub fn execute_rebalance ( env : Env , portfolio_id : u64 ) {
142167 // Check for emergency stop
143168 if let Some ( true ) = env. storage ( ) . instance ( ) . get ( & DataKey :: EmergencyStop ) {
144- panic ! ( "Emergency stop active" ) ;
169+ panic ! ( "Emergency stop active" ) ;
145170 }
146171
147- let mut portfolio: Portfolio = env. storage ( ) . persistent ( )
172+ let mut portfolio: Portfolio = env
173+ . storage ( )
174+ . persistent ( )
148175 . get ( & DataKey :: Portfolio ( portfolio_id) )
149176 . unwrap ( ) ;
150-
177+
151178 portfolio. user . require_auth ( ) ;
152179
153180 // Check cooldown (e.g., 1 hour = 3600 seconds)
154181 let current_time = env. ledger ( ) . timestamp ( ) ;
155182 if current_time < portfolio. last_rebalance + 3600 {
156183 panic ! ( "Cooldown active" ) ;
157184 }
158-
185+
159186 // Reflector check for stale data
160- let reflector_address: Address = env. storage ( ) . instance ( ) . get ( & DataKey :: ReflectorAddress ) . unwrap ( ) ;
187+ let reflector_address: Address = env
188+ . storage ( )
189+ . instance ( )
190+ . get ( & DataKey :: ReflectorAddress )
191+ . unwrap ( ) ;
161192 let reflector_client = ReflectorClient :: new ( & env, & reflector_address) ;
162-
193+
163194 // Verify prices satisfy freshness requirement (e.g., 1 hour)
164195 for ( asset, _) in portfolio. target_allocations . iter ( ) {
165- if let Some ( price_data) = reflector_client. lastprice ( & crate :: reflector:: Asset :: Stellar ( asset. clone ( ) ) ) {
196+ if let Some ( price_data) =
197+ reflector_client. lastprice ( & crate :: reflector:: Asset :: Stellar ( asset. clone ( ) ) )
198+ {
166199 if price_data. is_stale ( current_time, 3600 ) {
167- panic ! ( "Stale price data" ) ;
200+ panic ! ( "Stale price data" ) ;
168201 }
169202 } else {
170203 // If price is missing, we can't safely rebalance
@@ -175,12 +208,12 @@ impl PortfolioRebalancer {
175208 // Perform rebalance logic (simplified: update last_rebalance and emit event)
176209 // In a real contract, this would execute trades or generate instructions
177210 portfolio. last_rebalance = current_time;
178- env. storage ( ) . persistent ( ) . set ( & DataKey :: Portfolio ( portfolio_id ) , & portfolio ) ;
179-
180- env . events ( ) . publish (
181- ( "portfolio" , "rebalanced" ) ,
182- ( portfolio_id , current_time )
183- ) ;
211+ env. storage ( )
212+ . persistent ( )
213+ . set ( & DataKey :: Portfolio ( portfolio_id ) , & portfolio ) ;
214+
215+ env . events ( )
216+ . publish ( ( "portfolio" , "rebalanced" ) , ( portfolio_id , current_time ) ) ;
184217 }
185218
186219 pub fn set_emergency_stop ( env : Env , stop : bool ) {
0 commit comments