Cộng đồng thường hỏi: làm thế nào một dự án đã qua audit vẫn bị tấn công? Câu trả lời nằm trong mã nguồn. Không nằm ở audit hay bảo hiểm. Nằm ở logic toán học mà nhóm phát triển cố tình bỏ qua.
Tuần trước, VietLend Protocol, một nền tảng cho vay cross-chain được xây trên Polygon, mất 12.4 triệu USD từ pool thanh khoản chính VNDC-USDC. Tin tức lan nhanh. Telegram group sập. Team im lặng 48 giờ. Cộng đồng bắt đầu đổ lỗi cho hacker. Nhưng tôi nhìn vào code.
Context: VietLend là một trong những giao thức DeFi Việt Nam nổi bật nhất trong năm 2023. TVL đạt 80 triệu USD trước vụ tấn công. Họ dùng mô hình isolated pool với cơ chế thanh lý tự động. Hợp đồng thông minh do một team audit nội bộ review – không có báo cáo bên thứ ba nào được công bố. Đó là dấu hiệu đỏ đầu tiên.
Core insight: Lỗ hổng nằm ở chức năng flashLoanAndRepay trong pool USDC. Hàm này cho phép người dùng vay flash, sau đó trả nợ bằng token tương tự trong cùng một giao dịch. Vấn đề: không có cập nhật trạng thái nợ trước khi gọi callback đến hợp đồng bên ngoài. Tôi đã thấy pattern này hàng trăm lần. Đó là lỗi reentrancy cổ điển, nhưng được ngụy trang bởi một lớp abstract gọi là IVietLendCallback. Stack trace cho thấy:
flashLoanAndRepay(amount, params) → _calculateDebt()→ cập nhậttotalBorrownhưng không cập nhậtuserDebtcủa caller.- Gọi
IVietLendCallback(receiver).executeOperation(params)– đây là điểm yếu. - Trong callback, hacker gọi lại
flashLoanAndRepayvới cùngamountnhưngreceiverkhác, khiến_calculateDebtsử dụng giá trịtotalBorrowchưa được cập nhật từ lần gọi trước. - Kết quả: hacker có thể rút nhiều tài sản hơn số nợ thực tế, lặp lại đến khi pool cạn.
Contrarian angle: Nhiều người cho rằng vấn đề là audit chưa kỹ. Nhưng thực tế, ngay cả audit của các công ty lớn cũng bỏ qua dạng lỗi này nếu chúng được giấu trong abstract layer. Cộng đồng thường hỏi: tại sao không dùng reentrancy guard? Câu trả lời nằm trong mã nguồn: team đã dùng nonReentrant modifier trên hàm cấp cao nhưng không apply lên internal function _flashLoanAndRepay. Họ nghĩ rằng internal function không cần vì chỉ được gọi từ bên trong. Họ sai.
Takeaway: Không có dự án nào là an toàn tuyệt đối, nhưng có những dự án tử tế hơn những dự án khác. VietLend không tử tế. Họ giấu audit, không public testnet, và code có quá nhiều flag đỏ. Nếu bạn đang hold token VTL, hãy tự hỏi: bạn đã kiểm tra stack trace chưa? Nếu chưa, đừng hỏi tại sao lại mất tiền. Hãy hỏi tại sao bạn tin vào một giao thức không minh bạch.
Bài học cho người dùng: không bao giờ gửi tài sản vào pool mà bạn không hiểu logic cơ bản. Cộng đồng thường hỏi: làm sao để biết? Câu trả lời nằm trong mã nguồn. Đọc nó. Hoặc ít nhất, hãy kiểm tra xem có ai đó uy tín đã đọc nó chưa. Nếu không, bạn đang chơi trò chơi may rủi, không phải đầu tư.
Tôi không viết bài này để kết tội VietLend. Tôi viết để mổ xẻ một mẫu lỗi có thể tái diễn ở bất kỳ dự án nào. Nếu bạn là developer, hãy học từ sai lầm của họ. Nếu bạn là investor, hãy học từ đau thương của người khác. Thị trường đi ngang không tha thứ cho sự cẩu thả.