Foto de Mitchell Luo no Unsplash
Você já se deu conta da necessidade de rastrear transações de contratos inteligentes enquanto testava ou aprendia com protocolos ou bifurcações de contratos inteligentes?
O hardhat-tracer pode ajudá-lo.
Encontre essa ferramenta aqui: https://github.com/zemse/hardhat-tracer
Do arquivo Readme, fica bastante claro como usar a ferramenta. Ainda assim, eu gostaria de fazer uma apresentação desta ferramenta usando alguns exemplos simples de contratos.
Para esse exemplo, nós veremos a bandeira trace, mas ele também fornece outras bandeiras como fulltrace e hash para rastreamento.
Estes são os contratos que estamos usando:
- Contrato ERC20 simples que cunha o suprimento de token dado
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ERC20Mock is ERC20, Ownable {
constructor(
string memory name
string memory symbol,
uint256 supply)
public ERC20(name, symbol) {
_mint(msg.sender, supply);
}
}
- OtherContract (um contrato que chama contrato EventEmitter para emitir evento):
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./EventEmitter.sol";
contract OtherContract{
address emitter;
function takeTokenAndCallOtherFunction(address _tokenAddr) public {
IERC20(_tokenAddr).transferFrom(msg.sender, address(this), 5 ether);
emitTokensTaken(msg.sender);
}
function setEmitterAddress(address _emitter) external {
emitter=_emitter;
}
function emitTokensTaken(address _from) private {
EventEmitter(emitter).emitTokenFrom(_from);
}
}
EventEmitter (que realmente emite o evento)
pragma solidity 0.8.4;
contract EventEmitter{
event TokensFrom(address From);
function emitTokenFrom(address _from) public {
emit TokensFrom(_from);
}
}
Um rápido fluxo de trabalho:
O chamador aprova o OtherContract para gastar 5 tokens.
O chamador define o endereço do contrato EventEmitter com a função setEmitterAddress que está no contrato OtherContract.
O chamador chama a função takeTokenAndCallOtherFunction **do contrato OtherContract que então, chama a função emitTokensTaken.
A função emitTokensTaken chama a função emitTokenFrom localizada no contrato EventEmitter.
A função emitTokenFrom simplesmente emite o evento TokensFrom.
Instale e defina o hardhat-tracer:
Instale-o com o npm i hardhat-tracer
Acrescente-o ao hardhat.config.js: require(“hardhat-tracer”);
Vamos escrever um caso de teste simples para nossos contratos inteligentes:
No seguinte teste você pode ver quatro chamadas de função:
it("test",async () => {
// Aprove o OtherContract.
console.log("------------------------------------ approve tokens ------------------------------------");
await token1Instance.approve(OtherContractInstance.address,ethers.utils.parseEther("5"));
console.log("----------------------------------------------------------------------------------------\n");
console.log("----------------------------------- set emitter addr -----------------------------------");
await OtherContractInstance.setEmitterAddress(EventEmitterInstance.address);
console.log("----------------------------------------------------------------------------------------\n");
console.log("-------------------------- call takeTokenAndCallOtherFunction --------------------------")
await OtherContractInstance.takeTokenAndCallOtherFunction(token1Instance.address);
console.log("----------------------------------------------------------------------------------------\n")
console.log("---------------------- failed takeTokenAndCallOtherFunction call -----------------------")
await expect(OtherContractInstance.takeTokenAndCallOtherFunction(token1Instance.address)).to.be.revertedWith("ERC20: insufficient allowance");
console.log("----------------------------------------------------------------------------------------\n")
});
- A primeira chamada aprova o endereço OtherContract para gastar 5 Tokens.
- A segunda chamada define o endereço do contrato EventEmitter no OtherContract, usando setEmitterAddress
- A terceira chamada chama takeTokenAndCallOtherFunction com o endereço do token para o qual demos a nossa aprovação.
- E a quarta é para uma chamada malsucedida takeTokenAndCallOtherFunction em que não há tokens aprovados restantes para serem gastos pelo OtherContract.
Vamos rastrear essas chamadas com o hardhat-tracer:
agora basta executar o arquivo de teste com o comando test + a bandeira trace:
eg: > npx hardhat test test/token-trace.js --trace
Você pode ver o rastreamento da transação no terminal:
Test screenshot 1
O hardhat-tracer permite que você defina tags de nome Address para identificar endereços. Você pode adicioná-las antes das chamadas de teste ou após criações de instância de contrato para definir endereços como este:
hre.tracer.nameTags[OtherContractInstance.address] = "OtherContract";
hre.tracer.nameTags[EventEmitterInstance.address] = "EventEmitter";
hre.tracer.nameTags[token1Instance.address] = "TKN1";
hre.tracer.nameTags[alice.address] = "Alice";
Uma vez que estas tags são adicionadas, você pode ver nomes/tags diretos para endereços no rastreamento:
Na captura de tela acima, você pode ver rastreamentos para a criação de contratos com argumentos do construtor e propriedade sendo transferidos do endereço zero para o remetente msg.sender.
Chegando à primeira chamada de caso de teste, a chamada para aprovação de tokens:
A segunda chamada é OtherContract.setEmitterAddress() para definir o endereço do remetente.
A terceira é uma chamada interessante, que basicamente chama outra função que então, chama novamente a função no contrato Emitter. A captura de tela abaixo mostra os rastreamentos para a função OtherContract.takeTokenAndCallOtherFunction() que chama a função transferFrom(), que então estabelece a aprovação para zero (aprovação foi de 5 tokens) e transfere 5 tokens para o endereço OtherContract. Você pode ver os rastros do evento para Approval e Transfer.
Depois disso ele chama a função emitTokensTaken() que então, chama a função emitTokenFrom() no contrato do remetente que finalmente emite o evento TokensFrom. Você pode ver isso na captura de tela abaixo:
Finalmente, a última é para chamada takeTokenAndCallOtherFunction() malsucedida que será revertida. Aqui você poderá ver que a takeTokenAndCallOtherFunction() está chamando transferFrom() que está falhando por causa da permissão 0 (a função takeTokenAndCallOtherFunction precisa de pelo menos uma permissão de 5 tokens). E então você pode ver uma mensagem de retorno por insuficiência de permissão.
O exemplo acima foi bem pequeno e não tem chamadas externas de contrato complexas, mas ao passar por códigos complexos como bifurcações de grandes projetos e mesmo com uma rede bifurcada que contém muitas chamadas, o rastreamento é muito útil para entender o que está acontecendo em nível de código.
Sobre QuillAudits
Empresa líder em auditoria de contratos inteligentes, comprometida com a segurança de projetos de Blockchain com soluções de segurança Web3 de última geração.
É uma plataforma de auditoria que analisa e verifica rigorosamente contratos inteligentes para detectar vulnerabilidades de segurança por meio de revisão manual eficaz com ferramentas de análise estática e dinâmica, analisadores de gas, bem como simuladores. Além disso, o processo de auditoria também inclui testes unitários extensivos, bem como análise estrutural.
Realizamos tanto auditorias de contratos inteligentes quanto testes de invasão para encontrar potenciais vulnerabilidades de segurança que podem prejudicar a integridade da plataforma.
Para mais discussões e consultas sobre o mesmo tópico, junte-se à discussão em Grupo do Telegram QuillHash —
Para estar em dia com o nosso trabalho, junte-se à nossa comunidade:-
Telegram | Twitter | Facebook | LinkedIn
Este artigo foi escrito por Prajwal More e traduzido por Fátima Lima. O original pode ser lido aqui.
Latest comments (0)