O padrão de proxy permite que contratos inteligentes atualizem sua lógica, mantendo seus endereços na cadeia e valores de estado. A chamada para o contrato de proxy executará o código do contrato lógico por meio de delegateCall para modificar o estado do contrato de proxy.
Este artigo fornecerá uma visão geral dos tipos de contratos de proxy, incidentes e recomendações de segurança relacionados e práticas recomendadas para o uso de contratos de proxy.
Introdução aos Contratos Atualizáveis e Modo Proxy
Todos nós conhecemos o recurso "não adulterável" do blockchain, e o código do contrato inteligente não pode ser modificado após ser implantado no blockchain.
Portanto, quando os desenvolvedores desejam atualizar o código do contrato para atualizações lógicas, correções de bugs ou atualizações de segurança, eles devem implantar um novo contrato e um novo endereço de contrato será gerado.
Para resolver esse problema, você pode usar o modo proxy.
O modo proxy realiza a capacidade de atualização do contrato sem alterar o endereço de implantação do contrato, que atualmente é o modo de atualização de contrato mais comum.
O modo proxy é um sistema de contrato atualizável, incluindo contrato de proxy e contrato de implementação lógica.
O contrato de proxy lida com a interação do usuário e armazenamento de dados e estado do contrato. A chamada do usuário para o contrato de proxy executará o código do contrato lógico por meio de delegatecall(), alterando assim o estado do contrato de proxy. A atualização é realizada atualizando o endereço de contrato lógico registrado no slot de armazenamento predeterminado do contrato de proxy.
Os três modos de proxy mais convencionais são proxy transparente, proxy UUPS e proxy Beacon.
Proxy transparente
No modo de proxy transparente, a função de atualização é implementada no contrato de proxy. A função de administrador do contrato de proxy recebe autoridade direta para operar o contrato de proxy para atualizar o endereço de implementação lógica correspondente ao proxy. Os chamadores sem privilégios de administrador delegarão suas chamadas ao contrato de implementação.
Nota: O administrador proxy do contrato não pode ser um papel chave na implementação lógica do contrato, nem pode ser um usuário comum, porque o administrador proxy não pode interagir com o contrato de implementação.
Proxy UUPS
No modo UUPS (Universal Upgradeable Proxy Standard), a função de atualização do contrato é implementada no contrato lógico. Como o mecanismo de atualização é armazenado no contrato lógico, a versão atualizada pode excluir a lógica relacionada à atualização para proibir futuras atualizações. Nesse modo, todas as chamadas para o contrato proxy são encaminhadas para o contrato de implementação lógica.
Beacon Proxy
O modo de proxy Beacon permite que vários contratos de proxy compartilhem a mesma implementação lógica fazendo referência ao contrato Beacon. O contrato Beacon fornece o endereço do contrato de implementação lógica para o contrato proxy chamado. Ao atualizar para um novo endereço de implementação lógica, apenas o endereço registrado no contrato Beacon precisa ser atualizado.
Uso indevido de proxy e incidentes de segurança
Os desenvolvedores podem utilizar contratos de modo proxy para implementar sistemas de contrato atualizáveis. No entanto, o modo proxy também possui certos limites operacionais.Se usado de forma inadequada, pode trazer problemas de segurança devastadores para o projeto. As seções a seguir mostram os incidentes relacionados ao uso indevido de proxy e os riscos de centralização que os proxies representam.
Divulgação de chave gerenciada por proxy
O administrador do proxy controla o mecanismo de atualização do modo de proxy transparente, se a chave privada do administrador vazar, o invasor pode atualizar o contrato lógico e executar sua própria lógica maliciosa no estado do proxy.
Em 5 de março de 2021, a PAID Network sofreu um ataque de "cunhagem" causado por um gerenciamento inadequado de chaves privadas. A Rede PAGA foi explorada por um invasor que roubou a chave privada do administrador do proxy e acionou um mecanismo de atualização para alterar o contrato lógico.
Após a atualização, o invasor pode destruir o PAID do usuário e cunhar um lote de PAID para si mesmo, que pode ser vendido posteriormente. Não há vulnerabilidade de segurança no código em si, mas o invasor obteve a chave privada para atualizar o contrato do administrador.
** Implementação de proxy UUPS não inicializada **
Para o modo de proxy UUPS, durante a inicialização do contrato de proxy, os parâmetros iniciais são passados para o contrato de proxy pelo chamador e, em seguida, o contrato de proxy chama a função initialize() no contrato lógico para obter a inicialização.
A função initialize() geralmente é protegida com o modificador "initializer" para restringir a função a ser chamada apenas uma vez. Depois de chamar a função initialize(), do ponto de vista do contrato proxy, o contrato lógico é inicializado.
No entanto, da perspectiva do contrato lógico, o contrato lógico não é inicializado porque initialize() não é chamado diretamente no contrato lógico. Dado que o contrato lógico em si não é inicializado, qualquer um pode chamar a função initialize() para inicializá-lo, definir a variável de estado para um valor malicioso e potencialmente assumir o contrato lógico.
O impacto de um contrato lógico sendo assumido depende do código do contrato no sistema. No pior caso, um invasor pode atualizar o contrato lógico no modo de proxy UUPS para um contrato malicioso e executar uma chamada de função de "autodestruição", que pode fazer com que todo o contrato de proxy se torne inútil, e os ativos no contrato serão ser permanentemente destruído perdido.
caso
① Parity Multisig Freeze: O contrato lógico não foi inicializado. O invasor aciona a inicialização de muitas carteiras e bloqueia o ether no contrato chamando selfdestruct().
② Harvest Finance, Teller, KeeperDAO e Rivermen usam contratos lógicos não inicializados, o que permitirá aos invasores definir os parâmetros de inicialização dos contratos arbitrariamente e executar selfdestruct() durante o delegatecall() para destruir o contrato de proxy.
Conflito de armazenamento
Em um sistema de contrato atualizável, o contrato de proxy não declara variáveis de estado, mas usa slots de armazenamento pseudo-aleatórios para armazenar dados importantes.
Os contratos de proxy armazenam os valores das variáveis de estado do contrato lógico em relação a onde foram declarados. Se o contrato de proxy declarar suas próprias variáveis de estado e tanto o proxy quanto o contrato lógico tentarem usar o mesmo slot de armazenamento, ocorrerá um conflito de armazenamento.
O contrato de proxy fornecido pela biblioteca OpenZeppelin não declara variáveis de estado no contrato, mas com base no padrão EIP 1967, salva o valor que precisa ser armazenado (como o endereço de gerenciamento) em um slot de armazenamento específico para evitar conflitos.
caso
Em 23 de julho de 2022, horário de Pequim, a plataforma de música descentralizada Audius foi hackeada. O incidente foi causado pela introdução de uma nova lógica no contrato de proxy, resultando em conflitos de armazenamento.
O contrato de proxy declara uma variável de estado de endereço proxyAdmin e seu valor será lido incorretamente quando o código do contrato lógico for executado.
O valor de proxyAdmin personalizado pela parte do projeto foi erroneamente considerado como o valor de inicializado e inicializado, de modo que o modificador inicializador retornou um resultado errado, o que permitiu ao invasor chamar a função initialize() novamente e conceder a si mesmo autoridade para gerenciar o contrato. Os invasores então alteraram os parâmetros de votação e aprovaram sua proposta maliciosa para roubar os ativos do Audius.
Chame delegatecall() em contrato lógico ou contrato não confiável
Suponha que delegatecall() exista em um contrato lógico e o contrato não valide corretamente o destino da chamada. Nesse caso, um invasor pode explorar essa função para executar chamadas para contratos maliciosos que eles controlam, para subverter implementações de lógica ou para executar lógica personalizada.
Da mesma forma, se houver uma função address.call() irrestrita no contrato lógico, uma vez que o invasor forneça o endereço e os campos de dados de forma maliciosa, ela poderá ser usada como um contrato de proxy.
caso
Pickle Finance, Furucombo e ataques dYdX.
Nesses incidentes, o contrato vulnerável foi aprovado pelo token do usuário e há um call()/delegatecall() no contrato fornecido pelo usuário para chamar o endereço e os dados do contrato, o invasor poderá chamar o contrato de função transferFrom() para sacar saldos do usuário. Durante o incidente dYdX, dYdX realizou seu próprio ataque de chapéu branco para proteger os fundos.
Melhores Práticas
geralmente
(1) Use o modo proxy somente quando necessário
Nem todo contrato precisa ser atualizado. Como mostrado acima, há muitos riscos envolvidos no uso do padrão proxy. A propriedade "atualizável" também gera problemas de confiança, pois os administradores proxy podem atualizar contratos sem o consentimento da comunidade. Recomendamos integrar o padrão de proxy em projetos somente quando necessário.
(2) Não modifique a biblioteca proxy
A biblioteca de contratos de proxy é complexa, especialmente a parte que trata do gerenciamento de armazenamento e dos mecanismos de atualização. Quaisquer erros na modificação afetarão o trabalho dos contratos proxy e lógicos. Um grande número de bugs relacionados a agentes de alta gravidade que encontramos durante nossas auditorias foram causados por modificações incorretas na biblioteca de agentes. O incidente Audius é um excelente exemplo das consequências da modificação imprópria dos contratos de agência.
Pontos-chave da operação e gestão do contrato de agência
(1) Inicialize o contrato lógico
Um invasor pode assumir um contrato lógico não inicializado e potencialmente comprometer o sistema de contrato de proxy. Portanto, inicialize o contrato lógico após a implantação ou use _disableInitializers() no construtor do contrato lógico para desativar automaticamente a inicialização.
(2) Garantir a segurança da conta de gerenciamento do agente
Um sistema de contrato atualizável geralmente requer uma função privilegiada de "administrador proxy" para gerenciar atualizações de contrato. Se a chave de gerenciamento vazar, o invasor pode atualizar livremente o contrato para um contrato malicioso, que pode roubar os ativos dos usuários. Recomendamos o gerenciamento cuidadoso das chaves privadas das contas de administrador proxy para evitar qualquer risco potencial de invasão. As carteiras multi-assinatura podem ser usadas para evitar falhas de gerenciamento de chave de ponto único.
(3) Use uma conta separada para gerenciamento de proxy transparente
O gerenciamento de proxy e a governança lógica devem ser endereços separados para evitar a perda de interação com a implementação lógica. Se o gerenciamento de proxy e a governança lógica se referirem ao mesmo endereço, nenhuma chamada será encaminhada para executar funções privilegiadas, proibindo alterações nas funções de governança.
Relacionado ao armazenamento do contrato de proxy
(1) Tenha cuidado ao declarar variáveis de estado em contratos proxy
Conforme explicado no hack do Audius, os contratos de proxy devem ser cuidadosos ao declarar suas próprias variáveis de estado. Variáveis de estado declaradas da maneira normal em contratos de proxy podem causar conflitos de dados durante a leitura e gravação de dados. Se o contrato de proxy exigir uma variável de estado, salve o valor em um slot de armazenamento como EIP1967 para evitar conflitos ao executar o código de contrato lógico.
(2) Manter a ordem de declaração das variáveis e o tipo do contrato lógico
Cada versão do contrato lógico deve manter a mesma ordem e tipo de variáveis de estado, e novas variáveis de estado precisam ser adicionadas ao final das variáveis existentes. Caso contrário, as chamadas de delegado podem fazer com que os contratos de proxy leiam ou substituam valores armazenados incorretos, e dados antigos podem ser associados a variáveis recém-declaradas, o que pode causar sérios problemas para os aplicativos.
(3) Incluir lacunas de armazenamento no contrato base
Os contratos lógicos precisam incluir lacunas de armazenamento no código do contrato para antecipar novas variáveis de estado ao implantar novas implementações lógicas. Depois de adicionar uma nova variável de estado, o tamanho da lacuna precisa ser atualizado adequadamente.
(4) Não defina o valor da variável de estado no construtor ou no processo de declaração
Atribuir uma variável de estado durante a declaração ou no construtor afeta apenas o valor no contrato lógico, não o contrato proxy. Parâmetros não imutáveis devem ser atribuídos usando a função initialize().
Herança do Contrato
(1) Contratos atualizáveis só podem herdar de outros contratos atualizáveis
Os contratos atualizáveis têm uma estrutura diferente dos contratos não atualizáveis. Por exemplo, o construtor não é compatível com a alteração do estado do agente, ele usa a função initialize() para definir as variáveis de estado.
Qualquer contrato herdado de outro contrato precisa usar a função initialize() de seu contrato herdado para atribuir suas respectivas variáveis. Ao usar a biblioteca OpenZeppelin ou escrever seu próprio código, certifique-se de que os contratos atualizáveis possam herdar apenas outros contratos atualizáveis.
(2) Não instanciar novos contratos em contratos lógicos
Os contratos criados e instanciados por meio do Solidity não poderão ser atualizados. Os contratos devem ser implantados individualmente e passar seu endereço como um parâmetro para o contrato lógico atualizável para atingir um estado atualizável.
(3) Risco de inicialização do contrato principal
Ao inicializar o contrato pai, a função __{ContractName}_init inicializará seu contrato pai. Várias chamadas para __{ContractName}_init podem resultar em uma segunda inicialização do contrato pai. Observe que __{ContractName}_init_unchained() inicializará apenas os parâmetros de {ContractName} e não chamará o inicializador de seu contrato pai.
No entanto, esta não é uma prática recomendada, porque todos os contratos pai precisam ser inicializados, e não inicializar os contratos necessários causará problemas de execução futura.
Implementação do contrato lógico
Evite selfdestruct() ou selegatecall()/call() para contratos não confiáveis
Se houver selfdestruct() ou delegatecall() no contrato, é possível que um invasor use essas funções para interromper a implementação da lógica ou executar a lógica personalizada. Os desenvolvedores devem validar a entrada do usuário e não permitir que os contratos executem chamadas delegadas/chamadas para contratos não confiáveis. Além disso, não é recomendado usar delegatecall() em contratos lógicos porque seria complicado gerenciar o layout de armazenamento na cadeia de delegados de vários contratos.
Escrito no final
Os contratos de proxy ignoram a natureza imutável dos blockchains, permitindo que os protocolos atualizem sua lógica de código após a implantação. No entanto, o desenvolvimento de contratos de proxy ainda precisa ser muito cuidadoso, e a implementação incorreta pode causar problemas de segurança e lógica do projeto.
No geral, a melhor prática é usar soluções autorizadas e amplamente testadas, pois os modos Transparent, UUPS e Beacon Proxy têm mecanismos de atualização comprovados para seus respectivos casos de uso. Além disso, funções privilegiadas para escalar agentes também devem ser gerenciadas com segurança para evitar que invasores alterem a lógica do agente.
O contrato de implementação de lógica também deve ter cuidado para não usar delegatecall(), que pode impedir que invasores executem algum código mal-intencionado, como selfdestruct().
Embora seguir as melhores práticas garanta implantações estáveis de contrato de proxy, mantendo a flexibilidade atualizável, todo o código está sujeito a novos problemas de segurança ou lógica que podem comprometer o projeto. Portanto, todo o código é melhor auditado por uma equipe de especialistas em segurança com experiência em auditoria e proteção de protocolos de contrato de proxy.
Ver original
O conteúdo é apenas para referência, não uma solicitação ou oferta. Nenhum aconselhamento fiscal, de investimento ou jurídico é fornecido. Consulte a isenção de responsabilidade para obter mais informações sobre riscos.
Quebrando a imutabilidade do blockchain: como o modelo proxy pode alcançar atualizações de contratos inteligentes
O padrão de proxy permite que contratos inteligentes atualizem sua lógica, mantendo seus endereços na cadeia e valores de estado. A chamada para o contrato de proxy executará o código do contrato lógico por meio de delegateCall para modificar o estado do contrato de proxy.
Este artigo fornecerá uma visão geral dos tipos de contratos de proxy, incidentes e recomendações de segurança relacionados e práticas recomendadas para o uso de contratos de proxy.
Introdução aos Contratos Atualizáveis e Modo Proxy
Todos nós conhecemos o recurso "não adulterável" do blockchain, e o código do contrato inteligente não pode ser modificado após ser implantado no blockchain.
Portanto, quando os desenvolvedores desejam atualizar o código do contrato para atualizações lógicas, correções de bugs ou atualizações de segurança, eles devem implantar um novo contrato e um novo endereço de contrato será gerado.
Para resolver esse problema, você pode usar o modo proxy.
O modo proxy realiza a capacidade de atualização do contrato sem alterar o endereço de implantação do contrato, que atualmente é o modo de atualização de contrato mais comum.
O modo proxy é um sistema de contrato atualizável, incluindo contrato de proxy e contrato de implementação lógica.
O contrato de proxy lida com a interação do usuário e armazenamento de dados e estado do contrato. A chamada do usuário para o contrato de proxy executará o código do contrato lógico por meio de delegatecall(), alterando assim o estado do contrato de proxy. A atualização é realizada atualizando o endereço de contrato lógico registrado no slot de armazenamento predeterminado do contrato de proxy.
Os três modos de proxy mais convencionais são proxy transparente, proxy UUPS e proxy Beacon.
Proxy transparente
No modo de proxy transparente, a função de atualização é implementada no contrato de proxy. A função de administrador do contrato de proxy recebe autoridade direta para operar o contrato de proxy para atualizar o endereço de implementação lógica correspondente ao proxy. Os chamadores sem privilégios de administrador delegarão suas chamadas ao contrato de implementação.
Nota: O administrador proxy do contrato não pode ser um papel chave na implementação lógica do contrato, nem pode ser um usuário comum, porque o administrador proxy não pode interagir com o contrato de implementação.
Proxy UUPS
No modo UUPS (Universal Upgradeable Proxy Standard), a função de atualização do contrato é implementada no contrato lógico. Como o mecanismo de atualização é armazenado no contrato lógico, a versão atualizada pode excluir a lógica relacionada à atualização para proibir futuras atualizações. Nesse modo, todas as chamadas para o contrato proxy são encaminhadas para o contrato de implementação lógica.
Beacon Proxy
O modo de proxy Beacon permite que vários contratos de proxy compartilhem a mesma implementação lógica fazendo referência ao contrato Beacon. O contrato Beacon fornece o endereço do contrato de implementação lógica para o contrato proxy chamado. Ao atualizar para um novo endereço de implementação lógica, apenas o endereço registrado no contrato Beacon precisa ser atualizado.
Uso indevido de proxy e incidentes de segurança
Os desenvolvedores podem utilizar contratos de modo proxy para implementar sistemas de contrato atualizáveis. No entanto, o modo proxy também possui certos limites operacionais.Se usado de forma inadequada, pode trazer problemas de segurança devastadores para o projeto. As seções a seguir mostram os incidentes relacionados ao uso indevido de proxy e os riscos de centralização que os proxies representam.
Divulgação de chave gerenciada por proxy
O administrador do proxy controla o mecanismo de atualização do modo de proxy transparente, se a chave privada do administrador vazar, o invasor pode atualizar o contrato lógico e executar sua própria lógica maliciosa no estado do proxy.
Em 5 de março de 2021, a PAID Network sofreu um ataque de "cunhagem" causado por um gerenciamento inadequado de chaves privadas. A Rede PAGA foi explorada por um invasor que roubou a chave privada do administrador do proxy e acionou um mecanismo de atualização para alterar o contrato lógico.
Após a atualização, o invasor pode destruir o PAID do usuário e cunhar um lote de PAID para si mesmo, que pode ser vendido posteriormente. Não há vulnerabilidade de segurança no código em si, mas o invasor obteve a chave privada para atualizar o contrato do administrador.
** Implementação de proxy UUPS não inicializada **
Para o modo de proxy UUPS, durante a inicialização do contrato de proxy, os parâmetros iniciais são passados para o contrato de proxy pelo chamador e, em seguida, o contrato de proxy chama a função initialize() no contrato lógico para obter a inicialização.
A função initialize() geralmente é protegida com o modificador "initializer" para restringir a função a ser chamada apenas uma vez. Depois de chamar a função initialize(), do ponto de vista do contrato proxy, o contrato lógico é inicializado.
No entanto, da perspectiva do contrato lógico, o contrato lógico não é inicializado porque initialize() não é chamado diretamente no contrato lógico. Dado que o contrato lógico em si não é inicializado, qualquer um pode chamar a função initialize() para inicializá-lo, definir a variável de estado para um valor malicioso e potencialmente assumir o contrato lógico.
O impacto de um contrato lógico sendo assumido depende do código do contrato no sistema. No pior caso, um invasor pode atualizar o contrato lógico no modo de proxy UUPS para um contrato malicioso e executar uma chamada de função de "autodestruição", que pode fazer com que todo o contrato de proxy se torne inútil, e os ativos no contrato serão ser permanentemente destruído perdido.
caso
① Parity Multisig Freeze: O contrato lógico não foi inicializado. O invasor aciona a inicialização de muitas carteiras e bloqueia o ether no contrato chamando selfdestruct().
② Harvest Finance, Teller, KeeperDAO e Rivermen usam contratos lógicos não inicializados, o que permitirá aos invasores definir os parâmetros de inicialização dos contratos arbitrariamente e executar selfdestruct() durante o delegatecall() para destruir o contrato de proxy.
Conflito de armazenamento
Em um sistema de contrato atualizável, o contrato de proxy não declara variáveis de estado, mas usa slots de armazenamento pseudo-aleatórios para armazenar dados importantes.
Os contratos de proxy armazenam os valores das variáveis de estado do contrato lógico em relação a onde foram declarados. Se o contrato de proxy declarar suas próprias variáveis de estado e tanto o proxy quanto o contrato lógico tentarem usar o mesmo slot de armazenamento, ocorrerá um conflito de armazenamento.
O contrato de proxy fornecido pela biblioteca OpenZeppelin não declara variáveis de estado no contrato, mas com base no padrão EIP 1967, salva o valor que precisa ser armazenado (como o endereço de gerenciamento) em um slot de armazenamento específico para evitar conflitos.
caso
Em 23 de julho de 2022, horário de Pequim, a plataforma de música descentralizada Audius foi hackeada. O incidente foi causado pela introdução de uma nova lógica no contrato de proxy, resultando em conflitos de armazenamento.
O contrato de proxy declara uma variável de estado de endereço proxyAdmin e seu valor será lido incorretamente quando o código do contrato lógico for executado.
O valor de proxyAdmin personalizado pela parte do projeto foi erroneamente considerado como o valor de inicializado e inicializado, de modo que o modificador inicializador retornou um resultado errado, o que permitiu ao invasor chamar a função initialize() novamente e conceder a si mesmo autoridade para gerenciar o contrato. Os invasores então alteraram os parâmetros de votação e aprovaram sua proposta maliciosa para roubar os ativos do Audius.
Chame delegatecall() em contrato lógico ou contrato não confiável
Suponha que delegatecall() exista em um contrato lógico e o contrato não valide corretamente o destino da chamada. Nesse caso, um invasor pode explorar essa função para executar chamadas para contratos maliciosos que eles controlam, para subverter implementações de lógica ou para executar lógica personalizada.
Da mesma forma, se houver uma função address.call() irrestrita no contrato lógico, uma vez que o invasor forneça o endereço e os campos de dados de forma maliciosa, ela poderá ser usada como um contrato de proxy.
caso
Pickle Finance, Furucombo e ataques dYdX.
Nesses incidentes, o contrato vulnerável foi aprovado pelo token do usuário e há um call()/delegatecall() no contrato fornecido pelo usuário para chamar o endereço e os dados do contrato, o invasor poderá chamar o contrato de função transferFrom() para sacar saldos do usuário. Durante o incidente dYdX, dYdX realizou seu próprio ataque de chapéu branco para proteger os fundos.
Melhores Práticas
geralmente
(1) Use o modo proxy somente quando necessário
Nem todo contrato precisa ser atualizado. Como mostrado acima, há muitos riscos envolvidos no uso do padrão proxy. A propriedade "atualizável" também gera problemas de confiança, pois os administradores proxy podem atualizar contratos sem o consentimento da comunidade. Recomendamos integrar o padrão de proxy em projetos somente quando necessário.
(2) Não modifique a biblioteca proxy
A biblioteca de contratos de proxy é complexa, especialmente a parte que trata do gerenciamento de armazenamento e dos mecanismos de atualização. Quaisquer erros na modificação afetarão o trabalho dos contratos proxy e lógicos. Um grande número de bugs relacionados a agentes de alta gravidade que encontramos durante nossas auditorias foram causados por modificações incorretas na biblioteca de agentes. O incidente Audius é um excelente exemplo das consequências da modificação imprópria dos contratos de agência.
Pontos-chave da operação e gestão do contrato de agência
(1) Inicialize o contrato lógico
Um invasor pode assumir um contrato lógico não inicializado e potencialmente comprometer o sistema de contrato de proxy. Portanto, inicialize o contrato lógico após a implantação ou use _disableInitializers() no construtor do contrato lógico para desativar automaticamente a inicialização.
(2) Garantir a segurança da conta de gerenciamento do agente
Um sistema de contrato atualizável geralmente requer uma função privilegiada de "administrador proxy" para gerenciar atualizações de contrato. Se a chave de gerenciamento vazar, o invasor pode atualizar livremente o contrato para um contrato malicioso, que pode roubar os ativos dos usuários. Recomendamos o gerenciamento cuidadoso das chaves privadas das contas de administrador proxy para evitar qualquer risco potencial de invasão. As carteiras multi-assinatura podem ser usadas para evitar falhas de gerenciamento de chave de ponto único.
(3) Use uma conta separada para gerenciamento de proxy transparente
O gerenciamento de proxy e a governança lógica devem ser endereços separados para evitar a perda de interação com a implementação lógica. Se o gerenciamento de proxy e a governança lógica se referirem ao mesmo endereço, nenhuma chamada será encaminhada para executar funções privilegiadas, proibindo alterações nas funções de governança.
Relacionado ao armazenamento do contrato de proxy
(1) Tenha cuidado ao declarar variáveis de estado em contratos proxy
Conforme explicado no hack do Audius, os contratos de proxy devem ser cuidadosos ao declarar suas próprias variáveis de estado. Variáveis de estado declaradas da maneira normal em contratos de proxy podem causar conflitos de dados durante a leitura e gravação de dados. Se o contrato de proxy exigir uma variável de estado, salve o valor em um slot de armazenamento como EIP1967 para evitar conflitos ao executar o código de contrato lógico.
(2) Manter a ordem de declaração das variáveis e o tipo do contrato lógico
Cada versão do contrato lógico deve manter a mesma ordem e tipo de variáveis de estado, e novas variáveis de estado precisam ser adicionadas ao final das variáveis existentes. Caso contrário, as chamadas de delegado podem fazer com que os contratos de proxy leiam ou substituam valores armazenados incorretos, e dados antigos podem ser associados a variáveis recém-declaradas, o que pode causar sérios problemas para os aplicativos.
(3) Incluir lacunas de armazenamento no contrato base
Os contratos lógicos precisam incluir lacunas de armazenamento no código do contrato para antecipar novas variáveis de estado ao implantar novas implementações lógicas. Depois de adicionar uma nova variável de estado, o tamanho da lacuna precisa ser atualizado adequadamente.
(4) Não defina o valor da variável de estado no construtor ou no processo de declaração
Atribuir uma variável de estado durante a declaração ou no construtor afeta apenas o valor no contrato lógico, não o contrato proxy. Parâmetros não imutáveis devem ser atribuídos usando a função initialize().
Herança do Contrato
(1) Contratos atualizáveis só podem herdar de outros contratos atualizáveis
Os contratos atualizáveis têm uma estrutura diferente dos contratos não atualizáveis. Por exemplo, o construtor não é compatível com a alteração do estado do agente, ele usa a função initialize() para definir as variáveis de estado.
Qualquer contrato herdado de outro contrato precisa usar a função initialize() de seu contrato herdado para atribuir suas respectivas variáveis. Ao usar a biblioteca OpenZeppelin ou escrever seu próprio código, certifique-se de que os contratos atualizáveis possam herdar apenas outros contratos atualizáveis.
(2) Não instanciar novos contratos em contratos lógicos
Os contratos criados e instanciados por meio do Solidity não poderão ser atualizados. Os contratos devem ser implantados individualmente e passar seu endereço como um parâmetro para o contrato lógico atualizável para atingir um estado atualizável.
(3) Risco de inicialização do contrato principal
Ao inicializar o contrato pai, a função __{ContractName}_init inicializará seu contrato pai. Várias chamadas para __{ContractName}_init podem resultar em uma segunda inicialização do contrato pai. Observe que __{ContractName}_init_unchained() inicializará apenas os parâmetros de {ContractName} e não chamará o inicializador de seu contrato pai.
No entanto, esta não é uma prática recomendada, porque todos os contratos pai precisam ser inicializados, e não inicializar os contratos necessários causará problemas de execução futura.
Implementação do contrato lógico
Evite selfdestruct() ou selegatecall()/call() para contratos não confiáveis
Se houver selfdestruct() ou delegatecall() no contrato, é possível que um invasor use essas funções para interromper a implementação da lógica ou executar a lógica personalizada. Os desenvolvedores devem validar a entrada do usuário e não permitir que os contratos executem chamadas delegadas/chamadas para contratos não confiáveis. Além disso, não é recomendado usar delegatecall() em contratos lógicos porque seria complicado gerenciar o layout de armazenamento na cadeia de delegados de vários contratos.
Escrito no final
Os contratos de proxy ignoram a natureza imutável dos blockchains, permitindo que os protocolos atualizem sua lógica de código após a implantação. No entanto, o desenvolvimento de contratos de proxy ainda precisa ser muito cuidadoso, e a implementação incorreta pode causar problemas de segurança e lógica do projeto.
No geral, a melhor prática é usar soluções autorizadas e amplamente testadas, pois os modos Transparent, UUPS e Beacon Proxy têm mecanismos de atualização comprovados para seus respectivos casos de uso. Além disso, funções privilegiadas para escalar agentes também devem ser gerenciadas com segurança para evitar que invasores alterem a lógica do agente.
O contrato de implementação de lógica também deve ter cuidado para não usar delegatecall(), que pode impedir que invasores executem algum código mal-intencionado, como selfdestruct().
Embora seguir as melhores práticas garanta implantações estáveis de contrato de proxy, mantendo a flexibilidade atualizável, todo o código está sujeito a novos problemas de segurança ou lógica que podem comprometer o projeto. Portanto, todo o código é melhor auditado por uma equipe de especialistas em segurança com experiência em auditoria e proteção de protocolos de contrato de proxy.