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;
}
}