Skip to main content

cougr_core/accounts/
policy.rs

1use soroban_sdk::{Address, BytesN, Env};
2
3use super::device_storage::DeviceStorage;
4use super::error::AccountError;
5use super::intent::SignedIntent;
6use super::recovery_storage::RecoveryStorage;
7use super::storage::SessionStorage;
8
9/// Generic policy interface used by the account kernel and related subsystems.
10pub trait Policy<C> {
11    fn evaluate(&self, env: &Env, context: &C) -> Result<(), AccountError>;
12}
13
14pub struct IntentContext<'a> {
15    pub account: &'a Address,
16    pub intent: &'a SignedIntent,
17}
18
19pub struct SessionContext<'a> {
20    pub account: &'a Address,
21    pub intent: &'a SignedIntent,
22}
23
24pub struct DeviceContext<'a> {
25    pub account: &'a Address,
26    pub key_id: &'a BytesN<32>,
27}
28
29pub struct RecoveryContext<'a> {
30    pub account: &'a Address,
31    pub guardian: &'a Address,
32}
33
34/// Reject expired intents.
35pub struct IntentExpiryPolicy;
36
37impl Policy<IntentContext<'_>> for IntentExpiryPolicy {
38    fn evaluate(&self, env: &Env, context: &IntentContext<'_>) -> Result<(), AccountError> {
39        if env.ledger().timestamp() >= context.intent.expires_at {
40            return Err(AccountError::IntentExpired);
41        }
42        Ok(())
43    }
44}
45
46/// Enforces session existence, expiry, scope, budget and nonce.
47pub struct SessionPolicy;
48
49impl Policy<SessionContext<'_>> for SessionPolicy {
50    fn evaluate(&self, env: &Env, context: &SessionContext<'_>) -> Result<(), AccountError> {
51        let session =
52            SessionStorage::load(env, context.account, &context.intent.signer.session_key_id)
53                .ok_or(AccountError::SessionRevoked)?;
54
55        if env.ledger().timestamp() >= session.scope.expires_at {
56            return Err(AccountError::SessionExpired);
57        }
58        if session.operations_used >= session.scope.max_operations {
59            return Err(AccountError::SessionBudgetExceeded);
60        }
61        if session.next_nonce != context.intent.nonce {
62            return Err(AccountError::NonceMismatch);
63        }
64        if !SessionStorage::is_action_allowed(&session.scope, &context.intent.action.system_name) {
65            return Err(AccountError::ActionNotAllowed);
66        }
67        Ok(())
68    }
69}
70
71/// Ensures a bound device key is active under the account's device policy.
72pub struct ActiveDevicePolicy;
73
74impl Policy<DeviceContext<'_>> for ActiveDevicePolicy {
75    fn evaluate(&self, env: &Env, context: &DeviceContext<'_>) -> Result<(), AccountError> {
76        let devices = DeviceStorage::load_devices(env, context.account);
77        for i in 0..devices.len() {
78            if let Some(device) = devices.get(i) {
79                if &device.key_id == context.key_id && device.is_active {
80                    return Ok(());
81                }
82            }
83        }
84        Err(AccountError::DeviceNotFound)
85    }
86}
87
88/// Ensures an address is currently configured as a guardian.
89pub struct GuardianPolicy;
90
91impl Policy<RecoveryContext<'_>> for GuardianPolicy {
92    fn evaluate(&self, env: &Env, context: &RecoveryContext<'_>) -> Result<(), AccountError> {
93        let guardians = RecoveryStorage::load_guardians(env, context.account);
94        for i in 0..guardians.len() {
95            if let Some(guardian) = guardians.get(i) {
96                if &guardian.address == context.guardian {
97                    return Ok(());
98                }
99            }
100        }
101        Err(AccountError::Unauthorized)
102    }
103}