Forge Standard Library es una colección de bibliotecas y contratos útiles para usar con Forge and Foundry. Aprovecha los códigos de trucos de Forge para hacer que escribir pruebas sea más fácil y rápido, al tiempo que mejora la UX de los códigos de trucos.
Aprenda a utilizar Forge-Std con el Foundry Book (Guía de Forge-Std).
forge install foundry-rs/forge-std
Este es un contrato de ayuda para errores y reversiones. En Forge, este contrato es particularmente útil para el código de trucos expectRevert
, ya que proporciona todos los errores integrados del compilador.
Consulte el contrato en sí para conocer todos los códigos de error.
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 es un contrato bastante grande debido a toda la sobrecarga para que la UX sea decente. Principalmente, envuelve el record
y accesses
a códigos de trucos. Siempre puede encontrar y escribir las ranuras de almacenamiento asociadas con una variable particular sin conocer el diseño del almacenamiento. La principal advertencia es que si bien se puede encontrar una ranura para variables de almacenamiento empaquetadas, no podemos escribir en esa variable de forma segura. Si un usuario intenta escribir en una ranura empaquetada, la ejecución arroja un error, a menos que no esté inicializada ( bytes32(0)
).
Esto funciona grabando todos los SLOAD
y SSTORE
durante una llamada de función. Si hay una sola ranura leída o escrita, inmediatamente devuelve la ranura. De lo contrario, detrás de escena, iteramos y verificamos cada uno (asumiendo que el usuario pasó un parámetro depth
). Si la variable es una estructura, puede pasar un parámetro depth
que es básicamente la profundidad del campo.
Es decir:
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 es un contenedor de varios códigos de trucos que necesitan contenedores para que sean más amigables para los desarrolladores. Actualmente solo existen funciones relacionadas con prank
. En general, los usuarios pueden esperar que se coloque ETH en una dirección de prank
, pero este no es el caso por razones de seguridad. Explícitamente, esta función hoax
solo debe usarse para direcciones que tengan saldos esperados, ya que se sobrescribirá. Si una dirección ya tiene ETH, deberías usar prank
. Si desea cambiar ese saldo explícitamente, simplemente use deal
. Si quieres hacer ambas cosas, hoax
también es adecuado para ti.
// 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 " );
}
}
Contiene varias afirmaciones.
console.log
El uso sigue el mismo formato que Hardhat. Se recomienda utilizar console2.sol
como se muestra a continuación, ya que mostrará los registros decodificados en los seguimientos de Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console2.sol " ;
...
console2. log (someValue);
Si necesita compatibilidad con Hardhat, debe usar el console.sol
estándar en su lugar. Debido a un error en console.sol
, los registros que utilizan los tipos uint256
o int256
no se descodificarán correctamente en los seguimientos de Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console.sol " ;
...
console. log (someValue);
Consulte nuestras pautas de contribución.
Primero, vea si la respuesta a su pregunta se puede encontrar en el libro.
Si la respuesta no está ahí:
Si desea contribuir o seguir la discusión de los colaboradores, puede utilizar nuestro telegrama principal para conversar con nosotros sobre el desarrollo de Foundry.
La biblioteca estándar de Forge se ofrece bajo licencia MIT o Apache 2.0.