Foundry 101
Khi mới tìm hiểu về web3 mình thấy cũng có khá nhiều công cụ giúp chúng ta phát triển SC (smartcontract) như Truffle, HardHat, Foundry. Trong số đó mình thấy Foundry khá dễ dàng trong việc tiếp cận và sử dụng, đơn giản bởi vì Foundry không cần phải sử dụng những đoạn mã js để deploy/test/debug các SC mà thay vào đó là sử dụng luôn những đoạn mã solidity. Ngoài ra Foundry cũng được sử dụng bởi các hacker để build PoC cho những lỗ hổng họ phát hiện. Thông tin đầy đủ các bạn có thể tham khảo thêm tại https://book.getfoundry.sh/
Để cài đặt Foundry chỉ cần chạy command dưới đây:
curl -L https://foundry.paradigm.xyz | bash
Sau đó chạy: foundryup
forge: build, compile, debug, deploy smart contracts.
cast: interact with the blockchain via RPC calls.
anvil: create local ethereum node.
chisel: solidity CLI shell for debugging.
Khởi tạo một project sử dụng forge.
Đây là lỗi do chưa config git.
git config –global user.email "[email protected]"
git config –global user.email "devtest"
Thực hiện build để kiểm tra xem có lỗi hay không.
Cấu trúc của một project.
lib: nơi chứa các thư viện, vd: OpenZeppelin.
script: dùng để deploy smartcontract lên network.
src: chứa mã nguồn của smartcontract.
test: là nơi viết các unit-test cho smartcontract trước khi được deploy lên network.
foundry.toml: nơi chứa thông tin cấu hình, vd: rpc url, key…vv.
Vd: thực hiện deploy smartcontract lên U2U network.
forge create --rpc-url "https://rpc-nebulas-testnet.uniultra.xyz" --private-key "your_private_key" src/Test1.sol:Test1
Sau khi deploy thành công.
Kiểm tra trên explorer. Vì smartcontract chưa được verify nên chỉ hiện thị ở dạng bytecode.
Để cho dễ hình dung hơn, chúng ta sẽ thực hiện tạo một ERC20 token và thực hiện các công việc như: deploy, transfer,…vv.
Sử dụng lại project TestCode đã tạo trước đó, cài thư viện OpenZeppelin với câu lệnh:
forge install OpenZeppelin/openzeppelin-contracts
Tiếp theo sẽ tạo một file MyToken.sol nằm trong folder /src
Token name: MyToken
Symbol: MTK
Decimals: 18
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
address public owner;
mapping(address=>address) public owner_spender;
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 100000);
owner = msg.sender;
}
function newAllowance(address _owner, address _spender) public {
allowance(_owner, _spender);
owner_spender[_owner] = _spender;
}
function newMint(address to, uint256 value) external{
require(msg.sender == owner,"Not owner !!!");
require(value > 0,"Check Value Error !!!");
_mint(to, value);
}
}Nói qua về ý nghĩa của đoạn code này: deployer account sẽ mint 100k token khi thực hiện deploy contract này lên network và deployer account sẽ được gán cho biến owner. Ngoài ra có định nghĩa thêm 2 function newMint và newAllowance.
newAllowance: tracking approver & spender
newMint: mint token (chỉ cho phép owner được thực hiện)
Để deploy smartcontract cần có chain network, sẽ có 2 option mà foundry cung cấp:
fork network: deploy lên bất kỳ network nào vì foundry hỗ trợ fork.
local test network: foundry sử dụng anvil để tạo local network (anvil cũng hỗ trợ fork network bất kỳ với any block number -> khá là hữu ích cho việc test các smartcontract đã được deploy trên mainnet)
Ở đây sẽ sử dụng local test network. Sử dụng câu lệnh sau để tạo local network và tạo 4 EOA account:
EOA(0) : deployer/owner 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
EOA(1) : user1 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
EOA(2) : user2 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
EOA(3) : user3 0x90F79bf6EB2c4f870365E785982E1f101E93b906
rpc url lúc này là: http://127.0.0.1:8545 (giá trị này có thể cấu hình luôn trong file foundry.toml)
giá trị PRIVATE_KEY chính là privatekey của EOA(0) account hay chính là deployer account.
Sau khi đã tạo test network, tiến hành deploy MyToken.sol, trong folder /script tạo một file Deploy.s.sol.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
contract Deploy is Script {
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); //lấy từ biến môi trường
vm.startBroadcast(deployerPrivateKey);
// Make a new token ~ deploy
MyToken mtk = new MyToken();
vm.stopBroadcast();
//vm.broadcast();
}
}vm.startBroadcast (cheatcode: https://book.getfoundry.sh/cheatcodes/start-broadcast).
Tại sao lại sử dụng startBroadcast là bởi vì khi deploy một smartcontract thì EOA account sẽ sinh ra transaction và cần sign (sử dụng privatekey) transaction đó. Do đó transaction sẽ được ký bởi EOA(0).
Chạy câu lệnh sau để deploy:
forge script ./script/Deploy.s.sol --broadcast -vvvv --rpc-url rpcapi
Deployed Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Như vậy đã deploy contract MyToken thành công, tiếp theo sẽ tương tác với contract này như thế nào ? Ở đây sẽ viết test script để tương tác (tham khảo: writing-testa)
Trong folder /test tạo 1 file MyTokenTest.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
import "forge-std/console.sol";
contract MyTokenTest is Test {
function setUp() public {}
function test_Transfer() public {
// user accounts
address owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; //EOA(0)
address user1 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; //EOA(1)
address user2 = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; //EOA(2)
address user3 = 0x90F79bf6EB2c4f870365E785982E1f101E93b906; //EOA(3)
// fork from local network
uint256 rpcapi = vm.createFork("rpcapi");
vm.selectFork(rpcapi);
}Test script sẽ không update storage trên network, mà nó chỉ đơn thuần fork network xong và run trong một isolated EVM. Vì lúc deploy contract đã thực hiện luôn mint 100k token cho tài khoản owner, thử kiểm tra xem tài khoản đó đã có 100k token hay chưa.
forge test --match-contract MyTokenTest --match-test test_Transfer -vvv
Mint 5k token cho các tài khoản user1->user3.
Chuyển 100 token từ tài khoản từ user2 tới user3.
user3 approve cho user1 chuyển 1000 token từ user3 tới user1.
Đoạn test script đầy đủ:
function test_Transfer() public {
// user accounts
address owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; //EOA(0)
address user1 = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; //EOA(1)
address user2 = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; //EOA(2)
address user3 = 0x90F79bf6EB2c4f870365E785982E1f101E93b906; //EOA(3)
// fork from local network
uint256 rpcapi = vm.createFork("rpcapi");
vm.selectFork(rpcapi);
//get contract MyToken
MyToken mtk = MyToken(0x5FbDB2315678afecb367f032d93F642f64180aa3);
//get token of owner account
emit log_named_uint("Token of Owner is ", mtk.balanceOf(owner));
emit log_named_uint("Token of user1 is ", mtk.balanceOf(user1));
emit log_named_uint("Token of user2 is ", mtk.balanceOf(user2));
emit log_named_uint("Token of user3 is ", mtk.balanceOf(user3));
emit log("====> Mint 5k token to each one of users");
vm.startBroadcast(owner);
mtk.newMint(user1, 5000);
mtk.newMint(user2, 5000);
mtk.newMint(user3, 5000);
vm.stopBroadcast();
emit log_named_uint("Token of user1 is ", mtk.balanceOf(user1));
emit log_named_uint("Token of user2 is ", mtk.balanceOf(user2));
emit log_named_uint("Token of user3 is ", mtk.balanceOf(user3));
emit log("====> Transfer 100 tokens from user2 to user3");
vm.startBroadcast(user2);
mtk.transfer(user3, 100);
vm.stopBroadcast();
emit log_named_uint("Token of user1 is ", mtk.balanceOf(user1));
emit log_named_uint("Token of user2 is ", mtk.balanceOf(user2));
emit log_named_uint("Token of user3 is ", mtk.balanceOf(user3));
// From user3 approve user1 to spend 1k tokens
emit log("====> user3 approve user1 to transfer 1000 token");
vm.startBroadcast(user3);
emit log_named_address("Approver", user3);
mtk.approve(user1, 1000);
mtk.newAllowance(user3, user1);
emit log_named_address("spender", mtk.owner_spender(user3));
vm.stopBroadcast();
emit log("===> user1 transfer 1000 tokens from user3 to user1");
vm.startBroadcast(user1);
mtk.transferFrom(user3, user1, 1000);
vm.stopBroadcast();
emit log("-> After transfered");
emit log_named_uint("Token of user1 is ", mtk.balanceOf(user1));
emit log_named_uint("Token of user2 is ", mtk.balanceOf(user2));
emit log_named_uint("Token of user3 is ", mtk.balanceOf(user3));
}Qua đó chúng ta có thể thấy sử dụng foundry để làm việc với smartcontract đem lại thuận tiện, chỉ cần code solidity rất dễ nhớ. Ngoài ra foundry hỗ trợ khá nhiều cheatcode hữu ích khác, các bạn có tham khảo tại trang chủ để hiểu rõ hơn cách dùng.


















