ETH Price: $2,152.52 (-0.24%)

Contract Diff Checker

Contract Name:
NCITreasury

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title NCITreasury
 * @notice Primary treasury for the New Coin Index protocol.
 *
 * @dev Receives ETH from NCIToken's auto-swap tax mechanism and maintains
 *      internal accounting across four allocation buckets:
 *
 *        70%  → Index Capital   (used by NCIAcquisitionEngine for positions)
 *        15%  → Infrastructure  (protocol ops, development, hosting)
 *        10%  → Emergency Reserve (liquidty backstop, black-swan fund)
 *         5%  → Community       (grants, incentives, DAO allocation)
 *
 *      Access levels:
 *        Owner    - full admin (set operator, update wallets, emergency withdraw)
 *        Operator - authorised to call allocateForAcquisition() and
 *                   receiveFromAcquisition(); intended to be NCIAcquisitionEngine
 *
 *      Time-lock: any single withdrawal > TIMELOCK_THRESHOLD (10 ETH) must be
 *      queued for TIMELOCK_DELAY (48 hours) before execution.
 */

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract NCITreasury is Ownable, ReentrancyGuard {
    // =========================================================================
    // Constants
    // =========================================================================

    uint256 public constant TIMELOCK_THRESHOLD = 10 ether;
    uint256 public constant TIMELOCK_DELAY     = 48 hours;

    uint256 public constant INDEX_SHARE_BPS     = 7_000; // 70%
    uint256 public constant INFRA_SHARE_BPS     = 1_500; // 15%
    uint256 public constant EMERGENCY_SHARE_BPS = 1_000; // 10%
    uint256 public constant COMMUNITY_SHARE_BPS =   500; //  5%
    uint256 public constant BPS_DENOMINATOR     = 10_000;

    // =========================================================================
    // State - balances (virtual accounting, not separate ETH pools)
    // =========================================================================

    /// @notice Portion of treasury ETH allocated to index capital activities.
    uint256 public indexCapitalBalance;

    /// @notice Portion allocated to infrastructure / operations.
    uint256 public infraBalance;

    /// @notice Portion allocated to the emergency reserve.
    uint256 public emergencyBalance;

    /// @notice Portion allocated to community programmes.
    uint256 public communityBalance;

    /// @notice Total ETH ever received by this contract.
    uint256 public totalETHReceived;

    /// @notice Total ETH ever disbursed (to operator for acquisitions, to wallets, etc.).
    uint256 public totalETHDisbursed;

    // =========================================================================
    // State - access control
    // =========================================================================

    /// @notice Authorised operator (expected to be NCIAcquisitionEngine).
    address public operator;

    // =========================================================================
    // State - time-lock queue
    // =========================================================================

    struct WithdrawalRequest {
        address payable recipient;
        uint256 amount;
        uint256 unlocksAt;
        bool    executed;
        bool    cancelled;
        string  reason;
    }

    /// @notice Sequential ID counter for withdrawal requests.
    uint256 public nextRequestId;

    mapping(uint256 => WithdrawalRequest) public withdrawalRequests;

    // =========================================================================
    // Events
    // =========================================================================

    event RevenueReceived(uint256 amount, uint256 toIndex, uint256 toInfra, uint256 toEmergency, uint256 toCommunity);
    event AcquisitionFunded(address indexed acquisitionEngine, uint256 amount, uint256 requestId);
    event AcquisitionProceedsReceived(uint256 amount, uint256 profit);
    event WithdrawalQueued(uint256 indexed requestId, address indexed recipient, uint256 amount, uint256 unlocksAt, string reason);
    event WithdrawalExecuted(uint256 indexed requestId, address indexed recipient, uint256 amount);
    event WithdrawalCancelled(uint256 indexed requestId);
    event ImmediateWithdrawal(address indexed recipient, uint256 amount, string bucket, string reason);
    event OperatorUpdated(address indexed oldOperator, address indexed newOperator);
    event EmergencyWithdraw(address indexed to, uint256 amount);

    // =========================================================================
    // Modifiers
    // =========================================================================

    modifier onlyOperator() {
        require(msg.sender == operator || msg.sender == owner(), "Treasury: not operator");
        _;
    }

    // =========================================================================
    // Constructor
    // =========================================================================

    constructor() Ownable() {}

    // =========================================================================
    // Revenue ingress
    // =========================================================================

    /**
     * @notice Receive ETH from NCIToken and split it into allocation buckets.
     * @dev Called by NCIToken._distributeETH() via INCITreasury interface.
     *      The split mirrors the token contract's TREASURY_SHARE allocation
     *      (this function handles the internal sub-split within the treasury).
     */
    function receiveRevenue() external payable {
        uint256 amount = msg.value;
        require(amount > 0, "Treasury: zero value");

        uint256 toIndex     = (amount * INDEX_SHARE_BPS)     / BPS_DENOMINATOR;
        uint256 toInfra     = (amount * INFRA_SHARE_BPS)     / BPS_DENOMINATOR;
        uint256 toEmergency = (amount * EMERGENCY_SHARE_BPS) / BPS_DENOMINATOR;
        uint256 toCommunity = amount - toIndex - toInfra - toEmergency;

        indexCapitalBalance += toIndex;
        infraBalance        += toInfra;
        emergencyBalance    += toEmergency;
        communityBalance    += toCommunity;
        totalETHReceived    += amount;

        emit RevenueReceived(amount, toIndex, toInfra, toEmergency, toCommunity);
    }

    /// @notice Plain ETH receive - allocates using same split as receiveRevenue().
    receive() external payable {
        if (msg.value > 0) {
            uint256 amount   = msg.value;
            uint256 toIndex  = (amount * INDEX_SHARE_BPS)     / BPS_DENOMINATOR;
            uint256 toInfra  = (amount * INFRA_SHARE_BPS)     / BPS_DENOMINATOR;
            uint256 toEmerg  = (amount * EMERGENCY_SHARE_BPS) / BPS_DENOMINATOR;
            uint256 toComm   = amount - toIndex - toInfra - toEmerg;

            indexCapitalBalance += toIndex;
            infraBalance        += toInfra;
            emergencyBalance    += toEmerg;
            communityBalance    += toComm;
            totalETHReceived    += amount;

            emit RevenueReceived(amount, toIndex, toInfra, toEmerg, toComm);
        }
    }

    // =========================================================================
    // Operator - acquisition funding
    // =========================================================================

    /**
     * @notice Send ETH from the index capital bucket to the operator address
     *         (NCIAcquisitionEngine) to fund a token acquisition.
     * @param amount     ETH to send (wei).
     * @param requestId  Off-chain request reference for traceability.
     */
    function allocateForAcquisition(uint256 amount, uint256 requestId)
        external
        onlyOperator
        nonReentrant
    {
        require(amount > 0,                          "Treasury: zero amount");
        require(amount <= indexCapitalBalance,        "Treasury: insufficient index capital");
        require(address(this).balance >= amount,      "Treasury: insufficient ETH balance");

        indexCapitalBalance -= amount;
        totalETHDisbursed   += amount;

        (bool success,) = payable(operator).call{value: amount}("");
        require(success, "Treasury: ETH transfer failed");

        emit AcquisitionFunded(operator, amount, requestId);
    }

    /**
     * @notice Receive ETH back from the acquisition engine (position exit
     *         proceeds).  Any amount above `costBasis` is treated as profit
     *         and re-allocated proportionally.
     * @param costBasis  The original amount that was sent out for this position.
     */
    function receiveFromAcquisition(uint256 costBasis) external payable onlyOperator {
        require(msg.value > 0, "Treasury: zero value");

        uint256 profit = msg.value > costBasis ? msg.value - costBasis : 0;

        // Return cost basis to index capital
        uint256 toIndex = costBasis > msg.value ? msg.value : costBasis;
        indexCapitalBalance += toIndex;

        // Distribute profit across all buckets
        if (profit > 0) {
            uint256 profitToIndex  = (profit * INDEX_SHARE_BPS)     / BPS_DENOMINATOR;
            uint256 profitToInfra  = (profit * INFRA_SHARE_BPS)     / BPS_DENOMINATOR;
            uint256 profitToEmerg  = (profit * EMERGENCY_SHARE_BPS) / BPS_DENOMINATOR;
            uint256 profitToComm   = profit - profitToIndex - profitToInfra - profitToEmerg;

            indexCapitalBalance += profitToIndex;
            infraBalance        += profitToInfra;
            emergencyBalance    += profitToEmerg;
            communityBalance    += profitToComm;
        }

        totalETHReceived += msg.value;
        emit AcquisitionProceedsReceived(msg.value, profit);
    }

    // =========================================================================
    // Owner - withdrawals
    // =========================================================================

    /**
     * @notice Immediately withdraw a small amount (< TIMELOCK_THRESHOLD) from
     *         the specified bucket without queuing.
     * @param bucket     One of: "index", "infra", "emergency", "community".
     * @param recipient  Where to send the ETH.
     * @param amount     How much ETH (wei) to withdraw.
     * @param reason     Human-readable reason for the withdrawal.
     */
    function immediateWithdraw(
        string calldata bucket,
        address payable recipient,
        uint256 amount,
        string calldata reason
    ) external onlyOwner nonReentrant {
        require(amount > 0,                    "Treasury: zero amount");
        require(amount < TIMELOCK_THRESHOLD,   "Treasury: use queueWithdrawal for large amounts");
        require(recipient != address(0),       "Treasury: zero recipient");

        _deductFromBucket(bucket, amount);
        totalETHDisbursed += amount;

        (bool s,) = recipient.call{value: amount}("");
        require(s, "Treasury: send failed");

        emit ImmediateWithdrawal(recipient, amount, bucket, reason);
    }

    /**
     * @notice Queue a large withdrawal (>= TIMELOCK_THRESHOLD).  Must be
     *         executed 48 hours later via executeWithdrawal().
     */
    function queueWithdrawal(
        string calldata bucket,
        address payable recipient,
        uint256 amount,
        string calldata reason
    ) external onlyOwner returns (uint256 requestId) {
        require(amount > 0,              "Treasury: zero amount");
        require(recipient != address(0), "Treasury: zero recipient");

        // Validate bucket balance now (actual deduction happens on execute)
        _validateBucketBalance(bucket, amount);

        requestId = nextRequestId++;
        uint256 unlocksAt = block.timestamp + TIMELOCK_DELAY;

        withdrawalRequests[requestId] = WithdrawalRequest({
            recipient: recipient,
            amount:    amount,
            unlocksAt: unlocksAt,
            executed:  false,
            cancelled: false,
            reason:    reason
        });

        emit WithdrawalQueued(requestId, recipient, amount, unlocksAt, reason);
    }

    /**
     * @notice Execute a previously queued withdrawal after the time-lock expires.
     */
    function executeWithdrawal(uint256 requestId, string calldata bucket)
        external
        onlyOwner
        nonReentrant
    {
        WithdrawalRequest storage req = withdrawalRequests[requestId];
        require(!req.executed,                    "Treasury: already executed");
        require(!req.cancelled,                   "Treasury: cancelled");
        require(block.timestamp >= req.unlocksAt, "Treasury: time-lock active");
        require(req.amount > 0,                   "Treasury: zero request");

        _deductFromBucket(bucket, req.amount);
        req.executed      = true;
        totalETHDisbursed += req.amount;

        (bool s,) = req.recipient.call{value: req.amount}("");
        require(s, "Treasury: send failed");

        emit WithdrawalExecuted(requestId, req.recipient, req.amount);
    }

    /// @notice Cancel a queued (not yet executed) withdrawal.
    function cancelWithdrawal(uint256 requestId) external onlyOwner {
        WithdrawalRequest storage req = withdrawalRequests[requestId];
        require(!req.executed,  "Treasury: already executed");
        require(!req.cancelled, "Treasury: already cancelled");
        req.cancelled = true;
        emit WithdrawalCancelled(requestId);
    }

    // =========================================================================
    // Owner - admin
    // =========================================================================

    /**
     * @notice Set the authorised operator address (should be NCIAcquisitionEngine).
     */
    function setOperator(address _operator) external onlyOwner {
        require(_operator != address(0), "Treasury: zero operator");
        emit OperatorUpdated(operator, _operator);
        operator = _operator;
    }

    /**
     * @notice Emergency drain - sends all ETH to owner.  Should only be used
     *         in a true emergency after ownership has been confirmed safe.
     */
    function emergencyWithdraw() external onlyOwner nonReentrant {
        uint256 bal = address(this).balance;
        require(bal > 0, "Treasury: nothing to withdraw");

        // Reset all bucket balances
        indexCapitalBalance = 0;
        infraBalance        = 0;
        emergencyBalance    = 0;
        communityBalance    = 0;

        (bool s,) = owner().call{value: bal}("");
        require(s, "Treasury: emergency withdraw failed");

        emit EmergencyWithdraw(owner(), bal);
    }

    // =========================================================================
    // View functions
    // =========================================================================

    /// @notice Real ETH balance held by this contract.
    function contractETHBalance() external view returns (uint256) {
        return address(this).balance;
    }

    /// @notice Returns all four virtual bucket balances in one call.
    function getAllBalances()
        external
        view
        returns (
            uint256 index,
            uint256 infra,
            uint256 emergency,
            uint256 community
        )
    {
        return (indexCapitalBalance, infraBalance, emergencyBalance, communityBalance);
    }

    /// @notice Returns details of a specific withdrawal request.
    function getWithdrawalRequest(uint256 requestId)
        external
        view
        returns (WithdrawalRequest memory)
    {
        return withdrawalRequests[requestId];
    }

    /// @notice Returns the number of seconds remaining on a time-locked request
    ///         (0 if unlocked or does not exist).
    function timelockRemaining(uint256 requestId) external view returns (uint256) {
        WithdrawalRequest storage req = withdrawalRequests[requestId];
        if (req.executed || req.cancelled || block.timestamp >= req.unlocksAt) {
            return 0;
        }
        return req.unlocksAt - block.timestamp;
    }

    // =========================================================================
    // Internal helpers
    // =========================================================================

    function _deductFromBucket(string memory bucket, uint256 amount) private {
        bytes32 b = keccak256(bytes(bucket));
        if (b == keccak256("index")) {
            require(indexCapitalBalance >= amount, "Treasury: insufficient index capital");
            indexCapitalBalance -= amount;
        } else if (b == keccak256("infra")) {
            require(infraBalance >= amount, "Treasury: insufficient infra balance");
            infraBalance -= amount;
        } else if (b == keccak256("emergency")) {
            require(emergencyBalance >= amount, "Treasury: insufficient emergency balance");
            emergencyBalance -= amount;
        } else if (b == keccak256("community")) {
            require(communityBalance >= amount, "Treasury: insufficient community balance");
            communityBalance -= amount;
        } else {
            revert("Treasury: unknown bucket");
        }
    }

    function _validateBucketBalance(string memory bucket, uint256 amount) private view {
        bytes32 b = keccak256(bytes(bucket));
        if (b == keccak256("index")) {
            require(indexCapitalBalance >= amount, "Treasury: insufficient index capital");
        } else if (b == keccak256("infra")) {
            require(infraBalance >= amount, "Treasury: insufficient infra balance");
        } else if (b == keccak256("emergency")) {
            require(emergencyBalance >= amount, "Treasury: insufficient emergency balance");
        } else if (b == keccak256("community")) {
            require(communityBalance >= amount, "Treasury: insufficient community balance");
        } else {
            revert("Treasury: unknown bucket");
        }
    }

    // =========================================================================
    // Emergency Token Recovery
    // =========================================================================

    /**
     * @notice Rescue ERC-20 tokens accidentally sent to this contract.
     * @dev    This contract is not designed to hold ERC-20 tokens directly -
     *         it only holds ETH. Any ERC-20 received is considered stuck and
     *         recoverable by the owner only.
     * @param  token   Address of the stuck ERC-20 token.
     * @param  to      Recipient of the recovered tokens.
     * @param  amount  Amount to recover. Pass 0 to recover entire balance.
     */
    function rescueERC20(address token, address to, uint256 amount) external onlyOwner nonReentrant {
        require(token != address(0), "Treasury: zero token");
        require(to    != address(0), "Treasury: zero address");
        // Minimal ERC-20 interface - avoids importing full IERC20
        (bool ok, bytes memory data) = token.call(
            abi.encodeWithSignature("balanceOf(address)", address(this))
        );
        require(ok, "Treasury: balanceOf failed");
        uint256 bal = abi.decode(data, (uint256));
        uint256 send = amount == 0 ? bal : amount;
        require(send <= bal, "Treasury: insufficient token balance");
        (bool sent,) = token.call(abi.encodeWithSignature("transfer(address,uint256)", to, send));
        require(sent, "Treasury: token transfer failed");
        emit ERC20Rescued(token, to, send);
    }

    /// @notice Emitted when ERC-20 tokens are rescued from the treasury.
    event ERC20Rescued(address indexed token, address indexed to, uint256 amount);
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):