Um novo modelo de segurança de contrato DeFi: foco na invariância de protocolo

Resumo

Não apenas escreva instruções require para funções específicas; escreva instruções require para seus protocolos. Verificações de conformidade de função (requisitos)-eficácia (efeitos)-interações (interações) + invariância de protocolo (Iniants) ou o modo FREI-PI pode ajudar seu contrato a ser mais seguro, porque força os desenvolvedores a se concentrarem na segurança em nível de função. para invariantes no nível do protocolo.

motivação

Em março de 2023, a Euler Finance foi hackeada e perdeu $ 200 milhões. Euler Finance é um mercado de empréstimos onde os usuários podem depositar garantias e tomar empréstimos contra elas. Ele tem alguns recursos exclusivos; na verdade, eles são um mercado de empréstimos comparável ao Compound Finance e ao Aave.

Você pode ler um post mortem sobre esse hack aqui. Seu conteúdo principal é a falta de verificações de integridade em uma função específica, permitindo que os usuários quebrem a invariância fundamental do mercado de empréstimos.

Iniciantes Fundamentais

No centro da maioria dos protocolos DeFi está uma imutabilidade, uma propriedade do estado do programa que deve ser sempre verdadeira. Também é possível ter múltiplos invariantes, mas, em geral, eles são construídos em torno de uma ideia central. aqui estão alguns exemplos:

  • Como no mercado de empréstimos: os usuários não podem tomar nenhuma ação para colocar qualquer conta em uma posição de garantia insegura ou menos segura ("menos segura" significa que já está abaixo do limite mínimo de segurança e, portanto, não pode ser retirada mais).
  • Em AMM DEX: x * y == k, x + y == k, etc.
  • Em staking de mineração de liquidez: os usuários só devem poder sacar a quantidade de tokens de staking que depositaram.

O que deu errado com a Euler Finance não foi necessariamente que eles adicionaram recursos, não escreveram testes ou não seguiram as melhores práticas tradicionais. Eles auditaram a atualização e fizeram testes, mas ainda falharam. O problema central é que eles se esquecem de uma invariante central do mercado de empréstimos (assim como os auditores!).

*Observação: não estou querendo pegar no pé do Euler, eles são um time talentoso, mas esse é um caso recente. *

O cerne do problema

Você provavelmente está pensando "Bem, isso mesmo. É por isso que eles foram hackeados; eles esqueceram uma declaração obrigatória". Sim e não.

Mas por que eles esqueceriam a declaração de requisição?

verificar - validar - a interação não é boa o suficiente

Um padrão comum recomendado para desenvolvedores de solidity é o padrão Checks-Effects-Interactions. É muito útil para eliminar bugs relacionados à reentrância e geralmente aumenta a quantidade de validação de entrada que um desenvolvedor precisa fazer. _Mas_, está sujeito ao problema de não ver a floresta por causa das árvores.

O que ele ensina ao desenvolvedor é: "Primeiro eu escrevo minha declaração de requisição, depois faço a validação, então talvez eu faça qualquer interação, então estou seguro". O problema é que, na maioria das vezes, torna-se uma mistura de verificações e efeitos - nada mal, hein? As interações ainda são finais, então a reentrância não é um problema. Mas força os usuários a se concentrar em funções mais específicas e transições de estado individuais, em vez do contexto global e mais amplo. Isso significa que:

O mero padrão verificar-validar-interação faz com que os desenvolvedores se esqueçam das principais invariantes de seus protocolos.

Ainda é um padrão excelente para desenvolvedores, mas a invariância de protocolo sempre deve ser assegurada (sério, você ainda deveria estar usando o CEI!).

Faça certo: modo FREI-PI

Tomemos, por exemplo, este trecho do contrato SoloMargin da dYdX (código-fonte), que é um mercado de empréstimo e um contrato comercial alavancado. Este é um bom exemplo do que chamo de padrão Function Requirements-Effects-Interactions + Protocol Iniants, ou padrão FREI-PI.

Como tal, acredito que este seja o único mercado de empréstimos no mercado de empréstimos em estágio inicial que não possui nenhuma vulnerabilidade relacionada ao mercado. Composto e Aave não têm problemas diretos, mas seus garfos têm. E bZx foi hackeado muitas vezes.

Examine o código abaixo e observe as seguintes abstrações:

  1. Verifique os parâmetros de entrada (_verifyInputs).
  2. Ação (conversão de dados, manipulação de estado)
  3. Verifique o estado final (_verifyFinalState).

