Hello! Welcome back to our module on Auditing and Optimizing Solana Programs.
In the last lesson, we put on our security auditor hats and learned to spot several common and dangerous vulnerabilities: improper authority checks, type confusion, and integer overflows. We saw that on Solana, where the client controls everything, you must trust nothing and validate everything.
Today, we shift from identifying problems to proactively preventing them. This is where the Anchor framework truly shines. Instead of writing verbose, manual if checks in every instruction, you can declare your security requirements directly within your Accounts structs. This lesson will teach you how to leverage Anchor's powerful account constraints to build a strong defensive wall around your program logic, focusing specifically on mitigating the re-initialization and type confusion attacks we discussed previously.
The Power of Declarative Security
In the previous lesson, we saw mitigations that looked like this:
// Manual signer check
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Manual ownership check
if config.owner != program_id {
return Err(ProgramError::IllegalOwner);
}
// Manual type check
if user.discriminant != AccountDiscriminant::Admin {
return Err(ProgramError::InvalidAccountData);
}
This approach works, but it's repetitive, error-prone, and clutters your business logic. Anchor's philosophy is to handle these boilerplate security checks before your instruction handler is even called. You declare the rules, and Anchor enforces them.
1. Mitigating Re-initialization Attacks
A re-initialization attack occurs when a malicious user calls an initialize function on an account that is already in use. This can wipe the existing data and reset the account to its initial state, potentially leading to theft of funds or a denial of service.
Anchor's primary defense against this is the init constraint.
Program security in anchor framework, Solana smart contract ...
Let's read a concise explanation of this attack and Anchor's approach to fixing it.
Please read the section '4) Reinitialization Attacks'. Focus on understanding: How the init constraint works to prevent re-initialization. The cautionary note about init_if_needed and why it demands extra care.
As the resource explains, the init constraint does two crucial things:
- It makes a Cross-Program Invocation (CPI) to the Solana System Program to create a new account. The System Program will fail if an account with the same address already exists (i.e., has a non-zero lamport balance). This is the first line of defense.
- After successfully creating the account, Anchor writes an 8-byte discriminator as the first 8 bytes of the new account's data.
This combination makes re-initialization impossible. Any subsequent attempt to call an instruction with init on the same account address will fail.
The init_if_needed constraint is a special tool for making instructions idempotent (callable multiple times without changing the result beyond the initial execution). However, as noted, it's dangerous because it will not prevent your instruction handler from running on an already-initialized account. If you use it, you must add manual checks inside your handler to prevent state from being reset. For most cases, you should stick with init.
2. Preventing Type Confusion (Type Cosplay)
In our last lesson, we discussed the "type cosplay" vulnerability, where a program is tricked into using an account of one type as if it were another.

Anchor's solution to this is simple, automatic, and highly effective: the discriminator. As mentioned above, when an account is initialized with init, Anchor writes a unique 8-byte identifier derived from the account's struct name (e.g., from MyAccount).
Then, whenever you use the Account<'info, MyAccount> type in an Accounts struct, Anchor automatically performs a check: it reads the first 8 bytes of the account's data and verifies that they match the MyAccount discriminator. If they don't, the transaction is rejected before your code runs. This single feature, used implicitly every time you define an account, completely eliminates the standard type confusion attack vector.
A Hitchhiker's Guide to Solana Program Security
The Helius security guide provides an excellent summary of how Anchor handles this automatically.
Read the section 'Type Cosplay'. Pay close attention to the 'Recommended Mitigation' section and the final paragraph that explains how Anchor's Account<'info, T> wrapper automates this protection.
3. A Practical Example: The has_one and close Constraints
Let's look at how these constraints appear in practice and introduce a few more that are essential for robust security.

