Web3 Study Log 003
2025-7-5
這幾天各種各樣的瑣事,處理完了,真的煩,估計能消停一段時間了…
今天終于能夠坐下來好好學習,今天學習了chainlink的使用,能夠獲取 ETH/USD 實時價格,然后寫了一個簡單的眾籌項目,用到現在學習到所有知識,智能合約涉及到錢的地方,確實要謹慎謹慎再謹慎,今天一個提款條件寫錯了,錢籌集完整之后,提不出來…幸好只是Testnet。
明天準備繼續寫一個項目,DeFi 預售合約TokenPresale,說項目可能太夸張了,就是一道綜合練習題,鞏固下目前所學習的知識。背景為:一個新項目要發幣,在正式上線前對早期支持者進行預售。規則如下:
- 項目方設置目標:募集多少 USD 價值的 ETH,例如 $5000。
- 使用 Chainlink ETH/USD 匯率,支持 ETH 捐贈,捐贈最小值為 10 USD。
- 項目方設置預售結束時間(例如一周后)。
- 預售結束后:
- 項目方可提款;
- 投資人不可退款。
- 上線后,投資人可 線性解鎖領取代幣(Token),解鎖期為 180 天。
- 投資人通過 claimTokens() 領取尚未解鎖的部分,領取多少由合約計算。
Web3 Study Log 003
2025-07-05
been dealing with a bunch of random life stuff lately — finally cleared up
man it was annoying lol… but looks like i’ll finally get some peace for a while
today i finally sat down to actually study
learned how to use Chainlink to fetch real-time ETH/USD price
then built a simple crowdfunding contract that brings together everything i’ve learned so far
any time a smart contract handles funds… you really gotta be careful
made a mistake in the withdraw condition — funds hit the goal but couldn’t be withdrawn lol
good thing it was just on testnet
tomorrow i’m planning to build another contract — a TokenPresale for a fake DeFi project
not really a full project tbh, more like a comprehensive practice exercise
goal is just to solidify what i’ve learned so far
here’s the idea:
- project owner sets a target funding goal in USD, e.g. $5000
- use Chainlink ETH/USD price feed — support ETH donations, min $10 USD per donation
- project owner sets a deadline (e.g. 1 week from start)
- after deadline:
- project owner can withdraw
- investors can’t refund
- once token launches, investors can claim tokens linearly over 180 days
- claimTokens() lets investors withdraw their unlocked tokens, based on contract calculation
Solidity Section 3 學習筆記:智能合約中的 ETH 眾籌與 Chainlink 預言機集成
一、學習目標
通過構建一個具備以下功能的智能合約,系統掌握 Solidity 中的重要語法與實際開發場景:
- 使用 Chainlink AggregatorV3Interface 獲取 ETH/USD 實時價格
- 實現一個 ETH 捐贈眾籌合約,支持募資、價格換算、提款功能
- 使用 fallback() 和 receive() 函數接收 ETH
- 掌握 call/send/transfer 差異
- 使用 modifier 進行權限控制
- 使用 error 和 revert 優化 gas
- 使用 library 封裝常用邏輯
- 了解 calldata 的作用及檢查方式
- 在 Remix 上調試合約的部署與調用
二、關鍵概念與代碼講解
1. Chainlink ETH/USD 預言機價格獲取
調用方式
AggregatorV3Interface priceFeed = AggregatorV3Interface(address);
(, int answer,,,) = priceFeed.latestRoundData();
精度換算
- Chainlink 的 answer 返回的是帶 8 位精度的價格(如 2000 * 1e8)
- ETH 金額通常以 wei(1 ETH = 1e18 wei)傳入
- 為對齊精度,需將 answer * 1e10 擴展為 1e18 精度,再與 ETH 金額相乘,最后除以 1e18
2. 捐贈邏輯與單位換算
uint256 usd = (ethPrice * ethAmountInWei) / 1e18;
- ethAmountInWei 是傳入的實際金額(比如 msg.value)
- 如果捐入 0.02 ETH,即 0.02 * 1e18 wei,與 ETH 價格相乘換算為 USD
3. 捐贈函數 fund() 實現
- 時間控制:block.timestamp < i_deadline
- 限制重復捐贈者:通過 mapping hasFunded 和 funders[]
- 檢查目標達成(大于即可):if (usdTotal >= i_goal) { goalReached = true; }
require(!goalReached, "Goal already reached");
4. fallback() 和 receive()
- 用于接收 ETH,當調用沒有 calldata 時觸發 receive()
- 有 calldata 或調用未定義函數時觸發 fallback()
- 推薦都定義為 external payable
5. 權限控制 modifier 與 error
modifier onlyOwner {if (msg.sender != i_owner) revert NotOwner();_;
}modifier fundSuccessOrTimeout {if (!(block.timestamp >= i_deadline || getTotalRaisedInUsd() >= i_goal)) {revert NotFinish();}_;
}
- 使用 revert ErrorName() 可節省 gas(與 require(“string”) 比較)
- 使用 !() 表示否定整個布爾表達式
6. call/send/transfer 的差異
方法 | gas 限制 | 返回值 | 推薦程度 |
---|---|---|---|
transfer | 固定 2300 gas | 無返回值 | 不推薦 |
send | 固定 2300 gas | 返回 bool | 不推薦 |
call | 可設定 gas / value / calldata | 返回 (bool, bytes) | 推薦 ? |
常見調用方式:
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Call failed");
三、調試常見問題總結
問題1:fund 調用失敗
- 原因:getLatestETHPriceInUSD() 返回值類型未匹配,或傳入 ETH 金額過少導致 USD 捐贈金額遠小于 i_goal,報錯
- 解決:確保傳入 ETH 數量與 Chainlink 的實時價格能夠換算為合適的 USD 金額
問題2:withdraw 提款失敗
- 檢查是否滿足 withdraw 的 modifier 條件,即:
block.timestamp >= i_deadline || getTotalRaisedInUsd() >= i_goal
- 如果 goalReached = true 但 getTotalRaisedInUsd() 小于 i_goal(例如匯率波動導致回落),仍會失敗
- 建議改寫為檢查 goalReached 標志位配合 block.timestamp >= deadline 控制
四、library 的使用
將 ETH 轉 USD 的邏輯封裝為 library PriceConverter,用法如下:
using PriceConverter for uint256;totalAmount.getLatestETHPriceInUSD();
注意事項:
- library 中不能使用狀態變量
- 通常定義為純函數或 view
- 實現邏輯更清晰、可重用性更高
五、calldata 與 fallback 判斷技巧
if (msg.data.length == 0) {// receive() triggered
} else {// fallback() triggered
}
口訣總結:
轉賬沒數據:走 receive()
其他情況:走 fallback()
想看有沒有數據:看 msg.data.length
六、調試 Remix 的注意事項
- 發送 ETH 需在 fund 函數旁邊的 value 字段輸入,如:0.01(單位是 ETH)
- fallback 區域中的 calldata 應填寫 16 進制值(以 0x 開頭)
- 如果要使用 fallback 或 receive 自動轉入,需要將 ETH 發送給合約地址