TokenDistributor

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/TokenDistributor.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.30;

// Copied and modified from: https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol

import {ForeignReserveV1} from "@anoma/token/ForeignReserveV1.sol";
import {XanV1} from "@anoma/token/XanV1.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";

import {ITokenDistributor} from "./interfaces/ITokenDistributor.sol";
import {Allocation} from "./libs/Allocation.sol";

/// @title TokenDistributor
/// @author Uniswap, 2020; modified by Anoma Foundation, 2025
/// @notice The Anoma (XAN) token distributor contract distributing claimable
/// [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens via a Merkle tree.
/// @custom:security-contact security@anoma.foundation
contract TokenDistributor is ITokenDistributor {
    using SafeERC20 for XanV1;
    using MerkleProof for bytes32[];
    using Allocation for Allocation.Data;

    /// @notice The token to distribute.
    XanV1 internal immutable _XAN;

    /// @notice The foreign reserve receiving the claim fees.
    address internal immutable _FOREIGN_RESERVE;

    /// @notice The root of the Merkle tree containing the allocations.
    bytes32 internal immutable _ALLOCATION_TREE_ROOT;

    /// @notice The start time of the claim.
    uint256 internal immutable _START_TIME;

    /// @notice The end time of the claim.
    uint256 internal immutable _END_TIME;

    /// @notice A packed array of booleans containing the information who claimed.
    mapping(uint256 claimedWordIndex => uint256 claimedWord) private _claimedBitMap;

    error AddressZero();

    error StartTimeAfterEndTime();
    error StartTimeInTheFuture();
    error StartTimeInThePast();
    error EndTimeInTheFuture();
    error EndTimeNowOrInThePast();

    error FeeMismatch(uint256 expected, uint256 actual);
    error FeeTransferFailed();

    /// @notice Thrown if the allocation has already been claimed from the distributor.
    /// @param index The index in the balance tree that was already claimed.
    error AllocationAlreadyClaimed(uint256 index);

    /// @notice Thrown if a claim is invalid.
    /// @param allocation The invalid allocation.
    error AllocationInvalid(Allocation.Data allocation);

    /// @notice Initializes the distributor.
    /// @param allocationRoot The Merkle root of the allocation tree.
    /// @param startTime The start time of the claim period.
    /// @param endTime The end time of the claim period.
    /// @param governanceCouncil The address of the governance council.
    constructor(bytes32 allocationRoot, uint48 startTime, uint48 endTime, address governanceCouncil) {
        // Check input arguments
        {
            if (startTime + 1 > endTime) {
                revert StartTimeAfterEndTime();
            }

            uint48 currentTime = Time.timestamp();

            if (startTime < currentTime) {
                revert StartTimeInThePast();
            }
            _START_TIME = startTime;

            if (endTime < currentTime + 1) {
                revert EndTimeNowOrInThePast();
            }
            _END_TIME = endTime;

            if (governanceCouncil == address(0)) {
                revert AddressZero();
            }
        }

        _XAN = XanV1(
            address(
                new ERC1967Proxy({
                    implementation: address(new XanV1()),
                    _data: abi.encodeCall(
                        XanV1.initializeV1, ( /* initialMintRecipient: */ address(this), governanceCouncil)
                    )
                })
            )
        );

        _FOREIGN_RESERVE = address(
            new ERC1967Proxy({
                implementation: address(new ForeignReserveV1()),
                _data: abi.encodeCall(ForeignReserveV1.initializeV1, /* initialForeignReserveOwner: */ address(_XAN))
            })
        );

        _ALLOCATION_TREE_ROOT = allocationRoot;
    }

    /// @inheritdoc ITokenDistributor
    function claim(Allocation.Data calldata allocation, bytes32[] calldata proof) external payable override {
        uint48 currentTime = Time.timestamp();

        if (currentTime < _START_TIME) {
            revert StartTimeInTheFuture();
        }

        if (_END_TIME < currentTime + 1) {
            revert EndTimeNowOrInThePast();
        }

        if (_isClaimed(allocation.index)) {
            revert AllocationAlreadyClaimed(allocation.index);
        }

        if (!_verifyProof({allocation: allocation, proof: proof})) {
            revert AllocationInvalid({allocation: allocation});
        }

        if (allocation.fee != msg.value) {
            revert FeeMismatch({expected: allocation.fee, actual: msg.value});
        }

        _setClaimed(allocation.index);
        emit AllocationClaimed(allocation);

        if (allocation.fee > 0) {
            // Send the fee to the foreign reserve.

            // slither-disable-next-line low-level-calls
            (bool success,) = _FOREIGN_RESERVE.call{value: allocation.fee}("");

            if (!success) {
                revert FeeTransferFailed();
            }
        }

        if (allocation.locked > 0) {
            _XAN.transferAndLock({to: allocation.to, value: allocation.locked});
        }
        if (allocation.unlocked > 0) {
            _XAN.safeTransfer({to: allocation.to, value: allocation.unlocked});
        }
    }

    /// @inheritdoc ITokenDistributor
    function burnUnclaimedTokens() external override {
        if (Time.timestamp() < _END_TIME) {
            revert EndTimeInTheFuture();
        }

        _XAN.burn(_XAN.balanceOf(address(this)));
    }

    /// @inheritdoc ITokenDistributor
    function token() external view override returns (address addr) {
        addr = address(_XAN);
    }

    /// @inheritdoc ITokenDistributor
    function foreignReserve() external view override returns (address addr) {
        addr = address(_FOREIGN_RESERVE);
    }

    /// @inheritdoc ITokenDistributor
    function allocationTreeRoot() external view override returns (bytes32 root) {
        root = _ALLOCATION_TREE_ROOT;
    }

    /// @inheritdoc ITokenDistributor
    function isClaimed(uint256 index) external view override returns (bool claimed) {
        claimed = _isClaimed(index);
    }

    /// @notice Sets an index in the merkle tree to be claimed.
    /// @param index The index in the balance tree to be claimed.
    function _setClaimed(uint256 index) internal {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        _claimedBitMap[claimedWordIndex] = _claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
    }

    /// @notice Checks whether an index is claimed or not.
    /// @param index The index in the balance tree to check.
    /// @return claimed Wether the index is claimed or not.
    function _isClaimed(uint256 index) internal view returns (bool claimed) {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        uint256 claimedWord = _claimedBitMap[claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        claimed = (claimedWord & mask) == mask;
    }

    /// @notice Verifies an inclusion proof of an allocation in the allocation Merkle tree.
    /// @param allocation The allocation to prove.
    /// @param proof The inclusion proof.
    /// @return isValid Whether the proof is valid or not.
    function _verifyProof(Allocation.Data calldata allocation, bytes32[] calldata proof)
        internal
        view
        returns (bool isValid)
    {
        isValid = (proof.processProof(allocation.hash()) == _ALLOCATION_TREE_ROOT);
    }
}
"
    },
    "lib/token/src/ForeignReserveV1.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardTransientUpgradeable} from
    "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {IForeignReserveV1} from "./interfaces/IForeignReserveV1.sol";

