Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fc516a5
feat(cli): Add program ID mismatch check before building
swaroop-osec Oct 23, 2025
f85a0b8
feat(cli): Skip program ID mismatch check if --skip-lint is used
swaroop-osec Oct 23, 2025
8eb0895
chore(tests): Add --skip-lint option to anchor build command
swaroop-osec Oct 23, 2025
00c0406
fix(tests): Sync keys before building programs in safety checks
swaroop-osec Oct 23, 2025
aee5b8b
fix(cli): Update program ID mismatch message to include '--skip-lint'…
swaroop-osec Oct 23, 2025
84d1d6d
chore(tests): Fix safety-checks test
swaroop-osec Oct 23, 2025
70e98e5
fix(tests): Fix typescript error
swaroop-osec Oct 23, 2025
00b2178
chore(tests): Add cleanup commands to safety-checks test
swaroop-osec Oct 23, 2025
0f59f0b
refactor(tests): Update confirmation options in CPI return and events…
swaroop-osec Oct 23, 2025
c7606e7
refactor(tests): prettier
swaroop-osec Oct 23, 2025
b289fe2
feat(cli): Introduce --ignore-keys option to skip program ID mismatch…
swaroop-osec Oct 24, 2025
103b06b
refactor(tests): Restore test.sh from master
swaroop-osec Oct 24, 2025
7da9bfc
fix(tests): Update safety-checks test to use --ignore-keys
swaroop-osec Oct 24, 2025
94567f9
Merge branch 'master' into feat/issue-3985
swaroop-osec Oct 24, 2025
be62982
refactor(cli): Clippy Fixes
swaroop-osec Oct 24, 2025
b6296dd
chore(tutorial): Update test script to include --ignore-keys option a…
swaroop-osec Oct 24, 2025
81671e3
fix(tests): Format bench.json
swaroop-osec Oct 24, 2025
00232ad
fix(tests): Add --ignore-keys option to stack-memory test build command
swaroop-osec Oct 24, 2025
0eb5a9c
fix(tests): Remove newline at end of bench.json
swaroop-osec Oct 24, 2025
473a55a
fix(tests): Format stack-memory.ts
swaroop-osec Oct 24, 2025
ee50cd7
feat(docs): Update CHANGELOG.md
swaroop-osec Oct 24, 2025
1ddbe4b
fix(tests): Set ignore_keys to true in anchor test
swaroop-osec Nov 6, 2025
96b39de
fix(tests): Remove --ignore-keys option from anchor test
swaroop-osec Nov 6, 2025
5d362d1
feat(cli): Updated docs
swaroop-osec Nov 6, 2025
ea8b254
refactor(cli): Fix clippy warnings
swaroop-osec Nov 6, 2025
dc40703
Merge branch 'master' into feat/issue-3985
jacobcreech Nov 27, 2025
1ff62e4
chore: fix merge conflict
jacobcreech Nov 27, 2025
686dde0
fix: merge conflict
jacobcreech Nov 27, 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
8 changes: 4 additions & 4 deletions .github/workflows/reusable-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ jobs:
path: ~/.cargo/bin/
- run: chmod +rwx ~/.cargo/bin/anchor

- run: cd ${{ matrix.node.path }} && anchor build --skip-lint
- run: cd ${{ matrix.node.path }} && anchor build --skip-lint --ignore-keys
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.node.name }}
Expand Down Expand Up @@ -276,7 +276,7 @@ jobs:
name: start validator
- run: cd tests/bpf-upgradeable-state && yarn --frozen-lockfile
- run: cd tests/bpf-upgradeable-state
- run: cd tests/bpf-upgradeable-state && anchor build --skip-lint
- run: cd tests/bpf-upgradeable-state && anchor build --skip-lint --ignore-keys
- run: cd tests/bpf-upgradeable-state && solana program deploy --program-id program_with_different_programdata.json target/deploy/bpf_upgradeable_state.so
- run: cd tests/bpf-upgradeable-state && cp bpf_upgradeable_state-keypair.json target/deploy/bpf_upgradeable_state-keypair.json && anchor test --skip-local-validator --skip-build --skip-lint
- run: cd tests/bpf-upgradeable-state && npx tsc --noEmit
Expand Down Expand Up @@ -438,7 +438,7 @@ jobs:
path: tests/safety-checks
- cmd: cd tests/custom-coder && anchor test --skip-lint && npx tsc --noEmit
path: tests/custom-coder
- cmd: cd tests/custom-discriminator && anchor test
- cmd: cd tests/custom-discriminator && anchor test --skip-lint && npx tsc --noEmit
path: tests/custom-discriminator
- cmd: cd tests/validator-clone && anchor test --skip-lint && npx tsc --noEmit
path: tests/validator-clone
Expand All @@ -462,7 +462,7 @@ jobs:
path: tests/bench
- cmd: cd tests/idl && ./test.sh
path: tests/idl
- cmd: cd tests/lazy-account && anchor test
- cmd: cd tests/lazy-account && anchor test --skip-lint
path: tests/lazy-account
- cmd: cd tests/test-instruction-validation && ./test.sh
path: tests/test-instruction-validation
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The minor version will be incremented upon a breaking change and the patch versi

