Skip to content

Commit

Permalink
feat(spending_limits): implement RemoveSpendingLimit for autonomous m…
Browse files Browse the repository at this point in the history
…ultisigs
  • Loading branch information
vovacodes committed May 15, 2023
1 parent b24b9db commit ce140d6
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 4 deletions.
26 changes: 24 additions & 2 deletions programs/multisig/src/instructions/config_transaction_execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ pub struct ConfigTransactionExecute<'info> {
)]
pub transaction: Account<'info, ConfigTransaction>,

/// The account that will be charged in case the multisig account needs to reallocate space,
/// for example when adding a new member or a spending limit.
/// The account that will be charged/credited in case the config transaction causes space reallocation,
/// for example when adding a new member, adding or removing a spending limit.
/// This is usually the same as `member`, but can be a different account if needed.
#[account(mut)]
pub rent_payer: Option<Signer<'info>>,
Expand Down Expand Up @@ -226,6 +226,27 @@ impl<'info> ConfigTransactionExecute<'info> {
destinations: destinations.to_vec(),
}
.try_serialize(&mut &mut spending_limit_info.data.borrow_mut()[..])?;
}

ConfigAction::RemoveSpendingLimit {
spending_limit: spending_limit_key,
} => {
// Find the SpendingLimit account in `remaining_accounts`.
let spending_limit_info = ctx
.remaining_accounts
.iter()
.find(|acc| acc.key == spending_limit_key)
.ok_or(MultisigError::MissingAccount)?;

// `rent_payer` must also be present.
let rent_payer = &ctx
.accounts
.rent_payer
.as_ref()
.ok_or(MultisigError::MissingAccount)?;

let spending_limit = Account::<SpendingLimit>::try_from(spending_limit_info)?;
spending_limit.close(rent_payer.to_account_info())?;

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

let abs_members_delta =
Expand Down
2 changes: 2 additions & 0 deletions programs/multisig/src/state/config_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ pub enum ConfigAction {
/// If empty, funds can be sent to any address.
destinations: Vec<Pubkey>,
},
/// Remove a spending limit from the multisig.
RemoveSpendingLimit { spending_limit: Pubkey },
}
13 changes: 11 additions & 2 deletions sdk/multisig/idl/multisig.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@
"isSigner": true,
"isOptional": true,
"docs": [
"The account that will be charged in case the multisig account needs to reallocate space,",
"for example when adding a new member or a spending limit.",
"The account that will be charged/credited in case the config transaction causes space reallocation,",
"for example when adding a new member, adding or removing a spending limit.",
"This is usually the same as `member`, but can be a different account if needed."
]
},
Expand Down Expand Up @@ -1930,6 +1930,15 @@
}
}
]
},
{
"name": "RemoveSpendingLimit",
"fields": [
{
"name": "spending_limit",
"type": "publicKey"
}
]
}
]
}
Expand Down
13 changes: 13 additions & 0 deletions sdk/multisig/src/generated/types/ConfigAction.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions tests/suites/examples/spending-limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,90 @@ describe("Examples / Spending Limits", () => {
/Spending limit exceeded/
);
});

it("remove Spending Limits for autonomous multisig", async () => {
const [solSpendingLimitPda] = multisig.getSpendingLimitPda({
multisigPda,
createKey: solSpendingLimitParams.createKey,
});
const [splSpendingLimitPda] = multisig.getSpendingLimitPda({
multisigPda,
createKey: splSpendingLimitParams.createKey,
});

const transactionIndex = 2n;

// Create the Config Transaction, Proposal for it, and approve the Proposal.
const message = new TransactionMessage({
payerKey: members.almighty.publicKey,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
instructions: [
multisig.instructions.configTransactionCreate({
multisigPda,
transactionIndex,
creator: members.almighty.publicKey,
actions: [
{
__kind: "RemoveSpendingLimit",
spendingLimit: solSpendingLimitPda,
},
{
__kind: "RemoveSpendingLimit",
spendingLimit: splSpendingLimitPda,
},
],
}),
multisig.instructions.proposalCreate({
multisigPda,
transactionIndex,
rentPayer: members.almighty.publicKey,
}),
multisig.instructions.proposalApprove({
multisigPda,
transactionIndex,
member: members.almighty.publicKey,
}),
],
}).compileToV0Message();

const tx = new VersionedTransaction(message);
tx.sign([members.almighty]);

let signature = await connection
.sendTransaction(tx, {
skipPreflight: true,
})
.catch((err) => {
console.log(err.logs);
throw err;
});
await connection.confirmTransaction(signature);

// Execute the Config Transaction which will remove the Spending Limits.
signature = await multisig.rpc
.configTransactionExecute({
connection,
feePayer: members.executor,
multisigPda,
transactionIndex,
member: members.executor,
rentPayer: members.executor,
spendingLimits: [solSpendingLimitPda, splSpendingLimitPda],
})
.catch((err) => {
console.log(err.logs);
throw err;
});
await connection.confirmTransaction(signature);

// The Spending Limits should be gone.
assert.strictEqual(
await connection.getAccountInfo(solSpendingLimitPda),
null
);
assert.strictEqual(
await connection.getAccountInfo(splSpendingLimitPda),
null
);
});
});

0 comments on commit ce140d6

Please sign in to comment.