Hi,
This contract got hacked, and I don't see how it happened, here is the contract, and one of the hacker transactions:
// SPDX-License-Identifier: Propietary
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract CastleVampiresGame is Ownable(0x2867d29563BB9c011AA82E2718146Ba1a7070955), ReentrancyGuard {
IERC20 public tokenAddress;
mapping(address => bool) public wonLottery;
mapping(address => uint8) public tierReward;
mapping(address => bool) public isInGame;
mapping(address => uint256) public ticketExpiration;
uint256 public tier3Amount = 0.0175 ether;
uint256 public tier2Amount = 0.015 ether;
uint256 public tier1Amount = 0.01 ether;
uint256 public ticketCost = 0.01 ether;
uint256 public tokensNeededToPlay = 1 ether;
//The numbers that will decide what tier rewards will be given
uint8 public oddsToWin = 7;
uint8 public tier1Limit = 60;
uint8 public tier2Limit = 94;
event Claimed(address player ,uint amount);
event DiceResult(address player ,bool result);
event TierWon(address player ,uint8 tier);
event Dice(uint256 diceNum);
event Tier(uint8 tierNum);
event GameEntered(address player);
constructor(address _tokenAddress) {
tokenAddress = IERC20(_tokenAddress);
}
// Function to receive Ether. msg.data must be empty
receive() external payable {}
function EnterGame() external payable nonReentrant{
require(msg.value == ticketCost, "Incorrect amount");
require(getPlayerTokenBalance() >= tokensNeededToPlay, "More tokens needed in wallet to play");
isInGame[msg.sender] = true;
ticketExpiration[msg.sender] = block.timestamp + 6 minutes;
emit GameEntered(msg.sender);
}
//claim amount to send in wei
function claim() public nonReentrant {
require (address(this).balance >= 0 , "Not enough funds");
require (wonLottery[msg.sender], "Can only claim if won the dice lottey");
wonLottery[msg.sender] = false;
address payable _to = payable(msg.sender);
if (tierReward[msg.sender] == 1) {
bool sent = _to.send(tier1Amount);
require(sent, "Failed to send Ether");
emit Claimed(msg.sender,tier1Amount);
} else if (tierReward[msg.sender] == 2) {
bool sent = _to.send(tier2Amount);
require(sent, "Failed to send Ether");
emit Claimed(msg.sender,tier2Amount);
} else if (tierReward[msg.sender] == 3) {
bool sent = _to.send(tier3Amount);
require(sent, "Failed to send Ether");
emit Claimed(msg.sender,tier3Amount);
}
tierReward[msg.sender] = 0;
}
function getNumberWin() private returns (uint8){
bytes32 numberR = keccak256(abi.encodePacked(msg.sender, block.prevrandao, block.timestamp));
uint256 convertedNumber = uint256(numberR);
uint8 lastDigit = uint8(convertedNumber % 10);
emit Dice(convertedNumber);
return (lastDigit);
}
function getNumberTier() private view returns (uint8) {
uint numberR = block.prevrandao + block.timestamp / 2;
// Get the last two digits directly
uint8 lastTwoDigits = uint8(numberR % 100);
return lastTwoDigits;
}
function throwDice() public {
require (!wonLottery[msg.sender], "You already won, go and claim your prize");
require(isInGame[msg.sender] == true, "Not in game");
require(block.timestamp < ticketExpiration[msg.sender] , "Ticket expired");
if (getNumberWin() < oddsToWin){
wonLottery[msg.sender] = true;
diceTier();
emit DiceResult(msg.sender, true);
} else {
emit DiceResult(msg.sender, false);
}
isInGame[msg.sender] = false;
}
function diceTier() private returns (string memory){
uint8 result = getNumberTier();
emit Tier(result);
if (result <= tier1Limit) {
emit TierWon(msg.sender, 1);
tierReward[msg.sender] = 1;
return "Tier 1";
} else if (result > tier1Limit && result <= tier2Limit) {
emit TierWon(msg.sender, 2);
tierReward[msg.sender] = 2;
return "Tier 2";
} else if (result > tier2Limit) {
emit TierWon(msg.sender, 3);
tierReward[msg.sender] = 3;
return "Tier 3";
} else {
emit TierWon(msg.sender, 1);
return "Tier1";
}
}
// new amount to claim in wei units
function updateTier3Amount(uint256 newAmount) external onlyOwner {
tier3Amount = newAmount;
}
function updateTier2Amount(uint256 newAmount) external onlyOwner {
tier2Amount = newAmount;
}
function updateTier1Amount(uint256 newAmount) external onlyOwner {
tier1Amount = newAmount;
}
//Change the difficulty of the tiers
function updateTier1Limit(uint8 newLimit) external onlyOwner {
tier1Limit = newLimit;
}
function updateTier2Limit(uint8 newLimit) external onlyOwner {
tier2Limit = newLimit;
}
function updateOddsToWin(uint8 newOdds) external onlyOwner {
oddsToWin = newOdds;
}
//Change the amount of tokens needed to be on the wallet to play, in wei units
function updateAmountOfTokensToPlay(uint256 newLimit) external onlyOwner {
tokensNeededToPlay = newLimit;
}
function getPlayerTokenBalance() public view returns (uint256){
return tokenAddress.balanceOf(address(msg.sender));
}
function updateERC20TokenAddress (address _tokenAddress) external onlyOwner {
tokenAddress = IERC20(_tokenAddress);
}
//Withdraw Eth
function withdraw() external onlyOwner {
payable(msg.sender).transfer(address(this).balance);
}
}