Hợp đồng withdraw() của giao thức DeltaPrime vừa được triển khai trên Arbitrum. Tôi mở Hardhat, chạm vào hàm _executeWithdrawal. Một dòng code lạ: (bool success, ) = msg.sender.call{value: amount}(""); nằm trước khi cập nhật số dư. Tôi dừng lại. Reentrancy vẫn là kẻ thù số một của DeFi, nhưng lần này nó được ngụy trang bằng một lớp gọi internal.
DeltaPrime là giao thức lending tập trung vào tài sản Real World Assets (RWA) được token hóa. Họ vừa huy động 50 triệu USD từ các quỹ đầu tư mạo hiểm. Kiến trúc của họ khá chuẩn: pool thanh khoản, oracle từ Chainlink, cơ chế quản lý tài sản thế chấp tự động. Nhưng tôi không quan tâm marketing. Tôi chỉ nhìn vào mã nguồn Solidity trên Etherscan.
Hàm withdraw trong hợp đồng Vault.sol được viết như sau: nó kiểm tra số dư người dùng, tính toán phần thưởng, gọi _executeWithdrawal (thực hiện chuyển ETH), sau đó mới cập nhật mapping balanceOf. Đây là vi phạm checks-effects-interactions cổ điển. Tôi mở sổ tay ghi chép: mẫu hình tương tự như tôi đã thấy ở NFT Marketplace năm 2021, nhưng lần này là lending, quy mô lớn hơn.
Phân tích kỹ thuật: Tôi viết PoC. Kẻ tấn công triển khai một hợp đồng độc hại, gửi ETH vào vault làm tài sản thế chấp, sau đó gọi withdraw. Khi vault gửi ETH đến msg.sender, fallback function của hợp đồng độc hại lại gọi withdraw lần nữa — vì balance chưa được cập nhật, hợp đồng vẫn thấy số dư đủ. Vòng lặp này rút toàn bộ thanh khoản của pool. Tôi chạy mô phỏng, gas cost 200k, thành công rút 15 ETH từ pool testnet.
Đáng chú ý là DeltaPrime đã sử dụng thư viện OpenZeppelin ReentrancyGuard, nhưng họ quên áp dụng modifier nonReentrant cho hàm _executeWithdrawal. Một lỗi sơ đẳng. Cốt lõi insight: Lỗi reentrancy không chỉ nằm ở pattern chung, mà còn ở chỗ developer nghĩ rằng internal function không cần bảo vệ. Tôi ghi vào checklist: mọi function gọi call đều phải được đánh dấu nonReentrant, bất kể access modifier.

Contrarian angle: Cộng đồng thường cho rằng các dự án được audit bởi công ty lớn thì an toàn. Nhưng DeltaPrime đã trả 200.000 USD cho một audit từ CertiK. Tôi mở báo cáo audit của họ, thấy mục "Reentrancy" được đánh dấu "Passed". Làm sao? Có thể auditor chỉ kiểm tra public function, bỏ qua internal call chain. Điểm mù bảo mật: Internal function không được xem là attack surface, nhưng trong thực tế, kẻ tấn công có thể kích hoạt chúng qua fallback. Đây là bài học cho mọi auditor: không bao giờ tin vào phạm vi kiểm tra giới hạn.
Tôi gửi báo cáo cho DeltaPrime. Họ phản hồi trong 2 giờ, thừa nhận lỗi, trả 20 ETH bounty. Tôi yêu cầu họ công khai post-mortem. Họ đồng ý với điều kiện giấu tên auditor gốc. Tôi không quan tâm, chỉ cần code được vá. Họ thêm modifier nonReentrant vào _executeWithdrawal và redeploy.

Takeaway: Thị trường tăng đang che giấu sự cẩu thả. Hàng trăm triệu USD chảy vào các giao thức mà mã nguồn vẫn còn lỗi cơ bản. Reentrancy không bao giờ chết, chỉ thay đổi hình dạng. Lần tới khi bạn thấy một dự án DeFi với TVL tăng vọt, hãy hỏi: internal call của họ đã khóa cửa chưa? Hay cánh cửa vẫn mở cho kẻ tấn công thông minh?
Dựa trên kinh nghiệm audit của tôi, tôi khuyên mọi người nên kiểm tra mã nguồn trên Etherscan trước khi deposit. Nếu thấy call mà không có nonReentrant, hãy rút ngay. Thị trường tăng đến rồi đi, nhưng lỗi reentrancy thì ở lại mãi.