@@ -30,7 +30,7 @@ use asset_hub_westend_runtime::{
3030 } ,
3131 AllPalletsWithoutSystem , Assets , Balances , Block , ExistentialDeposit , ForeignAssets ,
3232 ForeignAssetsInstance , MetadataDepositBase , MetadataDepositPerByte , ParachainSystem ,
33- PolkadotXcm , Revive , Runtime , RuntimeCall , RuntimeEvent , RuntimeOrigin , SessionKeys ,
33+ PolkadotXcm , Proxy , Revive , Runtime , RuntimeCall , RuntimeEvent , RuntimeOrigin , SessionKeys ,
3434 ToRococoXcmRouterInstance , TrustBackedAssetsInstance , Uniques , WeightToFee , XcmpQueue ,
3535} ;
3636pub use asset_hub_westend_runtime:: { AssetConversion , AssetDeposit , CollatorSelection , System } ;
@@ -2090,3 +2090,173 @@ fn session_keys_are_compatible_between_ah_and_rc() {
20902090 "Session key type IDs must match between AssetHub and Westend"
20912091 ) ;
20922092}
2093+
2094+ #[ test]
2095+ fn staking_proxy_can_manage_staking_operator ( ) {
2096+ use asset_hub_westend_runtime:: ProxyType ;
2097+ use frame_support:: traits:: InstanceFilter ;
2098+
2099+ // GIVEN: Staking proxy type
2100+ let staking_proxy = ProxyType :: Staking ;
2101+
2102+ // WHEN: checking if Staking can add/remove StakingOperator proxies
2103+ let add_call = RuntimeCall :: Proxy ( pallet_proxy:: Call :: add_proxy {
2104+ delegate : AccountId :: from ( BOB ) . into ( ) ,
2105+ proxy_type : ProxyType :: StakingOperator ,
2106+ delay : 0 ,
2107+ } ) ;
2108+ let remove_call = RuntimeCall :: Proxy ( pallet_proxy:: Call :: remove_proxy {
2109+ delegate : AccountId :: from ( BOB ) . into ( ) ,
2110+ proxy_type : ProxyType :: StakingOperator ,
2111+ delay : 0 ,
2112+ } ) ;
2113+
2114+ // THEN: Staking proxy can manage StakingOperator proxies and is its superset
2115+ assert ! ( staking_proxy. filter( & add_call) ) ;
2116+ assert ! ( staking_proxy. filter( & remove_call) ) ;
2117+ assert ! ( staking_proxy. is_superset( & ProxyType :: StakingOperator ) ) ;
2118+ }
2119+
2120+ /// Verifies StakingOperator filter allows validator operations and session key management,
2121+ /// but forbids fund management.
2122+ #[ test]
2123+ fn staking_operator_filter_allows_validator_ops_and_session_keys ( ) {
2124+ use asset_hub_westend_runtime:: ProxyType ;
2125+ use frame_support:: traits:: InstanceFilter ;
2126+ use pallet_staking_async:: { Call as StakingCall , RewardDestination , ValidatorPrefs } ;
2127+ use pallet_staking_async_rc_client:: Call as RcClientCall ;
2128+
2129+ let operator = ProxyType :: StakingOperator ;
2130+
2131+ // StakingOperator can perform validator operations
2132+ assert ! ( operator
2133+ . filter( & RuntimeCall :: Staking ( StakingCall :: validate { prefs: ValidatorPrefs :: default ( ) } ) ) ) ;
2134+ assert ! ( operator. filter( & RuntimeCall :: Staking ( StakingCall :: chill { } ) ) ) ;
2135+ assert ! ( operator. filter( & RuntimeCall :: Staking ( StakingCall :: kick { who: vec![ ] } ) ) ) ;
2136+
2137+ // StakingOperator can manage session keys
2138+ assert ! ( operator. filter( & RuntimeCall :: StakingRcClient ( RcClientCall :: set_keys {
2139+ keys: Default :: default ( ) ,
2140+ proof: Default :: default ( ) ,
2141+ max_delivery_and_remote_execution_fee: None ,
2142+ } ) ) ) ;
2143+ assert ! ( operator. filter( & RuntimeCall :: StakingRcClient ( RcClientCall :: purge_keys {
2144+ max_delivery_and_remote_execution_fee: None ,
2145+ } ) ) ) ;
2146+
2147+ // StakingOperator can batch operations
2148+ assert ! ( operator. filter( & RuntimeCall :: Utility ( pallet_utility:: Call :: batch { calls: vec![ ] } ) ) ) ;
2149+
2150+ // StakingOperator cannot manage funds or nominations
2151+ assert ! ( !operator. filter( & RuntimeCall :: Staking ( StakingCall :: bond {
2152+ value: 100 ,
2153+ payee: RewardDestination :: Staked
2154+ } ) ) ) ;
2155+ assert ! ( !operator. filter( & RuntimeCall :: Staking ( StakingCall :: unbond { value: 100 } ) ) ) ;
2156+ assert ! ( !operator. filter( & RuntimeCall :: Staking ( StakingCall :: nominate { targets: vec![ ] } ) ) ) ;
2157+ assert ! ( !operator. filter( & RuntimeCall :: Staking ( StakingCall :: set_payee {
2158+ payee: RewardDestination :: Staked
2159+ } ) ) ) ;
2160+ }
2161+
2162+ /// Test that a pure proxy stash can delegate to a StakingOperator
2163+ /// who can then call validate, chill, and manage session keys.
2164+ #[ test]
2165+ fn pure_proxy_stash_can_delegate_to_staking_operator ( ) {
2166+ use asset_hub_westend_runtime:: ProxyType ;
2167+
2168+ let controller: AccountId = ALICE . into ( ) ;
2169+ let operator: AccountId = BOB . into ( ) ;
2170+
2171+ ExtBuilder :: < Runtime > :: default ( )
2172+ . with_collators ( vec ! [ AccountId :: from( ALICE ) ] )
2173+ . with_session_keys ( vec ! [ (
2174+ AccountId :: from( ALICE ) ,
2175+ AccountId :: from( ALICE ) ,
2176+ SessionKeys { aura: AuraId :: from( sp_core:: sr25519:: Public :: from_raw( ALICE ) ) } ,
2177+ ) ] )
2178+ . build ( )
2179+ . execute_with ( || {
2180+ // GIVEN: fund controller and operator
2181+ assert_ok ! ( Balances :: mint_into( & controller, 100 * UNITS ) ) ;
2182+ assert_ok ! ( Balances :: mint_into( & operator, 100 * UNITS ) ) ;
2183+
2184+ // WHEN: controller creates a pure proxy stash with Staking proxy type
2185+ assert_ok ! ( Proxy :: create_pure(
2186+ RuntimeOrigin :: signed( controller. clone( ) ) ,
2187+ ProxyType :: Staking ,
2188+ 0 ,
2189+ 0
2190+ ) ) ;
2191+ let pure_stash = Proxy :: pure_account ( & controller, & ProxyType :: Staking , 0 , None ) ;
2192+
2193+ // Fund the pure proxy stash
2194+ assert_ok ! ( Balances :: mint_into( & pure_stash, 100 * UNITS ) ) ;
2195+
2196+ // WHEN: controller (via Staking proxy) adds StakingOperator proxy for the operator
2197+ let add_operator_call = RuntimeCall :: Proxy ( pallet_proxy:: Call :: add_proxy {
2198+ delegate : operator. clone ( ) . into ( ) ,
2199+ proxy_type : ProxyType :: StakingOperator ,
2200+ delay : 0 ,
2201+ } ) ;
2202+ assert_ok ! ( Proxy :: proxy(
2203+ RuntimeOrigin :: signed( controller. clone( ) ) ,
2204+ pure_stash. clone( ) . into( ) ,
2205+ None ,
2206+ Box :: new( add_operator_call) ,
2207+ ) ) ;
2208+
2209+ // THEN: operator can call chill on behalf of pure proxy stash
2210+ let chill_call = RuntimeCall :: Staking ( pallet_staking_async:: Call :: chill { } ) ;
2211+ assert_ok ! ( Proxy :: proxy(
2212+ RuntimeOrigin :: signed( operator. clone( ) ) ,
2213+ pure_stash. clone( ) . into( ) ,
2214+ None ,
2215+ Box :: new( chill_call) ,
2216+ ) ) ;
2217+
2218+ // THEN: operator can call validate on behalf of pure proxy stash
2219+ let validate_call = RuntimeCall :: Staking ( pallet_staking_async:: Call :: validate {
2220+ prefs : Default :: default ( ) ,
2221+ } ) ;
2222+ assert_ok ! ( Proxy :: proxy(
2223+ RuntimeOrigin :: signed( operator. clone( ) ) ,
2224+ pure_stash. clone( ) . into( ) ,
2225+ None ,
2226+ Box :: new( validate_call) ,
2227+ ) ) ;
2228+
2229+ // THEN: operator can call purge_keys (session key management on AssetHub)
2230+ let purge_keys_call =
2231+ RuntimeCall :: StakingRcClient ( pallet_staking_async_rc_client:: Call :: purge_keys {
2232+ max_delivery_and_remote_execution_fee : None ,
2233+ } ) ;
2234+ assert_ok ! ( Proxy :: proxy(
2235+ RuntimeOrigin :: signed( operator. clone( ) ) ,
2236+ pure_stash. clone( ) . into( ) ,
2237+ None ,
2238+ Box :: new( purge_keys_call) ,
2239+ ) ) ;
2240+
2241+ // THEN: operator CANNOT call bond (fund management is forbidden)
2242+ // Note: Proxy::proxy returns Ok(()) even when the proxied call fails due to filter.
2243+ // The actual result is emitted as a ProxyExecuted event.
2244+ let bond_call = RuntimeCall :: Staking ( pallet_staking_async:: Call :: bond {
2245+ value : 10 * UNITS ,
2246+ payee : pallet_staking_async:: RewardDestination :: Staked ,
2247+ } ) ;
2248+ assert_ok ! ( Proxy :: proxy(
2249+ RuntimeOrigin :: signed( operator. clone( ) ) ,
2250+ pure_stash. clone( ) . into( ) ,
2251+ None ,
2252+ Box :: new( bond_call) ,
2253+ ) ) ;
2254+ // Check that the proxied call failed due to filter (CallFiltered error)
2255+ System :: assert_last_event (
2256+ pallet_proxy:: Event :: ProxyExecuted {
2257+ result : Err ( frame_system:: Error :: < Runtime > :: CallFiltered . into ( ) ) ,
2258+ }
2259+ . into ( ) ,
2260+ ) ;
2261+ } ) ;
2262+ }
0 commit comments