@@ -987,6 +987,10 @@ pub enum DataKey {
987987 /// Whether a governance unpause proposal is currently pending
988988 UnpauseProposed ,
989989 Task ( u64 ) ,
990+ /// Per-task delegated permission bitmask for a non-creator address
991+ /// (Issue #778). Same bit layout as `TaskConfig.permissions`
992+ /// (`PERM_CAN_PAUSE` etc.) — absence means no delegated access.
993+ TaskDelegate ( u64 , Address ) ,
990994 Counter ,
991995 ActiveTasks ,
992996 Token ,
@@ -2390,6 +2394,113 @@ impl SoroTaskContract {
23902394 exit_security_guard ( & env) ;
23912395 }
23922396
2397+ /// Grant (or update) a delegate's permission bitmask for a task
2398+ /// (Issue #778). Creator-only. Pass `permissions = 0` to revoke —
2399+ /// equivalent to `revoke_task_delegate`, kept as a separate,
2400+ /// more-discoverable entrypoint below.
2401+ pub fn set_task_delegate ( env : Env , task_id : u64 , delegate : Address , permissions : u32 ) {
2402+ enter_security_guard ( & env) ;
2403+ let task_key = DataKey :: Task ( task_id) ;
2404+ let config: TaskConfig = env
2405+ . storage ( )
2406+ . persistent ( )
2407+ . get ( & task_key)
2408+ . expect ( "Task not found" ) ;
2409+ config. creator . require_auth ( ) ;
2410+
2411+ let delegate_key = DataKey :: TaskDelegate ( task_id, delegate. clone ( ) ) ;
2412+ if permissions == 0 {
2413+ env. storage ( ) . persistent ( ) . remove ( & delegate_key) ;
2414+ } else {
2415+ env. storage ( ) . persistent ( ) . set ( & delegate_key, & permissions) ;
2416+ }
2417+
2418+ env. events ( ) . publish (
2419+ (
2420+ Symbol :: new ( & env, "TaskDelegateSet" ) ,
2421+ Symbol :: new ( & env, "v1" ) ,
2422+ task_id,
2423+ ) ,
2424+ ( delegate, permissions) ,
2425+ ) ;
2426+ exit_security_guard ( & env) ;
2427+ }
2428+
2429+ /// Revoke a delegate's access to a task entirely (Issue #778). Creator-only.
2430+ pub fn revoke_task_delegate ( env : Env , task_id : u64 , delegate : Address ) {
2431+ enter_security_guard ( & env) ;
2432+ let task_key = DataKey :: Task ( task_id) ;
2433+ let config: TaskConfig = env
2434+ . storage ( )
2435+ . persistent ( )
2436+ . get ( & task_key)
2437+ . expect ( "Task not found" ) ;
2438+ config. creator . require_auth ( ) ;
2439+
2440+ env. storage ( )
2441+ . persistent ( )
2442+ . remove ( & DataKey :: TaskDelegate ( task_id, delegate. clone ( ) ) ) ;
2443+
2444+ env. events ( ) . publish (
2445+ (
2446+ Symbol :: new ( & env, "TaskDelegateRevoked" ) ,
2447+ Symbol :: new ( & env, "v1" ) ,
2448+ task_id,
2449+ ) ,
2450+ delegate,
2451+ ) ;
2452+ exit_security_guard ( & env) ;
2453+ }
2454+
2455+ /// Pause a task as either its creator or a delegate holding
2456+ /// `PERM_CAN_PAUSE` (Issue #778). Added alongside — not replacing —
2457+ /// `pause_task`, which remains creator-only and unchanged: Soroban has
2458+ /// no implicit caller identity, so delegated authorization needs an
2459+ /// explicit `caller` parameter, which would be a breaking signature
2460+ /// change to the existing entrypoint.
2461+ pub fn pause_task_as ( env : Env , task_id : u64 , caller : Address ) {
2462+ enter_security_guard ( & env) ;
2463+ caller. require_auth ( ) ;
2464+
2465+ let task_key = DataKey :: Task ( task_id) ;
2466+ let mut config: TaskConfig = env
2467+ . storage ( )
2468+ . persistent ( )
2469+ . get ( & task_key)
2470+ . expect ( "Task not found" ) ;
2471+
2472+ if caller != config. creator {
2473+ let delegate_permissions: u32 = env
2474+ . storage ( )
2475+ . persistent ( )
2476+ . get ( & DataKey :: TaskDelegate ( task_id, caller. clone ( ) ) )
2477+ . unwrap_or ( 0 ) ;
2478+ if delegate_permissions & PERM_CAN_PAUSE == 0 {
2479+ panic_with_error ! ( & env, Error :: Unauthorized ) ;
2480+ }
2481+ } else if config. permissions != 0 && ( config. permissions & PERM_CAN_PAUSE ) == 0 {
2482+ panic_with_error ! ( & env, Error :: Unauthorized ) ;
2483+ }
2484+
2485+ if !config. is_active {
2486+ panic_with_error ! ( & env, Error :: TaskAlreadyPaused ) ;
2487+ }
2488+
2489+ config. is_active = false ;
2490+ env. storage ( ) . persistent ( ) . set ( & task_key, & config) ;
2491+ remove_active_task_id ( & env, task_id) ;
2492+
2493+ env. events ( ) . publish (
2494+ (
2495+ Symbol :: new ( & env, "TaskPaused" ) ,
2496+ Symbol :: new ( & env, "v1" ) ,
2497+ task_id,
2498+ ) ,
2499+ caller,
2500+ ) ;
2501+ exit_security_guard ( & env) ;
2502+ }
2503+
23932504 /// Sets the maximum allowable single-update oracle price volatility threshold in basis points (bps).
23942505 pub fn set_max_volatility_bps ( env : Env , admin : Address , max_bps : u32 ) {
23952506 admin. require_auth ( ) ;
0 commit comments