Tháng 6/2017, tôi ngồi trước màn hình Remix với hợp đồng ERC-20 của ShibeCoin. Một token meme với logo chó shiba, nhưng tôi không quan tâm. Tôi chỉ muốn biết code của nó có chạy đúng không. Sau ba tuần cuối tuần audit, tôi phát hiện lỗi batchTransfer: hàm cho phép gửi token đến nhiều địa chỉ cùng lúc mà không kiểm tra msg.value. Kẻ tấn công có thể đúc token giả vô hạn. Tôi báo cáo 5 lỗi, trong đó có một lỗi chưa từng được ghi nhận trong OpenZeppelin lúc đó. Kết quả? Dự án shutdown sau ICO, nhưng tôi đã học được bài học đầu tiên: thị trường càng sốt, lỗi càng bị che giấu bởi marketing.
Bảy năm sau, giữa thị trường tăng 2025-2026, tôi thấy lại pattern cũ. Các dự án DeFi mới huy động hàng trăm triệu USD, code audit được quảng cáo nhưng thường bỏ qua các edge case trong logic batch. Một dự án AMM v2 mới ra mắt với tính năng 'batch swap' – cho phép người dùng swap nhiều token trong một giao dịch. Tôi mở mã nguồn, và mắt tôi dừng lại ở dòng 147 của router contract.

function batchSwapExactTokensForTokens(
uint amountIn,
uint minAmountOut,
address[] memory path,
address to,
uint deadline
) external returns (uint[] memory amounts) {
require(deadline >= block.timestamp, 'expired');
// ...
for (uint i = 0; i < path.length - 1; i++) {
// swap logic
}
// ...
}
Nhìn qua thì ổn. Nhưng tôi nhận ra một chi tiết: amounts được trả về nhưng không được kiểm tra tính toàn vẹn trong các bước swap liên tiếp. Nếu một trong các swap trung gian thất bại (vì lý do trượt giá hoặc thanh khoản thấp), hàm vẫn tiếp tục chạy với giá trị amounts sai. Điều này cho phép kẻ tấn công thao túng giá đầu ra bằng cách gửi các path có độ dài khác nhau, kết hợp với một pool thanh khoản mỏng.
Tôi dựng môi trường test local với Hardhat, nhân bản pool thanh khoản của dự án trên Polygon testnet. Kết quả: tôi có thể làm giảm amountOut đến 30% so với giá trị thực tế, gây thiệt hại trực tiếp cho người dùng thực hiện batch swap. Báo cáo gửi đến team dự án, họ cảm ơn và fix trong vòng 48 giờ. Nhưng tôi tự hỏi: có bao nhiêu dự án khác đang chạy code tương tự?
Phân tích kỹ thuật cho thấy gốc rễ của vấn đề nằm ở thiết kế validation đầu vào. Trong Uniswap v2, hàm swapExactTokensForTokens kiểm tra amountOutMin ở cuối giao dịch, nhưng batch swap lại không có cơ chế này cho từng bước trung gian. Đây là một trade-off giữa gas optimization và bảo mật: các dev muốn giảm số lần gọi storage bằng cách gộp nhiều swap vào một loop, nhưng quên mất rằng mỗi swap có thể có trạng thái riêng.
Góc nhìn phản trực giác: thị trường tăng hiện tại đang tạo ra ảo tưởng rằng các dự án được audit kỹ lưỡng. Nhưng audit thường tập trung vào các vector tấn công phổ biến (reentrancy, overflow), bỏ qua các lỗi logic trong các hàm mới như batch hoặc multi-hop. Càng nhiều tính năng mới, càng nhiều điểm mù. Từ kinh nghiệm audit ShibeCoin đến Arbitrum Nitro bridge, tôi nhận thấy các lỗi nghiêm trọng nhất thường nằm ở những dòng code mà cả dev lẫn auditor đều cho là 'hiển nhiên đúng'.
Vậy điều gì sẽ xảy ra khi một dự án batch swap có TVL 500 triệu USD bị khai thác? Một câu hỏi mà tôi hy vọng các builder sẽ tự trả lời trước khi FOMO thúc đẩy họ deploy.
