Skip to content

Commit 69deafb

Browse files
Merge pull request #16 from Squads-Protocol/audit/certora-remediations
Audit/certora remediations
2 parents 6a6c7e2 + 32e38d5 commit 69deafb

File tree

5 files changed

+65
-29
lines changed

5 files changed

+65
-29
lines changed

programs/squads_smart_account_program/src/instructions/smart_account_create.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ impl<'info> CreateSmartAccount<'info> {
126126
),
127127
creation_fee,
128128
)?;
129-
msg!("Creation fee: {}", creation_fee / LAMPORTS_PER_SOL);
130129
}
131130

132131
// Increment the smart account index.

programs/squads_smart_account_program/src/state/policies/implementations/spending_limit_policy.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl PolicyPayloadConversionTrait for SpendingLimitPolicyCreationPayload {
124124
Ok(SpendingLimitPolicy {
125125
spending_limit: SpendingLimitV2 {
126126
mint: self.mint,
127-
time_constraints: self.time_constraints,
127+
time_constraints: modified_time_constraints,
128128
quantity_constraints: self.quantity_constraints,
129129
usage: usage_state,
130130
},
@@ -333,7 +333,10 @@ impl SpendingLimitPolicy {
333333
);
334334

335335
// Check that the source account is not the same as the destination account
336-
require!(source_account_info.key() != destination_account_info.key(), SmartAccountError::InvalidAccount);
336+
require!(
337+
source_account_info.key() != destination_account_info.key(),
338+
SmartAccountError::InvalidAccount
339+
);
337340

338341
// Check the system program
339342
require!(

programs/squads_smart_account_program/src/state/policies/utils/account_tracking.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use anchor_lang::{prelude::*, Ids};
22
use anchor_spl::token_interface::{TokenAccount, TokenInterface};
33

4-
use crate::{errors::SmartAccountError, state::policies::utils::spending_limit_v2::SpendingLimitV2};
4+
use crate::{
5+
errors::SmartAccountError, state::policies::utils::spending_limit_v2::SpendingLimitV2,
6+
};
57

68
pub struct TrackedTokenAccount<'info> {
79
pub account: &'info AccountInfo<'info>,
810
pub balance: u64,
9-
pub delegate: Option<Pubkey>,
11+
pub delegate: Option<(Pubkey, u64)>,
1012
pub authority: Pubkey,
1113
}
1214

@@ -43,10 +45,8 @@ pub fn check_pre_balances<'info>(
4345
// owned by the executing account
4446
let token_program_ids = TokenInterface::ids();
4547
for account in accounts {
46-
4748
// Only track accounts owned by a token program and that are writable
4849
if token_program_ids.contains(&account.owner) && account.is_writable {
49-
5050
// This may fail for accounts that are not token accounts, so skip if it does
5151
let Ok(token_account) = InterfaceAccount::<TokenAccount>::try_from(account) else {
5252
continue;
@@ -55,7 +55,7 @@ pub fn check_pre_balances<'info>(
5555
if token_account.owner == executing_account {
5656
let balance = token_account.amount;
5757
let delegate = if let Some(delegate_key) = Option::from(token_account.delegate) {
58-
Some(delegate_key)
58+
Some((delegate_key, token_account.delegated_amount))
5959
} else {
6060
None
6161
};
@@ -91,6 +91,7 @@ impl<'info> Balances<'info> {
9191
let current_lamports = self.executing_account.account.lamports();
9292

9393
// Check the SOL spending limit
94+
// Note: Assumes spending limits have been deduplicated, and no two spending limits can have the same mint
9495
if let Some(spending_limit) = spending_limits.iter_mut().find(|spending_limit| {
9596
spending_limit.mint() == Pubkey::default()
9697
&& spending_limit.is_active(current_timestamp).is_ok()
@@ -165,12 +166,24 @@ impl<'info> Balances<'info> {
165166
);
166167
}
167168

168-
// Ensure the delegate and authority have not changed
169-
let post_delegate = Option::from(post_token_account.delegate);
170-
require!(
171-
post_delegate == tracked_token_account.delegate,
172-
SmartAccountError::ProgramInteractionIllegalTokenAccountModification
173-
);
169+
// Ensure the delegate, and authority have not changed. Delegated
170+
// amount may decrease
171+
let post_delegate: Option<(Pubkey, u64)> =
172+
if let Some(delegate_key) = Option::from(post_token_account.delegate) {
173+
Some((delegate_key, post_token_account.delegated_amount))
174+
} else {
175+
None
176+
};
177+
match (post_delegate, tracked_token_account.delegate) {
178+
(Some(post_delegate), Some(tracked_delegate)) => {
179+
require_eq!(post_delegate.0, tracked_delegate.0);
180+
require_gte!(post_delegate.1, tracked_delegate.1);
181+
}
182+
(None, None) => {}
183+
_ => {
184+
return Err(SmartAccountError::ProgramInteractionIllegalTokenAccountModification.into());
185+
}
186+
};
174187
require_eq!(
175188
post_token_account.owner,
176189
tracked_token_account.authority,

programs/squads_smart_account_program/src/state/settings.rs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,16 @@ impl Settings {
495495
PolicyCreationPayload::ProgramInteraction(creation_payload) => {
496496
PolicyState::ProgramInteraction(creation_payload.to_policy_state()?)
497497
}
498-
PolicyCreationPayload::SpendingLimit(creation_payload) => {
498+
PolicyCreationPayload::SpendingLimit(mut creation_payload) => {
499+
// If accumulate unused is true, and the policy has a
500+
// start date in the past, set it to the current
501+
// timestamp to avoid unintended accumulated usage
502+
let current_timestamp = Clock::get()?.unix_timestamp;
503+
if creation_payload.time_constraints.accumulate_unused
504+
&& creation_payload.time_constraints.start < current_timestamp
505+
{
506+
creation_payload.time_constraints.start = current_timestamp;
507+
}
499508
PolicyState::SpendingLimit(creation_payload.to_policy_state()?)
500509
}
501510
PolicyCreationPayload::SettingsChange(creation_payload) => {
@@ -589,18 +598,26 @@ impl Settings {
589598
.as_ref()
590599
.ok_or(SmartAccountError::MissingAccount)?;
591600

592-
let new_policy_state = match policy_update_payload.clone() {
593-
PolicyCreationPayload::InternalFundTransfer(creation_payload) => {
594-
PolicyState::InternalFundTransfer(creation_payload.to_policy_state()?)
595-
}
596-
PolicyCreationPayload::ProgramInteraction(creation_payload) => {
597-
PolicyState::ProgramInteraction(creation_payload.to_policy_state()?)
598-
}
599-
PolicyCreationPayload::SpendingLimit(creation_payload) => {
600-
PolicyState::SpendingLimit(creation_payload.to_policy_state()?)
601-
}
602-
PolicyCreationPayload::SettingsChange(creation_payload) => {
603-
PolicyState::SettingsChange(creation_payload.to_policy_state()?)
601+
// Only accept updates to the same policy type
602+
let new_policy_state = match (&policy.policy_state, policy_update_payload.clone()) {
603+
(
604+
PolicyState::InternalFundTransfer(_),
605+
PolicyCreationPayload::InternalFundTransfer(creation_payload),
606+
) => PolicyState::InternalFundTransfer(creation_payload.to_policy_state()?),
607+
(
608+
PolicyState::ProgramInteraction(_),
609+
PolicyCreationPayload::ProgramInteraction(creation_payload),
610+
) => PolicyState::ProgramInteraction(creation_payload.to_policy_state()?),
611+
(
612+
PolicyState::SpendingLimit(_),
613+
PolicyCreationPayload::SpendingLimit(creation_payload),
614+
) => PolicyState::SpendingLimit(creation_payload.to_policy_state()?),
615+
(
616+
PolicyState::SettingsChange(_),
617+
PolicyCreationPayload::SettingsChange(creation_payload),
618+
) => PolicyState::SettingsChange(creation_payload.to_policy_state()?),
619+
(_, _) => {
620+
return err!(SmartAccountError::InvalidPolicyPayload);
604621
}
605622
};
606623

programs/squads_smart_account_program/src/utils/context_validation.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ pub fn validate_synchronous_consensus(
77
remaining_accounts: &[AccountInfo],
88
) -> Result<()> {
99
// Settings must not be time locked
10-
require_eq!(consensus_account.time_lock(), 0, SmartAccountError::TimeLockNotZero);
10+
require_eq!(
11+
consensus_account.time_lock(),
12+
0,
13+
SmartAccountError::TimeLockNotZero
14+
);
1115

1216
// Get signers from remaining accounts using threshold
1317
let required_signer_count = consensus_account.threshold() as usize;
@@ -54,7 +58,7 @@ pub fn validate_synchronous_consensus(
5458

5559
// Check if we have all required permissions (Initiate | Vote | Execute = 7)
5660
require!(
57-
aggregated_permissions.mask == 7,
61+
aggregated_permissions.mask == Permissions::all().mask,
5862
SmartAccountError::InsufficientAggregatePermissions
5963
);
6064

0 commit comments

Comments
 (0)