ETH TO SOL
$npx skills add solana-foundation/eth-to-sol-skill

Constraints

Ethereum guards privileged functions with modifiers like onlyOwner. Anchor moves the check onto the instruction's account struct - has_one = owner runs before the handler and surfaces in the IDL.

SolidityVault.solETHEREUM
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// `onlyOwner` is a modifier - it runs before the function body. Same idea
/// applies anywhere there's a guard around a privileged function.
contract Vault {
    address public owner;
    uint16 public feeBps;
The privileged identity is just a storage slot.

owner is a single address on the contract. Nothing on the type system distinguishes it from any other address field - the "privileged" semantics are implied by the modifier that reads it later, not by the declaration itself.


    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }
The modifier fires before the function body.

onlyOwner runs require(msg.sender == owner) and then yields to the function via _;. It's a one-line guard, a code pattern, and a security mistake away from "forgot to add the modifier on the new function".


    function setFee(uint16 newFee) external onlyOwner {
        feeBps = newFee;
    }
Application is per-function and opt-in.

Every privileged function has to remember to apply onlyOwner. Adding a function later inherits no defaults - the check has to be remembered. Reviewers scan function-by-function looking for the missing decoration.

}

port ↓
Anchor 1.0.2programs/vault/src/lib.rsSOLANA
// Anchor has no modifier syntax. The equivalent check moves out of the
// handler body and onto the `#[derive(Accounts)]` struct - `has_one = owner`
// plus a `Signer<'info>` named `owner` does what `onlyOwner` did, but the
// check runs at account validation, before any state-mutating code.

use anchor_lang::prelude::*;

declare_id!("11111111111111111111111111111111");

#[program]
pub mod vault {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        ctx.accounts.config.owner = ctx.accounts.payer.key();
        ctx.accounts.config.fee_bps = 0;
        Ok(())
    }

    // Handler is now just business logic - the access check is enforced by
    // the `SetFee` struct below.
    pub fn set_fee(ctx: Context<SetFee>, new_fee: u16) -> Result<()> {
        ctx.accounts.config.fee_bps = new_fee;
        Ok(())
    }
}
The handler body is pure business logic.

By the time set_fee runs, both the signer and the has_one field match have already been verified. No require!(...) for access at the top - there's nothing to forget.


#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(
        init,
        payer = payer,
        space = 8 + 32 + 2,
        seeds = [b"config"],
        bump,
    )]
    pub config: Account<'info, Config>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct SetFee<'info> {
    #[account(
        mut,
        seeds = [b"config"],
        bump,
        has_one = owner, // <- the modifier, declarative
`has_one = owner` is the modifier, declarative.

Anchor has no modifier syntax and doesn't need one. The same check moves out of the function body and onto the #[derive(Accounts)] struct. has_one = owner plus a Signer<'info> field named owner says "the account's owner field must equal the owner signer in this instruction". Wrong signer → instruction reverts at account validation, before any state can be mutated.

    )]
    pub config: Account<'info, Config>,
    pub owner: Signer<'info>,
}
The struct *is* the access policy.

A Solidity reviewer scans functions for missing onlyOwner. A Solana reviewer scans #[derive(Accounts)] structs and sees every access rule at the top of every instruction. Constraints also surface in the program's IDL, so off-chain tooling can read the access model without parsing function bodies. Other useful constraints in the same family: constraint = x == y @ MyError::Bad, seeds = [...] + bump, address = HARDCODED_KEY. All run before the handler.


#[account]
pub struct Config {
    pub owner: Pubkey,
    pub fee_bps: u16,
}