Đào mã thấy lỗi, im lặng là vàng. Nhưng lần này tôi không thể im.
Một giao thức lending mới nổi trên Arbitrum — gọi tắt là Tempest — đã hút hơn 120 triệu USD TVL chỉ sau 3 tuần ra mắt. Tôi không audit chính thức cho họ. Chỉ tò mò, vì một người bạn hỏi: "Sao APR pool USDC của nó cao gấp 3 lần Aave?"
Tôi mở block explorer. Đọc contract LendingPool.sol. Dòng 47. Một biến state được khai báo là singleton — không mapping, không array. Lập tức, mồ hôi túa ra.
Bối cảnh: Tempest dùng cơ chế đặt cọc thanh khoản tập trung (concentrated liquidity) kết hợp với oracle TWAP từ Chainlink. Nghe có vẻ hiện đại. Nhưng logic đặt cọc của nó chỉ cho phép một người dùng duy nhất làm depositor tại một thời điểm — do thiết kế singleton để "tối ưu gas".
Core: Tôi phân tích sâu hàm deposit(). Nó kiểm tra xem địa chỉ msg.sender có khớp với currentDepositor không. Nếu khớp, nó cộng dồn số dư. Nếu không, nó ghi đè lên — và không trả lại số dư cũ. Đây là lỗi quản lý trạng thái cơ bản. Một hacker có thể gọi deposit() từ nhiều account khác nhau, mỗi lần ghi đè lên số dư của account trước. Và khi rút, chỉ account cuối cùng mới rút được toàn bộ.
Nhưng điều contrarian ở đây: Ai cũng nghĩ lỗi này dễ phát hiện. Thực tế, nó lọt qua audit của hai công ty bảo mật lớn? Vì họ tập trung vào oracle và reentrancy, bỏ qua singleton pattern — một anti-pattern rõ ràng nhưng bị che giấu bởi hàng trăm dòng code flash loan.
Takeaway: DeFi không tha thứ cho mã nguồn cẩu thả. Nhưng nó cũng không tha thứ cho những auditor chỉ biết chạy tool phân tích tĩnh mà không đọc kỹ logic nghiệp vụ. Thanh khoản cạn, bẫy còn đó. Lần tới khi bạn thấy APR cao bất thường, hãy hỏi: "Contract của nó có singleton không?"
Tôi đã báo cáo lỗi cho team Tempest qua Discord. Họ silent. 48 giờ sau, pool USDC mất 40% TVL. Im lặng là vàng, nhưng đôi khi vàng rỉ sét.