Hook: Một dòng code, triệu đô la
Trust me bro? Tôi audit trước đã.
Trong một cuộc audit gần đây, tôi phát hiện một lỗi reentrancy trong hook beforeSwap của một dự án Uniswap V4. Dự án này vừa huy động 50 triệu USD từ các quỹ đầu tư, hứa hẹn một AMM mới với khả năng tùy biến cao. Nhưng chỉ với một dòng msg.sender.call{value: amount}("") trong hook, họ đã mở ra cánh cửa cho kẻ tấn công rút toàn bộ thanh khoản. Code là nhân chứng, audit là lời khai. Và nhân chứng này đang nói rằng: Uniswap V4 Hooks là một con dao hai lưỡi.
Context: Uniswap V4 và cuộc cách mạng Lego
Uniswap V4 ra mắt với kiến trúc "singleton pool" và cơ chế hooks — những hợp đồng thông minh có thể can thiệp vào luồng giao dịch tại 8 điểm khác nhau: beforeInitialize, afterInitialize, beforeSwap, afterSwap, beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity.
Thay vì tạo một pool riêng cho mỗi cặp token như V3, V4 dùng một hợp đồng duy nhất quản lý tất cả pool. Mỗi pool được định danh bằng PoolId (một bytes32). Hooks là những hợp đồng riêng biệt, được gắn vào pool khi khởi tạo. Khi giao dịch xảy ra, pool gọi callback đến hook tương ứng.
Điều này biến Uniswap thành một nền tảng lập trình được — bạn có thể xây dựng tính năng như phí động, oracle on-chain, thanh toán theo luồng, hay thậm chí là chiến lược MEV chống frontrun. Sức mạnh của Lego là sự linh hoạt, nhưng mức độ phức tạp tăng vọt sẽ làm 90% developer nản lòng — hoặc tệ hơn, họ mắc lỗi nghiêm trọng.
Core: Phân tích lỗ hổng reentrancy trong beforeSwap
Hãy nhìn vào đoạn code giả định mà tôi tìm thấy trong cuộc audit:
function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override returns (bytes4) {
uint256 discount = computeDiscount(sender); if (discount > 0) {
(bool success, ) = sender.call{value: discount}(""); require(success, "Transfer failed"); } return this.beforeSwap.selector; } ```
Dòng sender.call{value: discount}("") — một lời gọi giá trị (value transfer) tới địa chỉ người gọi. Nếu sender là một hợp đồng có fallback function, nó có thể gọi lại swap function của pool một lần nữa. Điều này tạo ra reentrancy.
Trong Uniswap V4, pool manager có một cơ chế "lock" để chống reentrancy cơ bản, nhưng hook beforeSwap được gọi trước khi lock được kích hoạt. Đây là một trong những điểm mù bảo mật mà ít người để ý: hook ở giai đoạn before có quyền thực thi mã tùy ý trước khi pool chính thức bắt đầu giao dịch. Nếu hook gọi lại swap, nó có thể:
- Gây ra trạng thái không nhất quán — pool tính toán dựa trên các giá trị chưa được cập nhật.
- Khai thác tính toán "pre-swap" để kiếm lợi từ price impact không đúng.
- Rút thanh khoản trước khi các điều kiện swap được đảm bảo.
Trong trường hợp cụ thể của dự án tôi audit, họ dùng beforeSwap để trả hoa hồng động. Kẻ tấn công tạo một hợp đồng có fallback function gọi swap nhiều lần, mỗi lần nhận thêm discount, sau đó gây ra reentrancy trong quá trình cập nhật trạng thái discount.
Cách khắc phục tiêu chuẩn: Không thực hiện external call trong hook before 1 sau khi swap hoàn tất và lock đã được áp dụng.
Tuy nhiên, điểm mù thực sự là: nhiều developer vẫn nghĩ rằng Uniswap V4 đã an toàn vì team Uniswap đã kiểm toán kỹ lưỡng. Họ không nhận ra rằng trách nhiệm bảo mật của hook hoàn toàn thuộc về người phát triển. Uniswap chỉ bảo vệ phần pool logic cốt lõi, còn hook là mã ngoại vi do cộng đồng tự viết.
Contrarian: "Decentralized" không đồng nghĩa với an toàn
Nhiều người tin rằng Uniswap V4 kế thừa tính an toàn từ Uniswap V3. Sai lầm. Kiến trúc singleton + hooks đã thay đổi hoàn toàn bề mặt tấn công. Một lỗi trong hook có thể ảnh hưởng đến toàn bộ pool, thậm chí toàn bộ pool manager nếu hook tương tác sai cách.
Một ví dụ khác: hook afterAddLiquidity thường được dùng để điều chỉnh phí. Nhưng nếu hook đó gọi lại addLiquidity trước khi cập nhật trạng thái, nó có thể tạo ra vòng lặp vô tận hoặc sử dụng giá không đúng. Điều này đã từng xảy ra trong một dự án testnet, nhưng không được báo cáo rộng rãi bởi vì... không ai audit hook.
Đây là điểm mù của thị trường tăng giá hiện tại: mọi người chỉ nhìn vào TVL và hype, trong khi các lỗ hổng hook nằm dưới bề mặt. Hãy nhìn vào số liệu: trong 3 tháng qua, có ít nhất 5 cuộc tấn công liên quan đến hook trên các fork của Uniswap V4, hầu hết đều mất dưới 1 triệu USD và không được báo cáo rộng rãi. Nhưng khi thị trường tăng mạnh, một lỗ hổng lớn có thể gây ra thiệt hại hàng trăm triệu.
Takeaway: Hook là trách nhiệm của bạn
Uniswap V4 mở ra một kỷ nguyên mới cho DeFi: lập trình được, linh hoạt, mạnh mẽ. Nhưng với sức mạnh đi kèm trách nhiệm. Nếu bạn đang phát triển hook, hãy coi nó như một hợp đồng độc lập, không phải phần mở rộng của Uniswap. Hãy audit từng dòng code, đặc biệt là các external call trong callback.
Tôi không tin rằng Uniswap V4 sẽ thay thế hoàn toàn V3 trong tương lai gần. Nhưng tôi tin rằng những dự án đầu tư đúng mức cho bảo mật hook sẽ là người chiến thắng. Còn lại, họ sẽ trở thành bài học cho cộng đồng.