/// @title ForeignReserveV1
/// @author Anoma Foundation, 2025
/// @notice The interface of the foreign reserve contract, an arbitrary executor owned by the Anoma (XAN) token and
/// receiving fees from the [Anoma token distributor](https://github.com/anoma/token-distributor) contract.
/// @custom:security-contact security@anoma.foundation
contract ForeignReserveV1 is
    IForeignReserveV1,
    Initializable,
    OwnableUpgradeable,
    UUPSUpgradeable,
    ReentrancyGuardTransientUpgradeable
{
    using Address for address;

    /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized.
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @notice Emits an event if native tokens are received.
    receive() external payable /* solhint-disable-line comprehensive-interface*/ {
        emit NativeTokenReceived(msg.sender, msg.value);
    }

    /// @notice Initializes the contract and sets the owner.
    /// @param initialOwner The initial owner.
    function initializeV1( /* solhint-disable-line comprehensive-interface*/ address initialOwner)
        external
        initializer
    {
        __Ownable_init(initialOwner);
        __UUPSUpgradeable_init();
        __ReentrancyGuardTransient_init();
    }

    /// @notice Executes arbitrary calls without reentrancy and if called by the owner.
    /// @param target The address to call
    /// @param value ETH to send with the call
    /// @param data Calldata to send
    /// @return result The raw result returned from the call
    function execute(address target, uint256 value, bytes calldata data)
        external
        payable
        override
        nonReentrant
        onlyOwner
        returns (bytes memory result)
    {
        result = target.functionCallWithValue(data, value);
    }

    /// @notice Restricts upgrades to a new implementation to the owner.
    /// @param newImplementation The new implementation.
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner 
    // solhint-disable-next-line no-empty-blocks
    {}
}
"
    },
    "lib/token/src/XanV1.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.30;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ERC20BurnableUpgradeable} from
    "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {ERC20PermitUpgradeable} from
    "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";

