Anchor constraints são as regras declarativas que transformam uma lista de contas de transação em um contexto tipado e de falha fechada para sua instrução. No Solana, cada conta é apenas uma pubkey que o cliente escolheu passar. Sem verificações, um atacante pode substituir um mint diferente, um vault PDA diferente ou uma conta gravável que ele controla. As structs #[derive(Accounts)] do Anchor, os tipos wrapper e os atributos #[account(...)] existem para que esses erros falhem no tempo de validação em vez de em movimentações de fundos em produção.
Esta página é o guarda-chuva para Contas, Constraints e Validação. Páginas irmãs aprofundam-se em tipos de conta, ordem de validação, seeds e bump, init / init_if_needed, espaço e aluguel, invariantes personalizadas e helpers de token. Aqui você obtém um modelo coerente para que essas páginas se encaixem como zooms, não como histórias separadas.
O Anchor valida cada campo em sua struct de contas antes que o handler seja executado: verificações de tipo wrapper (proprietário, signatário, programa id, desserialização), em seguida, constraints de atributo (mut, seeds, init, has_one, constraint, helpers de token).
Por que Importa: A segurança do Solana é majoritariamente validação de contas. Constraints são como você codifica "esta deve ser nossa PDA", "este pagador financia a criação", "esta ATA pertence a este mint e autoridade" e "esta relação de campo é mantida" sem criar manualmente cada verificação.
Conceitos Chave:Wrappers de Conta, atributos #[account], ordem de validação, seeds / bump, init / init_if_needed, espaço / isenção de aluguel, constraint personalizado, tokens e constraints de ATA, discriminadores.
Quando Usar: Toda instrução Anchor. Prefira o tipo mais restrito mais os atributos mínimos que provam propriedade, identidade e mutabilidade para esse fluxo.
Limitações / Trade-offs: Constraints não podem executar CPIs ou loops complexos; eles avaliam expressões sobre contas e argumentos de instrução. O uso excessivo de init_if_needed ou UncheckedAccount sem guardas reintroduz bugs clássicos de reinicialização e substituição. space incorreto desperdiça aluguel ou falha na serialização.
Programas Solana são stateless. Todo estado durável vive em contas. O runtime impõe apenas regras grosseiras: flags de signatário, flags de gravável e escritas do proprietário (apenas o programa proprietário pode modificar os dados de uma conta). Todo o resto (mint correto, PDA correto, relação de administrador correta) é trabalho do seu programa.
Programas Rust nativos verificam essas regras com ifs explícitos e retornos antecipados. Anchor 0.32.1 gera a mesma classe de verificações a partir de macros:
Contas de transação do cliente | v #[derive(Accounts)] -- tipos wrapper (Signer, Account<T>, Program, ...) | v #[account(...)] -- mut, seeds, bump, init, has_one, constraint, helpers de token | v Context<T> pronto -- handler executa com contas tipadas e validadas
Duas camadas se empilham:
Camada de Tipo. Escolher Signer<'info>, Account<'info, Vault>, Program<'info, System>, ou UncheckedAccount<'info> decide quais verificações automáticas são executadas (assinatura, proprietário + discriminador + carregamento Borsh, id do programa, ou nenhuma).
Camada de Atributo.#[account(mut, seeds = ..., bump, has_one = authority, constraint = ...)] compõe regras adicionais. Todos os atributos em um campo devem passar.
Clientes construídos com @solana/kit 7.0.0 (ou o cliente TS do Anchor) ainda montam a lista completa de contas. Constraints não buscam chaves ausentes; eles apenas aceitam ou rejeitam o que a transação já declarou. Seu IDL e código do cliente devem passar as mesmas pubkeys, seeds e programas que as macros esperam.
Escolha o wrapper mais restrito que corresponda à função:
Tipo
Verificações automáticas
Signer<'info>
is_signer
Account<'info, T>
Proprietário é este programa, discriminador de 8 bytes, desserializa T
InterfaceAccount<'info, T>
Proprietários compatíveis com Token-2022 / interface
Program<'info, T>
Corresponde ao id do programa para T
SystemAccount<'info>
Pertence ao System Program
UncheckedAccount<'info>
Nenhuma (você deve documentar e impor verificações)
AccountLoader é para layouts zero-copy; Interface / TokenInterface importam quando suportam tanto SPL Token quanto Token-2022. Usar UncheckedAccount para fluxos de fundos ou autoridade é uma saída deliberada, não um padrão. Detalhes: Tipos de Conta.
O Anchor não "adivinha" com base apenas na ordem dos campos da struct. Ele aplica um pipeline determinístico aproximadamente: tipo + proprietário + discriminador + carregamento Borsh para contas tipadas, então init / init_if_needed (cria quando necessário), então seeds, mut, has_one, address, e expressões de constraint personalizadas.
Consequências práticas:
O pagador e o programa do sistema devem ser válidos antes que init possa alocar.
Contas referenciadas em seeds precisam estar presentes e com chaves corretas para derivação de PDA.
Você não pode fazer CPI de uma expressão de constraint; efeitos colaterais pertencem ao handler após a validação.
Depurar uma falha significa ler o erro de constraint do Anchor (e logs), não reorganizar a ordem dos campos e esperar.
Veja Ordem de Validação de Conta ao projetar fluxos de criar-e-usar ou caçar "por que o init falhou antes do meu handler?".
seeds = [...] deve corresponder à derivação do lado do cliente (ordem de bytes, endianness para inteiros, ordem das chaves da conta).
bump em init encontra o bump canônico (mais alto); armazene-o nos dados da conta.
bump = account.bump em instruções posteriores reutiliza o valor armazenado para que você evite a busca de bump a cada vez.
Seeds incorretas significam um endereço diferente, não uma incompatibilidade suave. PDAs entre programas precisam de direcionamento explícito do programa; a derivação padrão é sob o id do programa atual. Padrões completos: Constraints de Seeds & Bump.
init cria uma nova conta na mesma instrução: alocação/atribuição CPI do sistema (e financiamento), define o proprietário para o seu programa, tamanho a partir de space. Falha se a conta já existir. Companheiros típicos: payer = ..., space = 8 + T::INIT_SPACE, seeds + bump opcionais, e system_program.
init_if_needed cria apenas quando a conta está ausente. Isso é conveniente para onboarding idempotente, e perigoso quando uma conta existente é aceita sem provar que é o seu estado inicializado corretamente. Prefira init simples para vaults de alto valor; se precisar de idempotência, adicione constraints rígidas no discriminador, autoridade e seeds quando a conta já existir.
init_if_needed está atrás de um recurso do cargo. Sem ele, a macro não compila:
[dependencies]anchor-lang = { version = "0.32.1", features = ["init-if-needed"] }
Requisitos: pagador signatário mutável com SOL suficiente, programa do sistema presente, espaço grande o suficiente para serialização. Mergulho profundo: init & init_if_needed.
Contas Solana que contêm dados devem permanecer isentos de aluguel. O space do Anchor é o comprimento de bytes alocado. Para contas Anchor normais, isso é 8 (discriminador) + payload. Use #[derive(InitSpace)] e T::INIT_SPACE, com #[max_len(n)] em campos String / Vec para que o tamanho seja conhecida em tempo de compilação.
Subalocar e a serialização falham ou corrompem o estado. Superalocar e os usuários pagam aluguel em excesso. Crescer mais tarde precisa de realloc (e migração cuidadosa). Fechar contas deve retornar lamports a um destinatário via close = ... para que o aluguel não fique preso. Detalhes: Espaço & Aluguel.
Os built-ins cobrem muito (mut, has_one, address, owner, seeds, init). Regras de domínio usam constraint = <expressão booleana>, opcionalmente mapeadas com @ MeuErro::Variante para erros claros do cliente:
#[instruction(amount: u64)] traz argumentos de instrução para expressões.
Prefira verificações declarativas para relacionamentos estáticos; use require! no handler para lógica dependente de oráculo, em loop ou de contas restantes.
Módulos de constraint reutilizáveis e helpers mantêm programas grandes consistentes. Veja Constraints Personalizadas e a cobertura irmã de atributos #[account] comuns.
Fluxos de token falham em produção quando o mint, o proprietário ou o id do programa de token não correspondem. anchor-spl adiciona helpers para que você não precise verificar manualmente cada campo:
token::mint / token::authority em contas de token
associated_token::mint / associated_token::authority para ATAs
init / init_if_needed em ATAs com token associado + programas do sistema + token na lista de contas
Para Token-2022, prefira tipos interface (InterfaceAccount, TokenInterface) para que as verificações de proprietário aceitem tanto o Token legado quanto o Token-2022. Sempre marque as contas de token de origem e destino como mut quando os saldos mudarem. Conceitualmente: fixe a identidade do mint, a autoridade e o id do programa da mesma forma que você fixa PDAs de vault com seeds.
Constraints são uma ferramenta de arquitetura de segurança, não um açúcar sintático. Em revisões de design, mapeie cada conta sensível para (1) um tipo, (2) prova de identidade (seeds, endereço, derivação ATA), (3) prova de relacionamento (has_one, correspondência de mint) e (4) ciclo de vida (init, close, realloc).
Padrão
Forma do Constraint
Observe
PDA de perfil de usuário
init + seeds + bump + space
Ordem das seeds do cliente; armazene o bump
Singleton de configuração
seeds [b"config"], has_one = admin
Autoridade de upgrade vs papel de admin
Saque de vault
seeds, constraint em saldos, correspondência de mint de token
CEI: valide, então modifique, então CPI
Garantia de ATA idempotente
init_if_needed + associated_token::*
Reinicialização só é segura com verificações existentes fortes
Caminho Token-2022
InterfaceAccount + TokenInterface
Id do programa incorreto falha a transferência
remaining_accounts ficam fora da struct tipada: valide cada chave e proprietário no handler antes de usar. Zero-copy e realloc mudam o dimensionamento e os tipos de loader, mas não a necessidade de constraints de seeds e autoridade.
No Agave 4.1.1 com Solana CLI 3.0.10, testes locais (anchor test, Surfpool, LiteSVM) devem afirmar tanto os caminhos de sucesso quanto as falhas deliberadas de constraint (mint incorreto, PDA incorreto, mut ausente). Falhas na validação são mais baratas e seguras do que a execução parcial do handler.
Anchor 0.32.1 continua a gerar metadados de conta IDL a partir dessas structs: os clientes veem quais contas são graváveis e quais são necessárias. Mantenha os constraints e os construtores de cliente sincronizados após alterações de seeds ou space.
"Se compila, as contas estão seguras." Tipos ajudam; ataques de substituição precisam de constraints de seeds, mint e autoridade também.
"A ordem dos campos na struct é a ordem de validação." Atributos e tipos de campo impulsionam o pipeline; não confie em reordenar campos para corrigir bugs de init.
"init_if_needed é sempre mais seguro em termos de UX." Frequentemente é uma segurança mais fraca. Prefira init a menos que os constraints de estado existente sejam herméticos.
"Space é apenas o sizeof em Rust da struct." Adicione o discriminador de 8 bytes; limite campos dinâmicos; o aluguel depende do tamanho alocado.
"bump sozinho sem seeds é suficiente." Seeds definem o endereço; bump seleciona entre pontos off-curve válidos para essas seeds.
"UncheckedAccount está bem se eu verificar no handler depois." Está bem apenas quando você realmente verifica todos os caminhos; perder um caminho é uma classe comum de exploração.
"Contas de token só precisam ser mutáveis." Mutabilidade sem constraints de mint/autoridade/ATA ainda permite drenagens de contas erradas ou CPIs falhas.
"Constraints personalizados substituem a necessidade de contas tipadas." Use ambos: tipos para proprietário e layout, constraints para invariantes de domínio.
Eles transformam listas de contas fornecidas pelo cliente em contextos validados e tipados para que proprietários incorretos, PDAs incorretos, signatários ausentes e relacionamentos quebrados falhem antes que sua lógica de negócios seja executada.
Qual é a diferença entre tipos de conta e atributos #[account]?
Tipos aplicam verificações básicas (signatário, proprietário, discriminador, id do programa). Atributos adicionam regras de ciclo de vida e relacionamento (init, seeds, mut, has_one, expressões personalizadas, helpers de token).
Quando a validação é executada em relação ao meu handler?
Toda a validação de conta para o Context é concluída primeiro. O corpo da sua função de instrução só é executado após a struct de contas estar totalmente construída e os constraints passarem.
Por que seeds e bump pertencem aos constraints?
Eles provam que a conta passada é a PDA que seu programa espera para as seeds dadas. Declarar isso uma vez mantém os handlers livres de derivação repetitiva e falha fechada em caso de incompatibilidade.
Devo usar init ou init_if_needed?
Use init quando a conta deve ser criada exatamente uma vez. Use init_if_needed apenas para fluxos idempotentes onde contas existentes ainda são totalmente validadas (tipo, seeds, autoridade, constraints de domínio).
Como dimensiono o espaço corretamente?
Use space = 8 + T::INIT_SPACE com InitSpace (e max_len em campos dinâmicos). Nunca omita o discriminador. Financie lamports isentos de aluguel através de um pagador mutável.
O que faz uma expressão de constraint personalizada?
Ela avalia um booleano sobre contas e argumentos de instrução. Anexe @ Erro::Variante para que clientes e logs recebam um código de falha específico em vez de um erro de constraint genérico.
Como os constraints de token e ATA se encaixam neste modelo?
São verificações de identidade especializadas: a conta de token deve corresponder ao mint e à autoridade (e geralmente à derivação do endereço associado). Eles se situam ao lado de seeds e mut da mesma forma que a identidade do vault.
Constraints podem realizar CPIs?
Não. A criação via init é gerenciada pelo framework. CPIs arbitrárias e lógica de múltiplos passos pertencem ao handler após o sucesso da validação.
Ainda preciso de require! nos handlers?
Sim, para regras dinâmicas ou de múltiplos passos (oráculos, contas restantes, loops). Prefira constraints declarativos para relacionamentos estáticos que são conhecidos no tempo de validação.
Quais versões da stack esta seção visa?
Exemplos estão alinhados com Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, e clientes como @solana/kit 7.0.0.