Analise smart-contract
27.03.2024

Hidden traps in Honeypot. How to recognize the bait and avoid losing assets?

Honeypot smart contracts are one type of fraudulent smart contracts that look attractive to users and promise financial benefits. However, such contracts contain hidden traps or malicious code, causing users to lose their funds on the contrary.

To protect yourself from Honeypot smart contracts you need to thoroughly research and verify the contract code before engaging with it. The world of smart contracts and blockchain is constantly evolving, with new vulnerabilities and methods of deception appearing regularly, so it is critical for developers, auditors and users to stay up to date with the latest trends and developments in this area.

There are many examples of Honeypot smart contracts. In this article we will look at some of the examples.

1. Fallback function honeypot

In this type of honeypot, the contract has a vulnerable "rollback" feature that seemingly allows anyone to own the contract and remove its balance. However, such a feature contains a hidden condition that prevents successful execution.

Example code:


pragma solidity 0.4.18

contract FallbackHoneypot {
    address public owner;

    function() public payable {
        if (owner == 0) {
            owner = msg.sender;
        } else {
            revert();
        }
    }

    function withdraw() public {
        require(msg.sender == owner);
        msg.sender.transfer(this.balance);
    }
}

In this example, the fallback function allows any user to become the owner. However, the revert() operator ensures that the ownership change never happens, leaving the attacker's funds trapped in the contract.

2. Hidden condition honeypot

Such contracts contain a hidden condition or requirement that must be met for the seemingly vulnerable function to succeed. Users may think they can use the contract, but the hidden condition ensures that they cannot.

Example code:


pragma solidity 0.4.18

contract HiddenConditionHoneypot {
    uint256 public counter = 0;

    function deposit() public payable {
        require(msg.value == 1 ether);
        counter++;
    }

    function withdraw() public {
        require(counter > 100);
        msg.sender.transfer(this.balance);
    }
}

In this contract, the user can deposit 1 ether, and it would seem that after 100 deposits, the user can withdraw the entire contract balance. However, the condition counter > 100 ensures that withdrawal is possible only after 101 deposits, which delays the funds in the contract.

3. Reentrancy honeypot

In this type of honeypot, the contract is vulnerable to a reentrancy attack, where an attacker can repeatedly invoke a contract function during withdrawal. However, the contract contains a hidden mechanism that prevents successful exploitation.

Example code:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount);
        if (msg.sender.call.value(_amount)()) {
            balances[msg.sender] -= _amount;
        }
    }
}

In this example, the contract is vulnerable to the reentrancy attack because of the use of msg.sender.call.value(_amount)(). However, the contract does not allow users to deposit more than the initial balance, effectively delaying their funds.

reentrancy honeypot

4. Gas limit honeypot

In this type of honeypot, the contract seems to allow the user to withdraw funds, but the amount of gas required to perform the withdrawal function exceeds the gas block limit, making the transaction impossible.

Example code:


pragma solidity 0.4.18

contract GasLimitHoneypot {
    uint256 constant public numArrayElements = 1000;
    uint256[numArrayElements] public someArray;

    function deposit() public payable {
        for (uint256 i = 0; i < numArrayElements; i++) {
            someArray[i] = 0;
        }
    }

    function withdraw() public {
        uint256 balance = address(this).balance;
        for (uint256 i = 0; i < numArrayElements; i++) {
            someArray[i] = 1;
        }
        msg.sender.transfer(balance);
    }
}

In this example, the deposit function initializes an array with 1000 elements. The withdraw function transfers the contract balance to the sender, but it modifies the array elements. The gas required to execute the loop exceeds the gas-block limit, which prevents withdrawal and delays it in the contract.

5. Timestamp manipulation honeypot

In this type of honeypot, the contract uses the Ethereum blockchain timestamp as a condition for execution. However, since miners can manipulate the block timestamp, the contract becomes vulnerable.

Example code:


pragma solidity 0.4.18

contract TimestampHoneypot {
    uint256 public lastInteraction;
    uint256 public prize;

    function deposit() public payable {
        lastInteraction = now;
        prize += msg.value;
    }

    function withdraw() public {
        require(now >= lastInteraction + 1 hours);
        msg.sender.transfer(prize);
        prize = 0;
    }
}

In this contract, users can deposit funds and withdraw the entire contract balance after 1 hour from the last interaction. However, since miners can manipulate the block timestamp, they can trigger the withdrawal function earlier than expected and thus actually steal funds.

6. Hidden transfer honeypot

In this type of honeypot, the contract contains a hidden transfer of funds to the attacker's address, which gives the appearance of a legitimate contract but in reality results in a loss of funds.

Example code:


pragma solidity 0.4.18

contract HiddenTransferHoneypot {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function deposit() public payable {
        if (msg.value >= 1 ether) {
            owner.transfer(msg.value);
        }
    }

    function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }
}

