Gas estimation errored with the following message (see below). The transaction execution will likely fail. Returned error: invalid opcode
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
contract P2PDonation is Ownable(msg.sender), ReentrancyGuard, Pausable {
IERC20 public usdtToken;
struct User {
uint64 balance;
uint32 level;
address referrer;
uint64 totalDonated;
uint64 referralRewards;
uint64 totalReceived;
uint64 totalWithdrawn;
bool exists;
}
struct Level {
uint256 requiredBalance;
uint256 totalPool;
string name;
}
uint256 public constant DEPOSIT_AMOUNT = 10 * 1e6; // Assuming USDT has 6 decimals
uint256 public constant MAX_LEVEL = 10;
uint256 public constant REFERRAL_BONUS_PERCENT = 1;
uint256 public constant TAX_PERCENT = 20;
Level[MAX_LEVEL] public levels;
mapping(address => User) public users;
address[] public queue;
mapping(address => uint256) public pendingWithdrawals;
address[] private founders;
uint8[] private founderShares;
// Events
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Upgraded(address indexed user, uint256 level);
event ReferralRewarded(address indexed referrer, address indexed referee, uint256 amount);
event EmergencyWithdrawal(address indexed owner, uint256 amount);
event ReferralBonusWithdrawn(address indexed user, uint256 bonusAmount);
event UserRecycled(address indexed user, uint32 level);
constructor(address _usdtTokenAddress) {
usdtToken = IERC20(_usdtTokenAddress);
transferOwnership(msg.sender);
initializeLevels();
initializeFounders();
}
function initializeLevels() internal {
levels[0] = Level({requiredBalance: 10 * 1e6, totalPool: 40 * 1e6, name: "1BABY"});
levels[1] = Level({requiredBalance: 16 * 1e6, totalPool: 64 * 1e6, name: "2PILOT"});
levels[2] = Level({requiredBalance: 25 * 1e6, totalPool: 104 * 1e6, name: "3GOLD"});
levels[3] = Level({requiredBalance: 40 * 1e6, totalPool: 160 * 1e6, name: "4COMMANDER"});
levels[4] = Level({requiredBalance: 65 * 1e6, totalPool: 260 * 1e6, name: "5SENATOR"});
levels[5] = Level({requiredBalance: 104 * 1e6, totalPool: 416 * 1e6, name: "6DIRECTOR"});
levels[6] = Level({requiredBalance: 167 * 1e6, totalPool: 668 * 1e6, name: "7EEXUTIVE"});
levels[7] = Level({requiredBalance: 268 * 1e6, totalPool: 1072 * 1e6, name: "8PRIME_MINISTER"});
levels[8] = Level({requiredBalance: 429 * 1e6, totalPool: 1716 * 1e6, name: "9BLUE_DIAMOND"});
levels[9] = Level({requiredBalance: 1000 * 1e6, totalPool: 6300 * 1e6, name: "10PRESIDENT"});
}
function initializeFounders() internal {
founders = [
0xF8838532D1FD225E93520Edfbe19FA6aE6369320,
0x13E209F184BCEFBea537A2e50DB554f60e0fd1a9,
0xD24aF77C52701259FC404c5D64B0E55DC37f806C,
0xE137ee359438064bA10b881497a4F30A75a346cd,
0x9B7B2cBA5885A8B653a2aD8142C42138A4bCf523,
0xEdf7192e99a192C9c1428121BA963B8346B07c8c
];
founderShares = [7, 6, 4, 1, 1, 1];
}
// Deposit function
function deposit(address _referrer) external whenNotPaused nonReentrant {
validateDepositAmount(DEPOSIT_AMOUNT);
User storage user = users[msg.sender];
user.balance = uint64(DEPOSIT_AMOUNT);
initializeUserOnFirstDeposit(user, _referrer);
processUserDeposit(user);
transferFundsToContract();
emit Deposited(msg.sender, DEPOSIT_AMOUNT);
}
function validateDepositAmount(uint256 _amount) private pure {
require(_amount == DEPOSIT_AMOUNT, "Invalid deposit amount");
}
function initializeUserOnFirstDeposit(User storage user, address _referrer) private {
require(_referrer != address(0) && _referrer != msg.sender, "Invalid referrer");
require(!user.exists, "User already exists");
user.exists = true;
user.referrer = _referrer;
user.level = 1;
}
function processUserDeposit(User storage user) private {
// Reset user's balance if required
user.balance = uint64(DEPOSIT_AMOUNT);
// Add user to the queue
queue.push(msg.sender);
}
function transferFundsToContract() private {
require(usdtToken.transferFrom(msg.sender, address(this), DEPOSIT_AMOUNT), "Transfer failed");
}
// Withdraw earnings function
function withdrawEarnings() external whenNotPaused nonReentrant {
User storage user = users[msg.sender];
validateEnrollment(user);
uint256 withdrawalAmount = calculateWithdrawalAmount(user);
// Check if the withdrawal is possible given the user's current balance
require(withdrawalAmount <= user.balance, "Withdrawal amount exceeds balance");
performAutomaticUpgrade(user, withdrawalAmount);
uint256 tax = calculateTax(withdrawalAmount);
withdrawalAmount -= tax; // Apply tax
distributeTax(tax);
// Execute the withdrawal
executeWithdrawal(msg.sender, withdrawalAmount);
// Recycling logic for max level users
if(user.level == MAX_LEVEL && withdrawalAmount > 0) {
// After successful withdrawal, recycle the user back to level 1
recycleUser(user);
} else {
// Update the total withdrawn for users not at max level
user.totalWithdrawn += uint64(withdrawalAmount);
}
emit Withdrawn(msg.sender, withdrawalAmount);
}
function recycleUser(User storage user) private {
user.level = 1;
user.balance = uint64(levels[0].requiredBalance); // Set balance to required balance of level 1
// Resetting other user properties
user.totalDonated = 0;
user.referralRewards = 0;
user.totalReceived = 0;
user.totalWithdrawn = 0;
// You might want to reset other properties depending on your contract's logic
// Emit an event if necessary, for example:
emit UserRecycled(msg.sender, user.level);
}
function withdrawPendingAmount() external {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "No funds to withdraw");
pendingWithdrawals[msg.sender] = 0;
require(usdtToken.transfer(msg.sender, amount), "Transfer failed");
}
function validateEnrollment(User storage user) private view {
require(user.exists, "User is not enrolled");
require(user.balance > 0, "Insufficient balance");
}
function performAutomaticUpgrade(User storage user, uint256 withdrawalAmount) private {
uint256 upgradeCost = calculateUpgradeCost(user.level);
require(withdrawalAmount <= user.balance, "Withdrawal amount exceeds balance"); // Check
if (withdrawalAmount / 2 >= upgradeCost) {
user.balance -= uint64(upgradeCost); // Effects
handleUpgradeFinances(upgradeCost); // External Interaction (handled carefully)
user.level++;
emit Upgraded(msg.sender, user.level);
}
}
function executeWithdrawal(address userAddress, uint256 withdrawalAmount) private {
require(usdtToken.transfer(userAddress, withdrawalAmount), "Withdrawal transfer failed");
}
function calculateWithdrawalAmount(User storage user) private view returns (uint256) {
Level storage userLevel = levels[user.level - 1];
uint256 poolEarnings = userLevel.totalPool;
require(user.totalReceived <= poolEarnings, "Maximum earnings exceeded");
// Max withdrawal for max level users is the total pool minus the recycling fee
if(user.level == MAX_LEVEL) {
uint256 recyclingFee = 10 * 1e6; // Recycling fee of 10 USDT
uint256 maxWithdrawal = poolEarnings - recyclingFee;
// Ensure the user has not withdrawn more than they are allowed
require(user.totalWithdrawn <= maxWithdrawal, "Withdrawal exceeds limit");
return maxWithdrawal - user.totalWithdrawn; // Adjusted to deduct the recycling fee
} else {
// For users below max level, they can withdraw 50% of their balance
uint256 maxWithdrawal = poolEarnings - user.totalWithdrawn;
return maxWithdrawal / 2; // User can only withdraw 50% of their balance
}
}
function handleReferralBonusOnUpgrade(address referrer, uint256 upgradeCost) internal {
uint256 taxAmount = calculateTax(upgradeCost);
uint256 netUpgradeCost = upgradeCost - taxAmount;
uint256 referralBonus = netUpgradeCost * REFERRAL_BONUS_PERCENT / 100;
User storage referrerUser = users[referrer];
referrerUser.referralRewards += uint64(referralBonus);
emit ReferralRewarded(referrer, msg.sender, referralBonus);
// Record the referral bonus for withdrawal
recordPendingWithdrawal(referrer, referralBonus);
}
// Modified function to calculate tax
function calculateTax(uint256 amount) private pure returns (uint256) {
return amount * TAX_PERCENT / 100;
}
function distributeTax(uint256 taxAmount) private {
for (uint256 i = 0; i < founders.length; i++) {
uint256 share = taxAmount * founderShares[i] / 100;
require(usdtToken.transfer(founders[i], share), "Tax distribution failed");
}
}
function calculateUpgradeCost(uint256 currentLevel) private view returns (uint256) {
if (currentLevel >= MAX_LEVEL) {
// If the user is at the max level, return 0 as there's no upgrade cost.
return 0;
}
// Calculate the upgrade cost based on the level structure.
// 50% of the next level's pool is used as the upgrade cost.
Level storage nextLevel = levels[currentLevel]; // currentLevel is 1-indexed, array is 0-indexed
return nextLevel.totalPool / 2;
}
function recordPendingWithdrawal(address userAddress, uint256 withdrawalAmount) private {
pendingWithdrawals[userAddress] += withdrawalAmount;
}
function handleUpgradeFinances(uint256 upgradeCost) private {
if (queue.length > 0 && upgradeCost > 0) {
address frontUserAddress = queue[0];
User storage frontUser = users[frontUserAddress];
uint256 paymentAmount = upgradeCost;
uint256 contractBalance = usdtToken.balanceOf(address(this));
require(contractBalance >= paymentAmount, "Insufficient contract balance");
recordPendingWithdrawal(frontUserAddress, paymentAmount);
frontUser.totalReceived += uint64(paymentAmount);
emit Withdrawn(frontUserAddress, paymentAmount);
// Remove the front user from the queue
removeFromQueue(frontUserAddress);
// Handle referral bonus on upgrade for front user
if (frontUser.referrer != address(0)) {
handleReferralBonusOnUpgrade(frontUser.referrer, paymentAmount);
}
}
}
// Automatic upgrade logic
function performUpgrade() internal {
User storage user = users[msg.sender];
if (validateUpgradeEligibility(user)) {
uint256 upgradeCost = calculateUpgradeCost(user.level);
executeUpgrade(user, upgradeCost);
handleUpgradeFinances(upgradeCost);
settleFrontUser();
}
}
function validateUpgradeEligibility(User storage user) internal view returns (bool) {
uint256 requiredEarnings = levels[user.level - 1].totalPool;
return user.totalReceived >= requiredEarnings / 2;
}
function executeUpgrade(User storage user, uint256 upgradeCost) internal {
user.level += 1;
user.totalReceived -= uint64(upgradeCost);
emit Upgraded(msg.sender, user.level);
}
// Queue management logic
function addToQueue(address userAddress) internal {
queue.push(userAddress);
}
function removeFromQueue(address userAddress) internal {
// Ensure the queue is not empty
require(queue.length > 0, "Queue is empty");
// Check that the user to remove is at the front of the queue
require(queue[0] == userAddress, "User is not at the front of the queue");
// Remove the first element by shifting all elements to the left
for (uint256 i = 0; i < queue.length - 1; i++) {
queue[i] = queue[i + 1];
}
queue.pop(); // Remove the last element, which is now a duplicate of the second to last element
}
function settleFrontUser() internal {
require(queue.length > 0, "Queue is empty");
address frontUserAddress = queue[0];
User storage frontUser = users[frontUserAddress];
uint256 paymentAmount = calculateSettlementAmount(frontUser);
// Check if the contract has enough balance to make the payment
uint256 contractBalance = usdtToken.balanceOf(address(this));
require(contractBalance >= paymentAmount, "Insufficient contract balance");
// Record the payment to the front user as a pending withdrawal
recordPendingWithdrawal(frontUserAddress, paymentAmount);
frontUser.totalReceived += uint64(paymentAmount);
emit Withdrawn(frontUserAddress, paymentAmount);
// Handle the referral bonus for the referrer if applicable
if (frontUser.referrer != address(0)) {
handleReferralBonusOnSettlement(frontUser.referrer);
}
// Remove the front user from the queue
removeFromQueue(frontUserAddress);
}
function calculateSettlementAmount(User storage user) internal view returns (uint256) {
// Since payments are only made to the front user, check if the user is at the front of the queue
if (isUserFrontOfQueue(user)) {
Level storage userLevel = levels[user.level - 1];
return userLevel.totalPool; // Front user receives the full pool amount for their level
} else {
return 0; // Users not at the front of the queue receive no payment
}
}
function isUserFrontOfQueue(User storage user) internal view returns (bool) {
address userAddress = user.referrer;
return queue.length > 0 && queue[0] == userAddress;
}
function findUserIndexInQueue(address userAddress) internal view returns (uint256) {
for (uint256 i = 0; i < queue.length; i++) {
if (queue[i] == userAddress) {
return i;
}
}
revert("User not in queue");
}
// Owner can pause the contract in case of an emergency
function pause() external onlyOwner {
_pause();
}
// Owner can unpause the contract once the emergency is resolved
function unpause() external onlyOwner {
_unpause();
}
// Owner can withdraw the contract's funds in case of an emergency
function emergencyWithdraw() external onlyOwner {
uint256 balance = usdtToken.balanceOf(address(this));
require(balance > 0, "No funds to withdraw");
require(usdtToken.transfer(owner(), balance), "Emergency withdrawal failed");
emit EmergencyWithdrawal(owner(), balance);
}
// Function for users to withdraw their referral bonuses
function withdrawRefBonus() external whenNotPaused nonReentrant {
User storage user = users[msg.sender];
uint256 bonusAmount = user.referralRewards;
require(bonusAmount > 0, "No referral bonus to withdraw");
// Reset the referral bonus balance before transferring to prevent re-entrancy attacks
user.referralRewards = 0;
// Transfer the referral bonus to the user
require(usdtToken.transfer(msg.sender, bonusAmount), "Referral bonus transfer failed");
// Emit an event for the withdrawal
emit ReferralBonusWithdrawn(msg.sender, bonusAmount);
}
function handleReferralBonusOnSettlement(address referrer) internal {
uint256 referralBonus = DEPOSIT_AMOUNT * REFERRAL_BONUS_PERCENT / 100;
User storage referrerUser = users[referrer];
referrerUser.referralRewards += uint64(referralBonus);
emit ReferralRewarded(referrer, msg.sender, referralBonus);
// Record the referral bonus as a pending withdrawal
recordPendingWithdrawal(referrer, referralBonus);
}
}```