Die Forge-Standardbibliothek ist eine Sammlung hilfreicher Verträge und Bibliotheken zur Verwendung mit Forge and Foundry. Es nutzt die Cheatcodes von Forge, um das Schreiben von Tests einfacher und schneller zu machen und gleichzeitig die UX von Cheatcodes zu verbessern.
Erfahren Sie, wie Sie Forge-Std mit dem Foundry Book (Forge-Std-Handbuch) verwenden.
forge install foundry-rs/forge-std
Dies ist ein Hilfsvertrag für Fehler und Reverts. In Forge ist dieser Vertrag besonders hilfreich für den expectRevert
-Cheatcode, da er alle im Compiler integrierten Fehler bereitstellt.
Alle Fehlercodes finden Sie im Vertrag selbst.
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 ;
}
}
Dies ist ein ziemlich umfangreicher Auftrag aufgrund der Überlastung, um die UX anständig zu machen. In erster Linie ist es ein Wrapper um den record
und accesses
Cheatcodes zu. Es kann jederzeit den/die mit einer bestimmten Variablen verknüpften Speicherslot(s) finden und schreiben, ohne das Speicherlayout zu kennen. Die einzige große Einschränkung hierbei besteht darin, dass zwar ein Steckplatz für gepackte Speichervariablen gefunden werden kann, wir jedoch nicht sicher in diese Variable schreiben können. Wenn ein Benutzer versucht, in einen gepackten Slot zu schreiben, löst die Ausführung einen Fehler aus, es sei denn, er ist nicht initialisiert ( bytes32(0)
).
Dies funktioniert durch die Aufzeichnung aller SLOAD
s und SSTORE
s während eines Funktionsaufrufs. Wenn ein einzelner Steckplatz gelesen oder beschrieben wird, wird der Steckplatz sofort zurückgegeben. Andernfalls iterieren und überprüfen wir hinter den Kulissen jeden einzelnen Schritt (vorausgesetzt, der Benutzer hat einen depth
übergeben). Wenn die Variable eine Struktur ist, können Sie einen depth
übergeben, der im Grunde die Feldtiefe ist.
Dh:
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)
}
}
}
Dies ist ein Wrapper für verschiedene Cheatcodes, die Wrapper benötigen, um entwicklerfreundlicher zu sein. Derzeit gibt es nur Funktionen, die sich auf prank
beziehen. Im Allgemeinen können Benutzer damit rechnen, dass die ETH auf prank
in eine Adresse gebracht wird, dies ist jedoch aus Sicherheitsgründen nicht der Fall. Ausdrücklich sollte diese hoax
-Funktion nur für Adressen verwendet werden, die erwartete Salden aufweisen, da sie sonst überschrieben werden. Wenn eine Adresse bereits über ETH verfügt, sollten Sie einfach prank
verwenden. Wenn Sie diesen Kontostand explizit ändern möchten, verwenden Sie einfach deal
. Wenn Sie beides tun möchten, ist hoax
auch das Richtige für Sie.
// 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 " );
}
}
Enthält verschiedene Behauptungen.
console.log
Die Nutzung folgt dem gleichen Format wie bei Hardhat. Es wird empfohlen, console2.sol
wie unten gezeigt zu verwenden, da dadurch die dekodierten Protokolle in Forge-Traces angezeigt werden.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console2.sol " ;
...
console2. log (someValue);
Wenn Sie Kompatibilität mit Hardhat benötigen, müssen Sie stattdessen die console.sol
verwenden. Aufgrund eines Fehlers in console.sol
werden Protokolle, die die Typen uint256
oder int256
verwenden, in Forge-Traces nicht ordnungsgemäß dekodiert.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console.sol " ;
...
console. log (someValue);
Sehen Sie sich unsere Beitragsrichtlinien an.
Überprüfen Sie zunächst, ob die Antwort auf Ihre Frage im Buch zu finden ist.
Falls die Antwort nicht dabei ist:
Wenn Sie einen Beitrag leisten oder der Diskussion der Mitwirkenden folgen möchten, können Sie unser Haupttelegramm nutzen, um mit uns über die Entwicklung von Foundry zu chatten!
Die Forge-Standardbibliothek wird entweder unter MIT- oder Apache 2.0-Lizenz angeboten.