Anchor Constraints Explained
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.
Resumo
- 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,constraintpersonalizado, 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_neededouUncheckedAccountsem guardas reintroduz bugs clássicos de reinicialização e substituição.spaceincorreto desperdiça aluguel ou falha na serialização. - Tópicos Relacionados: Tipos de Conta, Ordem de Validação de Conta, Constraints de Seeds & Bump, init & init_if_needed, Espaço & Aluguel, Constraints Personalizadas.
Fundamentos
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 validadasDuas camadas se empilham:
- Camada de Tipo. Escolher
Signer<'info>,Account<'info, Vault>,Program<'info, System>, ouUncheckedAccount<'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.
Mecânicas e Interações
Tipos de conta como o primeiro filtro
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.
Ordem de validação
O Anchor não "adivinha" com base apenas na ordem dos campos da struct. Ele aplica um pipeline determinístico aproximadamente: verificações de tipo e proprietário, flags de signatário e mutabilidade, constraints de endereço e programa, init / init_if_needed (cria quando necessário), seeds e outros atributos, expressões de constraint personalizadas, e então carregamento completo de dados para contas tipadas.
Consequências práticas:
- O pagador e o programa do sistema devem ser válidos antes que
initpossa alocar. - Contas referenciadas em
seedsprecisam 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 e bump: provando PDAs
Endereços derivados de programa (PDAs) são endereços controlados pelo seu programa via seeds. Constraints tornam essa prova declarativa:
#[account(
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump,
)]
pub vault: Account<'info, Vault>,seeds = [...]deve corresponder à derivação do lado do cliente (ordem de bytes, endianness para inteiros, ordem das chaves da conta).bumpeminitencontra o bump canônico (mais alto); armazene-o nos dados da conta.bump = account.bumpem 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 e init_if_needed
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.
#[account(
init,
payer = user,
space = 8 + Profile::INIT_SPACE,
seeds = [b"profile", user.key().as_ref()],
bump,
)]
pub profile: Account<'info, Profile>,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.
Espaço e aluguel
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 conhecido 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.
Constraints e relacionamentos personalizados
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:
#[account(
mut,
has_one = admin,
constraint = config.fee_bps <= 10_000 @ ConfigError::FeeTooHigh,
)]
pub config: Account<'info, Config>,- Múltiplos constraints são combinados com AND.
#[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.
Constraints de Token e ATA (conceito)
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::authorityem contas de tokenassociated_token::mint/associated_token::authoritypara ATAsinit/init_if_neededem 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.
Considerações Avançadas e Aplicações
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.
Equívocos Comuns
- "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. Prefirainita 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.
- "
bumpsozinho 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.
FAQs
Que problema os constraints do Anchor resolvem?
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.
Para onde devo ir depois desta página?
Leia Tipos de Conta e Ordem de Validação de Conta, depois Constraints de Seeds & Bump, init & init_if_needed, Espaço & Aluguel, e Constraints Personalizadas à medida que você implementa instruções reais.
Relacionados
- Tipos de Conta - Signer, Account, Program, SystemAccount, UncheckedAccount
- Ordem de Validação de Conta - quando tipos, init e constraints são executados
- Constraints de Seeds & Bump - identidade PDA com seeds e bump armazenado
- init & init_if_needed - ciclo de vida de criação e risco de reinicialização
- Espaço & Aluguel - dimensionamento, discriminador, financiamento isento de aluguel
- Constraints Personalizadas - expressões de constraint, has_one, erros tipados
Versões da Stack: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, e @solana/kit 7.0.0.