As verificações-efeitos-interações usuais ainda são executadas. Vale a pena notar que a interação de validação de verificação com verificações adicionais não é equivalente ao FREI-PI - elas são semelhantes, mas servem a objetivos fundamentalmente diferentes. Portanto, os desenvolvedores devem pensar neles como diferentes: o FREI-PI, como uma abstração superior, visa a segurança do protocolo, enquanto o CEI visa a segurança funcional.

A estrutura deste contrato é realmente interessante - os usuários podem realizar as ações que quiserem (depositar, emprestar, negociar, transferir, liquidar, etc.) em uma cadeia de ações. Quer depositar 3 tokens diferentes, sacar o 4º e liquidar uma conta? É uma única chamada.

Este é o poder do FREI-PI: os usuários podem fazer o que quiserem dentro do protocolo, desde que as invariantes do mercado principal de empréstimos se mantenham no final da chamada: um único usuário não pode realizar nenhuma ação que coloque qualquer conta insegura ou mais posições de garantia inseguras. Para este contrato, isso é feito em _verifyFinalState , verificando as garantias de cada conta afetada para garantir que o acordo esteja melhor do que quando a transação foi iniciada.

Existem algumas invariantes adicionais incluídas nesta função que complementam as invariantes centrais e ajudam com funções secundárias como fechamento de mercados, mas são as verificações centrais que realmente mantêm o protocolo seguro.

FREI-PI centrado na entidade

Outro problema com o FREI-PI é o conceito centrado na entidade. Tomemos como exemplo um mercado de empréstimos e invariantes centrais assumidas:

Tecnicamente, esta não é a única invariante, mas é para a entidade do usuário (ainda é uma invariante do protocolo principal e, geralmente, as invariantes do usuário são invariantes do protocolo principal). Os mercados de empréstimo também costumam ter 2 entidades adicionais:

  • Oráculo
  • Gestão / Governança

Cada imutabilidade adicional torna o protocolo mais difícil de garantir, então quanto menos melhor. Na verdade, isso é o que Dan Elitzer disse em seu artigo intitulado: Por que o DeFi está quebrado e como corrigi-lo nº 1 Protocolo sem Oracle (dica: o artigo não diz realmente que os oráculos são o problema).

Oráculo

Para oráculos, pegue o exploit de US$ 130 milhões do Cream Finance. A imutabilidade central das entidades do oráculo:

Acontece que verificar oráculos em tempo de execução com FREI-PI é complicado, mas factível, com alguma premeditação. De um modo geral, Chainlink é uma boa escolha para confiar principalmente, satisfazendo a maior parte da imutabilidade. No caso raro de manipulação ou surpresa, pode ser benéfico ter salvaguardas que reduzam a flexibilidade em favor da precisão (como verificar se o último valor conhecido foi um pouco maior que o valor atual). Além disso, o sistema SoloMargin da dYdX faz um ótimo trabalho com seu oráculo DAI, aqui está o código (se você não sabe, acho que é o melhor sistema de contrato inteligente complexo já escrito).

Para obter mais informações sobre a avaliação do oráculo e destacar os recursos da equipe de Euler, eles escreveram um bom artigo sobre como manipular computacionalmente os preços do oráculo Uniswap V3 TWAP.

Administração / Governança

Criar invariantes para entidades gerenciadas é o mais complicado. Isso se deve principalmente ao fato de que a maior parte de seu papel é alterar outras invariantes existentes. Dito isso, se você puder evitar o uso de funções administrativas, evite.

Fundamentalmente, os principais invariantes de uma entidade gerenciada podem ser:

Interpretação: Os administradores podem fazer coisas que não devem quebrar invariantes, a menos que mudem drasticamente para proteger os fundos dos usuários (por exemplo: mover ativos para um contrato de resgate é a remoção de invariantes). Os administradores também devem ser considerados um usuário, portanto, a invariância do usuário do mercado de empréstimos principal também deve valer para eles (o que significa que eles não podem atacar outros usuários ou protocolos). Atualmente, algumas ações do administrador são impossíveis de verificar em tempo de execução via FREI-PI, mas com invariantes suficientemente fortes em outros lugares, esperamos que a maioria dos problemas possa ser mitigada. Digo atualmente, porque pode-se imaginar o uso de um sistema de prova zk que pode verificar todo o estado do contrato (por usuário, por oráculo, etc.).