The image above shows two instruction contexts, Create and Increment.
- The
Createstruct usesinitto securely initialize a newCounteraccount. - The
Incrementstruct usesmutto mark thecounteras mutable, but more interestingly, it useshas_one = authority.
The has_one = <account_name> constraint is a powerful tool for enforcing ownership or relationships. In this case, has_one = authority is shorthand for constraint = counter.authority == authority.key(). It automatically verifies that the public key stored in the counter account's authority field matches the key of the authority account passed into the instruction, which must also be a Signer. This elegantly solves the "Account Data Matching" vulnerability.
Let's explore this and other powerful constraints.
A Hitchhiker's Guide to Solana Program Security
Let's dive back into the Helius guide to see how has_one and other constraints can solve more common security issues declaratively.
Please read the following three sections: Account Data Matching: Understand how has_one and constraint prevent an attacker from substituting related accounts. Closing Accounts: See why improperly closing accounts is dangerous and how the #[account(close = destination)] constraint provides a secure, all-in-one solution. Duplicate Mutable Accounts: Learn how passing the same account twice can be an attack and how a simple constraint can prevent it.
To summarize the key security constraints you've now seen:
| Constraint | Purpose | Vulnerability Mitigated |
|---|---|---|
init | Creates an account and writes its discriminator. | Re-initialization Attack |
Account<'info, T> | Automatically checks the account discriminator. | Type Confusion (Cosplay) Attack |
has_one = <account> | Checks that a Pubkey field in one account matches the key of another. | Improper Authority / Account Data Mismatch |
close = <destination> | Securely closes an account by reclaiming lamports, zeroing data, and setting a closed discriminator. | Account Revival / Re-initialization Attack |
constraint = <expr> | Enforces an arbitrary boolean expression. | Custom logic errors (e.g., Duplicate Mutable Accounts) |
Signer<'info> | Verifies account.is_signer == true. | Missing Signer Check |
owner = <program_id> | Verifies account.owner == <program_id>. | Missing Ownership Check |
By composing these constraints, you can build a formidable, declarative security model for your program.
Test your understanding!
You are building a game where a central GameConfig account, controlled by an admin, holds the rules. One rule is that the admin can reset a player's PlayerStats account.
Here are the account structs:
#[account]
pub struct GameConfig {
pub admin: Pubkey,
pub game_is_active: bool,
}
#[account]
pub struct PlayerStats {
pub player: Pubkey,
pub score: u64,
pub level: u8,
}
Your task is to write the Accounts struct for a reset_player instruction. It must enforce these rules using only constraints:
- The
PlayerStatsaccount being reset must be mutable. - The
GameConfigaccount'sadminfield must match the public key of theauthoritycalling the instruction. - The
authoritymust have signed the transaction. - The transaction should only be allowed if
game_is_activeistruein theGameConfig.
Show answer
Here is the solution for the ResetPlayer accounts struct:
#[derive(Accounts)]
pub struct ResetPlayer<'info> {
#[account(mut)]
pub player_stats: Account<'info, PlayerStats>,
#[account(
has_one = admin,
constraint = game_config.game_is_active == true @ CustomError::GameNotActive
)]
pub game_config: Account<'info, GameConfig>,
// The `has_one = admin` on game_config checks that game_config.admin == admin.key().
// The `Signer` type ensures that the admin account has signed the transaction.
#[account(mut)] // admin may be the payer, so often marked as mut
pub admin: Signer<'info>,
}
#[error_code]
pub enum CustomError {
#[msg("The game is not currently active.")]
GameNotActive,
}
This struct effectively uses mut, has_one, Signer, and a constraint to enforce all the required security rules declaratively. The custom error on the constraint provides clearer feedback to the user if the check fails.
Conclusion
In this lesson, we transitioned from identifying security flaws to proactively preventing them using Anchor's rich set of account constraints. This declarative approach is a cornerstone of secure and maintainable Solana development.
Key Takeaways:
- Anchor constraints enforce security rules at the program's entrypoint, before your main logic runs.
- The
initconstraint, combined with the automatic discriminator check ofAccount<'info, T>, provides a powerful two-pronged defense against re-initialization and type confusion attacks. - Constraints like
has_one,close, and the general-purposeconstraintallow you to declaratively enforce complex relationships and state requirements, reducing boilerplate and human error. - Thinking in terms of constraints forces you to define the security invariants of your program explicitly, leading to more robust designs.
Preview of the Next Lesson
We've now covered how to build secure programs. But a secure program that is too slow or expensive to run is not practical. In our next lesson, we will shift our focus to performance by learning how to measure the compute unit consumption of a program's instructions. This will allow us to identify bottlenecks and begin the process of optimization, ensuring our applications are not just secure, but also efficient.
Can't find a good explanation? Sign up and we'll make it for you
Sign up