In this example, the contract seems to allow deposits and withdrawals. However, the deposit function contains a hidden transfer of funds to the owner (attacker) if the deposit amount is equal to or greater than 1 ether. Unsuspecting users who deposit funds will lose their ethers, as they will be transferred to the attacker's address.

7. Reentrancy honeypot

Reentrancy honeypot is based on exploiting the reentrancy vulnerability in contract, where a contract function can be called recursively before its state is updated.

Example code:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() public {
        uint256 amount = balances[msg.sender];
        (bool success,) = msg.sender.call.value(amount)("");
        require(success, "Withdrawal failed.");
        balances[msg.sender] = 0;
    }
}

In this example, the contract authorizes deposits and withdrawals. However, it is vulnerable to reentrancy attacks. An attacker could create a malicious contract that recursively calls the withdraw function before the balance is set to zero, depleting the entire contract balance.

8. Type casting and overflow honeypot

In this type of honeypot, the attacker uses type conversion and integer overflows to trick users into believing that the contract is safe, but it actually contains hidden traps.

Example code:


pragma solidity 0.4.18

contract TypeCastingHoneypot {
    uint8 public count = 0;
    uint256 public reward = 1 ether;

    function increment() public payable {
        require(msg.value >= reward);

        uint8 prevCount = count;
        count++;

        if (count < prevCount) {
            msg.sender.transfer(address(this).balance);
        }
    }
}

In this example, the contract rewards users who increment the count variable when they send an amount equal to or greater than the reward amount. However, due to the use of uint8 for the count variable, an integer overflow occurs when the value reaches 255. After the overflow, the count is reset to 0, and an attacker can obtain the entire balance of the contract.

overflow honeypot

9. Delegatecall honeypot

In this type of honeypot, the attacker uses the delegatecall function to execute malicious code on behalf of a seemingly secure contract.

Example code:


pragma solidity 0.4.18

contract DelegateCallHoneypot {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function() external payable {}

    function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }

    function execute(address _target, bytes memory _data) public {
        require(msg.sender == owner);
        (bool success,) = _target.delegatecall(_data);
        require(success, "Execution failed.");
    }
}

In this example, the contract allows deposits, withdrawals, and code execution through the execute function. However, an attacker can create malicious code that can manipulate the state of the contract, including the owner variable, by executing the delegatecall function. By changing the owner, an attacker can gain control of the contract and empty its balance.

10. Hidden storage manipulation honeypot

In this type of honeypot, the attacker covertly manipulates storage variables to trick users into taking control of the contract.

Example code:


pragma solidity 0.4.18

contract HiddenStorageManipulation {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    address public owner;

    constructor() public {
        owner = msg.sender;
        balances[owner] = 1000;
        totalSupply = 1000;
    }

    function transfer(address _to, uint256 _value) public {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;

        if (totalSupply - balances[owner] >= 1000) {
            owner = _to;
        }
    }
}

In this example, the contract looks like a simple token contract. Users can pass tokens to each other, and the owner is initially set to be the creator of the contract. However, an attacker can covertly manipulate the totalSupply and balances variables to regain control of the contract. When the difference between totalSupply and balances[owner] reaches 1000, the owner variable is updated, allowing the attacker to regain control of the contract.

11. Function name collision honeypot

In this type of honeypot, the attacker uses function name collisions to trick users into calling the wrong function.

Example code:


pragma solidity 0.4.18

contract FunctionNameCollision {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function withdraw() external {
        require(msg.sender == owner);
        msg.sender.transfer(address(this).balance);
    }

    function withdraw(uint256 amount) external {
        require(msg.sender == owner);
        owner.transfer(amount);
    }

    function() external payable {}
}

In this example, the contract has two withdrawal functions. One of them allows the owner to withdraw the entire balance and the other allows the owner to withdraw a certain amount. However, if the user tries to withdraw a certain amount by calling withdraw(uint256), due to a function name collision, the user will inadvertently call withdraw() without arguments, which will result in withdrawing the entire contract balance.

12. Delegatecall vulnerability honeypot

In this type of honeypot, the attacker uses delegatecall to execute a malicious function in another contract, which can lead to the theft of funds from users.

Example code:


pragma solidity 0.4.18

contract DelegatecallVulnerability {
    address public owner;

    constructor() public {
        owner = msg.sender;
    }

    function withdraw() external {
        require(msg.sender == owner);
        msg.sender.transfer(address(this).balance);
    }

    function () external payable {
        if (msg.value > 1 ether) {
            this.delegatecall(msg.data);
        }
    }
}

contract Malicious {
    function withdraw() external {
        msg.sender.transfer(address(this).balance);
    }
}                

In this example, the contract seems innocuous, with a simple withdraw() function that allows the owner to withdraw the balance of the contract. However, the contract also contains a fallback function that, when receiving more than 1 ether, performs a delegatecall of another contract using msg.data. If an attacker creates a malicious contract with withdraw() that steals the contract balance and sends more than 1 ether to the victim contract, the delegatecall will execute the malicious withdraw() function, stealing the contract balance.

vulnerability honeypot

13. Pseudo-integers overflow honeypot

