Mỗi lần kiểm toán là một lần tôi đặt câu hỏi: 'Nếu một protocol dùng Chainlink, sao nó vẫn bị reentrancy?'. Tuần trước, tôi nhận một smart contract từ một RWA project. Họ tự hào: 'Chúng tôi đã audit bởi 3 firm'. Nhưng tôi chỉ cần 10 phút để thấy một dòng code nguy hiểm: price = aggregator.latestRoundData().answer. Nó gọi safeTransfer bên trong, nhưng quên khóa reentrancy guard. Đây không phải bug của Chainlink. Đây là bug do ai đó copy-paste từ Uniswap V2 mà không hiểu tại sao Uniswap dùng require trước khi chuyển token. RWA on-chain đang làm tôi nhớ lại năm 2021: ai cũng muốn lên sàn nhanh, ai cũng quên audit thực sự.

Bối cảnh: RWA là câu chuyện kể của ba năm qua. Mọi người nói về việc đưa trái phiếu, bất động sản lên chain. Nhưng ít ai hỏi: 'Các tổ chức truyền thống có cần public chain của bạn không?'. Tôi đã audit gần 20 RWA project. Hầu hết đều copy cơ chế oracle từ DeFi cũ. Họ lấy giá từ Chainlink, nhưng quên rằng Chainlink feed không được thiết kế cho tài sản thanh khoản thấp. Một token RWA có thể chỉ có 5% giá trị thanh khoản trên Uniswap V3. Khi một flash loan tấn công, nó không cần thay đổi giá oracle. Nó chỉ cần tạo ra một vòng lặp: borrow → swap → deposit → borrow lại. Vì contract không có reentrancy guard, nó cho phép gọi lại hàm withdraw trước khi cập nhật số dư. Tôi đã thấy một bug tương tự trong Bancor năm 2017.
Core của vấn đề: Reentrancy không chỉ là gọi hàm bên ngoài. Nó là bất kỳ hành động nào cho phép contract gọi lại chính nó trước khi trạng thái được cập nhật. Trong RWA project tôi audit, họ dùng safeTransfer để gửi tiền cho user. Hàm safeTransfer của OpenZeppelin có callback. Nếu user là một contract độc hại, nó có thể gọi lại hàm withdraw của RWA contract. Code gốc: uint256 balance = balances[msg.sender]; require(balance >= amount); safeTransfer(token, msg.sender, amount); balances[msg.sender] -= amount;. Bạn thấy lỗi không? balances được cập nhật sau safeTransfer. Nếu callback gọi withdraw lần hai, balance vẫn là giá trị cũ. Kết quả: user rút gấp đôi số tiền. Đây là lỗi 'checks-effects-interactions' cổ điển. Nhưng nhiều developer vẫn mắc vì họ copy code từ contract 'trusted' mà không hiểu. Dựa trên kinh nghiệm audit của tôi, 70% RWA project có ít nhất một reentrancy bug dạng này.

Góc nhìn phản trực giác: Flash loan attack không cần oracle manipulation. Mọi người nghĩ flash loan chỉ để thao túng giá. Nhưng trong trường hợp này, attacker không cần thay đổi giá. Họ chỉ cần tận dụng lỗi reentrancy để rút toàn bộ thanh khoản. Tại sao điều này nguy hiểm hơn? Vì oracle manipulation có thể bị phát hiện qua off-chain monitoring (ví dụ: Chainlink phân phối dữ liệu từ nhiều nguồn). Reentrancy tấn công diễn ra trong một block. Nó gần như không thể phát hiện trước khi giao dịch hoàn thành. Một lần tôi audit một project Bridged USDC trên BSC. Họ có 10 triệu USDC trong pool. Chỉ cần một flash loan 10 triệu USDC, attacker có thể rút toàn bộ trong 2 lần gọi withdraw. Kết quả: pool mất 10 triệu, attacker lãi 10 triệu. Đây không phải lỗi toán học. Đây là lỗi logic về thứ tự cập nhật trạng thái.

Takeaway: Tôi không tin RWA on-chain sẽ thành công nếu code vẫn như 2016. Mỗi lần kiểm toán là một lần tôi thấy lỗi mới từ lỗi cũ. Reentrancy đã được biết đến 8 năm. Nhưng vì các project mới vội vàng launch, họ lặp lại lịch sử. Câu hỏi của tôi cho các developer: Bạn có chắc contract của bạn an toàn khi user có thể gọi lại nó trong một block không? Nếu câu trả lời là 'chắc rồi', hãy deploy và gọi cho tôi. Tôi sẽ mất 5 phút để proof bạn sai.