Skip to content

Commit 0449db1

Browse files
Policies: Make allowed recipients and amount fields optional (#103)
* Policies: Make the allowed recipients field optional * Amount too
1 parent 85d8ba7 commit 0449db1

File tree

4 files changed

+220
-88
lines changed

4 files changed

+220
-88
lines changed

contracts/smart-account-interfaces/src/auth/types.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ pub struct TokenTransferPolicy {
1414
/// The SAC token contract address this signer is allowed to call `transfer` on.
1515
pub token: Address,
1616
/// Maximum cumulative amount (in token's smallest unit) allowed per window.
17-
pub limit: i128,
17+
/// None = no amount restriction; other checks (expiration, token, recipients) still apply.
18+
pub limit: Option<i128>,
1819
/// Number of seconds after which the spent amount resets. 0 = no reset (lifetime limit).
1920
pub reset_window_secs: u64,
20-
/// Allowed recipient addresses. Empty = any recipient is allowed.
21-
pub allowed_recipients: Vec<Address>,
21+
/// Allowed recipient addresses. None = any recipient is allowed; Some([]) = no recipient allowed (deny all).
22+
pub allowed_recipients: Option<Vec<Address>>,
2223
/// Unix timestamp after which this policy expires. 0 = no expiration.
2324
pub expiration: u64,
2425
}

contracts/smart-account/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ classDiagram
354354
+token: Address
355355
+limit: i128
356356
+reset_window_secs: u64
357-
+allowed_recipients: Vec~Address~
357+
+allowed_recipients: Option~Vec~Address~~
358358
+expiration: u64
359359
}
360360
@@ -681,7 +681,7 @@ let spend_policy = TokenTransferPolicy {
681681
token: usdc_token_address,
682682
limit: 1000_0000000, // 1000 tokens (7-decimal)
683683
reset_window_secs: 86400, // resets daily
684-
allowed_recipients: vec![&env, treasury_address],
684+
allowed_recipients: Some(vec![&env, treasury_address]),
685685
expiration: 0, // no policy-level expiration
686686
};
687687

contracts/smart-account/src/auth/policy/token_transfer.rs

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ impl AuthorizationCheck for TokenTransferPolicy {
4848
}
4949

5050
// Check recipient allowlist if configured
51-
if !self.allowed_recipients.is_empty() {
51+
if let Some(recipients) = &self.allowed_recipients {
5252
if let Ok(recipient) = Address::try_from_val(env, &args.get(1).unwrap()) {
53-
if !self.allowed_recipients.iter().any(|a| a == recipient) {
53+
if !recipients.iter().any(|a| a == recipient) {
5454
return false;
5555
}
5656
} else {
@@ -75,69 +75,76 @@ impl AuthorizationCheck for TokenTransferPolicy {
7575
}
7676
}
7777

78-
// 3. Load spending tracker
79-
let tracker_key = SpendTrackerKey::TokenSpend(self.policy_id.clone(), signer_key.clone());
80-
let mut tracker: SpendingTracker =
81-
env.storage()
78+
// 3. Enforce cumulative limit if configured
79+
if let Some(limit) = self.limit {
80+
// Load spending tracker
81+
let tracker_key =
82+
SpendTrackerKey::TokenSpend(self.policy_id.clone(), signer_key.clone());
83+
let mut tracker: SpendingTracker = env
84+
.storage()
8285
.persistent()
8386
.get(&tracker_key)
8487
.unwrap_or(SpendingTracker {
8588
spent: 0,
8689
window_start: now,
8790
});
8891

89-
// 4. Check if the spending window should reset
90-
if self.reset_window_secs > 0
91-
&& now.saturating_sub(tracker.window_start) >= self.reset_window_secs
92-
{
93-
tracker.spent = 0;
94-
tracker.window_start = now;
95-
}
92+
// Check if the spending window should reset
93+
if self.reset_window_secs > 0
94+
&& now.saturating_sub(tracker.window_start) >= self.reset_window_secs
95+
{
96+
tracker.spent = 0;
97+
tracker.window_start = now;
98+
}
9699

97-
// 5. Check cumulative limit
98-
let new_total = tracker.spent.checked_add(total_amount).unwrap_or(i128::MAX);
99-
if new_total > self.limit {
100-
return false;
101-
}
100+
// Check cumulative limit
101+
let new_total = tracker.spent.checked_add(total_amount).unwrap_or(i128::MAX);
102+
if new_total > limit {
103+
return false;
104+
}
102105

103-
// 6. Update spending tracker
104-
tracker.spent = new_total;
105-
env.storage().persistent().set(&tracker_key, &tracker);
106-
env.storage().persistent().extend_ttl(
107-
&tracker_key,
108-
PERSISTENT_TTL_THRESHOLD,
109-
PERSISTENT_EXTEND_TO,
110-
);
106+
// Update spending tracker
107+
tracker.spent = new_total;
108+
env.storage().persistent().set(&tracker_key, &tracker);
109+
env.storage().persistent().extend_ttl(
110+
&tracker_key,
111+
PERSISTENT_TTL_THRESHOLD,
112+
PERSISTENT_EXTEND_TO,
113+
);
114+
}
111115

112116
true
113117
}
114118
}
115119

116120
impl PolicyCallback for TokenTransferPolicy {
117121
fn on_add(&self, env: &Env, signer_key: &SignerKey) -> Result<(), SmartAccountError> {
118-
// Validate policy parameters
119-
if self.limit <= 0 {
120-
return Err(SmartAccountError::InvalidPolicy);
122+
// Validate limit if configured
123+
if let Some(limit) = self.limit {
124+
if limit <= 0 {
125+
return Err(SmartAccountError::InvalidPolicy);
126+
}
127+
128+
// Initialize spending tracker
129+
let tracker_key =
130+
SpendTrackerKey::TokenSpend(self.policy_id.clone(), signer_key.clone());
131+
let tracker = SpendingTracker {
132+
spent: 0,
133+
window_start: env.ledger().timestamp(),
134+
};
135+
env.storage().persistent().set(&tracker_key, &tracker);
136+
env.storage().persistent().extend_ttl(
137+
&tracker_key,
138+
PERSISTENT_TTL_THRESHOLD,
139+
PERSISTENT_EXTEND_TO,
140+
);
121141
}
122142

123143
// If expiration is set, it must be in the future
124144
if self.expiration > 0 && self.expiration <= env.ledger().timestamp() {
125145
return Err(SmartAccountError::InvalidNotAfterTime);
126146
}
127147

128-
// Initialize spending tracker
129-
let tracker_key = SpendTrackerKey::TokenSpend(self.policy_id.clone(), signer_key.clone());
130-
let tracker = SpendingTracker {
131-
spent: 0,
132-
window_start: env.ledger().timestamp(),
133-
};
134-
env.storage().persistent().set(&tracker_key, &tracker);
135-
env.storage().persistent().extend_ttl(
136-
&tracker_key,
137-
PERSISTENT_TTL_THRESHOLD,
138-
PERSISTENT_EXTEND_TO,
139-
);
140-
141148
Ok(())
142149
}
143150

0 commit comments

Comments
 (0)