PresaleEnhanced

Description:

ERC20 token contract with Pausable, Oracle capabilities. Standard implementation for fungible tokens on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// Sources flattened with hardhat v2.26.3 https://hardhat.org

// SPDX-License-Identifier: MIT

// File contracts/PresaleEnhanced.sol

// Original license: SPDX_License_Identifier: MIT
pragma solidity ^0.8.30;

// Inline OpenZeppelin contracts - Essential interfaces only
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IERC20Metadata is IERC20 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
}

// Enhanced SafeERC20 functionality
library SafeERC20 {
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transfer failed");
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        (bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "SafeERC20: transferFrom failed");
    }
}

// Minimal AccessControl
contract AccessControl {
    mapping(bytes32 => mapping(address => bool)) private _roles;
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    modifier onlyRole(bytes32 role) {
        require(hasRole(role, msg.sender), "AccessControl: missing role");
        _;
    }

    function hasRole(bytes32 role, address account) public view returns (bool) {
        return _roles[role][account];
    }

    function _grantRole(bytes32 role, address account) internal {
        if (!hasRole(role, account)) {
            _roles[role][account] = true;
            emit RoleGranted(role, account, msg.sender);
        }
    }

    function grantRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
        _grantRole(role, account);
    }

    function revokeRole(bytes32 role, address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
        if (hasRole(role, account)) {
            _roles[role][account] = false;
            emit RoleRevoked(role, account, msg.sender);
        }
    }
}

// Minimal ReentrancyGuard
contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

// Minimal Pausable
contract Pausable {
    bool private _paused;
    event Paused(address account);
    event Unpaused(address account);

    constructor() {
        _paused = false;
    }

    modifier whenNotPaused() {
        require(!_paused, "Pausable: paused");
        _;
    }

    function paused() public view returns (bool) {
        return _paused;
    }

    function _pause() internal whenNotPaused {
        _paused = true;
        emit Paused(msg.sender);
    }

    function _unpause() internal {
        require(_paused, "Pausable: not paused");
        _paused = false;
        emit Unpaused(msg.sender);
    }
}

// Inline PrecisionMath library
library PrecisionMath {
    uint256 internal constant PRECISION = 10**18;

    function toInternal(uint256 value, uint8 decimals) internal pure returns (uint256) {
        if (decimals == 18) return value;
        if (decimals > 18) return value / (10**(decimals - 18));
        return value * (10**(18 - decimals));
    }

    function isWithinDeviation(uint256 price1, uint256 price2, uint256 bps) internal pure returns (bool) {
        uint256 higher = price1 > price2 ? price1 : price2;
        uint256 lower = price1 > price2 ? price2 : price1;
        uint256 deviation = ((higher - lower) * 10000) / higher;
        return deviation <= bps;
    }

    function mulDiv(uint256 a, uint256 b, uint256 c) internal pure returns (uint256) {
        return (a * b) / c;
    }
}

// Price Oracle interfaces
struct PriceData {
    uint256 price;
    uint256 timestamp;
    uint256 roundId;
    bool isValid;
}

interface IPriceOracle {
    function getReliablePrice() external view returns (uint256 price, uint256 timestamp, address source);
}

interface XenonAI {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

/**
 * @title Enhanced Presale Contract - Full Featured Version
 * @dev Secure presale with all original features + easy verification
 */
contract PresaleEnhanced is AccessControl, ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;
    using PrecisionMath for uint256;

    // Roles
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant PRICE_UPDATER_ROLE = keccak256("PRICE_UPDATER_ROLE");

    // Core contracts
    XenonAI public immutable projectToken;
    IPriceOracle public immutable priceOracle;
    address public treasuryWallet;

    // Payment tokens - Same structure as original
    struct PaymentToken {
        IERC20 token;
        uint8 decimals;
        bool isActive;
        string symbol;
    }

    mapping(address => PaymentToken) public paymentTokens;
    address[] public paymentTokenList;

    // Token allocation and pricing - Same as original
    uint256 public constant PRESALE_SUPPLY = 180_000_000 * 10**18; // 60% of 300M
    uint256 public tokensSold = 0;
    uint256 public tokenPrice = 3000000000000000; // $0.003 in 18 decimals

    // Statistics - Same as original
    uint256 public totalRaised = 0;
    uint256 public presaleStartTime;
    uint256 public presaleEndTime;

    // Dynamic limits - Same as original (CONFIGURABLE)
    uint256 public minPurchase = 1 * 10**18; // $1 minimum
    uint256 public maxPurchase = 50_000 * 10**18; // $50,000 maximum

    // Security settings - Same as original
    uint256 public constant MAX_PRICE_DEVIATION_BPS = 10000; // 100% max price change
    uint256 public emergencyWithdrawalDelay = 3 days;
    uint256 public emergencyWithdrawalRequest;

    // Batch operations - Same as original
    struct PurchaseData {
        address buyer;
        uint256 tokenAmount;
        uint256 usdAmount;
        address paymentToken;
    }

    // Tracking
    mapping(address => uint256) public purchases;
    mapping(address => bool) public hasParticipated;
    uint256 public totalParticipants;

    // Events - Same as original
    event TokensPurchased(address indexed buyer, uint256 tokenAmount, uint256 usdAmount, address indexed paymentToken, uint256 timestamp);
    event TokenPriceUpdated(uint256 newPrice, uint256 timestamp);
    event TreasuryUpdated(address indexed newTreasury, uint256 timestamp);
    event PaymentTokenAdded(address indexed token, string symbol, uint8 decimals);
    event PaymentTokenRemoved(address indexed token);
    event PaymentTokenStatusChanged(address indexed token, bool isActive);
    event EmergencyWithdrawalRequested(uint256 timestamp);
    event EmergencyWithdrawalExecuted(uint256 amount, uint256 timestamp);
    event PurchaseLimitsUpdated(uint256 minPurchase, uint256 maxPurchase);

    // Custom errors - Same as original
    error PresaleNotActive();
    error InvalidAmount();
    error PurchaseTooSmall();
    error PurchaseTooLarge();
    error SlippageTooHigh();
    error PresaleSupplyExceeded();
    error InvalidAddress();
    error PriceChangeTooLarge();
    error NoETHToWithdraw();
    error ETHTransferFailed();
    error TokenNotSupported();
    error EmergencyNotRequested();
    error TimelockNotExpired();

    constructor(
        address _projectToken,
        address _priceOracle,
        address _treasuryWallet,
        uint256 _presaleDuration,
        address[] memory _paymentTokens,
        string[] memory _symbols
    ) {
        if (_projectToken == address(0)) revert InvalidAddress();
        if (_priceOracle == address(0)) revert InvalidAddress();
        if (_treasuryWallet == address(0)) revert InvalidAddress();
        if (_presaleDuration == 0) revert InvalidAmount();
        if (_paymentTokens.length != _symbols.length) revert InvalidAmount();

        projectToken = XenonAI(_projectToken);
        priceOracle = IPriceOracle(_priceOracle);
        treasuryWallet = _treasuryWallet;
        presaleStartTime = block.timestamp;
        presaleEndTime = block.timestamp + _presaleDuration;

        // Setup roles
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
        _grantRole(OPERATOR_ROLE, msg.sender);
        _grantRole(PRICE_UPDATER_ROLE, msg.sender);

        // Add payment tokens
        for (uint256 i = 0; i < _paymentTokens.length; i++) {
            _addPaymentToken(_paymentTokens[i], _symbols[i]);
        }
    }

    // Internal helper - Same as original
    function _addPaymentToken(address tokenAddress, string memory symbol) private {
        if (tokenAddress == address(0)) revert InvalidAddress();

        uint8 decimals = IERC20Metadata(tokenAddress).decimals();

        paymentTokens[tokenAddress] = PaymentToken({
            token: IERC20(tokenAddress),
            decimals: decimals,
            isActive: true,
            symbol: symbol
        });

        paymentTokenList.push(tokenAddress);
        emit PaymentTokenAdded(tokenAddress, symbol, decimals);
    }

    // Check if presale is active - Same as original
    function _isPresaleActive() private view returns (bool) {
        return block.timestamp >= presaleStartTime &&
               block.timestamp <= presaleEndTime &&
               tokensSold < PRESALE_SUPPLY;
    }

    // Purchase with ETH - Enhanced with same logic as original
    function purchaseWithETH(uint256 minTokens) external payable nonReentrant whenNotPaused {
        if (!_isPresaleActive()) revert PresaleNotActive();
        if (msg.value == 0) revert InvalidAmount();

        // Get ETH price from oracle
        (uint256 ethPrice, , ) = priceOracle.getReliablePrice();

        // Convert to internal precision
        uint256 ethAmount18 = PrecisionMath.toInternal(msg.value, 18);
        uint256 usdAmount18 = PrecisionMath.mulDiv(ethAmount18, ethPrice, PrecisionMath.PRECISION);

        // Validate purchase limits
        if (usdAmount18 < minPurchase) revert PurchaseTooSmall();
        if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();

        // Calculate tokens
        uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);

        if (tokenAmount18 < minTokens) revert SlippageTooHigh();
        if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();

        // Update state
        _updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);

        // Transfer ETH to treasury
        (bool success, ) = payable(treasuryWallet).call{value: msg.value}("");
        if (!success) revert ETHTransferFailed();

        // Transfer tokens to buyer
        if (!projectToken.transfer(msg.sender, tokenAmount18)) revert ETHTransferFailed();

        emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, address(0), block.timestamp);
    }

    // Purchase with ERC20 tokens - Enhanced with same logic as original
    function purchaseWithToken(address tokenAddress, uint256 amount, uint256 minTokens) external nonReentrant whenNotPaused {
        if (!_isPresaleActive()) revert PresaleNotActive();

        PaymentToken storage paymentToken = paymentTokens[tokenAddress];
        if (!paymentToken.isActive) revert TokenNotSupported();
        if (amount == 0) revert InvalidAmount();

        // Convert amount to 18 decimals for calculations
        uint256 amount18 = PrecisionMath.toInternal(amount, paymentToken.decimals);

        // For stablecoins, assume 1:1 USD peg
        uint256 usdAmount18 = amount18;

        // Validate purchase limits
        if (usdAmount18 < minPurchase) revert PurchaseTooSmall();
        if (usdAmount18 > maxPurchase) revert PurchaseTooLarge();

        // Calculate tokens
        uint256 tokenAmount18 = PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);

        if (tokenAmount18 < minTokens) revert SlippageTooHigh();
        if (tokensSold + tokenAmount18 > PRESALE_SUPPLY) revert PresaleSupplyExceeded();

        // Update state
        _updatePurchaseState(msg.sender, tokenAmount18, usdAmount18);

        // Transfer payment token to treasury using SafeERC20
        paymentToken.token.safeTransferFrom(msg.sender, treasuryWallet, amount);

        // Transfer tokens to buyer
        if (!projectToken.transfer(msg.sender, tokenAmount18)) revert ETHTransferFailed();

        emit TokensPurchased(msg.sender, tokenAmount18, usdAmount18, tokenAddress, block.timestamp);
    }

    // Update purchase state - Same as original
    function _updatePurchaseState(address buyer, uint256 tokenAmount, uint256 usdAmount) private {
        tokensSold += tokenAmount;
        totalRaised += usdAmount;

        if (!hasParticipated[buyer]) {
            hasParticipated[buyer] = true;
            totalParticipants++;
        }

        purchases[buyer] += tokenAmount;
    }

    // Admin functions - Same as original
    function updateTokenPrice(uint256 newPrice) external onlyRole(PRICE_UPDATER_ROLE) {
        if (newPrice == 0) revert InvalidAmount();

        // Prevent extreme price changes
        if (tokenPrice > 0) {
            if (!PrecisionMath.isWithinDeviation(tokenPrice, newPrice, MAX_PRICE_DEVIATION_BPS)) {
                revert PriceChangeTooLarge();
            }
        }

        tokenPrice = newPrice;
        emit TokenPriceUpdated(newPrice, block.timestamp);
    }

    function updateTreasuryWallet(address newTreasury) external onlyRole(ADMIN_ROLE) {
        if (newTreasury == address(0)) revert InvalidAddress();
        treasuryWallet = newTreasury;
        emit TreasuryUpdated(newTreasury, block.timestamp);
    }

    // Purchase limit management - ADDED BACK
    function updatePurchaseLimits(uint256 _minPurchase, uint256 _maxPurchase) external onlyRole(ADMIN_ROLE) {
        if (_minPurchase == 0 || _maxPurchase == 0) revert InvalidAmount();
        if (_minPurchase >= _maxPurchase) revert InvalidAmount();

        minPurchase = _minPurchase;
        maxPurchase = _maxPurchase;

        emit PurchaseLimitsUpdated(_minPurchase, _maxPurchase);
    }

    // Payment token management - ADDED BACK
    function addPaymentToken(address tokenAddress, string memory symbol) external onlyRole(ADMIN_ROLE) {
        if (paymentTokens[tokenAddress].token != IERC20(address(0))) revert InvalidAddress();
        _addPaymentToken(tokenAddress, symbol);
    }

    function setPaymentTokenStatus(address tokenAddress, bool isActive) external onlyRole(ADMIN_ROLE) {
        PaymentToken storage paymentToken = paymentTokens[tokenAddress];
        if (address(paymentToken.token) == address(0)) revert TokenNotSupported();

        paymentToken.isActive = isActive;
        emit PaymentTokenStatusChanged(tokenAddress, isActive);
    }

    // Emergency functions - ADDED BACK
    function requestEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {
        emergencyWithdrawalRequest = block.timestamp;
        emit EmergencyWithdrawalRequested(block.timestamp);
    }

    function executeEmergencyWithdrawal() external onlyRole(ADMIN_ROLE) {
        if (emergencyWithdrawalRequest == 0) revert EmergencyNotRequested();
        if (block.timestamp < emergencyWithdrawalRequest + emergencyWithdrawalDelay) revert TimelockNotExpired();

        uint256 balance = address(this).balance;
        if (balance == 0) revert NoETHToWithdraw();

        emergencyWithdrawalRequest = 0;

        (bool success, ) = payable(treasuryWallet).call{value: balance}("");
        if (!success) revert ETHTransferFailed();

        emit EmergencyWithdrawalExecuted(balance, block.timestamp);
    }

    function pause() external onlyRole(ADMIN_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(ADMIN_ROLE) {
        _unpause();
    }

    // View functions - Same as original
    function calculateTokensForUSD(uint256 usdAmount) external view returns (uint256) {
        uint256 usdAmount18 = PrecisionMath.toInternal(usdAmount, 18);
        return PrecisionMath.mulDiv(usdAmount18, PrecisionMath.PRECISION, tokenPrice);
    }

    function calculateUSDForTokens(uint256 tokenAmount) external view returns (uint256) {
        return PrecisionMath.mulDiv(tokenAmount, tokenPrice, PrecisionMath.PRECISION);
    }

    function getPresaleStats() external view returns (
        uint256 _tokensSold,
        uint256 _totalRaised,
        uint256 _totalParticipants,
        uint256 _tokenPrice,
        uint256 _presaleEnd,
        bool _isActive,
        uint256 _remainingTokens,
        uint256 _currentTime
    ) {
        return (
            tokensSold,
            totalRaised,
            totalParticipants,
            tokenPrice,
            presaleEndTime,
            _isPresaleActive(),
            PRESALE_SUPPLY - tokensSold,
            block.timestamp
        );
    }

    function getPaymentTokens() external view returns (address[] memory) {
        return paymentTokenList;
    }

    function isPaymentTokenActive(address token) external view returns (bool) {
        return paymentTokens[token].isActive;
    }

    function getPurchaseLimits() external view returns (uint256 _minPurchase, uint256 _maxPurchase) {
        return (minPurchase, maxPurchase);
    }

    // Emergency receive function
    receive() external payable {
        // Allow contract to receive ETH
    }
}

Tags:
ERC20, Token, Pausable, Oracle|addr:0x315ef234ac3aabad737c6d79fa257ee4e468d4cd|verified:true|block:23431013|tx:0xa05fdc4bac18e1c22aac6af03f6d8a09b6e076fd347df9e2ddfb037ea65f0208|first_check:1758733077

Submitted on: 2025-09-24 18:58:02

Comments

Log in to comment.

No comments yet.