Somente para fins educacionais; não constitui aconselhamento de investimento. Investimentos podem causar perdas.
Resposta direta
Uma colisão de armazenamento do proxy ocorre quando o código de implementação lê ou grava um slot do proxy com significado diferente daquele definido pelo layout que criou o estado existente. Um upgrade pode fazer um saldo parecer um endereço, apagar o proprietário, corromper o slot-base de um mapping ou sobrescrever dados de controle do upgrade.
Com delegatecall, o bytecode da implementação é executado no contexto do proxy: armazenamento, saldo e address(this) pertencem ao proxy. Nomes de variáveis não ficam registrados on-chain; a EVM segue apenas o slot e o deslocamento em bytes calculados pelo novo código.
Por isso, um upgrade precisa preservar o layout implantado, não apenas compilar ou expor as mesmas funções. Antes da autorização, compare os layouts gerados pelo compilador com a versão exata implantada.
Como funciona
Normalmente, Solidity posiciona variáveis de estado a partir do slot 0, na ordem de declaração após a linearização C3 da herança. Valores menores que 32 bytes podem compartilhar um slot; structs e arrays têm regras adicionais, enquanto mappings e arrays dinâmicos derivam seus dados de um slot-base. Se esse slot mudar, a localização dos dados derivados também muda.
Há quatro limites de colisão distintos a auditar:
- Proxy versus implementação: campos do proxy, como implementação e administrador, não podem ocupar slots usados pelo estado da aplicação. O ERC-1967 define slots padronizados, evitados pelo compilador, para implementação, beacon e administrador.
- Implementação antiga versus nova: variáveis existentes precisam manter slots, deslocamentos e tipos compatíveis. Acrescentar uma variável no fim pode ser seguro; inserir, reordenar, remover ou mudar o tipo pode reinterpretar palavras existentes.
- Herança: adicionar estado a um contrato-base ou mudar a ordem de herança pode deslocar o armazenamento dos descendentes mesmo sem alterar o código-fonte deles.
- Armazenamento reservado ou com namespace: um gap usado corretamente reserva espaço para um contrato-base, e namespaces no estilo ERC-7201 isolam layouts. Nenhuma técnica permite mudanças arbitrárias dentro de um layout existente.
Exemplo
Suponha que a versão 1 tenha este layout:
uint256 totalAssets; // slot 0
address owner; // slot 1A versão 2 insere incorretamente uma variável no início:
bool paused; // slot 0, offset 0
uint256 totalAssets; // slot 1
address owner; // slot 2Após o upgrade, paused lê o byte inferior do antigo totalAssets, o novo totalAssets lê a palavra do antigo owner como inteiro e owner lê o que já estava no slot 2, geralmente zero. As palavras originais continuam armazenadas, mas o novo código lhes atribui outros significados. Uma transação pode ter sucesso enquanto aplica autorização ou contabilidade a interpretações corrompidas.
Riscos e verificações de upgrade
- Gere o layout de armazenamento das duas implementações e compare slot, deslocamento, tipo e herança com o contrato de referência realmente implantado.
- Em um layout linear convencional, adicione variáveis somente no fim. Não reordene campos, mude tipos, remova e reutilize nem altere contratos-base sem provar compatibilidade.
- Ao consumir um gap, reduza-o pelo número exato de slots reservados usados. Com namespaces, mantenha identificadores únicos e valide mudanças dentro de cada namespace existente.
- Não trate o ERC-1967 como proteção completa. Ele separa metadados do proxy dos slots da aplicação atribuídos pelo compilador, mas não torna compatíveis dois layouts de implementação.
- Teste o upgrade e qualquer reinicializador em um fork ou snapshot de estado. Verifique antes e depois proprietários, funções, saldos, allowances, entradas de mappings, pausa, slots de implementação e controles de reversão ou emergência.
Se um upgrade já pode ter causado uma colisão, interrompa novos upgrades e chamadas que alterem estado quando a governança permitir. Preserve o número do bloco anterior e o bytecode, compare o armazenamento bruto dos slots afetados e peça a especialistas um plano de migração com revisão independente. Repetir upgrades sem um mapa de armazenamento comprovado pode destruir mais estado recuperável.
Equívocos comuns
- “Os nomes não mudaram, então o layout é seguro.” Nomes não determinam posições. Tipos, ordem, empacotamento, herança e namespaces determinam.
- “Excluir uma variável libera seu slot.” O armazenamento do proxy persiste. Reutilizá-lo atribui novo significado à palavra antiga, salvo se uma migração revisada a apagar ou transformar.
- “Uma transação de teste bem-sucedida prova compatibilidade.” Ela pode tocar poucos slots. A validação e os testes de diferença de estado devem cobrir campos privilegiados, valores empacotados, mappings, arrays e armazenamento herdado.
Tópicos relacionados
- Risco de armazenamento do delegatecall
- Tomada do inicializador
- Contrato proxy
- Monitoramento de upgrades do proxy
- Contrato atualizável
Fontes
- Introducción a los contratos inteligentes - Solidity Documentation (acesso: 2026-08-21)
- Layout de variables de estado en almacenamiento - Solidity Documentation (acesso: 2026-08-21)
- ERC-1967: slots de almacenamiento de proxy - Ethereum Improvement Proposals (acesso: 2026-08-21)
- Cómo escribir contratos actualizables - OpenZeppelin Documentation (acesso: 2026-08-21)