Segurança na Web3: Desmistificando Imutabilidade e Elevando Padrões de Código

Bellum Galaxy
bellum-galaxy-community
4 min readMar 19, 2024

Este artigo foi escrito por Barba.

À medida que avançamos para um novo ciclo de mercado no ecossistema web3, percebemos que, para uma vasta parcela da população mundial, alguns conceitos básicos desse ecossistema ainda são obscuros, confusos e até desconhecidos. Portanto, nesta e na próxima semana, focaremos em artigos mais técnicos. Hoje, abordaremos um ponto chave: a Segurança.

Infelizmente, é comum ouvir adeptos da web3 confundirem imutabilidade com segurança. A imutabilidade garante que o contrato inteligente com o qual você interage não será alterado para beneficiar terceiros. Contudo, isso não assegura que o contrato esteja livre de bugs, erros de lógica ou brechas que possam prejudicar os usuários em momentos oportunos.

É responsabilidade do desenvolvedor empenhar-se ao máximo para elevar a segurança do seu código. E, como bem salientou Patrick Collins, “é responsabilidade do auditor fazer o seu melhor para encontrar e reportar toda e qualquer vulnerabilidade em um código”.

Focando em vulnerabilidades acidentais, existem inúmeros vetores que podem levar a exploits. Um exploit pode não ocorrer sempre de forma maliciosa, como em um ataque. Isso significa que um contrato imutável pode estar suscetível a vulnerabilidades. Portanto, imutabilidade não equivale a segurança, e é por isso que códigos claros e auditorias são cruciais na web3.

Vamos explorar um cenário simples:

  1. Você está verificando um contrato no Etherscan;
  2. Uma função chama sua atenção, você interage com essa função, e tudo ocorre normalmente.
  3. Então você identifica uma função chamada “Kill” e a executa.
  4. Para sua surpresa, a função também é executada corretamente.

Qual o resultado? Você “deleta” o contrato! Isso parece absurdo, mas aconteceu, e o prejuízo foi significativo. Existe um contexto por trás desse acontecimento, mas o ponto principal é:

Como minimizar o risco e facilitar a identificação de vulnerabilidades durante revisões de código e auditorias?

Auditorias são processos extremamente necessários e têm um alto custo. Logo, precisamos facilitar ao máximo o trabalho dos auditores para que possam se concentrar em encontrar vulnerabilidades em vez de tentar decifrar códigos complexos e obsoletos.

O que veremos a seguir é o resultado de dois princípios básicos: Disciplina e Organização.

Layout

Se você desenvolve, qual é o layout dos seus Contratos Inteligentes? Como desenvolvedores, nossa tarefa é manter códigos limpos, claros, objetivos e bem documentados, facilitando o entendimento por qualquer pessoa que os acesse. Informações adicionais sobre isso podem ser encontradas na documentação do Solidity.

Nesse link você pode acessar o layout que eu uso para desenvolver os projetos da Bellum Galaxy e parceiros.

Bellum Galaxy Security & Auditing logo.

Nomenclatura

Erros

Erros são uma maneira eficiente de indicar que uma ação não pode ser executada por algum motivo, além de auxiliar na depuração do código. É importante que os erros sejam descritivos para facilitar o entendimento e economizar gás.

Erros são comumente declarados da seguinte forma:

error DeuRuimCara();

Observando esse erro customizado, podemos identificar:

  1. Deu
  2. Ruim
  3. Cara

Isso representa um desperdício de gás e em alguns casos podem não ser tão descritivo.

A maneira recomendada de utilizar erros customizados é:

error NomeDoContrato_OValorInseridoEhMenorQueOValorPermitido(uint256 valorInserido, uint256 valorPermitido);

Agora, as informações que conseguimos identificar são:

  • Nome do contrato onde o erro foi disparado;
  • O motivo pelo qual o erro foi disparado;
  • O valor que você inseriu;
  • O valor mínimo permitido.

Variáveis

A nomeação das variáveis deve refletir o seu tipo de armazenamento, adotando padrões claros que facilitem a identificação e organização do código. Vejamos um exemplo que não deve ser seguido:

contract Exemplo {
uint256 public umNumero;
uint256 public immutable doisNumeros;
uint256 public constant tresNumeros = 3;

constructor(uint256 doisNumerosUm){
doisNumeros = doisNumerosUm;
}

function umaFuncao(uint256 umOutroNumero) public returns(uint256){
uint256 maisUmNumero = umOutroNumero + 50;
return maisUmNumero;
}
}

Embora neste contrato seja fácil identificar qual variável é qual, o que aconteceria se ele tivesse 600 linhas de código?

Para facilitar o entendimento, a organização e até mesmo a sua própria vida na revisão do código, por que não adotar este método:

contract Exemplo {
//Inicia com s_, de storage
uint256 public s_umNumero;

//Inicia com i_, de immutable, seguido do nome da variável
uint256 public immutable i_doisNumeros;

//Caixa alta, separado por _
uint256 public constant TRES_NUMEROS = 3;

//Remoção de "número mágico"
uint256 public constant BONUS_DA_FUNCAO = 50;

//Incluímos um _ antes do nome da variável
//indicando que é um parâmetro da função.
constructor(uint256 _doisNumerosUm){
i_doisNumeros = _doisNumerosUm;
}

//Incluímos um _ antes do nome da variável
//indicando que é um parâmetro da função.
function umaFuncao(uint256 _umOutroNumero) public returns(uint256){
//Substituímos o "número mágico", 50, pelo nome descritivo.
uint256 maisUmNumero = _umOutroNumero + BONUS_DA_FUNCAO;

return maisUmNumero;
}
}

Existem inúmeros ajustes que ainda podem ser feitos no que diz respeito à economia de gás, etc. Mas não é o momento oportuno, estamos focando no básico.

Eventos

Assim como os erros, os eventos devem ser descritivos e emitidos sempre após alterações no storage, aumentando a transparência e a rastreabilidade das operações.

contract UmContrato{
uint256 public s_umaVariavel;

event UmContrato_UmaVariavelFoiAtualizada(uint256 valorAnterior, uint256 valorAtual);

function umaFuncao(uint256 _umParametro) public {
uint256 umaVariavelNaMemoria = s_umaVariavel;
s_umaVariavel = _umParametro;

emit UmContrato_UmaVariavelFoiAtualizada(umaVariavelNaMemoria, _umParametro);
}
}

Funções

A nomenclatura das funções deve indicar sua visibilidade, diferenciando claramente aquelas que são acessíveis externamente das que são internas ou privadas.

//Funções que podem ser acessadas externamente, sejam públicas ou externas
function funcaoPublicaOuExterna() public{}
//Funções que são acessadas só internamente, sejam internas ou privadas
function _funcaoInternaOuExterna() private{}

Conclusão

A verificação da segurança de um contrato inteligente não é a última etapa do desenvolvimento, mas um aspecto que deve ser considerado desde a criação do arquivo até a finalização de toda a documentação, auditorias e plano de contingência pós-deploy. Como vimos, com organização e disciplina, é possível seguir padrões básicos que refletem diretamente na segurança do projeto.

Conecte-se Conosco:

Visite nosso site, junte-se ao nosso Discord, siga-nos no X, Instagram e no LinkedIn para ficar por dentro de nossas aventuras e insights.

--

--

Bellum Galaxy
bellum-galaxy-community

An educational community to bring science and technology to all.