Skip to content

Commit ce140d6

Browse files
committed
feat(spending_limits): implement RemoveSpendingLimit for autonomous multisigs
1 parent b24b9db commit ce140d6

File tree

5 files changed

+136
-4
lines changed

5 files changed

+136
-4
lines changed

programs/multisig/src/instructions/config_transaction_execute.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ pub struct ConfigTransactionExecute<'info> {
4545
)]
4646
pub transaction: Account<'info, ConfigTransaction>,
4747

48-
/// The account that will be charged in case the multisig account needs to reallocate space,
49-
/// for example when adding a new member or a spending limit.
48+
/// The account that will be charged/credited in case the config transaction causes space reallocation,
49+
/// for example when adding a new member, adding or removing a spending limit.
5050
/// This is usually the same as `member`, but can be a different account if needed.
5151
#[account(mut)]
5252
pub rent_payer: Option<Signer<'info>>,
@@ -226,6 +226,27 @@ impl<'info> ConfigTransactionExecute<'info> {
226226
destinations: destinations.to_vec(),
227227
}
228228
.try_serialize(&mut &mut spending_limit_info.data.borrow_mut()[..])?;
229+
}
230+
231+
ConfigAction::RemoveSpendingLimit {
232+
spending_limit: spending_limit_key,
233+
} => {
234+
// Find the SpendingLimit account in `remaining_accounts`.
235+
let spending_limit_info = ctx
236+
.remaining_accounts
237+
.iter()
238+
.find(|acc| acc.key == spending_limit_key)
239+
.ok_or(MultisigError::MissingAccount)?;
240+
241+
// `rent_payer` must also be present.
242+
let rent_payer = &ctx
243+
.accounts
244+
.rent_payer
245+
.as_ref()
246+
.ok_or(MultisigError::MissingAccount)?;
247+
248+
let spending_limit = Account::<SpendingLimit>::try_from(spending_limit_info)?;
249+
spending_limit.close(rent_payer.to_account_info())?;
229250

230251
// We don't need to invalidate prior transactions here because adding
231252
// a spending limit doesn't affect the consensus parameters of the multisig.
@@ -261,6 +282,7 @@ fn members_length_after_actions(members_length: usize, actions: &[ConfigAction])
261282
ConfigAction::ChangeThreshold { .. } => acc,
262283
ConfigAction::SetTimeLock { .. } => acc,
263284
ConfigAction::AddSpendingLimit { .. } => acc,
285+
ConfigAction::RemoveSpendingLimit { .. } => acc,
264286
});
265287

266288
let abs_members_delta =

programs/multisig/src/state/config_transaction.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,6 @@ pub enum ConfigAction {
7171
/// If empty, funds can be sent to any address.
7272
destinations: Vec<Pubkey>,
7373
},
74+
/// Remove a spending limit from the multisig.
75+
RemoveSpendingLimit { spending_limit: Pubkey },
7476
}

sdk/multisig/idl/multisig.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@
322322
"isSigner": true,
323323
"isOptional": true,
324324
"docs": [
325-
"The account that will be charged in case the multisig account needs to reallocate space,",
326-
"for example when adding a new member or a spending limit.",
325+
"The account that will be charged/credited in case the config transaction causes space reallocation,",
326+
"for example when adding a new member, adding or removing a spending limit.",
327327
"This is usually the same as `member`, but can be a different account if needed."
328328
]
329329
},
@@ -1930,6 +1930,15 @@
19301930
}
19311931
}
19321932
]
1933+
},
1934+
{
1935+
"name": "RemoveSpendingLimit",
1936+
"fields": [
1937+
{
1938+
"name": "spending_limit",
1939+
"type": "publicKey"
1940+
}
1941+
]
19331942
}
19341943
]
19351944
}

sdk/multisig/src/generated/types/ConfigAction.ts

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/suites/examples/spending-limits.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,90 @@ describe("Examples / Spending Limits", () => {
402402
/Spending limit exceeded/
403403
);
404404
});
405+
406+
it("remove Spending Limits for autonomous multisig", async () => {
407+
const [solSpendingLimitPda] = multisig.getSpendingLimitPda({
408+
multisigPda,
409+
createKey: solSpendingLimitParams.createKey,
410+
});
411+
const [splSpendingLimitPda] = multisig.getSpendingLimitPda({
412+
multisigPda,
413+
createKey: splSpendingLimitParams.createKey,
414+
});
415+
416+
const transactionIndex = 2n;
417+
418+
// Create the Config Transaction, Proposal for it, and approve the Proposal.
419+
const message = new TransactionMessage({
420+
payerKey: members.almighty.publicKey,
421+
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
422+
instructions: [
423+
multisig.instructions.configTransactionCreate({
424+
multisigPda,
425+
transactionIndex,
426+
creator: members.almighty.publicKey,
427+
actions: [
428+
{
429+
__kind: "RemoveSpendingLimit",
430+
spendingLimit: solSpendingLimitPda,
431+
},
432+
{
433+
__kind: "RemoveSpendingLimit",
434+
spendingLimit: splSpendingLimitPda,
435+
},
436+
],
437+
}),
438+
multisig.instructions.proposalCreate({
439+
multisigPda,
440+
transactionIndex,
441+
rentPayer: members.almighty.publicKey,
442+
}),
443+
multisig.instructions.proposalApprove({
444+
multisigPda,
445+
transactionIndex,
446+
member: members.almighty.publicKey,
447+
}),
448+
],
449+
}).compileToV0Message();
450+
451+
const tx = new VersionedTransaction(message);
452+
tx.sign([members.almighty]);
453+
454+
let signature = await connection
455+
.sendTransaction(tx, {
456+
skipPreflight: true,
457+
})
458+
.catch((err) => {
459+
console.log(err.logs);
460+
throw err;
461+
});
462+
await connection.confirmTransaction(signature);
463+
464+
// Execute the Config Transaction which will remove the Spending Limits.
465+
signature = await multisig.rpc
466+
.configTransactionExecute({
467+
connection,
468+
feePayer: members.executor,
469+
multisigPda,
470+
transactionIndex,
471+
member: members.executor,
472+
rentPayer: members.executor,
473+
spendingLimits: [solSpendingLimitPda, splSpendingLimitPda],
474+
})
475+
.catch((err) => {
476+
console.log(err.logs);
477+
throw err;
478+
});
479+
await connection.confirmTransaction(signature);
480+
481+
// The Spending Limits should be gone.
482+
assert.strictEqual(
483+
await connection.getAccountInfo(solSpendingLimitPda),
484+
null
485+
);
486+
assert.strictEqual(
487+
await connection.getAccountInfo(splSpendingLimitPda),
488+
null
489+
);
490+
});
405491
});

0 commit comments

Comments
 (0)