A Biblioteca Padrão do Forge é uma coleção de contratos e bibliotecas úteis para uso com o Forge e o Foundry. Ele aproveita os cheatcodes do Forge para tornar a escrita de testes mais fácil e rápida, ao mesmo tempo que melhora a UX dos cheatcodes.
Aprenda como usar o Forge-Std com o Foundry Book (Guia Forge-Std).
forge install foundry-rs/forge-std
Este é um contrato auxiliar para erros e reversões. No Forge, este contrato é particularmente útil para o cheatcode expectRevert
, pois fornece todos os erros internos do compilador.
Consulte o próprio contrato para todos os códigos de erro.
import " forge-std/Test.sol " ;
contract TestContract is Test {
ErrorsTest test;
function setUp () public {
test = new ErrorsTest ();
}
function testExpectArithmetic () public {
vm. expectRevert (stdError.arithmeticError);
test. arithmeticError ( 10 );
}
}
contract ErrorsTest {
function arithmeticError ( uint256 a ) public {
uint256 a = a - 100 ;
}
}
Este é um contrato bastante grande devido a toda a sobrecarga para tornar a UX decente. Principalmente, é um wrapper em torno do record
e accesses
cheatcodes. Ele sempre pode localizar e gravar o(s) slot(s) de armazenamento associado(s) a uma variável específica sem conhecer o layout de armazenamento. A única ressalva importante é que, embora um slot possa ser encontrado para variáveis de armazenamento compactadas, não podemos gravar nessa variável com segurança. Se um usuário tentar gravar em um slot compactado, a execução gerará um erro, a menos que não seja inicializado ( bytes32(0)
).
Isso funciona gravando todos os SLOAD
se SSTORE
durante uma chamada de função. Se houver um único slot lido ou gravado, ele retornará imediatamente o slot. Caso contrário, nos bastidores, iteramos e verificamos cada um deles (assumindo que o usuário passou um parâmetro depth
). Se a variável for uma estrutura, você pode passar um parâmetro depth
que é basicamente a profundidade do campo.
Ou seja:
struct T {
// depth 0
uint256 a;
// depth 1
uint256 b;
}
import " forge-std/Test.sol " ;
contract TestContract is Test {
using stdStorage for StdStorage;
Storage test;
function setUp () public {
test = new Storage ();
}
function testFindExists () public {
// Lets say we want to find the slot for the public
// variable `exists`. We just pass in the function selector
// to the `find` command
uint256 slot = stdstore. target ( address (test)). sig ( " exists() " ). find ();
assertEq (slot, 0 );
}
function testWriteExists () public {
// Lets say we want to write to the slot for the public
// variable `exists`. We just pass in the function selector
// to the `checked_write` command
stdstore. target ( address (test)). sig ( " exists() " ). checked_write ( 100 );
assertEq (test. exists (), 100 );
}
// It supports arbitrary storage layouts, like assembly based storage locations
function testFindHidden () public {
// `hidden` is a random hash of a bytes, iteration through slots would
// not find it. Our mechanism does
// Also, you can use the selector instead of a string
uint256 slot = stdstore. target ( address (test)). sig (test.hidden. selector ). find ();
assertEq (slot, uint256 ( keccak256 ( " my.random.var " )));
}
// If targeting a mapping, you have to pass in the keys necessary to perform the find
// i.e.:
function testFindMapping () public {
uint256 slot = stdstore
. target ( address (test))
. sig (test.map_addr. selector )
. with_key ( address ( this ))
. find ();
// in the `Storage` constructor, we wrote that this address' value was 1 in the map
// so when we load the slot, we expect it to be 1
assertEq ( uint (vm. load ( address (test), bytes32 (slot))), 1 );
}
// If the target is a struct, you can specify the field depth:
function testFindStruct () public {
// NOTE: see the depth parameter - 0 means 0th field, 1 means 1st field, etc.
uint256 slot_for_a_field = stdstore
. target ( address (test))
. sig (test.basicStruct. selector )
. depth ( 0 )
. find ();
uint256 slot_for_b_field = stdstore
. target ( address (test))
. sig (test.basicStruct. selector )
. depth ( 1 )
. find ();
assertEq ( uint (vm. load ( address (test), bytes32 (slot_for_a_field))), 1 );
assertEq ( uint (vm. load ( address (test), bytes32 (slot_for_b_field))), 2 );
}
}
// A complex storage contract
contract Storage {
struct UnpackedStruct {
uint256 a;
uint256 b;
}
constructor () {
map_addr[ msg . sender ] = 1 ;
}
uint256 public exists = 1 ;
mapping ( address => uint256 ) public map_addr;
// mapping(address => Packed) public map_packed;
mapping ( address => UnpackedStruct) public map_struct;
mapping ( address => mapping ( address => uint256 )) public deep_map;
mapping ( address => mapping ( address => UnpackedStruct)) public deep_map_struct;
UnpackedStruct public basicStruct = UnpackedStruct ({
a: 1 ,
b: 2
});
function hidden () public view returns ( bytes32 t ) {
// an extremely hidden storage slot
bytes32 slot = keccak256 ( " my.random.var " );
assembly {
t := sload (slot)
}
}
}
Este é um wrapper sobre diversos cheatcodes que precisam de wrappers para serem mais amigáveis ao desenvolvedor. Atualmente existem apenas funções relacionadas à prank
. Em geral, os usuários podem esperar que o ETH seja colocado em um endereço durante prank
, mas este não é o caso por razões de segurança. Explicitamente, esta função hoax
só deve ser usada para endereços que tenham saldos esperados, pois serão sobrescritas. Se um endereço já tiver ETH, você deve usar apenas prank
. Se você quiser alterar esse saldo explicitamente, basta usar deal
. Se você quiser fazer as duas coisas, hoax
também é adequada para você.
// SPDX-License-Identifier: MIT
pragma solidity ^ 0.8.0 ;
import " forge-std/Test.sol " ;
// Inherit the stdCheats
contract StdCheatsTest is Test {
Bar test;
function setUp () public {
test = new Bar ();
}
function testHoax () public {
// we call `hoax`, which gives the target address
// eth and then calls `prank`
hoax ( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
// overloaded to allow you to specify how much eth to
// initialize the address with
hoax ( address ( 1337 ), 1 );
test. bar {value: 1 }( address ( 1337 ));
}
function testStartHoax () public {
// we call `startHoax`, which gives the target address
// eth and then calls `startPrank`
//
// it is also overloaded so that you can specify an eth amount
startHoax ( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
test. bar {value: 100 }( address ( 1337 ));
vm. stopPrank ();
test. bar ( address ( this ));
}
}
contract Bar {
function bar ( address expectedSender ) public payable {
require ( msg . sender == expectedSender, " !prank " );
}
}
Contém várias afirmações.
console.log
O uso segue o mesmo formato do Hardhat. É recomendado usar console2.sol
conforme mostrado abaixo, pois isso mostrará os logs decodificados nos rastreamentos do Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console2.sol " ;
...
console2. log (someValue);
Se você precisar de compatibilidade com o Hardhat, deverá usar o console.sol
padrão. Devido a um bug no console.sol
, os logs que usam os tipos uint256
ou int256
não serão decodificados corretamente nos rastreamentos do Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console.sol " ;
...
console. log (someValue);
Veja nossas diretrizes de contribuição.
Primeiro, veja se a resposta à sua pergunta pode ser encontrada no livro.
Se a resposta não estiver lá:
Se quiser contribuir, ou acompanhar a discussão dos colaboradores, você pode usar nosso telegrama principal para conversar conosco sobre o desenvolvimento do Foundry!
A Biblioteca Padrão Forge é oferecida sob licença MIT ou Apache 2.0.