Стандартная библиотека Forge — это коллекция полезных контрактов и библиотек для использования с Forge и Foundry. Он использует чит-коды Forge, чтобы упростить и ускорить написание тестов, одновременно улучшая UX чит-кодов.
Узнайте, как использовать Forge-Std с помощью Foundry Book (Руководство по Forge-Std).
forge install foundry-rs/forge-std
Это вспомогательный контракт для ошибок и возвратов. В Forge этот контракт особенно полезен для чит- expectRevert
, поскольку он обеспечивает все встроенные ошибки компилятора.
Все коды ошибок смотрите в самом контракте.
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 ;
}
}
Это довольно большой контракт из-за всех перегрузок, необходимых для создания достойного UX. По сути, это оболочка record
и accesses
чит-кодам. Он всегда может найти и записать слот(ы) памяти, связанные с конкретной переменной, не зная структуры хранилища. Единственное важное предостережение заключается в том, что, хотя можно найти слот для упакованных переменных хранилища, мы не можем безопасно записывать в эту переменную. Если пользователь пытается выполнить запись в упакованный слот, выполнение выдает ошибку, если только он не инициализирован ( bytes32(0)
).
Это работает путем записи всех SLOAD
и SSTORE
во время вызова функции. Если есть один слот для чтения или записи, он немедленно возвращает этот слот. В противном случае мы незаметно перебираем и проверяем каждый из них (при условии, что пользователь передал параметр depth
). Если переменная является структурой, вы можете передать параметр depth
, который по сути является глубиной поля.
Т.е.:
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)
}
}
}
Это оболочка над разными чит-кодами, которым нужны оболочки, чтобы быть более дружелюбными к разработчикам. На данный момент есть только функции, связанные с prank
. В общем, пользователи могут ожидать, что ETH будет помещен в адрес при prank
, но это не так по соображениям безопасности. Явно эту функцию- hoax
следует использовать только для адресов, на которых ожидается баланс, поскольку он будет перезаписан. Если на адресе уже есть ETH, вам следует просто использовать prank
. Если вы хотите явно изменить этот баланс, просто используйте deal
. Если вы хотите сделать и то, и другое, вам подойдет и hoax
.
// 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 " );
}
}
Содержит различные утверждения.
console.log
Использование соответствует тому же формату, что и Hardhat. Рекомендуется использовать console2.sol
, как показано ниже, так как это покажет декодированные журналы в трассировках Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console2.sol " ;
...
console2. log (someValue);
Если вам нужна совместимость с Hardhat, вместо этого вы должны использовать стандартный console.sol
. Из-за ошибки в console.sol
журналы, использующие типы uint256
или int256
не будут правильно декодироваться в трассировках Forge.
// import it indirectly via Test.sol
import " forge-std/Test.sol " ;
// or directly import it
import " forge-std/console.sol " ;
...
console. log (someValue);
Ознакомьтесь с нашими рекомендациями по участию.
Сначала посмотрите, можно ли найти в книге ответ на ваш вопрос.
Если ответа нет:
Если вы хотите внести свой вклад или следить за обсуждением участников, вы можете использовать нашу основную телеграмму, чтобы поговорить с нами о развитии Foundry!
Стандартная библиотека Forge предлагается по лицензии MIT или Apache 2.0.