Skip to content

Commit

Permalink
allow cancellation of old transactions
Browse files Browse the repository at this point in the history
(with transaction accounts having outdated owner sets)

Co-authored-by: Markus Stoy <[email protected]>
  • Loading branch information
amilbourne and markus-lmax committed Apr 2, 2024
1 parent 1fb1424 commit 5f3e0af
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 1 deletion.
2 changes: 1 addition & 1 deletion programs/multisig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ pub struct ExecuteTransaction<'info> {

#[derive(Accounts)]
pub struct CancelTransaction<'info> {
#[account(constraint = multisig.owner_set_seqno == transaction.owner_set_seqno)]
#[account(constraint = multisig.owner_set_seqno >= transaction.owner_set_seqno)]
multisig: Box<Account<'info, Multisig>>,
#[account(mut, has_one = multisig, close = refundee)]
transaction: Box<Account<'info, Transaction>>,
Expand Down
37 changes: 37 additions & 0 deletions tests/multisigCancelTransactionlTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,43 @@ describe("Test transaction cancellation", async () => {
assert.strictEqual(transactionActInfo, null);
}).timeout(5000);

it("should let owner cancel transaction, even if the owner set has changed", async () => {
const multisig = await dsl.createMultisig(2, 3);
const [ownerA, ownerB, _ownerC] = multisig.owners;

// Create instruction to send funds from multisig
let transactionInstruction = SystemProgram.transfer({
fromPubkey: multisig.signer,
lamports: new BN(1_000_000_000),
toPubkey: provider.publicKey,
});
const transactionAddress: PublicKey = await dsl.proposeTransaction(ownerA, [transactionInstruction], multisig.address);

// Change owner set of the multisig while the TX account at transactionAddress is still pending
const newOwners = [ownerA.publicKey, ownerB.publicKey, Keypair.generate().publicKey];
let changeOwnersInstruction = await program.methods
.setOwners(newOwners)
.accounts({
multisig: multisig.address,
multisigSigner: multisig.signer,
})
.instruction();
const changeOwnersAddress: PublicKey = await dsl.proposeTransaction(ownerA, [changeOwnersInstruction], multisig.address);
await dsl.approveTransaction(ownerB, multisig.address, changeOwnersAddress);
await dsl.executeTransaction(changeOwnersAddress, changeOwnersInstruction, multisig.signer, multisig.address, ownerB, ownerA.publicKey);

// Now cancel the original transaction instruction (the corresponding TX account owner set will be outdated at this point)
await dsl.assertBalance(ownerB.publicKey, 0);
await dsl.cancelTransaction(transactionAddress, multisig.address, ownerB, ownerB.publicKey);
await dsl.assertBalance(ownerB.publicKey, 2_115_840); // this is the rent exemption amount

let transactionActInfo = await provider.connection.getAccountInfo(
transactionAddress,
"confirmed"
);
assert.strictEqual(transactionActInfo, null);
}).timeout(5000);

it("should not let a non-owner cancel transaction", async () => {
const multisig = await dsl.createMultisig(2, 3);
const [ownerA, _ownerB, _ownerC] = multisig.owners;
Expand Down

0 comments on commit 5f3e0af

Please sign in to comment.