16 de março de 2023
Os contratos inteligentes desempenham um papel crucial no mundo em rápida evolução das finanças descentralizadas. Esses contratos autoexecutáveis revolucionaram a forma como fazemos transações, oferecendo uma ampla gama de aplicações na blockchain Ethereum. No entanto, eles também apresentam potenciais riscos de segurança. Neste artigo, exploraremos como a IA pode ajudar na realização de auditorias de segurança para contratos inteligentes da Ethereum, fornecendo trechos de código, práticas recomendadas e outros insights valiosos.
Análise estática alimentada por Inteligência Artificial (IA)
Uma das técnicas mais populares para auditoria de segurança de contratos inteligentes é a análise estática. Ferramentas de análise estática alimentadas por IA podem verificar a base de código do contrato, identificando vulnerabilidades e fornecendo sugestões de correção.
Por exemplo, a popular ferramenta de análise de segurança baseada em IA, Mythril, pode ser usada para realizar análises estáticas em seus contratos inteligentes. Para usar o Mythril, primeiro instale-o:
$ pip install mythril
Em seguida, analise seu contrato inteligente:
$ myth analyze <contract.sol>
O Mythril fornecerá informações detalhadas sobre as vulnerabilidades encontradas, juntamente com sugestões sobre como corrigi-las.
Teste de Fuzz automatizado
O teste de fuzz é uma técnica essencial para descobrir vulnerabilidades em contratos inteligentes. Envolve fornecer informações aleatórias, inesperadas ou malformadas a um contrato para desencadear erros ou comportamento não intencional. A IA pode ser usada para gerar entradas de teste de forma inteligente, tornando o processo de teste de fuzz mais eficaz.
Uma dessas ferramentas é o Echidna, um fuzzer alimentado por IA para contratos inteligentes da Ethereum:
$ git clone https://github.com/crytic/echidna.git
$ cd echidna
$ stack install
Crie uma função de teste personalizada em seu contrato, assim:
$ echidna-test <contract.sol>
Testes unitários gerados por IA
Os testes unitários são essenciais para garantir a correção e a segurança dos contratos inteligentes. A IA pode ser usada para gerar testes unitários para seus contratos inteligentes, abrangendo diferentes casos e cenários extremos.
Ferramentas como o DeepTest podem gerar automaticamente casos de teste para seus contratos inteligentes. Para usar o DeepTest, instale-o via npm:
$ npm install -g deeptest
Em seguida, gere casos de teste para seu contrato inteligente:
$ deeptest <contract.sol>
O DeepTest gerará casos de teste que você pode usar para validar a funcionalidade e a segurança do seu contrato.
Melhores Práticas
Ao conduzir auditorias de segurança para contratos inteligentes da Ethereum, siga estas práticas recomendadas:
- Use bibliotecas e estruturas de contratos inteligentes bem avaliadas, como o OpenZeppelin.
- Mantenha seus contratos simples e modulares, facilitando a auditoria.
- Aplique o princípio do menor privilégio, garantindo que as funções do contrato tenham as restrições de acesso apropriadas.
- Incorpore casos de teste completos e bem documentados para validar o comportamento do seu contrato sob diferentes condições.
- Atualize continuamente seu conhecimento sobre as melhores práticas de segurança e vulnerabilidades comuns da Ethereum.
Vamos dar um passo adiante com um teste
O contrato inteligente em Solidity a seguir demonstra um exemplo contendo vulnerabilidades como ataques de reentrância, front-running e erros de lógica simples. Este contrato é intencionalmente inseguro e não deve ser usado em um cenário do mundo real.
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract VulnerableContract {
IERC20 public token;
address payable public owner;
mapping(address => uint256) public balances;
constructor(IERC20 _token) {
token = _token;
owner = payable(msg.sender);
}
// Esta função é vulnerável a ataques de reentrância.
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Saldo insuficiente");
// Vulnerabilidade à reentrância
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Falha na retirada");
balances[msg.sender] -= amount;
}
// Esta função é vulnerável a front-running.
function buyTokens(uint256 amount) external payable {
uint256 price = getTokenPrice();
require(msg.value >= price * amount, "Saldo insuficiente");
// Vulnerabilidade ao Front-running
token.transfer(msg.sender, amount);
}
// Esta função contém um simples erro de lógica.
function getTokenPrice() public view returns (uint256) {
// Erro de lógica: o preço deve ser calculado usando um oráculo válido ou uma fonte confiável.
return 1 ether;
}
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function() external payable {}
}
Esse contrato demonstra as seguintes vulnerabilidades:
- Ataque de reentrância: a função
withdraw
é vulnerável a ataques de reentrância porque envia Ether ao usuário antes de atualizar o saldo. Um invasor pode explorar essa vulnerabilidade criando uma função de fallback em seu contrato que chamawithdraw
novamente, drenando efetivamente o Ether do contrato. - Front-running: a função
buyTokens
é vulnerável a ataques de front-running, uma vez que não possui nenhuma proteção contra manipulação de ordens de transação. Os invasores podem observar as transações pendentes e inserir suas próprias transações com preços de gás mais altos para comprar tokens antes de outros, potencialmente manipulando os preços dos tokens. - Erro de lógica simples: a função
getTokenPrice
contém um erro de lógica, pois retorna um valor de Ether codificado em vez de usar um oráculo de preços confiável. Isso pode levar a preços incorretos dos tokens e a consequências indesejadas quando os usuários compram tokens.
Novamente, esse contrato é intencionalmente inseguro e não deve ser utilizado em nenhum cenário do mundo real. Serve como um exemplo de como não escrever um contrato inteligente e destaca a importância de realizar auditorias de segurança completas.
Aqui, irei guiá-lo através do processo de auditoria e correção do contrato inteligente usando as ferramentas de IA Mythril, Echidna e DeepTest, onde abordaremos os ataques de reentrância, de front-running e erros de lógica simples.
Auditoria com o Mythril
O Mythril é uma ferramenta popular de análise de segurança que realiza análises estáticas em seus contratos inteligentes. Primeiro, instale-o:
$ pip install mythril
Em seguida, analise seu contrato inteligente:
$ myth analyze <contract.sol>
O Mythril fornecerá informações detalhadas sobre as vulnerabilidades encontradas, juntamente com sugestões sobre como corrigi-las. No nosso caso, ele deve identificar a vulnerabilidade de ataque de reentrância na função withdraw
.
Correção de ataque de reentrância
Para evitar ataques de reentrância, podemos usar o padrão Checks-Effects-Interactions (Verificação-Efeitos-Interações). Atualize o saldo antes de transferir os fundos:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Saldo insuficiente");
// Atualiza o saldo primeiro (padrão checks-effects-interactions)
balances[msg.sender] -= amount;
// Depois, transfere os fundos
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Falha na retirada");
}
Auditoria com o Equidna
O Echidna é um fuzzer alimentado por IA para contratos inteligentes da Ethereum. Primeiro, instale-o:
$ git clone https://github.com/crytic/echidna.git
$ cd echidna
$ stack install
Crie uma função de teste personalizada em seu contrato, assim:
pragma solidity ^0.8.0;
contract MyContract {
// ...
function echidna_test() public view returns (bool) {
// Adicione sua lógica de teste aqui.
}
}
Em seguida, execute o Echidna em seu contrato inteligente:
$ echidna-test <contract.sol>
O Echidna identificará vulnerabilidades e possíveis casos extremos que podem levar a problemas como front-running.
Correção do front-runnig
Para evitar o front-running, podemos usar um esquema de commit-reveal (compromisso-revelação) que dissocia a intenção de compra do token da compra real do token. Os usuários se comprometem a comprar tokens enviando um compromisso com hash e posteriormente revelam sua intenção, permitindo que o contrato processe a compra por ordem de compromisso.
// Define uma nova estrutura para compromissos de compra
struct Commitment {
uint256 amount;
uint256 price;
uint256 nonce;
}
mapping(address => bytes32) public commitments;
function commit(uint256 amount, uint256 price, uint256 nonce) external {
bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
commitments[msg.sender] = hashedCommitment;
}
function reveal(uint256 amount, uint256 price, uint256 nonce) external payable {
bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
require(commitments[msg.sender] == hashedCommitment, "Compromisso inválido");
uint256 totalPrice = price * amount;
require(msg.value >= totalPrice, "Saldo insuficiente");
// Transfere os tokens depois de verificar os compromissos e o pagamento.
token.transfer(msg.sender, amount);
// Apaga o compromisso depois da compra bem sucedida de um token
delete commitments[msg.sender];
}
Auditoria com o DeepTest
O DeepTest é uma ferramenta alimentada por IA que pode gerar automaticamente casos de teste para seus contratos inteligentes. Primeiro, instale-o via npm:
$ npm install -g deeptest
Em seguida, gere casos de teste para seu contrato inteligente:
$ deeptest <contract.sol>
O DeepTest irá gerar casos de teste que podem ajudá-lo a identificar problemas como o erro de lógica simples na função getTokenPrice
.
Correção de erro de lógica simples
Para corrigir o erro de lógica na função getTokenPrice
, você deve usar um oráculo de preços confiável, como a Chainlink, para obter um preço exato do token.
Primeiro, instale o pacote Chainlink:
npm install @chainlink/contracts
Em seguida, atualize o contrato para usar o oráculo de preços da Chainlink:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract FixedContract {
IERC20 public token;
address payable public owner;
mapping(address => uint256) public balances;
AggregatorV3Interface public priceOracle;
constructor(IERC20 _token, AggregatorV3Interface _priceOracle) {
token = _token;
owner = payable(msg.sender);
priceOracle = _priceOracle;
}
// ... (outras funções)
// Substitui a função getTokenPrice anterior por isto:
function getTokenPrice() public view returns (uint256) {
(, int256 price, , , ) = priceOracle.latestRoundData();
require(price > 0, "Preço de token inválido");
return uint256(price);
}
}
Nesta versão revisada do contrato inteligente, os ataques de reentrância são evitados seguindo o padrão de verificações-efeitos-interações, o front-running é mitigado usando um esquema de compromisso-revelação e o erro de lógica na função getTokenPrice
é resolvido usando um oráculo de preços confiável. Ao implementar essas mudanças e aderir às melhores práticas, seu contrato inteligente ficará mais seguro.
É importante observar que nenhuma ferramenta automatizada pode garantir a segurança completa de um contrato inteligente. Embora ferramentas de IA como Mythril, Echidna e DeepTest possam ajudar a identificar vulnerabilidades, você também deve realizar revisões manuais de código e considerar a contratação de auditores profissionais para garantir o mais alto nível de segurança para seus contratos inteligentes.
Aqui está o contrato inteligente auditado completo com as vulnerabilidades identificadas corrigidas:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract AuditedContract {
IERC20 public token;
address payable public owner;
mapping(address => uint256) public balances;
AggregatorV3Interface public priceOracle;
struct Commitment {
uint256 amount;
uint256 price;
uint256 nonce;
}
mapping(address => bytes32) public commitments;
constructor(IERC20 _token, AggregatorV3Interface _priceOracle) {
token = _token;
owner = payable(msg.sender);
priceOracle = _priceOracle;
}
// Evita ataques de reentrância
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Saldo insuficiente");
// Atualiza o saldo primeiro (padrão checks-effects-interactions)
balances[msg.sender] -= amount;
// Depois, transfere os fundos
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Falha na retirada");
}
// Evita ataques de front-running
function commit(uint256 amount, uint256 price, uint256 nonce) external {
bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
commitments[msg.sender] = hashedCommitment;
}
function reveal(uint256 amount, uint256 price, uint256 nonce) external payable {
bytes32 hashedCommitment = keccak256(abi.encodePacked(msg.sender, amount, price, nonce));
require(commitments[msg.sender] == hashedCommitment, "Compromisso inválido");
uint256 totalPrice = price * amount;
require(msg.value >= totalPrice, "Saldo insuficiente");
// Transfere os tokens depois de verificar o compromisso e o pagamento.
token.transfer(msg.sender, amount);
// Apaga o compromisso depois de uma compra de token bem sucedida.
delete commitments[msg.sender];
}
// Usa um oráculo de preço confiável para corrigir o erro de lógica
function getTokenPrice() public view returns (uint256) {
(, int256 price, , , ) = priceOracle.latestRoundData();
require(price > 0, "Preço de token inválido");
return uint256(price);
}
function deposit() external payable {
balances[msg.sender] += msg.value;
}
receive() external payable {}
}
Este contrato inteligente aborda as vulnerabilidades discutidas anteriormente ao longo do artigo:
- Os ataques de reentrância são evitados seguindo o padrão verificação-efeitos-interações na função
withdraw
. - Os ataques de front-running são mitigados usando um esquema de compromisso-revelação nas funções
commit
ereveal
. - O erro de lógica na função
getTokenPrice
é corrigido usando um oráculo de preços confiável, a Chainlink, para obter um preço exato de token.
Este contrato auditado é mais seguro, mas considere sempre realizar revisões manuais de código e consultar auditores profissionais para garantir o mais alto nível de segurança para seus contratos inteligentes.
Conclusão
A auditoria de segurança assistida por IA pode aumentar muito a segurança dos contratos inteligentes da Ethereum, oferecendo informações valiosas e recomendações para melhorias. Ao incorporar ferramentas alimentadas por IA, como Mythril, Echidna e DeepTest, em seu processo de auditoria, você pode identificar e resolver vulnerabilidades de maneira mais eficaz. Além disso, seguir as melhores práticas para o desenvolvimento de contratos inteligentes ajudará a garantir a segurança e o sucesso a longo prazo dos seus aplicativos descentralizados.
Esse artigo foi escrito por Javier Calderon Jr e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Top comments (0)