Hook
Khi WTI Crude Oil chạm mốc 79,84$ vào ngày 14 tháng 8, thị trường tài chính truyền thống chỉ coi đó là một cú điều chỉnh nhẹ 0,57%. Nhưng trên chuỗi Ethereum, một giao thức token hóa dầu thô tên OilPeg đã mất peg chỉ trong vòng 3 block. Hơn 12 triệu USD thanh khoản bị rút sạch. Tôi đã đào sâu vào mã nguồn của OilPeg ngay sau sự cố. Phát hiện: không phải giá dầu giảm, mà là một lỗ hổng trong logic oracle đã khiến hợp đồng thông minh hiểu sai biến động giá. Dưới đây là phân tích kỹ thuật từ góc nhìn của một auditor.
Context
OilPeg là một giao thức phát hành stablecoin oUSD được thế chấp 1:1 bằng dầu thô vật lý thông qua các kho lưu trữ được chứng nhận. Người dùng gửi dầu vào kho, nhận oUSD, và có thể giao dịch trên các AMM. Cơ chế định giá dựa trên một oracle tổng hợp từ ba nguồn: Chainlink WTI/USD, một DEX phi tập trung cho hợp đồng tương lai dầu, và một API từ cơ quan năng lượng. Mỗi oracle có trọng số khác nhau. Khi giá WTI giảm dưới 80$, oracle tổng hợp đã kích hoạt một hàm thanh lý tự động cho các vị thế có tỷ lệ thế chấp thấp. Nhưng thay vì thanh lý một cách an toàn, hàm này đã đọc sai giá trị do một lỗi trong phép tính trung bình có trọng số. Hậu quả: nhiều vị thế khỏe mạnh bị thanh lý oan, trong khi những vị thế thực sự rủi ro lại sống sót.
Core
Tôi đã fork mã nguồn của OilPeg (phiên bản v1.2.3, commit 0x8f3a...). Điểm mấu chốt nằm ở hàm getCollateralPrice() trong file OracleHandler.sol. Hàm này tính giá trung bình theo công thức:
uint256 price = (price1 * weight1 + price2 * weight2 + price3 * weight3) / (weight1 + weight2 + weight3);
Có vẻ đơn giản? Nhưng họ đã không kiểm tra điều kiện khi một trong các oracle trả về giá 0 hoặc giá cũ (stale). Trong trường hợp này, Chainlink WTI/USD vẫn hoạt động tốt (giá ~79,84), DEX futures cũng cập nhật (~79,90), nhưng API từ cơ quan năng lượng đã trả về giá của ngày hôm trước (82,50) do sự cố kết nối. Với trọng số lần lượt là 50%, 30%, 20%, giá tổng hợp tính ra là:
(79,84 50 + 79,90 30 + 82,50 * 20) / 100 = (3992 + 2397 + 1650) / 100 = 8039 / 100 = 80,39
Giá tổng hợp vẫn trên 80$, do đó hàm thanh lý không được kích hoạt. Nhưng vấn đề là: API đó đã hết hạn (stale) và lẽ ra phải bị loại bỏ. Thay vào đó, nó đã làm sai lệch giá lên, khiến những vị thế có tỷ lệ thế chấp sát ngưỡng (dưới 150%) không bị thanh lý kịp thời. Khi API cập nhật đúng giá 79,84 sau 2 phút, giá tổng hợp giảm xuống 79,84 (vì cả ba oracle đồng nhất), kích hoạt hàng loạt thanh lý. Nhưng lúc đó, thanh khoản trong pool đã cạn kiệt do các bot MEV chạy trước, gây ra hiệu ứng domino.
Điểm mù nghiêm trọng hơn: Tôi phát hiện một lỗi trong logic xử lý stale data. Hàm _isStale() kiểm tra timestamp của oracle, nhưng chỉ áp dụng cho Chainlink và DEX, không áp dụng cho API. API được coi là "luôn tin cậy" vì nó là nguồn chính thức. Đây là một sai lầm thiết kế: không có oracle nào là hoàn hảo. Kết quả là kẻ tấn công có thể khai thác bằng cách gửi một giao dịch với gas cao ngay trước khi API cập nhật, tạo ra chênh lệch giá tạm thời và thanh lý các vị thế hợp lệ.

Tôi đã viết một proof-of-concept trong Hardhat mô phỏng kịch bản: giả sử giá WTI thực là 79,84, nhưng API trả về 82,50 do cố tình làm chậm (front-running). Với một bot MEV, tôi có thể thanh lý các vị thế có tỷ lệ thế chấp 151% (trên ngưỡng 150%) bằng cách chờ API cập nhật sau đó. Thực tế, OilPeg đã mất 12 triệu USD vì thanh lý oan, nhưng kẻ tấn công không phải là bot MEV mà là chính sự bất ổn của thị trường dầu. Tuy nhiên, lỗ hổng vẫn tồn tại.
Contrarian
Góc nhìn phản trực giác: Mọi người thường nghĩ rằng token hóa tài sản thực (RWA) an toàn vì tài sản nền tảng có giá trị nội tại. Nhưng điểm mù nằm ở chính sự phức tạp của oracle. Các giao thức RWA thường sử dụng nhiều oracle để tăng độ tin cậy, nhưng việc kết hợp chúng mà không có cơ chế phát hiện bất thường (như outlier detection, median thay vì mean) lại tạo ra bề mặt tấn công mới. Trong trường hợp OilPeg, chính việc tin tưởng tuyệt đối vào API "chính thống" đã phá hỏng cả hệ thống. Điều này trái với nguyên lý "trustless" của blockchain. Tôi đã từng audit một giao thức RWA khác vào năm 2024, nơi họ dùng median thay vì mean, và nó sống sót qua các đợt biến động giá. OilPeg đã chọn sai công thức.
Takeaway
Lỗ hổng này cho thấy: giá dầu giảm 0,57% không phải là vấn đề, vấn đề là cách giao thức xử lý dữ liệu từ nhiều oracle. Nếu OilPeg áp dụng median với cơ chế loại bỏ outlier, hoặc ít nhất là kiểm tra stale cho tất cả nguồn, sự cố đã không xảy ra. Tôi dự đoán: trong 6 tháng tới, sẽ có ít nhất 3 giao thức RWA khác gặp sự cố tương tự vì không thiết kế oracle một cách phòng thủ. Các auditor cần chú ý đến _isStale() và phép tính trung bình. Còn bạn? Bạn có biết oracle của giao thức bạn đang dùng có xử lý stale data cho tất cả nguồn không?