import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";

import {IXanV1} from "./interfaces/IXanV1.sol";
import {Council} from "./libs/Council.sol";
import {Locking} from "./libs/Locking.sol";
import {Parameters} from "./libs/Parameters.sol";
import {Voting} from "./libs/Voting.sol";

/// @title XanV1
/// @author Anoma Foundation, 2025
/// @notice The Anoma (XAN) token contract implementation version 1.
/// @custom:security-contact security@anoma.foundation
contract XanV1 is
    IXanV1,
    Initializable,
    ERC20Upgradeable,
    ERC20PermitUpgradeable,
    ERC20BurnableUpgradeable,
    UUPSUpgradeable
{
    using Voting for Voting.Data;
    using Council for Council.Data;

    /// @notice A struct containing data associated with the current implementation.
    /// @param lockingData The state associated with the locking mechanism for the current implementation.
    /// @param votingData  The state associated with the voting mechanism for the current implementation.
    /// @param councilData The state associated with the governance council for the current implementation.
    struct ImplementationData {
        Locking.Data lockingData;
        Voting.Data votingData;
        Council.Data councilData;
    }

    /// @notice The [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) storage of the contract.
    /// @custom:storage-location erc7201:anoma.storage.Xan.v1
    struct XanV1Storage {
        mapping(address currentProxyImplementation => ImplementationData) implementationSpecificData;
    }

    /// @notice The ERC-7201 storage location of the Xan V1 contract (see https://eips.ethereum.org/EIPS/eip-7201).
    /// @dev Obtained from
    /// `keccak256(abi.encode(uint256(keccak256("anoma.storage.Xan.v1")) - 1)) & ~bytes32(uint256(0xff))`.
    bytes32 internal constant _XAN_V1_STORAGE_LOCATION =
        0x52f7d5fb153315ca313a5634db151fa7e0b41cd83fe6719e93ed3cd02b69d200;

    error UnlockedBalanceInsufficient(address sender, uint256 unlockedBalance, uint256 valueToLock);
    error LockedBalanceInsufficient(address sender, uint256 lockedBalance);

    error ImplementationZero();
    error ImplementationNotMostVoted(address notMostVotedImpl);

    error UpgradeNotScheduled(address impl);
    error UpgradeAlreadyScheduled(address impl, uint48 endTime);
    error UpgradeCancellationInvalid(address impl, uint48 endTime);

    error QuorumOrMinLockedSupplyNotReached(address impl);
    error QuorumAndMinLockedSupplyReached(address impl);

    error DelayPeriodNotStarted(uint48 endTime);
    error DelayPeriodNotEnded(uint48 endTime);

    error UnauthorizedCaller(address caller);

    /// @notice Limits functions to be callable only by the governance council address.
    modifier onlyCouncil() {
        _checkCouncil();
        _;
    }

    /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized.
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @notice Initializes the XanV1 contract.
    /// @param initialMintRecipient The distributor address being the initial recipient of the minted tokens and
    /// authorized caller of the `transferAndLock` function.
    /// @param council The address of the governance council contract.
    function initializeV1( /* solhint-disable-line comprehensive-interface*/
        address initialMintRecipient,
        address council
    ) external initializer {
        // Initialize inherited contracts
        __ERC20_init({name_: Parameters.NAME, symbol_: Parameters.SYMBOL});
        __ERC20Permit_init({name: Parameters.NAME});
        __ERC20Burnable_init();
        __UUPSUpgradeable_init();

        // Initialize the XanV1 contract
        _mint(initialMintRecipient, Parameters.SUPPLY);
        _getLockingData().transferAndLockCaller = initialMintRecipient;
        _getCouncilData().council = council;
    }

    /// @inheritdoc IXanV1
    function lock(uint256 value) external override {
        _lock({account: msg.sender, value: value});
    }

    /// @inheritdoc IXanV1
    function transferAndLock(address to, uint256 value) external override {
        if (_getLockingData().transferAndLockCaller != msg.sender) {
            revert UnauthorizedCaller({caller: msg.sender});
        }
        _transfer({from: msg.sender, to: to, value: value});
        _lock({account: to, value: value});
    }

    /// @inheritdoc IXanV1
    function castVote(address proposedImpl) external override {
        address voter = msg.sender;

        Voting.Data storage votingData = _getVotingData();

        // Cast the vote for the proposed implementation
        {
            Voting.Ballot storage ballot = votingData.ballots[proposedImpl];

            // Cache the old votes of the voter.
            uint256 oldVotes = ballot.votes[voter];

            // Cache the locked balance.
            uint256 newVotes = lockedBalanceOf(voter);

            // Revert if the votes are not larger than the old votes.
            if (newVotes < oldVotes + 1) {
                revert LockedBalanceInsufficient({sender: voter, lockedBalance: newVotes});
            }

            // Calculate the votes that must be added.
            uint256 delta;
            unchecked {
                // Skip the underflow check because `lockedBalance > oldVotes` has been checked before.
                delta = newVotes - oldVotes;
            }

            // Update the votes.
            ballot.votes[voter] = newVotes;

            // Update the total votes.
            ballot.totalVotes += delta;

            emit VoteCast({voter: voter, impl: proposedImpl, value: delta});
        }

        // Update the most voted implementation if it has changed
        {
            address currentMostVotedImpl = votingData.mostVotedImpl;

            // Check if the proposed implementation now has more votes than the current most voted implementation.
            if (votingData.ballots[currentMostVotedImpl].totalVotes < votingData.ballots[proposedImpl].totalVotes) {
                // Update the most voted implementation to the proposed implementation
                votingData.mostVotedImpl = proposedImpl;

                emit MostVotedImplementationUpdated({newMostVotedImpl: proposedImpl});
            }
        }
    }

    /// @inheritdoc IXanV1
    function scheduleVoterBodyUpgrade() external override {
        Voting.Data storage votingData = _getVotingData();

        // Revert if another upgrade is scheduled by the voter body
        if (votingData.isUpgradeScheduled()) {
            revert UpgradeAlreadyScheduled(votingData.scheduledImpl, votingData.scheduledEndTime);
        }

        // Revert if the most voted implementation has not reached quorum
        {
            if (!_isQuorumAndMinLockedSupplyReached(votingData.mostVotedImpl)) {
                revert QuorumOrMinLockedSupplyNotReached(votingData.mostVotedImpl);
            }

            // Schedule the upgrade and emit the associated event.
            votingData.scheduledImpl = votingData.mostVotedImpl;
            votingData.scheduledEndTime = Time.timestamp() + Parameters.DELAY_DURATION;

            emit VoterBodyUpgradeScheduled(votingData.scheduledImpl, votingData.scheduledEndTime);
        }

        // Check if the council has proposed an upgrade and, if so, cancel
        {
            Council.Data storage councilData = _getCouncilData();
            if (councilData.isUpgradeScheduled()) {
                emit CouncilUpgradeVetoed(councilData.scheduledImpl);

                // Reset the scheduled upgrade
                councilData.scheduledImpl = address(0);
                councilData.scheduledEndTime = 0;
            }
        }
    }

    /// @inheritdoc IXanV1
    function cancelVoterBodyUpgrade() external override {
        Voting.Data storage votingData = _getVotingData();

        // Revert if no voter-body upgrade is scheduled
        if (!votingData.isUpgradeScheduled()) {
            revert UpgradeNotScheduled(address(0));
        }

        // Check that the delay period is over
        _checkDelayCriterion(votingData.scheduledEndTime);

        // Revert if the scheduled implementation still meets the quorum and minimum locked
        // supply requirements and is still the most voted implementation.
        if (
            _isQuorumAndMinLockedSupplyReached(votingData.scheduledImpl)
                && (votingData.scheduledImpl == votingData.mostVotedImpl)
        ) {
            revert UpgradeCancellationInvalid(votingData.scheduledImpl, votingData.scheduledEndTime);
        }

        emit VoterBodyUpgradeCancelled(votingData.scheduledImpl);

        // Reset the scheduled upgrade
        votingData.scheduledImpl = address(0);
        votingData.scheduledEndTime = 0;
    }

    /// @inheritdoc IXanV1
    function scheduleCouncilUpgrade(address impl) external override onlyCouncil {
        // Revert if a voter-body upgrade could be scheduled
        {
            Voting.Data storage votingData = _getVotingData();

            address mostVotedImpl = votingData.mostVotedImpl;

            if (_isQuorumAndMinLockedSupplyReached(mostVotedImpl)) {
                revert QuorumAndMinLockedSupplyReached(mostVotedImpl);
            }
        }

        Council.Data storage councilData = _getCouncilData();

        // Revert if a council upgrade is already scheduled
        if (councilData.isUpgradeScheduled()) {
            revert UpgradeAlreadyScheduled(councilData.scheduledImpl, councilData.scheduledEndTime);
        }

        // Schedule the council upgrade
        councilData.scheduledImpl = impl;
        councilData.scheduledEndTime = Time.timestamp() + Parameters.DELAY_DURATION;

        emit CouncilUpgradeScheduled(councilData.scheduledImpl, councilData.scheduledEndTime);
    }

    /// @inheritdoc IXanV1
    function cancelCouncilUpgrade() external override onlyCouncil {
        Council.Data storage councilData = _getCouncilData();

        // Revert if no council upgrade is scheduled
        if (!councilData.isUpgradeScheduled()) {
            revert UpgradeNotScheduled(address(0));
        }

        emit CouncilUpgradeCancelled(councilData.scheduledImpl);

        // Reset the scheduled upgrade
        councilData.scheduledImpl = address(0);
        councilData.scheduledEndTime = 0;
    }

    /// @inheritdoc IXanV1
    function vetoCouncilUpgrade() external override {
        Council.Data storage councilData = _getCouncilData();

        // Revert if no council upgrade is scheduled
        if (!councilData.isUpgradeScheduled()) {
            revert UpgradeNotScheduled(address(0));
        }

        // Get the most voted implementation.
        address mostVotedImpl = _getVotingData().mostVotedImpl;

        // Revert if the most voted implementation has not reached quorum or the minimum locked supply
        if (!_isQuorumAndMinLockedSupplyReached(mostVotedImpl)) {
            // The voter body has not reached quorum on any implementation.
            // This means that vetoing the council is not allowed.
            revert QuorumOrMinLockedSupplyNotReached(mostVotedImpl);
        }

        emit CouncilUpgradeVetoed(councilData.scheduledImpl);

        // Reset the scheduled upgrade
        councilData.scheduledImpl = address(0);
        councilData.scheduledEndTime = 0;
    }

    /// @inheritdoc IXanV1
    function getVotes(address voter, address proposedImpl) external view override returns (uint256 votes) {
        votes = _getVotingData().ballots[proposedImpl].votes[voter];
    }

    /// @inheritdoc IXanV1
    function mostVotedImplementation() external view override returns (address mostVotedImpl) {
        mostVotedImpl = _getVotingData().mostVotedImpl;
    }

    /// @inheritdoc IXanV1
    function lockedSupply() public view override returns (uint256 locked) {
        locked = _getLockingData().lockedSupply;
    }

    /// @inheritdoc IXanV1
    function calculateQuorumThreshold() public view override returns (uint256 threshold) {
        threshold = (lockedSupply() * Parameters.QUORUM_RATIO_NUMERATOR) / Parameters.QUORUM_RATIO_DENOMINATOR;
    }

    /// @inheritdoc IXanV1
    function totalVotes(address proposedImpl) public view override returns (uint256 votes) {
        votes = _getVotingData().ballots[proposedImpl].totalVotes;
    }

    /// @inheritdoc IXanV1
    function implementation() public view override returns (address thisImplementation) {
        thisImplementation = ERC1967Utils.getImplementation();
    }

    /// @inheritdoc IXanV1
    function scheduledVoterBodyUpgrade() public view override returns (address impl, uint48 endTime) {
        Voting.Data storage votingData = _getVotingData();
        impl = votingData.scheduledImpl;
        endTime = votingData.scheduledEndTime;
    }

    /// @inheritdoc IXanV1
    function scheduledCouncilUpgrade() public view override returns (address impl, uint48 endTime) {
        Council.Data storage councilData = _getCouncilData();
        impl = councilData.scheduledImpl;
        endTime = councilData.scheduledEndTime;
    }

    /// @inheritdoc IXanV1
    function governanceCouncil() public view override returns (address council) {
        council = _getCouncilData().council;
    }

    /// @inheritdoc IXanV1
    function unlockedBalanceOf(address from) public view override returns (uint256 unlockedBalance) {
        unlockedBalance = balanceOf(from) - lockedBalanceOf(from);
    }

    /// @inheritdoc IXanV1
    function lockedBalanceOf(address from) public view override returns (uint256 lockedBalance) {
        lockedBalance = _getLockingData().lockedBalances[from];
    }

    /// @notice Updates the balances. Only the unlocked token balances can be updated, except for the minting case,
    /// where `from == address(0)`.
    /// @param from The address to take the tokens from.
    /// @param to The address to give the tokens to.
    /// @param value The amount of tokens to update that must be unlocked.
    function _update(address from, address to, uint256 value) internal override {
        // Require the unlocked balance to be at least the updated value, except for the minting case,
        // where `from == address(0)`.
        // In this case, tokens are created ex-nihilo and formally sent from `address(0)` to the `to` address
        // without balance checks.
        if (from != address(0)) {
            uint256 unlockedBalance = unlockedBalanceOf(from);

            if (value > unlockedBalance) {
                revert UnlockedBalanceInsufficient({ // force linebreak
                    sender: from,
                    unlockedBalance: unlockedBalance,
                    valueToLock: value
                });
            }
        }

        super._update({from: from, to: to, value: value});
    }

    /// @notice Permanently locks tokens for an account for the current implementation until it gets upgraded.
    /// @param account The account to lock the tokens for.
    /// @param value The value to lock.
    function _lock(address account, uint256 value) internal {
        Locking.Data storage data = _getLockingData();

        uint256 unlockedBalance = unlockedBalanceOf(account);
        if (value > unlockedBalance) {
            revert UnlockedBalanceInsufficient({sender: account, unlockedBalance: unlockedBalance, valueToLock: value});
        }

        data.lockedSupply += value;
        data.lockedBalances[account] += value;

        emit Locked({account: account, value: value});
    }

    /// @notice Authorizes an upgrade.
    /// @param newImpl The new implementation to authorize the upgrade to.
    function _authorizeUpgrade(address newImpl) internal override {
        if (newImpl == address(0)) {
            revert ImplementationZero();
        }
        Voting.Data storage votingData = _getVotingData();
        Council.Data storage councilData = _getCouncilData();

        bool isScheduledByVoterBody = (newImpl == votingData.scheduledImpl);
        bool isScheduledByCouncil = (newImpl == councilData.scheduledImpl);

        // The implementation should never be scheduled by both entities.
        assert(!(isScheduledByVoterBody && isScheduledByCouncil));

        // Cache the most voted implementation proposed by the voter body.
        address mostVotedImpl = votingData.mostVotedImpl;

        if (isScheduledByVoterBody) {
            if (newImpl != mostVotedImpl) {
                revert ImplementationNotMostVoted({notMostVotedImpl: newImpl});
            }

            // This check is redundant, but kept for defense in depth.
            if (!_isQuorumAndMinLockedSupplyReached(mostVotedImpl)) {
                revert QuorumOrMinLockedSupplyNotReached(mostVotedImpl);
            }
            _checkDelayCriterion({endTime: votingData.scheduledEndTime});

            // Reset the scheduled upgrade
            votingData.scheduledImpl = address(0);
            votingData.scheduledEndTime = 0;
        } else if (isScheduledByCouncil) {
            // Check if the most voted implementation exists.
            if (mostVotedImpl != address(0)) {
                // Revert if the quorum and minimum locked supply is reached for the most-voted implementation proposed
                // by the voter body and it could therefore could be scheduled.
                if (_isQuorumAndMinLockedSupplyReached(mostVotedImpl)) {
                    revert QuorumAndMinLockedSupplyReached(mostVotedImpl);
                }
            }
            _checkDelayCriterion({endTime: councilData.scheduledEndTime});

            // Reset the scheduled upgrade
            councilData.scheduledImpl = address(0);
            councilData.scheduledEndTime = 0;
        } else {
            revert UpgradeNotScheduled(newImpl);
        }
    }

    /// @notice Throws an error if the sender is not the governance council.
    function _checkCouncil() internal view {
        if (governanceCouncil() != msg.sender) {
            revert UnauthorizedCaller({caller: msg.sender});
        }
    }

    /// @notice Returns whether the the quorum is reached for an implementation and whether the minimum locked supply
    /// is met.
    /// @param impl The implementation to check the quorum for.
    /// @return isReached Whether the quorum and minimum locked supply is reached or not.
    function _isQuorumAndMinLockedSupplyReached(address impl) internal view returns (bool isReached) {
        if (totalVotes(impl) < calculateQuorumThreshold() + 1) {
            return isReached = false;
        }
        if (lockedSupply() < Parameters.MIN_LOCKED_SUPPLY) {
            return isReached = false;
        }
        isReached = true;
    }

    /// @notice Checks if the delay period for a scheduled upgrade has ended and reverts with errors if not.
    /// @param endTime The end time of the delay period to check.
    function _checkDelayCriterion(uint48 endTime) internal view {
        if (endTime == 0) {
            revert DelayPeriodNotStarted(endTime);
        }

        if (Time.timestamp() < endTime) {
            revert DelayPeriodNotEnded(endTime);
        }
    }

    /// @notice Returns the data associated with locked token balance for the current implementation
    /// from the contract storage location.
    /// @return lockingData The data associated with locked tokens.
    function _getLockingData() internal view returns (Locking.Data storage lockingData) {
        lockingData = _getXanV1Storage().implementationSpecificData[implementation()].lockingData;
    }

    /// @notice Returns the data associated with upgrades from the current implementation proposed by the voter body
    /// from the contract storage location.
    /// @return votingData Data associated with upgrades from the current implementation proposed by the voter body.
    function _getVotingData() internal view returns (Voting.Data storage votingData) {
        votingData = _getXanV1Storage().implementationSpecificData[implementation()].votingData;
    }

    /// @notice Returns the data associated with the upgrade from the current implementation proposed by the council
    /// from the contract storage location.
    /// @return councilData Data associated with an upgrade from the current implementation proposed by the council.
    function _getCouncilData() internal view returns (Council.Data storage councilData) {
        councilData = _getXanV1Storage().implementationSpecificData[implementation()].councilData;
    }

    /// @notice Returns the storage from the Xan V1 storage location.
    /// @return xanV1Storage The data associated with the Xan V1 token storage.
    function _getXanV1Storage() internal pure returns (XanV1Storage storage xanV1Storage) {
        // solhint-disable no-inline-assembly
        {
            // slither-disable-next-line assembly
            assembly {
                xanV1Storage.slot := _XAN_V1_STORAGE_LOCATION
            }
        }
        // solhint-enable no-inline-assembly
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/ERC1967/ERC1967Proxy.sol)

pragma solidity ^0.8.22;

import {Proxy} from "../Proxy.sol";
import {ERC1967Utils} from "./ERC1967Utils.sol";

/**
 * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
 * implementation address that can be changed. This address is stored in storage in the location specified by
 * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the
 * implementation behind the proxy.
 */
contract ERC1967Proxy is Proxy {
    /**
     * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
     *
     * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
     * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
     *
     * Requirements:
     *
     * - If `data` is empty, `msg.value` must be zero.
     */
    constructor(address implementation, bytes memory _data) payable {
        ERC1967Utils.upgradeToAndCall(implementation, _data);
    }

    /**
     * @dev Returns the current implementation address.
     *
     * TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using
     * the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
     * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
     */
    function _implementation() internal view virtual override returns (address) {
        return ERC1967Utils.getImplementation();
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.

pragma solidity ^0.8.20;

import {Hashes} from "./Hashes.sol";

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the Merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates Merkle trees that are safe
 * against this attack out of the box.
 *
 * IMPORTANT: Consider memory side-effects when using custom hashing functions
 * that access memory in an unsafe way.
 *
 * NOTE: This library supports proof verification for merkle trees built using
 * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving
 * leaf inclusion in trees built using non-commutative hashing functions requires
 * additional logic that is not supported by this library.
 */
library MerkleProof {
    /**
     *@dev The multiproof provided is not valid.
     */
    error MerkleProofInvalidMultiproof();

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with the default hashing function.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function verify(
        bytes32[] memory proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProof(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in memory with a custom hashing function.
     */
    function processProof(
        bytes32[] memory proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with the default hashing function.
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function verifyCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processProofCalldata(proof, leaf, hasher) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leaves & pre-images are assumed to be sorted.
     *
     * This version handles proofs in calldata with a custom hashing function.
     */
    function processProofCalldata(
        bytes32[] calldata proof,
        bytes32 leaf,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = hasher(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = Hashes.commutativeKeccak256(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProof}.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bool) {
        return processMultiProof(proof, proofFlags, leaves, hasher) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in memory with a custom hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves,
        function(bytes32, bytes32) view returns (bytes32) hasher
    ) internal view returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < proofFlagsLen; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = hasher(a, b);
        }

        if (proofFlagsLen > 0) {
            if (proofPos != proof.length) {
                revert MerkleProofInvalidMultiproof();
            }
            unchecked {
                return hashes[proofFlagsLen - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
     * The `leaves` must be validated independently. See {processMultiProofCalldata}.
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * This version handles multiproofs in calldata with the default hashing function.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op,
     * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not
     * validating the leaves elsewhere.
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the Merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofFlagsLen = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proof.length != proofFlagsLen + 1) {
            revert MerkleProofInvalidMultiproof();
        }

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](proofFlagsLen);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been c

Tags:
ERC20, ERC165, Multisig, Burnable, Voting, Upgradeable, Multi-Signature, Factory|addr:0xca81f370d0adb9eeb746b136a4ec0cbc710062fc|verified:true|block:23390425|tx:0x35c24cf7b8e64b30896e8ca1c87f6ed552c11943af4c5c64bb514625a5b622fc|first_check:1758205700

Submitted on: 2025-09-18 16:28:22

Comments

Log in to comment.

No comments yet.