This type of honeypot is based on the fact that Solidity does not have built-in support for floating point numbers. Attackers can create contracts that outwardly use decimal numbers but actually manipulate integers fraudulently.

Example code:


pragma solidity 0.4.18

contract PseudoIntegersOverflow {
    mapping(address => uint256) public balances;

    function deposit() external payable {
        require(msg.value > 0);
        uint256 pseudoValue = msg.value * 100;
        balances[msg.sender] += pseudoValue;
    }

    function withdraw(uint256 amount) external {
        uint256 pseudoAmount = amount * 100;
        require(balances[msg.sender] >= pseudoAmount);
        balances[msg.sender] -= pseudoAmount;
        msg.sender.transfer(amount);
    }
}

This example gives the impression that the contract manages the user's balance with decimal precision by multiplying the values by 100 before saving them. However, this approach is misleading because Solidity does not support floating point numbers. When the user attempts to withdraw their funds, the contract incorrectly calculates the pseudo-sum due to integer truncation, and the user may not be able to withdraw their entire balance.

To protect against this type of honeypot, you need to understand the limitations of the Solidity language and how it handles numeric operations. Be careful when working with contracts that claim to support decimal precision, and always check the contract code for potential problems.

14. Hidden fees in smart contracts

Some malicious smart contracts can impose hidden payments on users without their knowledge. Such payments may be disguised in the contract code or triggered only under certain conditions.

Example code:


pragma solidity 0.4.18

contract HiddenFees {
    address public owner;
    uint256 public feePercentage;

    constructor(uint256 _feePercentage) public {
        owner = msg.sender;
        feePercentage = _feePercentage;
    }

    function withdraw(uint256 amount) external {
        uint256 fee = amount * feePercentage / 100;
        uint256 netAmount = amount - fee;
        require(address(this).balance >= netAmount);
        msg.sender.transfer(netAmount);
        owner.transfer(fee);
    }
}

In this example, the contract creator sets a fee (feePercentage) when deploying the contract. When interacting with the contract, users may not be aware of this fee. When a user withdraws their funds, the contract calculates the fee, subtracts it from the withdrawal amount, and sends the remainder to the user. The commission is then transferred to the contract owner.

To avoid falling into this trap, carefully review the contract code and make sure you understand all associated fees. Look for hidden or disguised settlements.

15. Hidden state manipulation

In some cases, malicious contracts may allow the contract owner or an attacker to manipulate the internal state of the contract, leading to unexpected consequences for other users.

Example code:


pragma solidity 0.4.18

contract HiddenStateManipulation {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() public {
        owner = msg.sender;
    }

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount);
        msg.sender.transfer(amount);
        balances[msg.sender] -= amount;
    }

    function manipulateBalance(address user, uint256 newBalance) external {
        require(msg.sender == owner);
        balances[user] = newBalance;
    }
}

In this example, the contract allows users to deposit and withdraw funds. However, the manipulateBalance function allows the contract owner to arbitrarily change the balance of any user. This can result in unexpected losses to users or allow the contract owner to steal funds.

The contract code should be scrutinized for features that allow state manipulation, especially if only the contract owner or certain addresses have access to them.

hidden state manipulation

16. Hidden token stealing

In some cases, malicious contracts may contain hidden features that allow the contract owner or attacker to steal tokens from unsuspecting users.

Example code:


pragma solidity 0.4.18

contract HiddenTokenStealing {
    address public owner;
    mapping(address => uint256) public balances;

    constructor() public {
        owner = msg.sender;
    }

    function deposit(IERC20 token, uint256 amount) external {
        require(token.transferFrom(msg.sender, address(this), amount));
        balances[msg.sender] += amount;
    }

    function withdraw(IERC20 token, uint256 amount) external {
        require(balances[msg.sender] >= amount);
        require(token.transfer(msg.sender, amount));
        balances[msg.sender] -= amount;
    }

    function stealTokens(IERC20 token, address user, uint256 amount) external {
        require(msg.sender == owner);
        require(token.transfer(owner, amount));
        balances[user] -= amount;
    }
}

In this example, the contract allows users to deposit and withdraw ERC20 tokens. However, the stealTokens function allows the contract owner to steal tokens from any user.

Conclusion:

Honeypot is just one of the many fraudulent schemes at DeFi that utilizes not only the withdrawal restriction, but also the various hidden traps described above.

In order not to fall for the bait you need to understand the peculiarities of the fraudulent scheme, the subtleties of the Solidity programming language and thoroughly study smart contracts before interacting with them.

By staying informed and conducting thorough due diligence, you can minimize the risks associated with the use of smart contracts.

 

If you have any doubts about the security or functionality of a smart contract use our Lotus Market platform.

 

The Lotus Market platform was created by a team of experienced developers and professional auditors. The goal of the project is to minimize the risks of investing in fraudulent tokens and help to trade cryptocurrencies safely and comfortably.

Regards, Lotus Market team.

All posts

Connect to a wallet

Metamask