Como exemplo de um administrador quebrando a imutabilidade, tome a ação de governança Compound que abriu o mercado de cETH em agosto de 2022. Fundamentalmente, esta atualização quebra a imutabilidade da Oracle: a Oracle fornece informações precisas e (relativamente) em tempo real. Devido à falta de funcionalidade, a Oracle pode fornecer informações incorretas. Uma verificação FREI-PI em tempo de execução, verificando se o Oracle afetado pode fornecer informações em tempo real, pode impedir que isso aconteça com a atualização. Isso pode ser incorporado ao _setPriceOracle para verificar se todos os ativos receberam as informações em tempo real. O bom do FREI-PI para funções administrativas é que as funções administrativas são relativamente insensíveis ao preço (ou pelo menos deveriam ser), portanto, mais uso de gás não deve ser um grande problema.

A complexidade é perigosa

Portanto, embora os invariantes mais importantes sejam os invariantes centrais do protocolo, também pode haver alguns invariantes centrados na entidade que devem ser mantidos pelos invariantes centrais. No entanto, o conjunto de invariantes mais simples (e menor) é provavelmente o mais seguro. Simples é bom Um exemplo brilhante é o Uniswap…

Por que o Uniswap nunca foi hackeado (provavelmente)

AMMs podem ter a invariância básica mais simples de qualquer primitiva DeFi: tokenBalanceX * tokenBalanceY == k (por exemplo, modelo de produto constante). Cada função no Uniswap V2 gira em torno deste invariante simples:

  1. Hortelã: adicionado a k
  2. Queimar: subtrair de k
  3. Swap: transfere x e y, mas mantém k.
  4. Skim: reajuste tokenBalanceX * tokenBalanceY para torná-lo igual a k e remova a parte redundante.

O segredo de segurança do Uniswap V2: o núcleo é uma imutabilidade simples e todas as funções estão a serviço dele. A única outra entidade que pode ser discutida é a governança, que pode ativar uma troca de taxa, que não afeta a imutabilidade central, apenas a distribuição da propriedade do saldo do token. Essa simplicidade em sua declaração de segurança é o motivo pelo qual o Uniswap nunca foi hackeado. Simplicidade não é de fato um desprezo pelos excelentes desenvolvedores dos contratos inteligentes da Uniswap, pelo contrário, são necessários excelentes engenheiros para encontrar a simplicidade.

Problema de gás

Meu Twitter já está cheio de gritos otimizadores de horror e dor de que essas verificações são desnecessárias e ineficientes. Duas coisas sobre esta questão:

  1. Você sabe o que mais é ineficiente? Tive que enviar mensagens para ~~Laurence~~ hackers norte-coreanos via etherscan, transferir dinheiro usando ETH e ameaçar que o FBI iria intervir.
  2. Você provavelmente já carregou todos os dados de que precisa do armazenamento, portanto, ao final da chamada, basta adicionar uma pequena verificação de solicitação aos dados quentes. Você quer que seu contrato custe uma taxa insignificante ou deixe-o morrer?

Se o custo for proibitivo, repense as variáveis centrais e tente simplificar.

O que isso significa para mim?

Como desenvolvedor, é importante definir e expressar as principais invariantes no início do processo de desenvolvimento. Como sugestão concreta: a primeira função a ser escrita é _verifyAfter, para verificar suas invariantes após cada chamada para seu contrato. Coloque-o em seu contrato e implante-o lá. Suplemente este invariante (e outros invariantes centrados na entidade) com testes invariantes mais amplos que são verificados antes da implantação (guia Foundry).

Os armazenamentos transitórios abrem algumas otimizações e melhorias interessantes que a Nascent experimentará -- sugiro que você considere como os armazenamentos transitórios podem ser usados como uma ferramenta para melhor segurança em contextos de chamada.

Neste artigo, não é gasto muito tempo na introdução do modelo FREI-PI para validação de entrada, mas também é muito importante. Definir os limites da entrada é uma tarefa desafiadora para evitar estouro e situações semelhantes. Considere verificar e acompanhar o progresso de nossa ferramenta: pirômetro (atualmente em versão beta, por favor, dê-nos uma estrela). Ele pode detalhar e ajudar a encontrar lugares onde você pode não estar fazendo a validação de entrada.

para concluir

Além de qualquer sigla cativante (FREI-PI) ou nome de esquema, a parte realmente importante é:

Encontre a simplicidade na imutabilidade central do seu protocolo. E trabalhe duro para garantir que nunca seja destruído (ou capturado antes).

Ver original
This page may contain third-party content, which is provided for information purposes only (not representations/warranties) and should not be considered as an endorsement of its views by Gate, nor as financial or professional advice. See Disclaimer for details.
  • Recompensa
  • Comentário
  • Compartilhar
Comentário
0/400
Sem comentários
  • Marcar
Faça trade de criptomoedas em qualquer lugar e a qualquer hora
qrCode
Escaneie o código para baixar o app da Gate
Comunidade
Português (Brasil)
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)