Você provavelmente já ouviu falar sobre todas as divulgações épicas no espaço de recompensas de bugs da Web3 recentemente e decidiu que talvez seja hora de abrir caminho para o Hall da Fama da Immunefi. Ou talvez você seja um hacker experiente e do bem, que usa o Hardhat para provar o conceito de seus indícios de bugs e ouviu falar sobre essa coisa brilhante chamada Foundry, mas não tem certeza se vale a pena mudar?
Este tutorial fornecerá uma introdução bem básica ao Foundry. Como parte deste tutorial, faremos uma Prova de Conceito (PoC, ou Proof of Concept) de teste simples e a executaremos. Na parte 2 desta série de tutoriais, mergulharemos em um exemplo de PoC mais detalhado usando uma vulnerabilidade da vida real.
Este artigo foi escrito por cergyk.eth.
Confira a parte 2 do tutorial aqui.
Instalação do Foundry e Bifurcação (Forking)
Em primeiro lugar, vamos instalar o Foundry com as instruções fornecidas aqui.
O forge init
irá inicializar um projeto de teste básico com a seguinte estrutura:
├── foundry.toml
├── lib
│ └── forge-std
├── script
│ └── Contract.s.sol
├── src
│ └── Contract.sol
└── test
└── Contract.t.sol
Podemos ver que foi definido um teste básico (Contract.t.sol
), e uma biblioteca padrão (forge-std
) também está disponível na pasta lib. (Esta biblioteca será útil para operar alguns truques na máquina virtual do Foundry, mas falarei mais sobre isso mais tarde).
Vamos configurar os detalhes da cadeia que queremos bifurcar - no nosso caso, a rede principal (Mainnet) da Ethereum. Para isso, altere a configuração do projeto no arquivo foundry.toml
:
[default]
src = 'src'
out = 'out'
libs = ['lib']
# Adicione estas linhas:
chain_id = 1
eth_rpc_url = 'https://eth-mainnet.alchemyapi.io/v2/{sua_chave_de_api_da_alchemy}'
block_number = 14812830
etherscan_api_key = '{sua_chave_de_api_do_etherscan}'
Observe que fornecer uma chave etherscan_api_key
melhora muito a legibilidade dos rastreamentos do Forge quando executados no modo detalhado.
Obtendo Fontes do Projeto
Agora, temos que obter as fontes do endereço implantado que queremos testar. Vamos pegar o YOP Finance como exemplo (confira o programa deles no Immunefi).
Não entrarei em detalhes sobre nenhuma vulnerabilidade específica em nenhum projeto específico. Uma redação completa será assunto de outro artigo, portanto, fique atento! Aqui vamos simplesmente configurar um script que faz o stake de alguns tokens YOP no protocolo.
O Foundry nos permite escrever nossos testes no Solidity, então podemos usar o código do contrato implantado e compilar nosso teste nele! Para baixar o código implantado, criei uma ferramenta de linha de comando que você pode conferir aqui: ethereum-sources-downloader.
npm i -g ethereum-sources-downloader
# Baixa fontes de staking na biblioteca.
ethereum-sources-downloader etherscan 0x5B705d7c6362A73fD56D5bCedF09f4E40C2d3670 lib
A ferramenta cria o diretório lib/StakingV2
, contendo as fontes do contrato implantado e suas dependências.
Vamos importar os contratos-alvo em nosso contrato de teste, Contract.t.sol
:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
IERC20 yopToken = IERC20(0xAE1eaAE3F627AAca434127644371b67B18444051);
StakingV2 staking = StakingV2(0x5B705d7c6362A73fD56D5bCedF09f4E40C2d3670);
address attacker = address(1); //...
}
Neste ponto, ao executar o comando forge test
, recebemos um aviso de que não foi possível encontrar os arquivos do @openzeppelin. Isso é esperado, pois eles são baixados na mesma pasta do projeto e o Foundry só conhece uma raiz para as dependências, que é lib/
.
É hora de aprender sobre outro recurso útil no Foundry, que são os remapeamentos.
O comando forge remappings
nos fornece:
StakingV2/=lib/StakingV2/
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
O que mostra a razão de lib
ser a raiz para as bibliotecas que importamos! Por sorte, podemos adicionar um novo remapeamento para a biblioteca @openzeppelin importada pelo projeto. Criamos o arquivo remappings.txt
na raiz do nosso projeto e acrescentamos a linha:
@openzeppelin/=lib/StakingV2/@openzeppelin/
Agora forge test
é executado com sucesso!
Faça o Airdrop de alguns tokens, Personifique e faça o Stake
Uma característica interessante do Foundry é que podemos sobrescrever o estado vinculado à exibição de um contrato. Vamos usar esse recurso para nos dar alguns tokens. Confira aqui as explicações detalhadas!
Vamos nos dar 500 unidades do token na configuração de teste:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
//...
function writeTokenBalance(
address who,
IERC20 token,
uint256 amt
) internal {
stdstore
.target(address(token))
.sig(token.balanceOf.selector)
.with_key(who)
.checked_write(amt);
} function setUp() public {
writeTokenBalance(attacker, yopToken, 500 ether);
}
//...
}
Agora temos que personificar o endereço que escolhemos para o atacante e chamar o protocolo. Precisamos trapacear um pouco para fazer isso. Os códigos de trapaça do Foundry são úteis: vm.startPrank(address)
nos permite personificar qualquer endereço!
#Contract.t.sol:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Test.sol";
import "StakingV2/contracts/staking/StakingV2.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ContractTest is Test {
//...
function testExample() public {
vm.startPrank(attacker);
uint8 lock_duration_months = 1;
yopToken.approve(address(staking), 500 ether);
staking.stake(500 ether, lock_duration_months);
}
}
Executamos o teste novamente com forge test
e pronto!
Running 1 test for test/Contract.t.sol:ContractTest
[PASS] testExample() (gas: 314937)
Test result: ok. 1 passed; 0 failed; finished in 9.30s
O código completo deste tutorial está disponível no GitHub.
Siga-me no Twitter para mais conteúdo de segurança Web3!
Artigo original escrito por cergyk.eth. Traduzido por Paulinho Giovannini.
Top comments (0)