🍯
Honeypot
Author: Temirzhan Yussupov
Source code: https://github.com/scaffold-eth/scaffold-eth-examples/tree/honeypot-example
Intended audience: Intermediate
Topics: Scaffold-eth basics, Solidity, Security
This little side quest will allow you to explore the concept of "honeypot". Honeypots are smart contracts designed to look like an easy target for hacking while in fact they are not.
They look vulnerable to an unsophisticated attacker, but if he tries to “break it” he will lose his money instead.
Let's start our environment for tinkering and exploring how honeypots work.
- 1.Clone the repo first
git clone https://github.com/scaffold-eth/scaffold-eth-examples.git honeypot-example
cd honeypot-example
git checkout honeypot-example
- 1.Install dependencies
yarn install
- 1.Start your React frontend
yarn start
- 1.
yarn chain
- 1.Deploy your smart contracts to a local blockchain
yarn deploy
This is how it looks like in my terminal:

image
If everything worked fine, you have to have something like this opened in your browser:

Let's navigate to
packages/hardhat/contracts
folder and check out what contracts we have there.This smart contract will be deployed as a bait for a hacker.
Try to read the source code of this contract and if you are familiar with a reentrancy attack, you probably should note that this contract is indeed vulnerable.
function withdraw(uint _amount) public {
require(_amount <= balances[msg.sender], "Insufficient funds");
(bool sent, ) = msg.sender.call{value: _amount}("");
require(sent, "Failed to send Ether");
balances[msg.sender] -= _amount;
honeyPot.log(msg.sender, _amount, "Withdraw");
}
In a
withdraw
method, the bank smart contract firstly sends funds to the user and only after that deducts this amount
from balances
mapping.This is exactly the reason for possibility to exploit reentrancy.
However, we have another interesting line at the end of the smart contract:
honeyPot.log(msg.sender, _amount, "Withdraw");
Just keep that in mind. We will return to it in a second.
Let's now investigate our main smart contract there. This one will catch our hacker and reveal its address!
function log(address _caller, uint _amount, string memory _action) public {
if (equal(_action, "Withdraw")) {
revert("It's a trap");
}
}
The method that interests us the most is called
log
. It simply compares our _action
to a value and if it is equal to "Withdraw", we revert our entire operation.Why we use
revert
there? If you do know yet, all operations on Ethereum are atomic. It means that if at some moment some operation on chains of operations fails, the entire transaction is reverted and nothing changes on the blockchain.Suppose that contract A calls contract B, and contract B calls contract C. Even if functions executed in contract A and B somehow mutated the state of blockchain, they will not be applied until we are sure that contract C function is executed correctly and there are no errors.
This is exactly the trick we are going to use to fool a hacker.
This contract is a typical exploitation contract for a reentrancy attack. It contains
fallback
function that causes "vulnerable" contract to send us funds again and again..fallback() external payable {
if (address(bank).balance >= target) {
bank.withdraw(target);
}
}
Once we withdraw money from a bank, our fallback will be called again because bank will send us some funds. This recursive nature will force bank to give us all its money... Or maybe not? 👹
So you remember that our Bank has some unusual function call in the end of a
withdraw
method.honeyPot.log(msg.sender, _amount, "Withdraw");
So how does it help us to prevent the attack?
Suppose that our Bank has
0.03
ETH locked in it. I am an evil hacker that wants to hack it. I deploy my Attack.sol
contract and call my attack
function along with 0.01
ETH sent with it.We force bank to execute its withdraw again and again. However, at some point balance of the bank becomes less than
0.01 ETH
and reentrancy comes to an end.After that, the following lines are executed:
balances[msg.sender] -= _amount;
honeyPot.log(msg.sender, _amount, "Withdraw");
Hacker is almost successful but
log
function reverts the entire chain of operations and no money was stolen! However, we now know the address of a hacker 😎Let's use our awesome frontend provided by
scaffold-eth
to make sure our assumption works fine.Run two different sessions. One will be for a simple user and one will be for an evil hacker.
Pro tip: Instead of opening anonymous window in a browser, you can use containers provided by Firefox to have two separate sessions in a single browser window.
This is how it looks like for me:

My first tab is for a simple user and second tab is for a hacker.
Let's send some funds to a bank as a simple user using
deposit
function.
Our bank now has 0.04 ETH locked in it. Let's now try to steal it as a hacker. Navigate to a second tab and enter the
Attack
mode.
Now let's execute
attack
function and send 0.01
ETH along with our function call.However, after we do it, we get an error saying that we
Failed to send the Ether
.
Now let's get rid of this
log
line in our Bank.sol
to make sure that without it the attack works just fine.honeyPot.log(msg.sender, _amount, "Withdraw");
Save your new contract and
yarn deploy
it again.Let's now again deposit 0.03 ETH into a bank and try to attack from an attacker perspective by just sending
0.01
ETH.
In this case, we are able to steal all the money from a bank! This indeed proves that our
log
function from a Honeypot
contract saves us from being hacked.Honeypot is a concept that is used in a wild quite often. Here are some resources to learn more about this technique.
Last modified 1yr ago