### Features

- cli: Added a `check_program_id_mismatch` in build time to check if the program ID in the source code matches the program ID in the keypair file ([#4018](https://github.com/solana-foundation/anchor/pull/4018)). This check will be skipped during `anchor test`.

### Fixes

- idl: Fix defined types with unsupported fields not producing an error ([#4088](https://github.com/solana-foundation/anchor/pull/4088)).
Expand Down
67 changes: 66 additions & 1 deletion cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ pub enum Command {
/// True if the build should not fail even if there are no "CHECK" comments
#[clap(long)]
skip_lint: bool,
/// Skip checking for program ID mismatch between keypair and declare_id
#[clap(long)]
ignore_keys: bool,
/// Do not build the IDL
#[clap(long)]
no_idl: bool,
Expand Down Expand Up @@ -316,6 +319,9 @@ pub enum Command {
/// no "CHECK" comments where normally required
#[clap(long)]
skip_lint: bool,
/// Skip checking for program ID mismatch between keypair and declare_id
#[clap(long)]
ignore_keys: bool,
/// Architecture to use when building the program
#[clap(value_enum, long, default_value = "sbf")]
arch: ProgramArch,
Expand Down Expand Up @@ -758,6 +764,7 @@ fn process_command(opts: Opts) -> Result<()> {
cargo_args,
env,
skip_lint,
ignore_keys,
no_docs,
arch,
} => build(
Expand All @@ -767,6 +774,7 @@ fn process_command(opts: Opts) -> Result<()> {
idl_ts,
verifiable,
skip_lint,
ignore_keys,
program_name,
solana_version,
docker_image,
Expand Down Expand Up @@ -868,6 +876,7 @@ fn process_command(opts: Opts) -> Result<()> {
skip_build,
skip_deploy,
skip_lint,
ignore_keys,
env,
cargo_args,
arch,
Expand All @@ -876,6 +885,7 @@ fn process_command(opts: Opts) -> Result<()> {
skip_build,
skip_deploy,
skip_lint,
ignore_keys,
env,
cargo_args,
arch,
Expand Down Expand Up @@ -1268,6 +1278,7 @@ pub fn build(
idl_ts: Option<String>,
verifiable: bool,
skip_lint: bool,
ignore_keys: bool,
program_name: Option<String>,
solana_version: Option<String>,
docker_image: Option<String>,
Expand Down Expand Up @@ -1296,6 +1307,11 @@ pub fn build(
check_anchor_version(&cfg).ok();
check_deps(&cfg).ok();

// Check for program ID mismatches before building (skip if --ignore-keys is used), Always skipped in anchor test
if !ignore_keys {
check_program_id_mismatch(&cfg, program_name.clone())?;
}

let idl_out = match idl {
Some(idl) => Some(PathBuf::from(idl)),
None => Some(cfg_parent.join("target").join("idl")),
Expand Down Expand Up @@ -2728,6 +2744,7 @@ fn test(
None,
false,
skip_lint,
true,
program_name.clone(),
None,
None,
Expand Down Expand Up @@ -3424,7 +3441,7 @@ fn deploy(
let retry_delay = std::time::Duration::from_millis(500);
let cache_delay = std::time::Duration::from_secs(2);

println!("Waiting for program {} to be confirmed...", program_id);
println!("Waiting for program {program_id} to be confirmed...");

for attempt in 0..max_retries {
if let Ok(account) = client.get_account(&program_id) {
Expand Down Expand Up @@ -3851,11 +3868,58 @@ fn keys_sync(cfg_override: &ConfigOverride, program_name: Option<String>) -> Res
})
}

/// Check if there's a mismatch between the program keypair and the `declare_id!` in the source code.
/// Returns an error if a mismatch is detected, prompting the user to run `anchor keys sync`.
fn check_program_id_mismatch(cfg: &WithPath<Config>, program_name: Option<String>) -> Result<()> {
let declare_id_regex = RegexBuilder::new(r#"^(([\w]+::)*)declare_id!\("(\w*)"\)"#)
.multi_line(true)
.build()
.unwrap();

for program in cfg.get_programs(program_name)? {
// Get the pubkey from the keypair file
let actual_program_id = program.pubkey()?.to_string();

// Check declaration in program files
let src_path = program.path.join("src");
let files_to_check = vec![src_path.join("lib.rs"), src_path.join("id.rs")];

for path in files_to_check {
let content = match fs::read_to_string(&path) {
Ok(content) => content,
Err(_) => continue,
};

let incorrect_program_id = declare_id_regex
.captures(&content)
.and_then(|captures| captures.get(3))
.filter(|program_id_match| program_id_match.as_str() != actual_program_id);

if let Some(program_id_match) = incorrect_program_id {
let declared_id = program_id_match.as_str();
return Err(anyhow!(
"Program ID mismatch detected for program '{}':\n \
Keypair file has: {}\n \
Source code has: {}\n\n\
Please run 'anchor keys sync' to update the program ID in your source code or use the '--ignore-keys' flag to skip this check.",
program.lib_name,
actual_program_id,
declared_id
));
}
}
}

Ok(())
}

#[allow(clippy::too_many_arguments)]
fn localnet(
cfg_override: &ConfigOverride,
skip_build: bool,
skip_deploy: bool,
skip_lint: bool,
ignore_keys: bool,
env_vars: Vec<String>,
cargo_args: Vec<String>,
arch: ProgramArch,
Expand All @@ -3870,6 +3934,7 @@ fn localnet(
None,
false,
skip_lint,
ignore_keys,
None,
None,
None,
Expand Down
14 changes: 5 additions & 9 deletions cli/src/rust_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ pub fn create_program(name: &str, template: ProgramTemplate, with_mollusk: bool)
fn rust_toolchain_toml() -> String {
format!(
r#"[toolchain]
channel = "{msrv}"
channel = "{ANCHOR_MSRV}"
components = ["rustfmt","clippy"]
profile = "minimal"
"#,
msrv = ANCHOR_MSRV
"#
)
}

Expand Down Expand Up @@ -748,15 +747,12 @@ name = "tests"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"
rust-version = "{msrv}"
rust-version = "{ANCHOR_MSRV}"

[dependencies]
anchor-client = "{version}"
anchor-client = "{VERSION}"
{name} = {{ version = "0.1.0", path = "../programs/{name}" }}
"#,
msrv = ANCHOR_MSRV,
version = VERSION,
name = name,
"#
)
}

Expand Down
10 changes: 5 additions & 5 deletions client/example/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ main() {
local events_pid="2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy"
local optional_pid="FNqz6pqLAwvMSds2FYjR4nKV3moVpPNtvkfGFrqLKrgG"

cd ../../tests/composite && anchor build && cd -
cd ../../tests/composite && anchor build --skip-lint --ignore-keys && cd -
[ $? -ne 0 ] && exit 1
cd ../../examples/tutorial/basic-2 && anchor build && cd -
cd ../../examples/tutorial/basic-2 && anchor build --skip-lint --ignore-keys && cd -
[ $? -ne 0 ] && exit 1
cd ../../examples/tutorial/basic-4 && anchor build && cd -
cd ../../examples/tutorial/basic-4 && anchor build --skip-lint --ignore-keys && cd -
[ $? -ne 0 ] && exit 1
cd ../../tests/events && anchor build && cd -
cd ../../tests/events && anchor build --skip-lint --ignore-keys && cd -
[ $? -ne 0 ] && exit 1
cd ../../tests/optional && anchor build && cd -
cd ../../tests/optional && anchor build --skip-lint --ignore-keys && cd -
[ $? -ne 0 ] && exit 1

#
Expand Down
6 changes: 5 additions & 1 deletion tests/bench/tests/stack-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ describe("Stack memory", () => {
// Expected error:
// Error: Function _ZN5bench9__private8__global13account_info117h88e5c10f03de9fddE
// Stack offset of 4424 exceeded max offset of 4096 by 328 bytes
const buildResult = spawn("anchor", ["build", "--skip-lint"]);
const buildResult = spawn("anchor", [
"build",
"--skip-lint",
"--ignore-keys",
]);
const output = buildResult.output.toString();
const matches = output.matchAll(
/global[\d]+([\w\d]+?)17.*by\s(\d+)\sbytes/g
Expand Down
4 changes: 4 additions & 0 deletions tests/cpi-returns/tests/cpi-return.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe("CPI return", () => {
.rpc(confirmOptions);
let t = await provider.connection.getTransaction(tx, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});

const [key, data, buffer] = getReturnLog(t);
Expand All @@ -81,6 +82,7 @@ describe("CPI return", () => {
.rpc(confirmOptions);
let t = await provider.connection.getTransaction(tx, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});
const [key, , buffer] = getReturnLog(t);
assert.equal(key, calleeProgram.programId);
Expand All @@ -98,6 +100,7 @@ describe("CPI return", () => {
.rpc(confirmOptions);
let t = await provider.connection.getTransaction(tx, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});

const [key, data, buffer] = getReturnLog(t);
Expand Down Expand Up @@ -136,6 +139,7 @@ describe("CPI return", () => {
.rpc(confirmOptions);
let t = await provider.connection.getTransaction(tx, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
});

const [key, data, buffer] = getReturnLog(t);
Expand Down
2 changes: 1 addition & 1 deletion tests/custom-discriminator/tests/custom-discriminator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("custom-discriminator", () => {
"confirmed"
);
const myAccount = await program.account.myAccount.fetch(
pubkeys.myAccount
pubkeys.myAccount as anchor.web3.PublicKey
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 What change caused this to be needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I really have no idea some times the typescript raises errors. And the tests were passed locally without this change. Another example

);
assert.strictEqual(myAccount.field, field);
});
Expand Down
5 changes: 4 additions & 1 deletion tests/events/tests/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ describe("Events", () => {
);
const txResult = await program.provider.connection.getTransaction(
txHash,
{ ...confirmOptions, maxSupportedTransactionVersion: 0 }
{
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
}
);

const ixData = anchor.utils.bytes.bs58.decode(
Expand Down
13 changes: 11 additions & 2 deletions tests/safety-checks/Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
[toolchain]

[features]
resolution = true
skip-lint = false

[programs.localnet]
unchecked_account = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
account_info = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
account_info = "99YPYSHjNekPHmMB7yrTxZCV2k2pRMKEqwwcqE62NqdA"
unchecked_account = "4es8ojkX4URUyar2pbuLXQ4WoFtUizb5EupfaEgHjYQF"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "localnet"
Expand Down
2 changes: 1 addition & 1 deletion tests/safety-checks/programs/unchecked-account/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2018"

[lib]
crate-type = ["cdylib", "lib"]
name = "safety_checks"
name = "unchecked_account"

[features]
no-entrypoint = []
Expand Down
6 changes: 3 additions & 3 deletions tests/safety-checks/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ echo "Building programs"
# Build the UncheckedAccount variant.
#
pushd programs/unchecked-account/
output=$(anchor build 2>&1 > /dev/null)
output=$(anchor build --ignore-keys 2>&1 > /dev/null)
if ! [[ $output =~ "Struct field \"unchecked\" is unsafe" ]]; then
echo "Error: expected /// CHECK error"
exit 1
Expand All @@ -17,7 +17,7 @@ popd
# Build the AccountInfo variant.
#
pushd programs/account-info/
output=$(anchor build 2>&1 > /dev/null)
output=$(anchor build --ignore-keys 2>&1 > /dev/null)
if ! [[ $output =~ "Struct field \"unchecked\" is unsafe" ]]; then
echo "Error: expected /// CHECK error"
exit 1
Expand All @@ -28,7 +28,7 @@ popd
# Build the control variant.
#
pushd programs/ignore-non-accounts/
if ! anchor build ; then
if ! anchor build --ignore-keys ; then
echo "Error: anchor build failed when it shouldn't have"
exit 1
fi
Expand Down