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
Submitted on: 2025-09-18 16:28:22
Comments
Log in to comment.
No comments yet.