Chỉ mới tuần trước, một dự án DeFi mới tên 'Nexus Yield' gọi vốn thành công 15 triệu USD từ các quỹ VC. Hôm nay, hợp đồng của họ bị khai thác mất 5 triệu USD. Nhưng thực tế, lỗi đã hiện hữu ngay từ dòng code đầu tiên.
Từ góc nhìn của một partner bảo mật crypto tại Thành Đô, tôi đã audit vài trăm hợp đồng. Mỗi lần thị trường tăng điểm, tôi lại thấy những dự án non nớt tràn vào, mang theo mã nguồn đầy lỗ hổng. Nexus Yield là một ví dụ điển hình.
## Bối Cảnh: Cuộc Chơi 'Yield Aggregator' Nexus Yield tự nhận là giao thức tổng hợp lợi nhuận trên nhiều chain, tối ưu hóa vị thế LP. Họ dùng chiến lược 'convex-style' để tăng APY. Trang web sạch sẽ, đội ngũ ẩn danh nhưng có 'LinkedIn' giả. Điều này kích hoạt 'thói quen hoài nghi thể chế hóa' của tôi: tôi kiểm tra chéo danh tiếng dự án trước khi audit.
## Mổ Xẻ Lạnh Lùng: Hàm 'withdraw' Và Lỗi 'Reentrancy' Cổ Điển Tôi đọc mã nguồn NexusYield.sol. Dòng 112: function withdraw(uint256 amount) external { uint256 shares = amount * totalSupply / poolBalance; _burn(msg.sender, shares); (bool success, ) = msg.sender.call{value: amount}(''); require(success, 'Transfer failed'); }.
Đây là lỗi 'reentrancy' kinh điển. Hàm gọi _burn trước, nhưng _burn không cập nhật totalSupply ngay lập tức? Sai. Thực tế, _burn cập nhật totalSupply và balanceOf sau khi emit event. Nhưng vấn đề là sau khi _burn hoàn tất, totalSupply đã giảm, nhưng poolBalance vẫn chưa thay đổi. Kẻ tấn công gọi withdraw nhiều lần trước khi poolBalance được đồng bộ, mỗi lần rút số lượng amount bằng với shares cũ. Chi tiết: poolBalance chỉ thay đổi khi token thực sự chuyển đi, nhưng totalSupply đã giảm. Tỷ lệ shares = amount * totalSupply / poolBalance trở nên méo mó.
Cụ thể: poolBalance = 1000 ETH, totalSupply = 1000 shares. Kẻ tấn công có 100 shares. Lần 1: withdraw 100 ETH. shares tính = 100 (1000-100)/ (1000) = 100900/1000 = 90 shares? Không, totalSupply đã update sau _burn. Công thức thực tế: shares = amount totalSupply / poolBalance. Sau _burn: totalSupply = 900, poolBalance vẫn 1000. shares = 100 900 / 1000 = 90 shares? Vô lý. Thực ra lỗi là poolBalance không giảm trước khi gọi _burn, nhưng totalSupply giảm. Kẻ tấn công có thể gọi với lượng ETH lớn hơn shares của nó để rút nhiều hơn. Nhưng tôi không đi sâu.
Bản chất: Hợp đồng không tuân theo mẫu 'checks-effects-interactions'. poolBalance chỉ giảm sau khi chuyển token, nhưng totalSupply giảm trước. Điều này tạo ra cơ hội cho reentrancy: kẻ tấn công gọi lại withdraw ngay sau khi nhận ETH, khi poolBalance chưa kịp giảm, dẫn đến shares tính sai.
## Phần Trái Ngược: Nhà Đầu Cơ Bảo Vệ Gì? Phần 'bò' cho rằng lỗi này chỉ do đội ngũ non. Nhưng theo tôi, đây là dấu hiệu của sự vô trách nhiệm có hệ thống. Họ không viết test cho reentrancy, không dùng OpenZeppelin ReentrancyGuard. Một dự án huy động 15 triệu USD mà không có audit độc lập? Họ chỉ có 'social audit' từ Discord. Trong thị trường tăng, nhiều người tin vào 'câu chuyện' hơn là code.
## Takeaway: Trách Nhiệm Của Người Audit Một dòng code, một tỷ đô la bay hơi. Lần này chỉ 5 triệu, nhưng bài học vẫn vậy. Khi bạn FOMO, hãy dừng lại và hỏi: mã nguồn đã được audit bởi ai? Nếu không có câu trả lời, hãy coi đó là một 'hố đen'.