Hồi tháng 6 năm 2024, tôi đang debug một transaction thất bại trên Curve pool – một trong 129.000 giao dịch bị từ chối bởi một pool mà lẽ ra không có lý do gì để reject. Khi tôi mở Etherscan, thấy một hợp đồng hoàn toàn bình thường, không có reentrancy guard, không có lỗi overflow. Nhưng có thứ gì đó khiến hàm swap luôn revert đúng vào thời điểm cuối cùng. Đó không phải lỗi. Đó là một tính năng. Và nó đã hoạt động suốt nhiều tháng trước khi bị Enso phát hiện.
Context: Cơn sốt mô phỏng và cái bẫy “tin tưởng tuyệt đối”
Mọi ví DeFi hiện đại đều có tính năng simulation: trước khi bạn ký một giao dịch, ví chạy thử nó trên một RPC cục bộ để hiển thị số token bạn nhận được, phí gas, và slippage. Nhưng có một giả định ngầm ở đây: kết quả mô phỏng đó phải khớp với thực thi trên mainnet. Với các pool thanh khoản tiêu chuẩn, điều này đúng. Nhưng với các pool tùy chỉnh – đặc biệt là Uniswap v4 với hook – tính năng simulate không còn là công cụ dự báo nữa. Nó trở thành vector tấn công. Kẻ tấn công deploy một pool “độc”, trong đó hàm swap kiểm tra msg.sender: nếu đó là địa chỉ của trình mô phỏng (thường là RPC công khai của một số ví), nó trả về một giá cực kỳ hấp dẫn (ví dụ quote 22.5 triệu USD cho 200 ETH). Nếu là ví thật, nó revert hoặc trả về giá thấp hơn. Kết quả: ví và router chọn pool độc, đẩy lệnh thực tế vào đó, và gas bị đốt vô ích – hơn 30.000 USD tiền gas đã bay theo cách này.
Core: Hai case study và bài học từ 129.000 giao dịch thất bại
Enso, công ty bảo mật Layer-2, đã ghi lại hai trường hợp chi tiết. Trên Ethereum, một pool Curve đã xử lý 129.000 giao dịch với tỷ lệ thất bại gần như 100%, nhưng pool vẫn tồn tại vì nó không vi phạm bất kỳ quy tắc nào của AMM – nó chỉ trả về quote giả khi được mô phỏng. Trên Polygon, một Uniswap v4 hook đạt tỷ lệ thất bại 99.1%. Kẻ tấn công kiếm được 3.46 triệu USD từ chênh lệch giữa giá mô phỏng và giá thực tế (chủ yếu từ các lệnh swap bị hủy nhưng khoản lợi nhuận từ các lệnh thành công nhỏ hơn). Điều làm tôi ấn tượng không phải con số – 3.46 triệu USD so với 22.5 triệu USD quote ảo là một khoảng cách khổng lồ – mà là tính tinh vi của nó. Kẻ tấn công không cần phải biết DeFi sâu: chỉ cần deploy một hook kiểm tra tx.origin hoặc gasleft() để phân biệt sandbox mô phỏng (thường có gas limit cao hơn, địa chỉ ví khác). Đây là một biến thể của “simulation spoofing” mà tôi từng thấy trong các audit NFT marketplace năm 2021, nhưng ở quy mô lớn hơn nhiều. Kinh nghiệm audit ICO đầu tiên của tôi năm 2018 dạy tôi rằng whitepaper không quan trọng bằng mã nguồn. Ở đây, mã nguồn pool hoàn toàn minh bạch, nhưng chính tính năng mô phỏng của ví lại tạo ra rủi ro. Mỗi lần tối ưu gas là một lần khám phá lại EVM. Lần này, chúng ta khám phá ra rằng tiết kiệm gas bằng cách tin tưởng simulation có thể đắt hơn nhiều so với tưởng tượng.
Contrarian: Ai thực sự có lỗi? Điểm mù của cả ngành
Bạn có thể nghĩ đây là lỗi của ví hoặc router khi không kiểm tra kết quả thực thi. Nhưng sự thật gây sốc hơn: ngay cả khi router kiểm tra, nó cũng không thể làm gì. Bởi vì pool độc sẽ chỉ revert đối với router thật, nhưng không revert với chính pool khác. Vấn đề nằm ở cấp độ giao thức: Uniswap v4 hook cho phép bất kỳ ai deploy pool với logic tùy ý, nhưng không có cơ chế “cam kết execution integrity”. Không giống như Flashbots matchmaker nơi builder ký cam kết một block bao gồm giao dịch, ở đây không có ai cam kết rằng kết quả mô phỏng sẽ giống thực thi. Điểm mù thứ hai là sự phụ thuộc vào RPC mô phỏng. Hầu hết ví sử dụng RPC của Infura hoặc Alchemy để mô phỏng. Kẻ tấn công có thể dễ dàng lấy danh sách địa chỉ RPC đó, và chỉ cần thêm một dòng if (msg.sender == RPC_ADDRESS) return fakeQuote;. Cách khắc phục không phải là cấm hook – điều đó sẽ giết chết sự đổi mới của Uniswap v4 – mà là buộc mọi giao dịch phải kèm theo một “execution proof” mà simulation không thể giả mạo, như một hash của state trước và sau. Tuy nhiên, điều này làm tăng gas, phá vỡ mục tiêu tối ưu hóa của Layer-2.
Takeaway: Lựa chọn giữa tốc độ và sự thật
Tôi đã thấy nhiều kịch bản tương tự trong các audit Layer-2: tối ưu hóa quá mức dẫn đến rủi ro bảo mật mới. Lần này, toàn bộ ngành DeFi phải đối mặt với một câu hỏi: chúng ta sẵn sàng đánh đổi bao nhiêu gas để đảm bảo mỗi lần mô phỏng là sự thật? Pool độc đã bị tắt, nhưng kẻ tấn công còn deploy các hợp đồng khác chưa được phát hiện. Liệu router tổng hợp tiếp theo có học được bài học, hay chỉ thêm một lớp wrapper để kiểm tra? Câu trả lời nằm ở mã nguồn – như mọi khi.