Forge Standard Library คือชุดสัญญาและไลบรารีที่เป็นประโยชน์สำหรับใช้กับ Forge และ Foundry มันใช้ประโยชน์จากสูตรโกงของ Forge เพื่อทำให้การทดสอบการเขียนง่ายขึ้นและเร็วขึ้น ขณะเดียวกันก็ปรับปรุง UX ของสูตรโกงด้วย
เรียนรู้วิธีใช้ Forge-Std กับ Foundry Book (คู่มือ Forge-Std)
forge install foundry-rs/forge-std
นี่คือสัญญาช่วยเหลือสำหรับข้อผิดพลาดและการเปลี่ยนกลับ ใน Forge สัญญานี้มีประโยชน์อย่างยิ่งสำหรับ cheatcode 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)
}
}
}
นี่เป็น wrapper ของ cheatcodes เบ็ดเตล็ดที่ต้องการ wrappers เพื่อให้เป็นมิตรกับนักพัฒนามากขึ้น ขณะนี้มีเพียงฟังก์ชันที่เกี่ยวข้องกับ 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 Trace
// 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 Trace
// 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 Standard Library นำเสนอภายใต้ลิขสิทธิ์ MIT หรือ Apache 2.0