@@ -44,6 +44,8 @@ const BASELINE_CREATE_PORTFOLIO_CPU: u64 = 2_500_000;
4444const BASELINE_CREATE_PORTFOLIO_MEM : u64 = 300_000 ;
4545const BASELINE_EXECUTE_REBALANCE_CPU : u64 = 5_000_000 ;
4646const BASELINE_EXECUTE_REBALANCE_MEM : u64 = 500_000 ;
47+ const BASELINE_EXECUTE_REBALANCE_MAX_ASSETS_CPU : u64 = 10_000_000 ; // Will adjust later
48+ const BASELINE_EXECUTE_REBALANCE_MAX_ASSETS_MEM : u64 = 1_000_000 ; // Will adjust later
4749const BASELINE_DEPOSIT_CPU : u64 = 2_000_000 ;
4850const BASELINE_DEPOSIT_MEM : u64 = 250_000 ;
4951
@@ -2089,6 +2091,68 @@ fn test_admin_force_rebalance_bypasses_cooldown() {
20892091 assert_eq ! ( portfolio. last_rebalance, 10010 ) ;
20902092}
20912093
2094+ #[ test]
2095+ #[ should_panic]
2096+ fn test_admin_force_rebalance_non_admin_rejected ( ) {
2097+ let env = Env :: default ( ) ;
2098+ env. mock_all_auths ( ) ;
2099+ let contract_id = env. register_contract ( None , PortfolioRebalancer ) ;
2100+ let client = PortfolioRebalancerClient :: new ( & env, & contract_id) ;
2101+ let reflector_id = env. register_contract ( None , reflector_contract:: MockReflector ) ;
2102+ let admin = Address :: generate ( & env) ;
2103+ let user = Address :: generate ( & env) ;
2104+ client. initialize ( & admin, & reflector_id) ;
2105+
2106+ let mut allocations = Map :: new ( & env) ;
2107+ let asset = Address :: generate ( & env) ;
2108+ allocations. set ( asset, 10000 ) ;
2109+ let pid = create_portfolio_with_defaults ( & env, & client, & user, & allocations, 5 , 50 ) ;
2110+
2111+ let unauthorized = Address :: generate ( & env) ;
2112+ let actual_balances = Map :: new ( & env) ;
2113+ client
2114+ . mock_auths ( & [ MockAuth {
2115+ address : & unauthorized,
2116+ invoke : & MockAuthInvoke {
2117+ contract : & contract_id,
2118+ fn_name : "admin_force_rebalance" ,
2119+ args : ( pid, actual_balances. clone ( ) ) . into_val ( & env) ,
2120+ sub_invokes : & [ ] ,
2121+ } ,
2122+ } ] )
2123+ . admin_force_rebalance ( & pid, & actual_balances) ;
2124+ }
2125+
2126+ #[ test]
2127+ fn test_admin_force_rebalance_admin_success ( ) {
2128+ let env = Env :: default ( ) ;
2129+ env. mock_all_auths ( ) ;
2130+ let contract_id = env. register_contract ( None , PortfolioRebalancer ) ;
2131+ let client = PortfolioRebalancerClient :: new ( & env, & contract_id) ;
2132+ let reflector_id = env. register_contract ( None , reflector_contract:: MockReflector ) ;
2133+ let admin = Address :: generate ( & env) ;
2134+ let user = Address :: generate ( & env) ;
2135+ client. initialize ( & admin, & reflector_id) ;
2136+
2137+ let mut allocations = Map :: new ( & env) ;
2138+ let asset = Address :: generate ( & env) ;
2139+ allocations. set ( asset, 10000 ) ;
2140+ let pid = create_portfolio_with_defaults ( & env, & client, & user, & allocations, 5 , 50 ) ;
2141+
2142+ let actual_balances = Map :: new ( & env) ;
2143+ client
2144+ . mock_auths ( & [ MockAuth {
2145+ address : & admin,
2146+ invoke : & MockAuthInvoke {
2147+ contract : & contract_id,
2148+ fn_name : "admin_force_rebalance" ,
2149+ args : ( pid, actual_balances. clone ( ) ) . into_val ( & env) ,
2150+ sub_invokes : & [ ] ,
2151+ } ,
2152+ } ] )
2153+ . admin_force_rebalance ( & pid, & actual_balances) ;
2154+ }
2155+
20922156#[ test]
20932157fn test_portfolio_invariants_helper_rejects_invalid_allocations ( ) {
20942158 let env = Env :: default ( ) ;
@@ -2262,6 +2326,74 @@ fn benchmark_deposit_gas() {
22622326 ) ;
22632327}
22642328
2329+ #[ test]
2330+ fn benchmark_execute_rebalance_max_assets ( ) {
2331+ let env = Env :: default ( ) ;
2332+ env. mock_all_auths ( ) ;
2333+ env. budget ( ) . reset_unlimited ( ) ;
2334+
2335+ let contract_id = env. register_contract ( None , PortfolioRebalancer ) ;
2336+ let client = PortfolioRebalancerClient :: new ( & env, & contract_id) ;
2337+ let reflector_id = env. register_contract ( None , reflector_contract:: MockReflector ) ;
2338+ let admin = Address :: generate ( & env) ;
2339+ let user = Address :: generate ( & env) ;
2340+ let _ = client. initialize ( & admin, & reflector_id) ;
2341+
2342+ let mut allocations = Map :: new ( & env) ;
2343+ for _ in 0 ..MAX_PORTFOLIO_ASSETS {
2344+ allocations. set ( Address :: generate ( & env) , ALLOCATION_DENOMINATOR / MAX_PORTFOLIO_ASSETS ) ;
2345+ }
2346+ let pid = create_portfolio_with_defaults ( & env, & client, & user, & allocations, 5 , 50 ) ;
2347+
2348+ env. ledger ( ) . with_mut ( |li| {
2349+ li. timestamp = 15_000 ;
2350+ } ) ;
2351+
2352+ env. budget ( ) . reset_tracker ( ) ;
2353+ let _ = client. execute_rebalance ( & pid, & Map :: new ( & env) ) ;
2354+
2355+ std:: println!( "CPU used for max assets: {}" , env. budget( ) . cpu_instruction_cost( ) ) ;
2356+ std:: println!( "MEM used for max assets: {}" , env. budget( ) . memory_bytes_cost( ) ) ;
2357+
2358+ assert_cost_within_tolerance (
2359+ "execute_rebalance_max_assets" ,
2360+ env. budget ( ) . cpu_instruction_cost ( ) ,
2361+ env. budget ( ) . memory_bytes_cost ( ) ,
2362+ BASELINE_EXECUTE_REBALANCE_MAX_ASSETS_CPU ,
2363+ BASELINE_EXECUTE_REBALANCE_MAX_ASSETS_MEM ,
2364+ ) ;
2365+ }
2366+
2367+ #[ test]
2368+ fn test_execute_rebalance_max_assets_plus_one_rejected ( ) {
2369+ let env = Env :: default ( ) ;
2370+ env. mock_all_auths ( ) ;
2371+
2372+ let contract_id = env. register_contract ( None , PortfolioRebalancer ) ;
2373+ let client = PortfolioRebalancerClient :: new ( & env, & contract_id) ;
2374+ let reflector_id = env. register_contract ( None , reflector_contract:: MockReflector ) ;
2375+ let admin = Address :: generate ( & env) ;
2376+ let user = Address :: generate ( & env) ;
2377+ let _ = client. initialize ( & admin, & reflector_id) ;
2378+
2379+ let mut too_many_allocations = Map :: new ( & env) ;
2380+ for _ in 0 ..( MAX_PORTFOLIO_ASSETS + 1 ) {
2381+ too_many_allocations. set ( Address :: generate ( & env) , ALLOCATION_DENOMINATOR / ( MAX_PORTFOLIO_ASSETS + 1 ) ) ;
2382+ }
2383+
2384+ let too_many_decimals = allocation_decimals ( & env, & too_many_allocations, DEFAULT_ASSET_DECIMALS ) ;
2385+
2386+ let result = client. try_create_portfolio (
2387+ & user,
2388+ & too_many_allocations,
2389+ & too_many_decimals,
2390+ & 5 ,
2391+ & 50 ,
2392+ & CURRENT_SLIPPAGE_POLICY_VERSION ,
2393+ ) ;
2394+ assert_eq ! ( result, Err ( Ok ( Error :: TooManyAssets ) ) ) ;
2395+ }
2396+
22652397// ── Issue #861: rebalance validates allocation sum ──────────────────────
22662398
22672399#[ test]
0 commit comments