### 1. Cài đặt foundry https://book.getfoundry.sh/ : tài liệu gốc Rất đơn giản chỉ cần chạy command sau là xong: > curl -L https://foundry.paradigm.xyz | bash Sau đó chạy: foundryup ![image](https://hackmd.io/_uploads/SJJ3k_9vp.png) - forge: To build, compile, debug, deploy your smart contracts - cast: To interact with the blockchain via RPC calls - anvil: Your local Ethereum node - chisel: Solidity CLI shell for debugging Khởi tạo project. ![image](https://hackmd.io/_uploads/r1OsWd5vp.png) Nếu gặp lỗi này thì chỉ cần config git > git config --global user.email "devtest@example.com" > git config --global user.email "devtest" Đoạn này dùng luôn tài khoản github (config linh tinh cũng được). Khởi tạo lại project. ![image](https://hackmd.io/_uploads/ryt1Q_qw6.png) Build thử để xem có chạy oke không. ![image](https://hackmd.io/_uploads/HyhZQOqDp.png) #### Deploy smartcontract Thực hiện deploy 1 sc lên u2u testnet. Vd ở đây có sc như sau: ![image](https://hackmd.io/_uploads/SJMoIs5DT.png) > forge create --rpc-url "https://rpc-nebulas-testnet.uniultra.xyz" --private-key "your_private_key" src/Test1.sol:Test1 Sau khi deploy. ![image](https://hackmd.io/_uploads/By1FdjqvT.png) Kiểm tra trên explorer. ![image](https://hackmd.io/_uploads/Sydh_icw6.png) Vì sc chưa được verify nên chỉ hiện thị ở dạng bytecode. Tham khảo: - https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/16-foundry-deployment/ - https://medium.com/@bugbountydegen/how-to-install-paradigm-foundry-to-debug-smart-contracts-part-1-8019d4bac613 - https://docs.moonbeam.network/builders/build/eth-api/dev-env/foundry/ ### 2. Foundry ERC20 Ở phần 1 mới chỉ nói qua Foundry, phần này sẽ đi chi tiết hơn về cách sử dụng. Cụ thể sẽ hoàn thành bài ERC20-1 của khóa học smartcontract hacking. ![image](https://hackmd.io/_uploads/SkjPMt996.png) Khởi tạo 1 project. > forge init TestCode ![image](https://hackmd.io/_uploads/HyaClF9qa.png) Vể cấu trúc thư mục của project như sau: - **lib**: cài thêm thư viện, vd: OpenZeppelin - **script**: dùng để deploy smartcontract lên network - **src**: đây chính là nơi 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 Để tạo ERC20 token dựa vào thư viện của OpenZ, sử dụng câu lệnh sau để cài lib. > 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 ```solidity // 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); } } ``` 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, ở đây 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. ![image](https://hackmd.io/_uploads/HyfG0FccT.png) - 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) ![image](https://hackmd.io/_uploads/SyBx15c56.png) tạo một file .evn để cấu hình biến môi trường trên linux. ![image](https://hackmd.io/_uploads/HyIq199c6.png) giá trị PRIVATE_KEY chính là privatekey của EOA(0) 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 ```solidity= // 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 đó. Dó đó 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 ![image](https://hackmd.io/_uploads/S1jaG9c9T.png) 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: https://book.getfoundry.sh/forge/writing-tests) Trong folder /test tạo 1 file MyTokenTest.t.sol ```solidity= // 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); } ``` Vì test script chỉ đơn thuần là test mà 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. ![image](https://hackmd.io/_uploads/H1Eld99q6.png) #### Mint 5k token cho các tài khoản user1->user3 ![image](https://hackmd.io/_uploads/HJmdOqqcp.png) #### Chuyển 100 token từ tài khoản từ user2 tới user3 ![image](https://hackmd.io/_uploads/HyuQKq59T.png) #### user3 approve cho user1 chuyển 1000 token từ user3 tới user1 ![image](https://hackmd.io/_uploads/B1q9K59qa.png) Đoạn test script đầy đủ: ```solidity= 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)); } ```