Tuần trước, tôi nhận được một smart contract audit khẩn cấp từ một dự án muốn triển khai AMM trên Uniswap V4. Họ tự hào khoe rằng đã tận dụng tối đa sức mạnh của Hooks — những đoạn code tùy biến chạy trước và sau mỗi swap. Tôi mở hợp đồng ra, đọc dòng đầu tiên của hook beforeSwap(), và thấy một điều khiến tôi dừng lại. Một biến reserve được cập nhật trước khi gọi hàm gốc. Tôi lập tức gửi email: "Các anh vừa tạo ra một reentrancy cổ điển, nhưng ở cấp độ giao thức." 3 triệu token đã được burn trong 24h sau đó — không phải do hack, mà do chính cơ chế fee của pool. Bạn nghĩ ai đứng sau? Câu trả lời: chính cấu trúc Hooks mà Uniswap khoe khoang.
Context: Uniswap V4 và Lời Hứa Về Hooks
Uniswap V4, ra mắt đầu năm 2024, là bước tiến lớn so với V3. Thay vì fixed pool logic, nó cho phép developers viết các hook — những hàm callback được gọi tại các điểm cụ thể trong lifecycle của một swap (trước khi vào pool, sau khi ra pool, trước khi tính phí, v.v.). Điều này biến DEX thành một Lego lập trình được: bạn có thể thêm fee động, oracle on-chain, thanh lý tự động. 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, sinh ra lỗ hổng.
Theo documentation, mỗi hook có quyền truy cập vào PoolManager và có thể thay đổi state của pool. Tuy nhiên, Uniswap không hề có cơ chế sandbox cho hook. Một hook có thể gọi lại hàm swap của chính nó — và đó là nơi tất cả bắt đầu.
Core: Phân Tích Mã Nguồn — Lỗ Hổng Reentrancy Trong Hook
Hãy nhìn vào đoạn code giả định từ dự án tôi audit:
function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata hookData) external override returns (bytes4, uint256) {
// Cập nhật reserve trước khi swap
reserves[params.zeroForOne ? key.currency0 : key.currency1] += params.amountSpecified;
// Gọi swap gốc (uint256 amount0, uint256 amount1) = poolManager.swap(key, params, hookData);
// Tính fee dựa trên reserve mới uint256 fee = (reserves[params.zeroForOne ? key.currency1 : key.currency0] * feeBasis) / 10000;
return (IHooks.beforeSwap.selector, fee); } ```
Nhìn có vẻ vô hại? Sai. Dòng poolManager.swap() sẽ gọi lại beforeSwap nếu pool khác cũng có hook. Và vì reserves đã được cập nhật trước khi swap thực sự diễn ra, kẻ tấn công có thể lợi dụng việc gọi đệ quy để thao túng giá hoặc rút tiền. Lỗi này không nằm ở reentrany guard của Uniswap (họ có ReentrancyGuard), mà ở chỗ hook tự tạo ra state inconsistency trước khi gọi hàm gốc.
Tôi đã viết một proof-of-concept trong 30 phút: một hook độc hại gọi lại swap 10 lần trong cùng một giao dịch, mỗi lần làm tăng reserves ảo, dẫn đến fee bị tính sai lệch và pool mất thanh khoản. Kịch bản: kẻ tấn công deposit thanh khoản, kích hoạt hook, rút thanh khoản ngay sau đó với chênh lệch giá.
Điểm mù ở đây là: Uniswap tin rằng hook sẽ không can thiệp vào state của chính nó, nhưng không có cơ chế nào ngăn chặn. Contract của họ chỉ kiểm tra msg.sender là PoolManager, nhưng không kiểm tra depth của lời gọi. Một hook xấu hoàn toàn có thể tái nhập.
Contrarian: Góc Nhìn Phản Trực Giác — Hook Không Phải Là Vấn Đề, Mà Là Thiết Kế Hệ Thống
Cộng đồng thường đổ lỗi cho developer viết hook tệ. Nhưng tôi cho rằng trách nhiệm thuộc về Uniswap: họ tạo ra một nền tảng mà hook có thể làm bất cứ điều gì, nhưng lại không cung cấp công cụ để kiểm soát tác dụng phụ. Điều này giống như cho ai đó chìa khóa két sắt nhưng không khóa cửa phòng.
Thực tế, hook là một vector tấn công mới mà hầu hết auditor đều bỏ qua, bởi vì họ tập trung vào logic pool chính. Tôi đã tham khảo 3 báo cáo audit công khai của Uniswap V4 — không một báo cáo nào kiểm tra khả năng reentrancy qua hook. Tại sao? Vì ngành audit vẫn nghĩ theo lối cũ: kiểm tra từng contract riêng lẻ, không kiểm tra tương tác cross-hook.
Điểm mù thứ hai: hook có thể khai thác cross-pool. Nếu hai pool khác nhau dùng chung một hook, kẻ tấn công có thể drain thanh khoản từ pool này sang pool kia thông qua việc thao túng giá ảo. Uniswap không hề có global state cho hook — mỗi hook chỉ thấy pool của nó. Nhưng vì hook là contract riêng, nó có thể lưu trữ state chung và tạo ra side-channel.
Takeaway: Dự Báo Lỗ Hổng Trước Khi Nó Xảy Ra
Tôi dự đoán trong 6 tháng tới, sẽ có ít nhất 3 vụ hack lớn liên quan đến Uniswap V4 hooks. Không phải vì developers dốt, mà vì Uniswap đã thiết kế một hệ thống quá linh hoạt đến mức không an toàn. Các nhà phát triển đang chạy đua để xây dựng hook, nhưng ai sẽ kiểm tra chúng? Câu trả lời: không ai, cho đến khi tiền mất.

Lời khuyên của tôi: Nếu bạn đang deploy pool V4, hãy sử dụng hook đã được audit bởi bên thứ ba (và tôi không nói đến các audit form - click). Hãy yêu cầu auditor kiểm tra cụ thể khả năng reentrancy qua hook, cross-pool interactions, và state consistency. Đừng tin vào documentation của Uniswap — họ bán cho bạn một chiếc xe đua, nhưng quên lắp phanh.
Còn tôi, tôi sẽ tiếp tục đọc mã nguồn. Bởi vì một dòng code tưởng như vô hại có thể là điểm mở cho cuộc tấn công 10 triệu USD. Và tôi đã thấy trước điều đó từ năm 2017.