Deploy to Local Chain
Deploy lên local chain thông qua RPC URL (là endpoint dùng để giao tiếp với một node trong chain):
forge create SimpleStorage --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80Nếu chạy anvil (local chain của Foundry) thì không cần specify rpc-url.
Ngoài ra, có thể dùng flag --interactive để nhập password thay vì specify trong câu lệnh để tránh việc password được lưu trong bash history. Ngoài --interactive, ta cũng có thể tạo file .env (nhớ thêm vào .gitignore):
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
RPC_URL=http://127.0.0.1:8545Sau đó, ta sử dụng lệnh source .env (trong Linux) để nạp các biến môi trường từ file .env vào shell.
Cuối cùng, thay thế các giá trị trong command trên bằng các biến môi trường:
forge create SimpleStorage --rpc-url $RPC_URL --private-key $PRIVATE_KEYKeystore
Việc lưu private key ở dạng bản rõ trong .env như đã đề cập ở Deploy to Local Chain là không an toàn. Foundry cung cấp lệnh keystore để tạo và quản lý key.
Cách import private key vào keystore:
cast wallet import <ACCOUNT_NAME> --interactiveEnter private key:
Enter password:
`<ACCOUNT_NAME>` keystore was saved successfully. Address: address-corresponding-to-private-keyKey sẽ được lưu vào thư mục ~/.foundry/keystores. Ví dụ:
cat C:\Users\quan.m.le\.foundry\keystores\account1
{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"847efed5e7b0fa2f8b6c91d0b445c2e4"},"ciphertext":"403e3bb18e45f2ca3d2424143bf47582589c9c2b20651e1ab38b9d6710ac93a9","kdf":"scrypt","kdfparams":{"dklen":32,"n":8192,"p":1,"r":8,"salt":"568b5df60d94d78b7ba38794ae1352426dd1c6fd8bae65beb241e6191cd716ea"},"mac":"c7eb923b53e2b4f488122da77652c468598562433a4968d3eb5e25fddf7eac67"},"id":"2c801f03-d8e5-42aa-b82e-84d076ba8ac2","version":3}Sau khi có keystore thì ta deploy sử dụng account name như sau:
forge script script/DeploySimpleStorage.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --account <ACCOUNT_NAME> --sender 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266Khi đó, ta sẽ cần phải nhập password để decrypt private key.
Deploy to Sepolia
Để deploy lên Sepolia, ta cần RPC URL của Sepolia network thông qua một node thuộc một dịch vụ Node as a Service (NaaS) chẳng hạn như Infura hoặc Alchemy. Nếu dùng Alchemy, chỉ cần tạo một ứng dụng và chọn mạng Etherum sau đó ta sẽ có được RPC URL. Kế đến, import private key của ví metamask thông qua cast và tiến hành deploy như sau:
forge script script/DeploySimpleStorage.s.sol --rpc-url https://eth-sepolia.g.alchemy.com/v2/me-e2dRMLIRdJi_Bf4nyX --broadcast --account account2 --sender 0xD88fa55281904436f2416F57bdFd4A563d8184e4Note
Câu lệnh trên dùng subcommand
script, xem thêm Foundry - Scripting.
Multi-Chain Deployment
Xét hàm sau:
function getVersion() public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(
0x694AA1769357215DE4FAC081bf1f309aDC325306
);
return priceFeed.version();
}Việc gán cứng address của contract implement AggregatorV3Interface là bad practice trong trường hợp ta deploy contract lên nhiều chain. Khi đó, ta phải thay đổi address mỗi lần deploy lên một chain khác nhau.
Để giải quyết vấn đề này, ta sẽ truyền vào address thông qua constructor của contract:
AggregatorV3Interface private s_priceFeed;
constructor(address _priceFeed) {
i_owner = msg.sender;
s_priceFeed = AggregatorV3Interface(_priceFeed);
}Với tiền tố s_ là để chỉ state variable.
Gọi sử dụng các hàm từ interfact như sau:
function getVersion() public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeed);
return priceFeed.version();
}Ví dụ sử dụng constructor trong script và test:
contract DeployFundMe is Script {
function run() external returns (FundMe) {
vm.startBroadcast();
FundMe fundMe = new FundMe(0x694AA1769357215DE4FAC081bf1f309aDC325306);
vm.stopBroadcast();
return fundMe;
}
}Tái sử dụng script trong test:
contract FundMeTest is Test {
FundMe private fundMe;
DeployFundMe private deployFundMe;
function setUp() external {
deployFundMe = new DeployFundMe();
fundMe = deployFundMe.run();
}
function testMinimumDollarIsFive() public view {
assertEq(fundMe.MINIMUM_USD(), 5e18);
}
function testOwnerIsMsgSender() public {
assertEq(fundMe.i_owner(), msg.sender);
}
function testPriceFeedVersionIsAccurate() public {
uint256 version = fundMe.getVersion();
assertEq(version, 4);
}
}Important
Lúc này, owner của
fundMekhông còn là địa chỉ của test contract nữa. Thay vào đó, nó sẽ là địa chỉ của sender mà gọi hàm của test contract.
Seealso
Do đó, owner của fundMe trong ngữ cảnh này sẽ là msg.sender.
Để viết smart contract mà có thể được deploy trong ngữ cảnh multichain. Ta có thể viết một helper script như sau:
import {Script} from "forge-std/Script.sol";
contract HelperConfig {
NetworkConfig public activeNetworkConfig;
struct NetworkConfig {
address priceFeed; // ETH/USD price feed address
}
constructor() {
if (block.chainid == 11155111) {
activeNetworkConfig = getSepoliaEthConfig();
} else {
activeNetworkConfig = getAnvilEthConfig();
}
}
function getSepoliaEthConfig() public pure returns (NetworkConfig memory) {
NetworkConfig memory sepoliaConfig = NetworkConfig({
priceFeed: 0x694AA1769357215DE4FAC081bf1f309aDC325306
});
return sepoliaConfig;
}
function getAnvilEthConfig() public pure returns (NetworkConfig memory) {}
}Khi được deploy, contract này sẽ dựa vào chain ID từ block.chainid để lựa chọn config phù hợp. Cụ thể hơn, nếu là Sepolia testnet thì nó sẽ trả về cấu hình có địa chỉ của price feed ở trên Sepolia testnet.
Seealso
Tra cứu chain ID: ChainList
Gọi sử dụng script trên ở trong deploy script như sau:
contract DeployFundMe is Script {
function run() external returns (FundMe) {
HelperConfig helperConfig = new HelperConfig();
address priceFeedAddress = helperConfig.activeNetworkConfig();
vm.startBroadcast();
FundMe fundMe = new FundMe(priceFeedAddress);
vm.stopBroadcast();
return fundMe;
}
}Bằng cách này, chúng ta sẽ không phải gán cứng địa chỉ của price feed contract trong deployment script nữa. Ngoài ra, deployment script có thể tự động xác định địa chỉ của price feed contract tùy thuộc vào chain đang được deploy.
Để cấu hình địa chỉ của price feed contract trên các chain khác, ta chỉ cần thêm vào các hàm get<chain_name>Config trong contract HelperConfig.