// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract GlobalBetsClone { struct Option { string name; uint256 price; uint256 pool; } Option[] public options; uint256 public totalPool; uint256 public constant PRECISION = 1e18; string public description; IERC20 public usdc; mapping(address => mapping(uint256 => uint256)) public userShares; uint256 public winningOptionIndex; bool public winnerDeclared = false; address public admin; bool private initialized; // To prevent re-initialization event SharesBought(address indexed user, uint256 indexed optionIndex, uint256 sharesBought, uint256 amountSpent); event SharesSold(address indexed user, uint256 indexed optionIndex, uint256 sharesSold, uint256 amountReceived); event PricesUpdated(uint256[] newPrices, uint256 totalPool); function initialize( address _usdcAddress, string memory _description, string[] memory optionNames, uint256[] memory initialPrices, address _admin ) external { require(!initialized, "Contract is already initialized"); initialized = true; admin = _admin; usdc = IERC20(_usdcAddress); description = _description; for (uint256 i = 0; i < initialPrices.length; i++) { options.push(Option({ name: optionNames[i], price: initialPrices[i], pool: 0 })); } } modifier onlyAdmin() { require(msg.sender == admin, "Only admin can call this function"); _; } function addLiquidity(uint256 liquidity) external onlyAdmin { // Transfer USDC from the admin to the contract usdc.transferFrom(msg.sender, address(this), liquidity); totalPool += liquidity; // Distribute the initial liquidity proportionally based on the current prices for (uint256 i = 0; i < options.length; i++) { uint256 poolShare = (options[i].price * totalPool) / PRECISION; options[i].pool = poolShare; } // Recalculate prices after adding initial liquidity updatePrices(); } function buy(uint256 optionIndex, uint256 betAmount) external { require(optionIndex < options.length, "Invalid option"); require(!winnerDeclared, "Winner already declared"); require(totalPool > 0, "Liquidity must be added before placing bets"); // Transfer USDC from the user to the contract usdc.transferFrom(msg.sender, address(this), betAmount); // Update the pool of the chosen option with the bet amount options[optionIndex].pool += betAmount; // Recalculate the total pool totalPool += betAmount; // Recalculate prices for all options updatePrices(); // Calculate the effective price for the user uint256 effectivePrice = getEffectivePrice(optionIndex, betAmount); // Calculate the number of shares the user receives uint256 sharesBought = (betAmount * PRECISION) / effectivePrice; // Record the user's shares userShares[msg.sender][optionIndex] += sharesBought; // Emit the SharesBought event emit SharesBought(msg.sender, optionIndex, sharesBought, betAmount); } function sell(uint256 optionIndex, uint256 shareAmount) external { require(optionIndex < options.length, "Invalid option"); require(!winnerDeclared, "Winner already declared"); require(userShares[msg.sender][optionIndex] >= shareAmount, "Insufficient shares"); // Calculate the value of the shares being sold based on the current price uint256 currentPrice = options[optionIndex].price; uint256 poolReduction = (shareAmount * currentPrice) / PRECISION; // Ensure the pool reduction does not make the pool go below 1 uint256 newOptionPool = options[optionIndex].pool > poolReduction ? options[optionIndex].pool - poolReduction : 1; uint256 newTotalPool = totalPool > poolReduction ? totalPool - poolReduction : 1; uint256 newPrice = (newOptionPool * PRECISION) / newTotalPool; // Calculate the effective price as the average of the current and new price uint256 effectivePrice = (currentPrice + newPrice) / 2; // Calculate the value of the shares being sold based on the effective price uint256 sellValue = (shareAmount * effectivePrice) / PRECISION; // Update the option's pool and the total pool options[optionIndex].pool = newOptionPool; totalPool = newTotalPool; // Recalculate prices for all options updatePrices(); // Deduct the user's shares userShares[msg.sender][optionIndex] -= shareAmount; // Transfer the sell value to the user in USDC usdc.transfer(msg.sender, sellValue); // Emit the SharesSold event emit SharesSold(msg.sender, optionIndex, shareAmount, sellValue); } function updatePrices() internal { uint256[] memory newPrices = new uint256[](options.length); // Update prices for all options based on their current pools for (uint256 i = 0; i < options.length; i++) { options[i].price = (options[i].pool * PRECISION) / totalPool; newPrices[i] = options[i].price; } // Emit the PricesUpdated event emit PricesUpdated(newPrices, totalPool); } function getEffectivePrice(uint256 optionIndex, uint256 betAmount) public view returns (uint256) { uint256 currentPrice = options[optionIndex].price; uint256 newPrice = (options[optionIndex].pool + betAmount) * PRECISION / (totalPool + betAmount); uint256 effectivePrice = (currentPrice + newPrice) / 2; return effectivePrice; } function declareWinner(uint256 optionIndex) external onlyAdmin { require(optionIndex < options.length, "Invalid option"); require(!winnerDeclared, "Winner already declared"); winningOptionIndex = optionIndex; winnerDeclared = true; } function claimWinnings() external { require(winnerDeclared, "Winner not declared yet"); uint256 userSharesInWinningOption = userShares[msg.sender][winningOptionIndex]; require(userSharesInWinningOption > 0, "No shares in winning option"); // Calculate user's winnings based on their share of the winning pool uint256 winnings = (userSharesInWinningOption * totalPool) / options[winningOptionIndex].pool; // Reset the user's shares in the winning option userShares[msg.sender][winningOptionIndex] = 0; // Transfer the winnings to the user in USDC usdc.transfer(msg.sender, winnings); } // Function to return all shares a user holds across all options function getUserShares(address user) external view returns (uint256[] memory) { uint256[] memory shares = new uint256[](options.length); for (uint256 i = 0; i < options.length; i++) { shares[i] = userShares[user][i]; } return shares; } function addOption(string memory optionName, uint256 initialPrice) external onlyAdmin { require(!winnerDeclared, "Cannot add options after the winner has been declared"); require(initialPrice > 0 && initialPrice < PRECISION, "Initial price must be between 0 and 1 (in 18 decimals)"); // Calculate the reduction factor for the existing options' prices uint256 reductionFactor = (PRECISION - initialPrice) * PRECISION / PRECISION; // Reduce the prices of existing options proportionally and adjust their pools for (uint256 i = 0; i < options.length; i++) { options[i].price = (options[i].price * reductionFactor) / PRECISION; options[i].pool = (options[i].pool * reductionFactor) / PRECISION; } // Add the new option with the specified initial price and an initial pool of 0 options.push(Option({ name: optionName, price: initialPrice, pool: 0 })); // Redistribute the total pool proportionally to all options uint256 remainingPool = totalPool; for (uint256 i = 0; i < options.length; i++) { options[i].pool = (options[i].price * totalPool) / PRECISION; remainingPool -= options[i].pool; } // Adjust any small remaining pool to the last option to ensure total pool consistency if (remainingPool > 0) { options[options.length - 1].pool += remainingPool; } // Recalculate prices to ensure consistency after redistribution updatePrices(); } function removeOption(uint256 optionIndex) external onlyAdmin { require(!winnerDeclared, "Cannot remove options after the winner has been declared"); require(optionIndex < options.length, "Invalid option index"); // Calculate the pool of the option to be removed uint256 removedPool = options[optionIndex].pool; // Remove the option by swapping it with the last option and then reducing the array length options[optionIndex] = options[options.length - 1]; options.pop(); // Redistribute the removed pool proportionally to the remaining options for (uint256 i = 0; i < options.length; i++) { uint256 proportionateShare = (options[i].pool * removedPool) / (totalPool - removedPool); options[i].pool += proportionateShare; } // Update the total pool (remains unchanged since the removed pool is redistributed) // Recalculate prices after removing the option updatePrices(); } // Function to return prices of all options function getOptionPrices() public view returns (uint256[] memory) { uint256[] memory prices = new uint256[](options.length); for (uint256 i = 0; i < options.length; i++) { prices[i] = options[i].price; } return prices; } // Fallback function to receive ETH when adding liquidity or handling other payments (not used here) receive() external payable {} }
0.4.18