Không có 'làm tròn an toàn' trong thế giới của những hợp đồng thông minh.
Năm 2020, DeFi Summer nóng đến mức ai cũng muốn launch pool thanh khoản. Tôi nhận một hợp đồng kiểm toán từ Uniswap Labs – không phải V3, mà là V2, cái đã chạy hàng tỷ USD. Thoạt nhìn, code sạch sẽ, test phủ 95%. Nhưng tôi có thói quen đọc từng dòng, kể cả những dòng toán học tưởng chừng đơn giản. Và tôi tìm thấy thứ gì đó: một lỗi logic trong cách tính phí khi swap token với số lượng nhỏ.
Context
Uniswap V2 sử dụng mô hình AMM xy=k. Mỗi swap trích 0.3% phí – 0.25% cho LP, 0.05% cho giao thức (nếu được bật). Công thức tính amountOut khá chuẩn: dựa trên lượng đầu vào, trừ phí, rồi áp dụng công thức xy. Vấn đề nằm ở chỗ làm tròn số nguyên trong EVM. Solidity không có số thập phân dấu phẩy động; mọi phép tính phải dùng số nguyên với precision. Trong Uniswap V2, phí được tính bằng cách nhân amountIn với 997/1000 (tức 0.3% phí, giữ lại 0.997). Tuy nhiên, do làm tròn xuống (floor division) khi chia cho 1000, một phần rất nhỏ giá trị bị mất mỗi giao dịch.
Tưởng như vô hại? Một giao dịch swap 1 USDC chỉ mất 0.000000001 USDC. Nhưng hãy nghĩ: Uniswap V2 xử lý hàng triệu giao dịch mỗi ngày. Tích lũy theo thời gian, số phí thất thoát lên đến 0.05% tổng phí của pool. Trong một pool lớn như USDC/ETH, số này có thể lên tới hàng chục nghìn USD mỗi tháng.
Core
Tôi mở Remix và viết proof-of-concept. Hàm swap trong UniswapV2Pair.sol: