WENBlocks
  • Getting Started
    • Overview
      • Technical Architecture
    • Key Features
  • Staking
    • Overview
    • Creating a Stake
    • Withdrawing the Stake
    • Claiming Rewards
  • Liquidity Program
    • How It Works
  • WNT
    • WNT — The Engine of the WEN Ecosystem
  • Airdrops
    • Airdrop Schedule
    • Server Management
    • Penalty Structure
    • How It Works
    • Example
  • Smart Contracts
  • Security
    • Overview
    • Internal Audits
    • External Audits
    • AI Audits
      • Grok
  • CODE
    • WENBlocksManager.sol
    • wnmContract.sol
    • wblkToken.sol
    • wuniContract.sol
    • wntToken.sol
    • syncnode.py
    • airdropGenerator.js
    • dayTrigger.js
  • Conclusion
    • Conclusion
  • Socials
    • Links
Powered by GitBook
On this page
  1. CODE

wnmContract.sol

The core staking and governance token contract implementing the wShares reward system, XEN burning multipliers, and daily inflation mechanics.

// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

interface IMintableERC20 is IERC20 {
    function mint(address to, uint256 amount) external;
    function burn(uint256 amount) external;
    function burnFrom(address account, uint256 amount) external;
}

interface IBurnableToken {
    function burn(address user, uint256 amount) external;
}

interface IBurnRedeemable {
    event Redeemed(
        address indexed user,
        address indexed xenContract,
        address indexed tokenContract,
        uint256 xenAmount,
        uint256 tokenAmount
    );

    function onTokenBurned(address user, uint256 amount) external;
}

interface IWENBlocksManager {
    function updateDailyPenalty() external;
}

contract Stakeable {
    
    /// @notice Precision factor used for reward calculations.
    /// @dev Used to scale integer math to handle decimal precision.
    uint256 constant PRECISION_RATE = 1E18;

    /// @notice Daily inflation amount of WNT tokens
    uint256 public WNT_INFLATION = 25000 * PRECISION_RATE;

    /// @notice The last day on which halving was triggered
    uint256 public lastHalvingDay;

    /// @dev Minimum amount of days to stake the WNM for.
    uint256 constant MIN_STAKE_DAYS = 7;

    /// @dev Maximum amount of days to stake the WNM for.
    uint256 constant MAX_STAKE_DAYS = 730;

    /// @dev The rate of which wShare price is increased - 1 per day.
    uint256 constant WSHARE_PRICE_INCREMENT = 1;

    /// @notice WBLK token contract used for bonus multipliers and burning.
    IMintableERC20 public wblk;

    /// @notice XEN token contract used for bonus multipliers and burning.
    IERC20 public xen;

    /// @notice Reference to the WENBlocksManager contract.
    /// @dev Used to update airdrop penalties during `incrementDay`.
    IWENBlocksManager public wenblocksmanager;
    
    /// @notice Minimum amount of XEN to burn.
    uint256 public constant minXENamount = 1000000000 * PRECISION_RATE;

    /// @notice Amount of external WNM rewards queued for next day.
    uint256 public wnmRewards;
    
    /// @notice Amount of external WBLK rewards queued for next day.
    uint256 public wblkRewards;
    
    /// @notice Amount of external WUNI rewards queued for next day.
    uint256 public wuniRewards;

    /// @notice Current active WEN day.
    /// @dev Used for reward and stake duration tracking.
    uint256 public currentDay;
    
    /// @notice Timestamp when the contract was deployed.
    /// @dev Used to determine the passing of a "WEN Day".
    uint256 public immutable launchTimestamp; //Launch time in timestamp
    
    /// @notice Number of seconds in a single WEN Day.
    /// @dev Used to validate `incrementDay` trigger.
    uint256 public constant DAY_IN_SECONDS = 86400; //Day in seconds
    
    /// @notice Total WNM tokens staked by all users.
    uint256 public totalStaked;
    
    /// @notice Current price of a wShare in USD cents.
    /// @dev Used in wShare calculation logic.
    uint256 public wsharePrice;
    
    /// @notice Total number of wShares currently allocated to all active stakes.
    uint256 public totalwshares;

    /// @notice Total number of burnt XEN.
    uint256 public totalXENburnt;

    /// @notice Total number of staked W.BLK.
    uint256 public totalwblkstaked;

    /// @notice Initializes the staking system on deployment.
    /// @dev Sets the initial WEN day, initial wShare price, and pushes a dummy stakeholder to avoid index 0.
    /// Also sets default external rewards to 1 to avoid division by zero.
    constructor() {
        stakeholders.push(); //push the list of stake holders (allow staking). Do not remove this dummy entry — used to prevent index=0 edge case
        currentDay = 1; //Set the initial WEN Day to 1. Called at launch
        wsharePrice = 200; //Set the initial wshare price of $200.00
        launchTimestamp = block.timestamp; // Save the deployment block timestamp
        lastHalvingDay = 0;
        wnmRewards = 0;
        wblkRewards = 0;
        wuniRewards = 0;
    }
    
    /// @notice Global staking metrics per WEN day.
    /// @dev Used for daily inflation and reward tracking.
    /// @param inflationAmount Daily inflation reference (used for WNT).
    /// @param wnmAmount Daily distributed WNM per share unit.
    /// @param wblkAmount Daily distributed WBLK per share unit.
    /// @param wuniAmount Daily distributed WUNI per share unit.
    /// @param sharesEnding Number of wShares expiring on this day.
    /// @param totalwshares Total active wShares on this day.
    /// @param totalWNMstaked Total WNM staked till this day.
    struct StakingInfo {
        uint256 inflationAmount;
        uint256 wnmAmount;
        uint256 wblkAmount;
        uint256 wuniAmount;
        uint256 sharesEnding;
        uint256 wSharesInExistence;
        uint256 totalWNMstaked;
    }

    /// @notice Summary of a user's total staked tokens and all active stakes.
    /// @dev Used by frontend to render staking dashboard.
    /// @param total_amount Sum of all staked amounts by the user.
    /// @param stakes Array of detailed `Stake` structs.
    struct StakingSummary {
        uint256 total_amount;
        Stake[] stakes;
    }

    /// @notice Represents a single user's stake.
    /// @dev Tracks stake metadata, associated user, durations, rewards, WBLK balance, and wShares.
    /// @param user The address of the staker.
    /// @param amount The amount of tokens staked (WNM).
    /// @param stakename Custom name/tag for the stake.
    /// @param startDay The WEN day the stake started.
    /// @param endDay The WEN day the stake ends.
    /// @param claimableWNT Accumulated WNT rewards for this stake.
    /// @param claimableWNM Accumulated WNM rewards for this stake.
    /// @param claimableWBLK Accumulated WBLK rewards for this stake.
    /// @param claimableWUNI Accumulated WUNI rewards for this stake.
    /// @param wblkamount WBLK held at the time of stake (used for bonus).
    /// @param wShares Number of wShares associated with this stake.
    struct Stake {
        address user;
        uint256 amount;
        string stakename;
        uint256 startDay;
        uint256 endDay;
        uint256 claimableWNT;
        uint256 claimableWNM;
        uint256 claimableWBLK;
        uint256 claimableWUNI;
        uint256 wblkamount;
        uint256 wShares;
    }
    /// @notice Groups stakes per user address.
    /// @dev Allows multiple stakes per user.
    /// @param user Address of the stakeholder.
    /// @param address_stakes Array of active and historical stakes by the user.
    struct Stakeholder {
        address user;
        Stake[] address_stakes;
    }

    /// @notice Dynamic array storing all registered stakeholders.
    /// @dev Each element holds the user's address and their array of individual stakes. Index 0 is reserved as a dummy.
    Stakeholder[] internal stakeholders;
    
    /// @notice Maps a user address to their stakeholder index in the `stakeholders` array.
    mapping(address => uint256) internal stakes;

    /// @notice Maps a user address to their burnt XEN.
    mapping(address => uint256) internal xenburnt;
    
    /// @notice Tracks staking-related data per WEN day.
    /// @dev Keyed by WEN day index.
    mapping(uint256 => StakingInfo) public stinfo;

    /// @notice Emitted when a new stake is created.
    /// @param user The address of the staker.
    /// @param amount Amount of tokens staked.
    /// @param stakename Name of the stake.
    /// @param startDay WEN day the stake began.
    /// @param endDay WEN day the stake ends.
    /// @param index Index of the user in the stakeholders array.
    /// @param wblkamount WBLK held at the time of staking (used for bonus).
    /// @param wShares Number of wShares assigned.
    event Staked(
        address indexed user,
        uint256 amount,
        string stakename,
        uint256 startDay,
        uint256 endDay,
        uint256 index,
        uint256 wblkamount,
        uint256 wShares
    );

    /// @notice Emitted when a stake is withdrawn and rewards claimed.
    /// @param user The address of the user withdrawing.
    /// @param stakeIndex Index of the stake in user's stake array.
    /// @param wnmAmount Amount of WNM returned (stake + reward).
    /// @param wntRewards Amount of WNT rewards returned.
    /// @param wblkRewards Amount of WBLK rewards returned.
    /// @param wuniRewards Amount of WUNI rewards returned.
    event Withdrawn(
        address indexed user,
        uint256 stakeIndex,
        uint256 wnmAmount,
        uint256 wntRewards,
        uint256 wblkRewards,
        uint256 wuniRewards
    );

    /// @notice Emitted when a new day is triggered. Emitted by incrementDay().
    /// @param day The day that has just funished.
    event NewDay(uint256 day);

    /// @notice Sets the WBLK token contract used for bonus calculations and burning.
    /// @dev This should be set once by the WNM token contract.
    /// @param _wblk The address of the deployed WBLK token (must implement `burn()`).
    function _setWBLK(address _wblk) internal {
        require(_wblk != address(0), "WBLK address cannot be zero");
        wblk = IMintableERC20(_wblk);
    }

    /// @notice Sets the XEN contract used for wShares bonus.
    /// @dev Called by the WNM token contract during initialization.
    /// @param _address The address of the XEN token.
    function _setXEN(address _address) internal {
        xen = IERC20(_address);
    }

    /// @notice Sets the WENBlocksManager contract used for penalty updates.
    /// @dev Called by the WNM token contract during initialization.
    /// @param _manager The address of the deployed WENBlocksManager contract.
    function _setWenblocksmanager(address _manager) internal {
        wenblocksmanager = IWENBlocksManager(_manager);
    }

    /// @notice Returns inflation, reward data, staked and wShares for a given WEN day.
    /// @param _day The WEN day to retrieve data for.
    /// @return day The input day.
    /// @return wntrewards The inflation value used for WNT reward calculations.
    /// @return wnmrewards The WNM reward value for the day.
    /// @return wblkrewards The WBLK reward value for the day.
    /// @return wunirewards The WUNI reward value for the day.
    /// @return expiredShares The number of wShares expiring on this day.
    /// @return wSharesInExistence The number of total wShares in existance on this day.
    /// @return totalWNMstaked The number of total WNM staked on this day.
    function getDayInfo(uint256 _day) public view returns(uint256 day, uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards, uint256 expiredShares, uint256 wSharesInExistence, uint256 totalWNMstaked) {
        StakingInfo storage info = stinfo[_day];
        return (_day, info.inflationAmount, info.wnmAmount, info.wblkAmount, info.wuniAmount, info.sharesEnding, info.wSharesInExistence, info.totalWNMstaked);
    }

    /// @notice Returns the total WNM currently staked across all users.
    /// @return Total amount of WNM staked.
    function getTotalStaked() public view returns (uint256) {
        return totalStaked;
    }

    /// @notice Returns the total XEN burnt by the user.
    /// @return Total amount of XEN burnt by the user.
    function getUserXENburnt(address user) public view returns (uint256) {
        return xenburnt[user];
    }

    /// @notice Returns the current active WEN day.
    /// @return The current day counter.
    function getCurrentDay() public view returns (uint256) {
        return currentDay;
    }

    /// @notice Returns the current USD price of 1 wShare.
    /// @return The wShare price scaled by USD cents (integer).
    function getwSharePrice() public view returns (uint256) {
        return wsharePrice;
    }

    /// @notice Advances the staking system by one WEN day.
    /// @dev Recalculates inflation metrics and invokes external penalty update.
    /// @custom:require At least 1 full WEN day must have passed since the last update.
    function incrementDay() public {
        require(block.timestamp >= (currentDay * DAY_IN_SECONDS) + launchTimestamp, "24 hours have not passed yet.");
        uint256 _day = currentDay + 1;
        wsharePrice += WSHARE_PRICE_INCREMENT;
        _calculateInflationAmount(_day);
        wenblocksmanager.updateDailyPenalty();
    }

    /// @notice Queues external staking rewards to be distributed the next day.
    /// @dev Called by WENBlocksManager after penalties are applied.
    /// @param _wnmAmount Amount of WNM to distribute.
    /// @param _wblkAmount Amount of WBLK to distribute.
    /// @param _wuniAmount Amount of WUNI to distribute.
    function _assignExternalStakingRewards(uint256 _wnmAmount, uint256 _wblkAmount, uint256 _wuniAmount) internal {
        wnmRewards += _wnmAmount;
        wblkRewards += _wblkAmount;
        wuniRewards += _wuniAmount;
    }

    /// @notice Registers a new stakeholder by assigning them an index.
    /// @dev Automatically invoked during first stake of a user.
    /// @param staker Address of the user.
    /// @return Index of the newly added stakeholder in the `stakeholders` array.
    function _addStakeholder(address staker) internal returns (uint256) {
        stakeholders.push();
        uint256 userIndex = stakeholders.length - 1;
        stakeholders[userIndex].user = staker;
        stakes[staker] = userIndex;
        return userIndex;
    }

    /// @notice Applies inflation calculations from the current day up to `_newday`.
    /// @dev Also resets daily rewards and updates global staking metrics.
    /// @param _newday Target day to process up to.
    function _calculateInflationAmount(uint256 _newday) private {
        for (uint256 _day = currentDay; _day < _newday; _day++) {
            if (_day % 365 == 0 && _day > lastHalvingDay) {
                WNT_INFLATION = WNT_INFLATION / 2;
                lastHalvingDay = _day;
            }
            StakingInfo memory stakeinfo = stinfo[_day];
            uint256 sharesToExpire = stinfo[_day].sharesEnding;
            totalwshares = totalwshares > sharesToExpire ? totalwshares - sharesToExpire : 0;
            stakeinfo.inflationAmount = totalwshares * PRECISION_RATE / WNT_INFLATION;
            stakeinfo.wnmAmount = wnmRewards > 0 ? totalwshares * PRECISION_RATE / wnmRewards : 0;
            stakeinfo.wblkAmount = wblkRewards > 0 ? totalwshares * PRECISION_RATE / wblkRewards : 0;
            stakeinfo.wuniAmount = wuniRewards > 0 ? totalwshares * PRECISION_RATE / wuniRewards : 0;
            stakeinfo.totalWNMstaked = totalStaked;
            stakeinfo.wSharesInExistence = totalwshares;
            stinfo[_day] = stakeinfo;
            currentDay ++;
            wnmRewards = 0;
            wblkRewards = 0;
            wuniRewards = 0;
            emit NewDay(currentDay);
        }
    }

    /// @notice Schedules wShares to expire on a future day.
    /// @param _day The WEN day when shares will expire.
    /// @param _amount Number of shares to expire.
    function _allocateFutureExpiringShares(uint256 _day, uint256 _amount) private {
        stinfo[_day].sharesEnding += _amount;
    }

    /// @notice Removes previously allocated expiring shares (used on withdrawal).
    /// @param _day The expiration day.
    /// @param _amount Number of shares to remove from expiry queue.
    function _removeFutureExpiringShares(uint256 _day, uint256 _amount) private {
        if (_day >= currentDay) {
            stinfo[_day].sharesEnding = stinfo[_day].sharesEnding > _amount ? stinfo[_day].sharesEnding - _amount : 0;
        }
    }

    /// @notice Calculates a multiplier based on staking duration.
    /// @dev Linear between 7–730 days, scaling 1.0x to 2.5x.
    /// @param _days Number of days the stake will last.
    /// @return Multiplier scaled by 100 (e.g., 125 = 1.25x).
    function _calculateDaysMultiplier(uint256 _days) internal pure returns (uint256) {
        uint256 minDays = 7;
        uint256 maxDays = 730;
        uint256 minMultiplier = 100; // 1.0x
        uint256 maxMultiplier = 250; // 2.5x

        if (_days <= minDays) {
            return minMultiplier;
        } else if (_days >= maxDays) {
            return maxMultiplier;
        }

        // Linear interpolation
        uint256 multiplier = minMultiplier + ((_days - minDays) * (maxMultiplier - minMultiplier)) / (maxDays - minDays);
        return multiplier;
    }

    /// @notice Determines bonus multiplier based on WBLK balance.
    /// @dev Uses tiered brackets (≥1, ≥10, ≥50, ≥100 WBLK).
    /// @param _amount Amount of WBLK tokens to lock.
    /// @return Bonus multiplier in whole numbers (e.g., 12, 15, 20...).
    function _calculateWBLKBonus(uint256 _amount) internal pure returns (uint256) {
        if (_amount >= 100e18) return 25;
        if (_amount >= 50e18) return 20;
        if (_amount >= 10e18) return 15;
        if (_amount >= 1e18) return 12;
        if (_amount == 0) return 10;
        return 10;
    }

    /// @notice Determines bonus multiplier based on XEN burnt.
    /// @dev Uses tiered brackets (≥1B, ≥2B, ≥3B, ≥4B XEN).
    /// @param owner The address that creates the stake.
    /// @return Bonus multiplier in whole numbers.
    function _calculateXENBonus(address owner) internal view returns (uint256) {
        uint256 userXENburnt = xenburnt[owner];
        return (userXENburnt / minXENamount) * 10 + 10;
    }

    /// @notice Calculates wShares based on amount, duration, WBLK, and airdrop.
    /// @dev Applies both duration and WBLK bonus logic. Adds +10% if airdrop.
    /// @param _amount WNM staked.
    /// @param _stakingdays Number of lock days selected.
    /// @param _wblkamount WBLK balance held by user.
    /// @param isAirdrop Whether stake is airdrop-initiated.
    /// @return Total number of wShares to assign to this stake.
    function _calculatewSharesToReceive(address owner, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount, bool isAirdrop) private view returns (uint256) {
        uint256 stakingdaysMultiplier = _calculateDaysMultiplier(_stakingdays);
        uint256 wblkbonus = _calculateWBLKBonus(_wblkamount);
        uint256 xenbonus = _calculateXENBonus(owner);
        uint256 wShares = _amount * wblkbonus * xenbonus * stakingdaysMultiplier / wsharePrice / 10;
        uint256 wSharesWithBonus = wShares * 110 / 100; // 10% bonus for airdrop stake
        if (isAirdrop) {
            return wSharesWithBonus;
        } else {
            return wShares;
        }
    }

    /// @notice Performs a new stake for a user.
    /// @dev Applies all multipliers, bonus, burns WBLK, emits event.
    /// @param owner Address of the staker.
    /// @param _stakename Custom stake label.
    /// @param _amount Amount of WNM to stake.
    /// @param _stakingdays Duration of stake in WEN days.
    /// @param isAirdrop Whether stake was created via airdrop (adds 10% wShare bonus).
    function _stake(address owner, string memory _stakename, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount, bool isAirdrop) internal {
        require(_amount >= 1e18, "Stake amount must be at least 1 WNM.");
        require(_stakingdays >= MIN_STAKE_DAYS && _stakingdays <= MAX_STAKE_DAYS, "Staking period not valid.");
        uint256 index = stakes[owner];
        uint256 wblkBalance = wblk.balanceOf(owner);
        if (!isAirdrop) {
            require(_wblkamount <= wblkBalance, "Insufficient W.BLK amount.");
        }
        uint256 _startDate = currentDay;
        uint256 _endDate = _startDate + _stakingdays;
        uint256 wShares = _calculatewSharesToReceive(owner, _amount, _stakingdays, _wblkamount, isAirdrop);
        if (index == 0) {
            index = _addStakeholder(owner);
        }
        if (!isAirdrop) {
            wblk.burnFrom(owner, _wblkamount);
        }
        stakeholders[index].address_stakes.push(Stake(owner, _amount, _stakename, _startDate, _endDate, 0, 0, 0, 0, _wblkamount, wShares));
        totalwshares += wShares;
        totalStaked += _amount;
        _allocateFutureExpiringShares(_endDate, wShares);
        emit Staked(owner, _amount, _stakename, _startDate, _endDate, index, wblkBalance, wShares);
    }


    /// @notice Calculates total pending rewards across all tokens for a specific stake.
    /// @dev Iterates over days the stake was active to compute rewards proportionally to wShares.
    /// @param _current_stake The stake struct for which rewards are to be calculated.
    /// @return _wntrewards Amount of WNT rewards earned.
    /// @return _wnmrewards Amount of WNM rewards earned.
    /// @return _wblkrewards Amount of WBLK rewards earned.
    /// @return _wunirewards Amount of WUNI rewards earned.
    function calculateStakeRewards(Stake memory _current_stake) internal view returns (uint256 _wntrewards, uint256 _wnmrewards, uint256 _wblkrewards, uint256 _wunirewards) {
        uint256 _startDay = _current_stake.startDay;
        uint256 _endDay = _current_stake.endDay;
        
        if (_startDay == currentDay || _current_stake.amount == 0) {
            return (0, 0, 0, 0);
        }

        uint256 _lastDay = _endDay > currentDay ? currentDay : _endDay;
        uint256 _wShares = _current_stake.wShares;

        for (uint256 _day = _startDay; _day < _lastDay; _day ++) {
            StakingInfo storage info = stinfo[_day];
            // 1000 * PRECISION_RATE / 
            if (info.inflationAmount > 0) {
                _wntrewards += (_wShares * PRECISION_RATE) / info.inflationAmount;
            }
            if (info.wnmAmount > 0) {
                _wnmrewards += (_wShares * PRECISION_RATE) / info.wnmAmount;
            }
            if (info.wblkAmount > 0) {
                _wblkrewards += (_wShares * PRECISION_RATE) / info.wblkAmount;
            }
            if (info.wuniAmount > 0) {
                _wunirewards += (_wShares * PRECISION_RATE) / info.wuniAmount;
            }
        }

        return (_wntrewards, _wnmrewards, _wblkrewards, _wunirewards);
    }

    /// @notice Withdraws a specific stake by index.
    /// @dev Calculates all rewards, early penalties, and cleans up stake state.
    /// @param _amount If `0`, user opts for early withdraw (penalty). Otherwise must match original stake.
    /// @param index The index of the stake to withdraw.
    /// @return WNM returned, WNT rewards, WBLK rewards, WUNI rewards (all after penalty logic).
    function _withdrawStake(uint256 _amount, uint256 index) internal returns (uint256, uint256, uint256, uint256) {
        // Grab user_index which is the index to use to grab the Stake[])
        uint256 user_index = stakes[msg.sender];
        Stake memory current_stake = stakeholders[user_index].address_stakes[index];
        uint256 stakeEndDay = current_stake.endDay;
        require(current_stake.amount > 0, "Stake already withdrawn or invalid");
        require(_amount == 0 || _amount == current_stake.amount);
        require(current_stake.user == msg.sender);
        (uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards)  = calculateStakeRewards(current_stake);
        uint256 sharesFee;
        uint256 wblkBalance;

        if (_amount != 0) {
            require(currentDay >= stakeEndDay, "Stake is not mature yet.");
            totalwshares -= current_stake.wShares;
            current_stake.amount = 0;
            totalStaked -= _amount;
            wblkBalance = current_stake.wblkamount;
            _removeFutureExpiringShares(current_stake.endDay, current_stake.wShares);
        }

        if (_amount == 0 && currentDay < stakeEndDay) {
            sharesFee = current_stake.wShares * 10 / 100;
            wblkBalance = 0;
        } else {
            sharesFee = 0;
            wblkBalance = current_stake.wblkamount;
        }

        // If stake is empty, 0, then remove it from the array of stakes
        if (current_stake.amount == 0) {
            delete stakeholders[user_index].address_stakes[index];
        } else {
            stakeholders[user_index].address_stakes[index].claimableWNM = 0;
            stakeholders[user_index].address_stakes[index].claimableWNT = 0;
            stakeholders[user_index].address_stakes[index].claimableWBLK = 0;
            stakeholders[user_index].address_stakes[index].claimableWUNI = 0;
            stakeholders[user_index].address_stakes[index].startDay = currentDay;
            stakeholders[user_index].address_stakes[index].wShares -= sharesFee;
        }
        return (_amount + wnmrewards, wntrewards, wblkrewards + wblkBalance, wunirewards);
    }

    /// @notice Retrieves all active stakes and pending rewards for a user.
    /// @dev Aggregates across all user stakes and returns dynamic array.
    /// @param _staker The wallet address to query.
    /// @return A `StakingSummary` struct with total amount and per-stake breakdown.
    function hasStake(address _staker) public view returns (StakingSummary memory) {
        // totalStakeAmount is used to count total staked amount of the address
        uint256 totalStakeAmount;
        // Keep a summary in memory since we need to calculate this
        StakingSummary memory summary = StakingSummary(0, stakeholders[stakes[_staker]].address_stakes);
        // Itterate all stakes and grab amount of stakes
        for (uint256 s = 0; s < summary.stakes.length; s += 1) {
            if (summary.stakes[s].amount > 0) {
                (uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards) = calculateStakeRewards(summary.stakes[s]);
                summary.stakes[s].claimableWNT = wntrewards;
                summary.stakes[s].claimableWNM = wnmrewards;
                summary.stakes[s].claimableWBLK = wblkrewards;
                summary.stakes[s].claimableWUNI = wunirewards;
                totalStakeAmount = totalStakeAmount + summary.stakes[s].amount;
            }
        }
        // Assign calculate amount to summary
        summary.total_amount = totalStakeAmount;
        return summary;
    }
}

/// @title WNMToken - Main token for staking in the WEN ecosystem
/// @author Yonko
/// @notice Implements staking logic, reward distribution, and controlled minting
/// @dev Inherits from ERC20, Ownable, Stakeable, and ReentrancyGuard
contract WNMToken is ERC20, Ownable, Stakeable, ReentrancyGuard, ERC165, IBurnRedeemable {
    /// @notice Address of the WNT reward token contract
    IMintableERC20 public wntContract;

    /// @notice Address of the WBLK reward token contract
    IMintableERC20 public wblkContract;

    /// @notice Address of the WUNI reward token contract
    IMintableERC20 public wuniContract;

    IBurnableToken public xenBurnable;

    /// @notice Address of the WENBlocksManager contract authorized for minting and airdrops
    address public manageraddress;

    /// @notice Initializes the WNMToken contract with reward token addresses and mints initial supply
    /// @dev Assigns WNT, WBLK, and WUNI contract addresses and mints initial tokens to deployer
    /// @param _wntAddress Address of the WNT token contract
    /// @param _wblkAddress Address of the WBLK token contract
    /// @param _wuniAddress Address of the WUNI token contract
    constructor(address _wntAddress, address _wblkAddress, address _wuniAddress, address _xenAddress) Ownable(msg.sender) ERC20("WENIUM", "WNM") {
        wntContract = IMintableERC20(_wntAddress);
        wblkContract = IMintableERC20(_wblkAddress);
        wuniContract = IMintableERC20(_wuniAddress);
        xenBurnable = IBurnableToken(_xenAddress);
        _setWBLK(_wblkAddress);
        _setXEN(_xenAddress);
        _mint(msg.sender, 2e18);
    }

    /// @notice Allows a user or manager to initiate a stake
    /// @dev Burns WNM from user if not called by manager; manager-triggered stakes apply AIRDROP logic
    /// @param owner The recipient of the stake (user or airdrop target)
    /// @param _stakename Human-readable name/label for the stake
    /// @param _amount Amount of WNM to stake (in wei)
    /// @param _stakingdays Number of days to lock tokens in stake
    /// @param _wblkamount Amount of W.BLK to to stake (in wei)
    function stake(address owner, string memory _stakename, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount) external {
        if (msg.sender != manageraddress) {
            uint256 userBalance = balanceOf(msg.sender);
            require(_amount <= userBalance, "DevToken: Cannot stake more than you own");
            _stake(msg.sender, _stakename, _amount, _stakingdays, _wblkamount, false);
            // Burn the amount of tokens on the sender
            _burn(msg.sender, _amount);
        } else if (msg.sender == manageraddress) {
            _stake(owner, "AIRDROP", _amount, _stakingdays, _wblkamount, true); // If the staker is the WENBlocksManager, then apply bonus staking since that's an AIRDROP
        }
    }
    
    /// @dev Restricts function access to the assigned manager address
    modifier onlyManager() {
        require(msg.sender == manageraddress, "Only the merkleTree contract can trigger the mint function");
        _;
    }

    /// @notice Sets the manager contract authorized for airdrops and reward assignment
    /// @dev Also updates the staking penalty integration by assigning the WENBlocksManager
    /// @param _manageraddress Address of the new manager contract
    function setManagerAddress(address _manageraddress) external onlyOwner {
        require(_manageraddress != address(0), "WBLK address cannot be zero");
        manageraddress = _manageraddress;
        _setWenblocksmanager(_manageraddress);
    }

    /// @notice Mints WNM tokens to a specified address
    /// @dev Restricted to the manager contract only
    /// @param to Address receiving the newly minted WNM tokens
    /// @param amount Amount of WNM tokens to mint (in wei)
    function mint(address to, uint256 amount) external onlyManager {
        _mint(to, amount);
    }
    
    /// @notice Assigns reward tokens for the upcoming inflation cycle
    /// @dev Used by manager to inject external rewards into staking system
    /// @param _wnmAmount Amount of WNM tokens to allocate
    /// @param _wblkAmount Amount of WBLK tokens to allocate
    /// @param _wuniAmount Amount of WUNI tokens to allocate
    function assignExternalStakingRewards(uint256 _wnmAmount, uint256 _wblkAmount, uint256 _wuniAmount) external onlyManager {
        _assignExternalStakingRewards(_wnmAmount, _wblkAmount, _wuniAmount);
    }

    /// @notice Withdraws a user's stake and distributes all earned rewards
    /// @dev Mints WNM back to user and distributes WNT, WBLK, and WUNI rewards
    /// @param amount Amount to withdraw (0 for early exit)
    /// @param stake_index Index of the stake in the user's stake list
    function withdrawStake(uint256 amount, uint256 stake_index) public nonReentrant {
        (uint256 wnmamount, uint256 wntamount, uint256 wblkamount, uint256 wuniamount) = _withdrawStake(amount, stake_index);
        // Return staked tokens to user
        _mint(msg.sender, wnmamount);
        wntContract.mint(msg.sender, wntamount);
        wblkContract.mint(msg.sender, wblkamount);
        wuniContract.mint(msg.sender, wuniamount);
    }

    /**
    * @notice Callback function triggered when XEN tokens are burned via `burnXEN`.
    * @dev Only callable by the configured XEN contract. Records total XEN burnt and emits a Redeemed event.
    * @param user The address of the user who initiated the XEN burn.
    * @param amount The amount of XEN that was burned.
    */
    function onTokenBurned(address user, uint256 amount) external override {
        require(msg.sender == address(xen), "Unauthorized burn source");
        totalXENburnt += amount;
        xenburnt[user] += amount;
        emit Redeemed(user, address(xen), address(this), amount, 0);
    }

    /**
    * @notice Burns a specified amount of XEN tokens from the caller’s wallet.
    * @dev Requires at least `minXENamount` to proceed. Transfers control to the XEN contract via `burn()`.
    * Emits a callback to `onTokenBurned()` once the burn is processed by the XEN contract.
    * @param amount The amount of XEN to burn. Must be greater than or equal to `minXENamount`.
    */
    function burnXEN(uint256 amount) external nonReentrant() {
        require(amount >= minXENamount, "You must burn at least 1B XEN.");
        require(xen.balanceOf(msg.sender) >= amount, "Insufficient balance.");
        xenBurnable.burn(msg.sender , amount);
    }
    
    /**
    * @notice Checks if this contract supports a given interface.
    * @dev Supports IBurnRedeemable and standard ERC165 interface detection.
    * @param interfaceId The interface identifier, as specified in ERC-165.
    * @return True if the interface is supported, false otherwise.
    */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IBurnRedeemable).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}
PreviousWENBlocksManager.solNextwblkToken.sol

Last updated 26 days ago