Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b425392
feat: Add duplicate mutable account constraint
swaroop-osec Aug 28, 2025
505727e
feat: tests for duplicate mutable accounts
swaroop-osec Aug 28, 2025
e42b1bc
feat: add test for duplicate mutable accounts in workflow
swaroop-osec Aug 28, 2025
da757c8
style(tests): prettier
swaroop-osec Aug 28, 2025
b077343
chore: update benchmarks
swaroop-osec Aug 28, 2025
62b76d0
feat: exclude UncheckedAccounts from duplicate mutable account checks
swaroop-osec Aug 28, 2025
4b3b85c
feat(tests): add duplicate-mutable-accounts to test scripts
swaroop-osec Aug 28, 2025
da22fa8
feat: enhance duplicate mutable account checks for optional fields
swaroop-osec Aug 28, 2025
0ee61f8
chore(bench): update
swaroop-osec Aug 28, 2025
a6f6c57
fix: update program IDs
swaroop-osec Aug 28, 2025
4035b0e
feat: allow duplicate accounts in realloc2 ix
swaroop-osec Aug 28, 2025
c65be84
chore(bench): update
swaroop-osec Aug 28, 2025
d55c2ef
chore(bench): update
swaroop-osec Aug 28, 2025
5c228f5
feat(tests): allow duplicate accounts in misc tests
swaroop-osec Aug 29, 2025
8dd5c8d
fix(bench):update
swaroop-osec Aug 29, 2025
a42335a
fix: update program ID for duplicate mutable accounts
swaroop-osec Aug 29, 2025
5237b78
fix: update program ID
swaroop-osec Aug 29, 2025
4a1503e
fix(bench): update
swaroop-osec Aug 29, 2025
6389a61
chore(docs): Updated docs and CHANGELOG.md
swaroop-osec Aug 29, 2025
bd20367
refactor: ignore init accounts
swaroop-osec Sep 16, 2025
e5fd855
fix(bench): update
swaroop-osec Sep 16, 2025
3d5406a
Merge branch 'master' into feat/issue-3825
swaroop-osec Sep 16, 2025
bb5a597
chore: formating
swaroop-osec Sep 16, 2025
6c3535b
refactor: optimize duplicate mutable checks generation
swaroop-osec Sep 16, 2025
3110653
refactor: replace BTreeSet with HashSet
swaroop-osec Sep 16, 2025
c3e4f45
Merge branch 'master' into feat/issue-3825
swaroop-osec Sep 19, 2025
a289a6d
Merge branch 'master' into feat/issue-3825
swaroop-osec Oct 23, 2025
8e61e4a
(chore): Update benchmarks
swaroop-osec Oct 23, 2025
9bebf80
test(events): use confirmOptions for transaction handling
swaroop-osec Oct 23, 2025
9516a8f
Merge branch 'master' into feat/issue-3825
swaroop-osec Oct 24, 2025
5f52676
chore: Update CHANGELOG.md
swaroop-osec Oct 25, 2025
8fa5a37
feat(lang): Added checks for duplicate mutable accounts in `remaining…
swaroop-osec Oct 30, 2025
7797270
feat(tests): Add nested duplicate account test to prevent mutable acc…
swaroop-osec Oct 30, 2025
bbff6e9
chore(bench): Update benchmarks
swaroop-osec Oct 30, 2025
74f1898
fix(lang): Exclude Signer accounts from duplicate mutable checks in a…
swaroop-osec Oct 31, 2025
2ab93ce
feat(tests): Add test to initialize multiple accounts with the same p…
swaroop-osec Oct 31, 2025
10da01a
chore(bench): Update
swaroop-osec Oct 31, 2025
116bf4c
Merge branch 'master' into feat/issue-3825
swaroop-osec Nov 21, 2025
21b3461
fix: package.json
swaroop-osec Nov 21, 2025
a196525
refactor: remove unused confirmOptions from event tests
swaroop-osec Nov 21, 2025
11c7e47
chore: update benchmarks
swaroop-osec Nov 21, 2025
0557e75
chore: update benchmarks
swaroop-osec Nov 21, 2025
ad3f631
Merge branch 'master' into feat/issue-3825
swaroop-osec Nov 25, 2025
aea833c
Update benchmarks
swaroop-osec Nov 25, 2025
b1a06c4
Merge branch 'master' into feat/issue-3825
swaroop-osec Nov 29, 2025
6f0944f
chore: Update benchmarks
swaroop-osec Nov 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/reusable-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ jobs:
path: tests/custom-program
- cmd: cd tests/typescript && anchor test --skip-lint && npx tsc --noEmit
path: tests/typescript
- cmd: cd tests/duplicate-mutable-accounts && anchor test --skip-lint
path: tests/duplicate-mutable-accounts
# zero-copy tests cause `/usr/bin/ld: final link failed: No space left on device`
# on GitHub runners. It is likely caused by `cargo test-sbf` since all other tests
# don't have this problem.
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The minor version will be incremented upon a breaking change and the patch versi

### Breaking

- lang: Disallow duplicate mutable accounts by default. But allows duplicate mutable accounts in instruction contexts using `dup` constraint ([#3946](https://github.com/solana-foundation/anchor/pull/3946)).

## [0.32.1] - 2025-10-09

### Features
Expand Down
6 changes: 3 additions & 3 deletions bench/BINARY_SIZE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ The programs and their tests are located in [/tests/bench](https://github.com/co

Solana version: 2.3.0

| Program | Binary Size | - |
| ------- | ----------- | ------------------------ |
| bench | 932,424 | 🟢 **-194,416 (17.25%)** |
| Program | Binary Size | - |
| ------- | ----------- | ----------------------- |
| bench | 1,015,624 | 🟢 **-111,216 (9.87%)** |

### Notable changes

Expand Down
114 changes: 57 additions & 57 deletions bench/COMPUTE_UNITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,93 +18,93 @@ Solana version: 2.3.0

| Instruction | Compute Units | - |
| --------------------------- | ------------- | --------------------- |
| accountInfo1 | 702 | 🔴 **+17 (2.48%)** |
| accountInfo2 | 1,124 | 🔴 **+71 (6.74%)** |
| accountInfo4 | 1,921 | 🔴 **+171 (9.77%)** |
| accountInfo8 | 3,480 | 🔴 **+345 (11.00%)** |
| accountInfo1 | 760 | 🔴 **+75 (10.95%)** |
| accountInfo2 | 1,180 | 🔴 **+127 (12.06%)** |
| accountInfo4 | 1,952 | 🔴 **+202 (11.54%)** |
| accountInfo8 | 3,530 | 🔴 **+395 (12.60%)** |
| accountEmptyInit1 | 4,770 | 🟢 **-145 (2.95%)** |
| accountEmpty1 | 738 | 🟢 **-36 (4.65%)** |
| accountEmpty1 | 773 | 🟢 **-1 (0.13%)** |
| accountEmptyInit2 | 8,487 | 🟢 **-306 (3.48%)** |
| accountEmpty2 | 1,138 | 🟢 **-36 (3.07%)** |
| accountEmpty2 | 1,173 | 🟢 **-1 (0.09%)** |
| accountEmptyInit4 | 15,915 | 🟢 **-833 (4.97%)** |
| accountEmpty4 | 1,923 | 🟢 **-39 (1.99%)** |
| accountEmpty4 | 1,960 | 🟢 **-2 (0.10%)** |
| accountEmptyInit8 | 30,779 | 🟢 **-1,578 (4.88%)** |
| accountEmpty8 | 3,500 | 🟢 **-48 (1.35%)** |
| accountEmpty8 | 3,539 | 🟢 **-9 (0.25%)** |
| accountSizedInit1 | 4,864 | 🟢 **-155 (3.09%)** |
| accountSized1 | 786 | 🟢 **-36 (4.38%)** |
| accountSized1 | 820 | 🟢 **-2 (0.24%)** |
| accountSizedInit2 | 8,654 | 🟢 **-327 (3.64%)** |
| accountSized2 | 1,198 | 🟢 **-42 (3.39%)** |
| accountSized2 | 1,236 | 🟢 **-4 (0.32%)** |
| accountSizedInit4 | 16,236 | 🟢 **-918 (5.35%)** |
| accountSized4 | 2,031 | 🟢 **-51 (2.45%)** |
| accountSized4 | 2,070 | 🟢 **-12 (0.58%)** |
| accountSizedInit8 | 31,363 | 🟢 **-1,650 (5.00%)** |
| accountSized8 | 3,694 | 🟢 **-68 (1.81%)** |
| accountSized8 | 3,731 | 🟢 **-31 (0.82%)** |
| accountUnsizedInit1 | 4,967 | 🟢 **-160 (3.12%)** |
| accountUnsized1 | 814 | 🟢 **-60 (6.86%)** |
| accountUnsized1 | 854 | 🟢 **-20 (2.29%)** |
| accountUnsizedInit2 | 8,841 | 🟢 **-410 (4.43%)** |
| accountUnsized2 | 1,240 | 🟢 **-86 (6.49%)** |
| accountUnsized2 | 1,281 | 🟢 **-45 (3.39%)** |
| accountUnsizedInit4 | 16,559 | 🟢 **-819 (4.71%)** |
| accountUnsized4 | 2,093 | 🟢 **-138 (6.19%)** |
| accountUnsized4 | 2,134 | 🟢 **-97 (4.35%)** |
| accountUnsizedInit8 | 31,985 | 🟢 **-1,976 (5.82%)** |
| accountUnsized8 | 3,797 | 🟢 **-238 (5.90%)** |
| accountUnsized8 | 3,837 | 🟢 **-198 (4.91%)** |
| boxedAccountEmptyInit1 | 4,864 | 🟢 **-143 (2.86%)** |
| boxedAccountEmpty1 | 831 | 🟢 **-33 (3.82%)** |
| boxedAccountEmpty1 | 864 | - |
| boxedAccountEmptyInit2 | 8,604 | 🟢 **-302 (3.39%)** |
| boxedAccountEmpty2 | 1,253 | 🟢 **-33 (2.57%)** |
| boxedAccountEmpty2 | 1,285 | 🟢 **-1 (0.08%)** |
| boxedAccountEmptyInit4 | 16,075 | 🟢 **-827 (4.89%)** |
| boxedAccountEmpty4 | 2,077 | 🟢 **-38 (1.80%)** |
| boxedAccountEmpty4 | 2,112 | 🟢 **-3 (0.14%)** |
| boxedAccountEmptyInit8 | 31,026 | 🟢 **-1,565 (4.80%)** |
| boxedAccountEmpty8 | 3,797 | 🟢 **-4 (0.11%)** |
| boxedAccountSizedInit1 | 4,952 | 🟢 **-151 (2.96%)** |
| boxedAccountSized1 | 877 | 🟢 **-35 (3.84%)** |
| boxedAccountSized1 | 913 | 🔴 **+1 (0.11%)** |
| boxedAccountSizedInit2 | 8,756 | 🟢 **-319 (3.52%)** |
| boxedAccountSized2 | 1,318 | 🟢 **-37 (2.73%)** |
| boxedAccountSized2 | 1,351 | 🟢 **-4 (0.30%)** |
| boxedAccountSizedInit4 | 16,355 | 🟢 **-859 (4.99%)** |
| boxedAccountSized4 | 2,185 | 🟢 **-46 (2.06%)** |
| boxedAccountSized4 | 2,218 | 🟢 **-13 (0.58%)** |
| boxedAccountSizedInit8 | 31,562 | 🟢 **-1,959 (5.84%)** |
| boxedAccountSized8 | 3,984 | 🟢 **-23 (0.57%)** |
| boxedAccountUnsizedInit1 | 5,044 | 🟢 **-158 (3.04%)** |
| boxedAccountUnsized1 | 907 | 🟢 **-57 (5.91%)** |
| boxedAccountUnsized1 | 943 | 🟢 **-21 (2.18%)** |
| boxedAccountUnsizedInit2 | 8,916 | 🟢 **-335 (3.62%)** |
| boxedAccountUnsized2 | 1,352 | 🟢 **-82 (5.72%)** |
| boxedAccountUnsized2 | 1,386 | 🟢 **-48 (3.35%)** |
| boxedAccountUnsizedInit4 | 16,651 | 🟢 **-891 (5.08%)** |
| boxedAccountUnsized4 | 2,234 | 🟢 **-133 (5.62%)** |
| boxedAccountUnsized4 | 2,270 | 🟢 **-97 (4.10%)** |
| boxedAccountUnsizedInit8 | 32,130 | 🟢 **-2,023 (5.92%)** |
| boxedAccountUnsized8 | 4,063 | 🟢 **-194 (4.56%)** |
| boxedInterfaceAccountMint1 | 1,092 | 🟢 **-18 (1.62%)** |
| boxedInterfaceAccountMint2 | 1,490 | 🟢 **-44 (2.87%)** |
| boxedInterfaceAccountMint4 | 2,276 | 🟢 **-94 (3.97%)** |
| boxedInterfaceAccountMint1 | 1,128 | 🔴 **+18 (1.62%)** |
| boxedInterfaceAccountMint2 | 1,523 | 🟢 **-11 (0.72%)** |
| boxedInterfaceAccountMint4 | 2,307 | 🟢 **-63 (2.66%)** |
| boxedInterfaceAccountMint8 | 3,907 | 🟢 **-157 (3.86%)** |
| boxedInterfaceAccountToken1 | 1,219 | 🟢 **-27 (2.17%)** |
| boxedInterfaceAccountToken2 | 1,732 | 🟢 **-62 (3.46%)** |
| boxedInterfaceAccountToken4 | 2,748 | 🟢 **-130 (4.52%)** |
| boxedInterfaceAccountToken1 | 1,255 | 🔴 **+9 (0.72%)** |
| boxedInterfaceAccountToken2 | 1,765 | 🟢 **-29 (1.62%)** |
| boxedInterfaceAccountToken4 | 2,779 | 🟢 **-99 (3.44%)** |
| boxedInterfaceAccountToken8 | 4,839 | 🟢 **-229 (4.52%)** |
| interfaceAccountMint1 | 1,107 | 🟢 **-19 (1.69%)** |
| interfaceAccountMint2 | 1,494 | 🟢 **-68 (4.35%)** |
| interfaceAccountMint4 | 2,276 | 🟢 **-156 (6.41%)** |
| interfaceAccountMint1 | 1,145 | 🔴 **+19 (1.69%)** |
| interfaceAccountMint2 | 1,533 | 🟢 **-29 (1.86%)** |
| interfaceAccountMint4 | 2,313 | 🟢 **-119 (4.89%)** |
| interfaceAccountMint8 | 3,835 | 🟢 **-328 (7.88%)** |
| interfaceAccountToken1 | 1,239 | 🟢 **-29 (2.29%)** |
| interfaceAccountToken2 | 1,753 | 🟢 **-96 (5.19%)** |
| interfaceAccountToken4 | 2,783 | 🟢 **-214 (7.14%)** |
| interface1 | 883 | 🔴 **+5 (0.57%)** |
| interface2 | 1,029 | 🔴 **+6 (0.59%)** |
| interface4 | 1,308 | 🔴 **+7 (0.54%)** |
| interface8 | 1,887 | 🔴 **+20 (1.07%)** |
| program1 | 899 | 🔴 **+9 (1.01%)** |
| program2 | 1,049 | 🔴 **+14 (1.35%)** |
| program4 | 1,336 | 🔴 **+23 (1.75%)** |
| program8 | 1,917 | 🔴 **+38 (2.02%)** |
| signer1 | 888 | 🔴 **+14 (1.60%)** |
| signer2 | 1,244 | 🔴 **+71 (6.05%)** |
| signer4 | 1,904 | 🔴 **+145 (8.24%)** |
| signer8 | 3,293 | 🔴 **+352 (11.97%)** |
| systemAccount1 | 910 | 🟢 **-1 (0.11%)** |
| systemAccount2 | 1,269 | 🔴 **+34 (2.75%)** |
| systemAccount4 | 1,972 | 🔴 **+101 (5.40%)** |
| systemAccount8 | 3,385 | 🔴 **+232 (7.36%)** |
| uncheckedAccount1 | 896 | 🔴 **+14 (1.59%)** |
| uncheckedAccount2 | 1,233 | 🔴 **+71 (6.11%)** |
| uncheckedAccount4 | 1,861 | 🔴 **+145 (8.45%)** |
| uncheckedAccount8 | 3,171 | 🔴 **+338 (11.93%)** |
| interfaceAccountToken1 | 1,279 | 🔴 **+11 (0.87%)** |
| interfaceAccountToken2 | 1,792 | 🟢 **-57 (3.08%)** |
| interfaceAccountToken4 | 2,825 | 🟢 **-172 (5.74%)** |
| interface1 | 918 | 🔴 **+40 (4.56%)** |
| interface2 | 1,064 | 🔴 **+41 (4.01%)** |
| interface4 | 1,345 | 🔴 **+44 (3.38%)** |
| interface8 | 1,912 | 🔴 **+45 (2.41%)** |
| program1 | 934 | 🔴 **+44 (4.94%)** |
| program2 | 1,084 | 🔴 **+49 (4.73%)** |
| program4 | 1,373 | 🔴 **+60 (4.57%)** |
| program8 | 1,956 | 🔴 **+77 (4.10%)** |
| signer1 | 923 | 🔴 **+49 (5.61%)** |
| signer2 | 1,272 | 🔴 **+99 (8.44%)** |
| signer4 | 1,957 | 🔴 **+198 (11.26%)** |
| signer8 | 3,332 | 🔴 **+391 (13.29%)** |
| systemAccount1 | 945 | 🔴 **+34 (3.73%)** |
| systemAccount2 | 1,304 | 🔴 **+69 (5.59%)** |
| systemAccount4 | 2,009 | 🔴 **+138 (7.38%)** |
| systemAccount8 | 3,424 | 🔴 **+271 (8.59%)** |
| uncheckedAccount1 | 931 | 🔴 **+49 (5.56%)** |
| uncheckedAccount2 | 1,263 | 🔴 **+101 (8.69%)** |
| uncheckedAccount4 | 1,911 | 🔴 **+195 (11.36%)** |
| uncheckedAccount8 | 3,207 | 🔴 **+374 (13.20%)** |

### Notable changes

Expand Down
91 changes: 89 additions & 2 deletions bench/STACK_MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,95 @@ The programs and their tests are located in [/tests/bench](https://github.com/co

Solana version: 2.3.0

| Instruction | Stack Memory | - |
| ----------- | ------------ | --- |
| Instruction | Stack Memory | - |
| ------------------------------ | ------------ | ------------------- |
| account_info1 | 46 | - |
| account_info2 | 88 | - |
| account_info4 | 88 | - |
| account_info8 | 88 | - |
| account_empty_init1 | 88 | - |
| account_empty_init2 | 88 | - |
| account_empty_init4 | 88 | - |
| account_empty_init8 | 88 | - |
| account_empty1 | 88 | - |
| account_empty2 | 88 | - |
| account_empty4 | 88 | - |
| account_empty8 | 88 | - |
| account_sized_init1 | 88 | - |
| account_sized_init2 | 88 | - |
| account_sized_init4 | 88 | - |
| account_sized_init8 | 88 | - |
| account_sized1 | 88 | - |
| account_sized2 | 88 | - |
| account_sized4 | 88 | - |
| account_sized8 | 88 | - |
| account_unsized_init1 | 88 | - |
| account_unsized_init2 | 88 | - |
| account_unsized_init4 | 88 | - |
| account_unsized_init8 | 88 | - |
| account_unsized1 | 88 | - |
| account_unsized2 | 88 | - |
| account_unsized4 | 88 | - |
| account_unsized8 | 88 | - |
| boxed_account_empty_init1 | 88 | - |
| boxed_account_empty_init2 | 88 | - |
| boxed_account_empty_init4 | 88 | - |
| boxed_account_empty_init8 | 88 | - |
| boxed_account_empty1 | 88 | - |
| boxed_account_empty2 | 88 | - |
| boxed_account_empty4 | 88 | - |
| boxed_account_empty8 | 96 | 🔴 **+8 (9.09%)** |
| boxed_account_sized_init1 | 88 | - |
| boxed_account_sized_init2 | 88 | - |
| boxed_account_sized_init4 | 88 | - |
| boxed_account_sized_init8 | 88 | - |
| boxed_account_sized1 | 88 | - |
| boxed_account_sized2 | 88 | - |
| boxed_account_sized4 | 88 | - |
| boxed_account_sized8 | 96 | 🔴 **+8 (9.09%)** |
| boxed_account_unsized_init1 | 88 | - |
| boxed_account_unsized_init2 | 88 | - |
| boxed_account_unsized_init4 | 88 | - |
| boxed_account_unsized_init8 | 88 | - |
| boxed_account_unsized1 | 88 | - |
| boxed_account_unsized2 | 88 | - |
| boxed_account_unsized4 | 88 | - |
| boxed_account_unsized8 | 96 | 🔴 **+8 (9.09%)** |
| boxed_interface_account_mint1 | 88 | - |
| boxed_interface_account_mint2 | 88 | - |
| boxed_interface_account_mint4 | 88 | - |
| boxed_interface_account_mint8 | 96 | 🔴 **+8 (9.09%)** |
| boxed_interface_account_token1 | 88 | - |
| boxed_interface_account_token2 | 88 | - |
| boxed_interface_account_token4 | 88 | - |
| boxed_interface_account_token8 | 96 | 🔴 **+8 (9.09%)** |
| interface_account_mint1 | 88 | - |
| interface_account_mint2 | 88 | - |
| interface_account_mint4 | 88 | - |
| interface_account_mint8 | 88 | - |
| interface_account_token1 | 104 | 🔴 **+24 (30.00%)** |
| interface_account_token2 | 104 | 🔴 **+24 (30.00%)** |
| interface_account_token4 | 104 | 🔴 **+24 (30.00%)** |
| interface1 | 88 | - |
| interface2 | 88 | - |
| interface4 | 88 | - |
| interface8 | 88 | - |
| program1 | 88 | - |
| program2 | 88 | - |
| program4 | 88 | - |
| program8 | 88 | - |
| signer1 | 88 | - |
| signer2 | 88 | - |
| signer4 | 88 | - |
| signer8 | 88 | - |
| system_account1 | 88 | - |
| system_account2 | 88 | - |
| system_account4 | 88 | - |
| system_account8 | 88 | - |
| unchecked_account1 | 88 | - |
| unchecked_account2 | 88 | - |
| unchecked_account4 | 88 | - |
| unchecked_account8 | 88 | - |

### Notable changes

Expand Down
27 changes: 27 additions & 0 deletions docs/content/docs/references/account-constraints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ Examples: [Github](https://github.com/solana-developers/anchor-examples/tree/mai
#[account(mut @ <custom_error>)]
```

### `#[account(dup)]`

Description: By default, Anchor will prevents duplicate mutable accounts to avoid potential security issues and unintended behavior.
The `dup` constraint explicitly allows this for cases where it's intentional and safe.

**Note**: This constraint only applies to mutable accounts (`mut`). Readonly accounts naturally allow duplicates without requiring the `dup` constraint.

```rust title="attribute"
#[account(mut, dup)]
#[account(mut, dup @ <custom_error>)]
```

```rust title="snippet"
#[derive(Accounts)]
pub struct AllowsDuplicateMutable<'info> {
#[account(mut)]
pub account1: Account<'info, Counter>,
// This account can be the same as account1
#[account(mut, dup)]
pub account2: Account<'info, Counter>,
}

pub fn allows_duplicate_mutable(ctx: Context<AllowsDuplicateMutable>) -> Result<()> {
Ok(())
}
```

### `#[account(init)]`

Description: Creates the account via a CPI to the system program and initializes
Expand Down
Loading