Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0xb1371882A69f7D9B2cd3c2a02B47f51b107ee5d7
Balance 0 ETH
Nonce 1
Code Size 4812 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

4812 bytes
0x608060405234801561001057600080fd5b50600436106101005760003560e01c806394483cfb11610097578063e06e6cd811610066578063e06e6cd8146102c5578063eb00b76e146102ec578063ececf4ef14610314578063fdea8e0b1461031c57600080fd5b806394483cfb146102295780639588968e14610250578063d3580ad814610270578063db584d251461029e57600080fd5b806384a1931f116100d357806384a1931f1461016f578063856ed8c91461019657806386f3b7ae146101e25780638ab9ff711461020257600080fd5b8063254800d41461010557806330b90b2c1461013f5780634b88904c146101495780637921f9d41461015c575b600080fd5b61012c7f000000000000000000000000000000000000000000000000000000006709134081565b6040519081526020015b60405180910390f35b610147610343565b005b61014761015736600461108b565b61036f565b61014761016a36600461108b565b610507565b61012c7f0000000000000000000000000000000000000000000000000000000069639ed081565b6101bd7f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b81565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610136565b61012c6101f0366004611132565b60016020526000908152604090205481565b6101bd7f0000000000000000000000008fc17671d853341d9e8b001f5fc3c892d09cb53a81565b6101bd7f0000000000000000000000000581ddf7a136c6837429a46c6cb7b388a3e5297181565b61012c61025e36600461114f565b60026020526000908152604090205481565b61028361027e36600461108b565b610535565b60408051938452602084019290925290820152606001610136565b6101bd7f00000000000000000000000000000000000000447e69651d841bd8d104bed49381565b61012c7f00000000000000000000000000000000000000000000000000000000000005dc81565b6102ff6102fa36600461108b565b61062b565b60408051928352602083019190915201610136565b610283610660565b6101bd7f0000000000000000000000007a655a234ddf076c3530ac847040f8d8c511502181565b61034b61073b565b6000610355610660565b925050506103628161077e565b5061036d6001600055565b565b61037761073b565b6103a17f0000000000000000000000000581ddf7a136c6837429a46c6cb7b388a3e52971836108c4565b60006103af8585858561062b565b915050806000036103ec576040517f0f3f861000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600260205260408120805483929061040a908490611197565b90915550506040517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481018290527f0000000000000000000000008fc17671d853341d9e8b001f5fc3c892d09cb53a73ffffffffffffffffffffffffffffffffffffffff169063a9059cbb906044016020604051808303816000875af11580156104a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104c691906111aa565b506040518190849033907fc2a5458528bf2bea9ee5c6e62b173268e6df4a507a78226b24f54951a842976190600090a4506105016001600055565b50505050565b61050f61073b565b600061051d85858585610535565b9250505061052a8161077e565b506105016001600055565b600080600061054687878787610b74565b6040517fc1f5e11c00000000000000000000000000000000000000000000000000000000815233600482015284907f0000000000000000000000007a655a234ddf076c3530ac847040f8d8c511502173ffffffffffffffffffffffffffffffffffffffff169063c1f5e11c90602401602060405180830381865afa1580156105d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f691906111cc565b6106009190611197565b33600090815260016020526040902054909350915061061f8383610cf5565b90509450945094915050565b60008061063a86868686610e8d565b60008481526002602052604090205491506106558383610cf5565b905094509492505050565b6040517fc1f5e11c0000000000000000000000000000000000000000000000000000000081523360048201526000908190819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000007a655a234ddf076c3530ac847040f8d8c5115021169063c1f5e11c90602401602060405180830381865afa1580156106f1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061071591906111cc565b3360009081526001602052604090205490935091506107348383610cf5565b9050909192565b600260005403610777576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002600055565b806000036107b8576040517f0f3f861000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b33600090815260016020526040812080548392906107d7908490611197565b90915550506040517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481018290527f0000000000000000000000008fc17671d853341d9e8b001f5fc3c892d09cb53a73ffffffffffffffffffffffffffffffffffffffff169063a9059cbb906044016020604051808303816000875af115801561086f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061089391906111aa565b50604051819033907f1e1dcbbf6b3cd717cbf65d14cad3c5f2a3d876e0bb0dfc3636818cbd625595b590600090a350565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810182905260009073ffffffffffffffffffffffffffffffffffffffff841690636352211e90602401602060405180830381865afa158015610932573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095691906111e5565b90503373ffffffffffffffffffffffffffffffffffffffff82160361097a57505050565b6040517fb9f3687400000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff8281166024830152848116604483015260648201849052600060848301527f00000000000000000000000000000000000000447e69651d841bd8d104bed493169063b9f368749060a401602060405180830381865afa158015610a22573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a4691906111aa565b15610a5057505050565b6040517faba69cf800000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff82811660248301528481166044830152606482018490527f00000000000000000000000000000000000076a84fef008cdabe6409d2fe638b169063aba69cf890608401602060405180830381865afa158015610af1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1591906111aa565b15610b1f57505050565b6040517f58a5e22200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201526024810183905260440160405180910390fd5b6040805133602082015290810183905260608101829052600090608001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905060007f0000000000000000000000007a655a234ddf076c3530ac847040f8d8c511502173ffffffffffffffffffffffffffffffffffffffff166354e9f7cd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c51573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c7591906111cc565b9050610cb78686808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508592508691506110009050565b610ced576040517f9cb946d500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505050505050565b600080612710610d257f00000000000000000000000000000000000000000000000000000000000005dc86611202565b610d2f9190611219565b90507f0000000000000000000000000000000000000000000000000000000067091340421015610d7d5782811015610d6b576000915050610e87565b610d758382611254565b915050610e87565b6000610d898286611254565b905060007f0000000000000000000000000000000000000000000000000000000069639ed04211610dba5742610ddc565b7f0000000000000000000000000000000000000000000000000000000069639ed05b9050600083610e2b7f00000000000000000000000000000000000000000000000000000000670913407f0000000000000000000000000000000000000000000000000000000069639ed0611254565b84610e567f000000000000000000000000000000000000000000000000000000006709134086611254565b610e609190611202565b610e6a9190611219565b610e749190611197565b9050610e808682611254565b9450505050505b92915050565b6040805160208101849052908101829052600090606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905060007f0000000000000000000000007a655a234ddf076c3530ac847040f8d8c511502173ffffffffffffffffffffffffffffffffffffffff1663013464316040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f64573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f8891906111cc565b9050610fca8686808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508592508691506110009050565b610ced576040517fbe758f2800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008261100d8584611016565b14949350505050565b600081815b8451811015611051576110478286838151811061103a5761103a611267565b6020026020010151611059565b915060010161101b565b509392505050565b6000818310611075576000828152602084905260409020611084565b60008381526020839052604090205b9392505050565b600080600080606085870312156110a157600080fd5b843567ffffffffffffffff808211156110b957600080fd5b818701915087601f8301126110cd57600080fd5b8135818111156110dc57600080fd5b8860208260051b85010111156110f157600080fd5b6020928301999098509187013596604001359550909350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461112f57600080fd5b50565b60006020828403121561114457600080fd5b81356110848161110d565b60006020828403121561116157600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820180821115610e8757610e87611168565b6000602082840312156111bc57600080fd5b8151801515811461108457600080fd5b6000602082840312156111de57600080fd5b5051919050565b6000602082840312156111f757600080fd5b81516110848161110d565b8082028115828204841417610e8757610e87611168565b60008261124f577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b81810381811115610e8757610e87611168565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea26469706673582212201dabf0deb73d99d7c51f2f6d19a3f4f95839d735012eede997b7199c48bde7a664736f6c63430008180033

Verified Source Code Full Match

Compiler: v0.8.24+commit.e11b9ed9 EVM: paris Optimization: Yes (10000000 runs)
Presale.sol 1003 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import {MerkleProof} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import {ECDSA} from "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import {MessageHashUtils} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC721} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {Ownable2Step, Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol";
import {Pausable} from "lib/openzeppelin-contracts/contracts/utils/Pausable.sol";
import {IDelegationRegistry} from "src/lib/IDelegationRegistry.sol";
import {IDelegateRegistry} from "lib/delegate-registry/src/IDelegateRegistry.sol";

/// @title Presale
/// @notice Block Games presale contract
/// @author karooolis
contract Presale is Ownable2Step, Pausable, ReentrancyGuard {
    /*==============================================================
                      CONSTANTS & IMMUTABLES
    ==============================================================*/

    /// @notice Event emitted when the merkle root for the allowed wallets is set
    bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");

    /// @notice Event emitted when the merkle root for the allowed wallets is set
    bytes32 public immutable cachedDomainSeparator;

    /// @notice Chain ID of the contract
    uint256 public immutable cachedChainId;

    // @notice Address of the contract
    address public immutable cachedThis;

    /// @notice Address of the Dice NFT
    address public immutable diceNFT;

    /// @notice Address of the wallet that will receive the funds
    address public immutable treasury;

    /// @notice Address of the delegate registry v1
    IDelegationRegistry public immutable delegateRegistryV1;

    /// @notice Address of the delegate registry v2
    IDelegateRegistry public immutable delegateRegistryV2;

    /*==============================================================
                            STRUCTS
    ==============================================================*/

    struct BuyWithDiceNFTParams {
        bytes32[] proof;
        uint256 tokenId;
        uint256 amount;
        uint256 maxAmount;
    }

    struct BuyWithAllowedNFTParams {
        bytes32[] proof;
        address collection;
        uint256 tokenId;
        uint256 amount;
        uint256 maxAmount;
    }

    struct AllowedWalletParams {
        bytes32[] proof;
        uint256 amount;
        uint256 maxAmount;
    }

    /*==============================================================
                       STORAGE VARIABLES
    ==============================================================*/

    enum Phase {
        Guaranteed,
        Lucky,
        Open
    }

    /// @notice Merkle root for the allowed wallets, and their caps (wallet + ETH amount)
    bytes32 public allowedWalletsMerkleRoot;

    /// @notice Merkle root for the Dice NFTs, and their caps (tokenId + ETH amount)
    bytes32 public diceNFTsMerkleRoot;

    /// @notice Merkle root for the allowed NFT collections, and their caps (collection + ETH amount)
    bytes32 public allowedNFTsMerkleRoot;

    /// @notice Merkle root for Open phase alocations (wallet + ETH amount + tokens amount)
    bytes32 public openPhaseAllocationsMerkleRoot;

    /// @notice Merkle root for Dice NFT eligible tokens (tokenId + tokens amount)
    bytes32 public diceNFTsTokensEligibleMerkleRoot;

    /// @notice Guaranteed allocation (ETH)
    uint256 public guaranteedAllocation;

    /// @notice Lucky allocation for each Lucky Tier 1 (ETH)
    uint256 public luckyAllocationTier1;

    /// @notice Lucky allocation for each Lucky Tier 2 (ETH)
    uint256 public luckyAllocationTier2;

    /// @notice Lucky allocation for each Lucky Tier 3 (ETH)
    uint256 public luckyAllocationTier3;

    /// @notice Signer of lucky signatures
    address public luckySigner;

    /// @notice Guaranteed phase start
    uint256 public guaranteedStart;

    /// @notice Guaranteed phase end
    uint256 public guaranteedEnd;

    /// @notice Lucky phase start
    uint256 public luckyStart;

    /// @notice Lucky phase end
    uint256 public luckyEnd;

    /// @notice Open phase start
    uint256 public openStart;

    /// @notice Open phase end
    uint256 public openEnd;

    /// @notice Wallet cap for the Open phase
    uint256 public openPhaseWalletCap;

    /// @notice Contribution step for the Open phase
    uint256 public openPhaseContributionStep;

    /// @notice Guaranteed phase exchange rate
    uint256 public guaranteedExchangeRate;

    /// @notice Lucky phase Tier 1 exchange rate
    uint256 public luckyExchangeRate1;

    /// @notice Lucky phase Tier 2 exchange rate
    uint256 public luckyExchangeRate2;

    /// @notice Lucky phase Tier 3 exchange rate
    uint256 public luckyExchangeRate3;

    /// @notice Guaranteed phase raised ETH
    uint256 public guaranteedRaisedETH;

    /// @notice Lucky phase raised ETH
    uint256 public luckyRaisedETH;

    /// @notice Open phase raised ETH
    uint256 public openRaisedETH;

    /// @notice Contributed ETH in allowed NFTs (tokenId => amount)
    mapping(uint256 => uint256) public diceNFTContributed;

    /// @notice Contributed in allowed wallets (wallet => amount)
    mapping(address => uint256) public allowedWalletContributed;

    /// @notice Contributed in allowed NFTs (collection => (tokenId => amount)
    mapping(address => mapping(uint256 => uint256)) public allowedNFTContributed;

    /// @notice Contributed in Lucky phase (wallet => amount)
    mapping(address => uint256) public luckyPhaseContributed;

    /// @notice Contributed in Lucky phase with signature (signature => amount)
    mapping(bytes => uint256) public raffleTicketContributed;

    /// @notice Contributed in Open phase (wallet => amount)
    mapping(address => uint256) public openPhaseContributed;

    /// @notice Tokens vestable after contribution (wallet => amount)
    mapping(address => uint256) public tokensEligible;

    /*==============================================================
                            MODIFIERS
    ==============================================================*/

    /// @notice Modifier to check if the phase is active
    /// @param _phase The phase to check
    modifier phaseGuard(Phase _phase) {
        bool isPhaseActive = isPhaseActive(_phase);
        if (!isPhaseActive) {
            revert PhaseNotActive(_phase);
        }
        _;
    }

    /// @notice Check if the presale has not started
    modifier onlyBeforePresale() {
        if (
            (guaranteedStart != 0 && block.timestamp > guaranteedStart)
                || (luckyStart != 0 && block.timestamp > luckyStart) || (openStart != 0 && block.timestamp > openStart)
        ) {
            revert OnlyBeforePresale();
        }
        _;
    }

    /// @notice Check if the presale has ended
    modifier onlyAfterPresale() {
        if (
            (guaranteedEnd != 0 && block.timestamp < guaranteedEnd) || (luckyEnd != 0 && block.timestamp < luckyEnd)
                || (openEnd != 0 && block.timestamp < openEnd)
        ) {
            revert OnlyAfterPresale();
        }
        _;
    }

    /*==============================================================
                            FUNCTIONS
    ==============================================================*/

    /// @param _diceNFT Address of the Dice NFT
    /// @param _treasury Address of the wallet that will receive the funds
    /// @param _initialOwner Address of the initial owner
    /// @param _delegateRegistryV1 Address of the delegate registry v1
    /// @param _delegateRegistryV2 Address of the delegate registry v2
    constructor(
        address _diceNFT,
        address _treasury,
        address _initialOwner,
        address _delegateRegistryV1,
        address _delegateRegistryV2
    ) Ownable(_initialOwner) {
        diceNFT = _diceNFT;
        treasury = _treasury;
        delegateRegistryV1 = IDelegationRegistry(_delegateRegistryV1);
        delegateRegistryV2 = IDelegateRegistry(_delegateRegistryV2);

        // Domain separator for EIP-712
        cachedChainId = block.chainid;
        cachedThis = address(this);
        cachedDomainSeparator = _constructDomainSeparator();
    }

    /// @notice Buy tokens with Dice NFTs
    /// @dev This function allows users to buy tokens with Dice NFTs during the guaranteed phase
    /// @param _params Array of BuyWithDiceNFTParams
    function buyWithDiceNFTs(BuyWithDiceNFTParams[] calldata _params) public payable {
        uint256 ethContributed;
        for (uint256 i = 0; i < _params.length; i++) {
            _buyWithDiceNFT(_params[i].amount, _params[i].proof, _params[i].tokenId, _params[i].maxAmount);
            ethContributed += _params[i].amount;
        }

        if (ethContributed != msg.value) {
            revert ContributionAmountMismatch();
        }
    }

    /// @notice Buy tokens with a Dice NFT
    /// @dev This function allows users to buy tokens with a Dice NFT during the guaranteed phase
    /// @param _proof Merkle proof
    /// @param _tokenId ID of the NFT
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    function buyWithDiceNFT(bytes32[] calldata _proof, uint256 _tokenId, uint256 _maxAmount) external payable {
        _buyWithDiceNFT(msg.value, _proof, _tokenId, _maxAmount);
    }

    /// @notice Buy tokens with an allowlisted wallet
    /// @dev This function relies on the merkle proof to verify the caller's eligibility
    /// @param _proof Merkle proof
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    function buyWithAllowedWallet(bytes32[] calldata _proof, uint256 _maxAmount) external payable {
        _buyWithAllowedWallet(msg.value, _proof, _maxAmount);
    }

    /// @notice Buy tokens with an allowlisted wallet
    /// @param _params AllowedWalletParams
    function buyWithAllowedNFTs(BuyWithAllowedNFTParams[] calldata _params) external payable {
        uint256 ethContributed;
        for (uint256 i = 0; i < _params.length; i++) {
            _buyWithAllowedNFT(
                _params[i].amount, _params[i].proof, _params[i].collection, _params[i].tokenId, _params[i].maxAmount
            );
            ethContributed += _params[i].amount;
        }

        if (ethContributed != msg.value) {
            revert ContributionAmountMismatch();
        }
    }

    /// @notice Buy tokens with an allowlisted NFT
    /// @dev This function allows users to buy tokens with an allowlisted NFT
    /// @param _proof Merkle proof
    /// @param _collection Address of the NFT collection
    /// @param _tokenId ID of the NFT
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    function buyWithAllowedNFT(bytes32[] calldata _proof, address _collection, uint256 _tokenId, uint256 _maxAmount)
        external
        payable
    {
        _buyWithAllowedNFT(msg.value, _proof, _collection, _tokenId, _maxAmount);
    }

    /// @notice Buy tokens in the Lucky phase
    /// @dev This function uses a signature to verify the maximum investment amount
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    /// @param _signature Signature of the maximum amount and the sender's address
    function buyLuckyTier(uint256 _maxAmount, bytes calldata _signature) external payable {
        _buyLuckyTier(msg.value, _maxAmount, _signature);
    }

    /// @notice Buy tokens in the Open phase
    function buyOpenPhase() external payable {
        _buyOpenPhase(msg.value);
    }

    /// @notice Buy tokens in all phases
    /// @param _diceNFTParams Array of BuyWithDiceNFTParams
    /// @param _allowedNFTParams Array of BuyWithAllowedNFTParams
    /// @param _allowedWalletParams AllowedWalletParams
    /// @param _luckyPhaseSignature Signature of the maximum amount and the sender's address
    /// @param _luckyPhaseAmount Amount of ETH to contribute in the lucky phase
    /// @param _luckyPhaseMaxAmount Maximum amount of ETH that can be contributed in the lucky phase
    /// @param _openPhaseAmount Amount of ETH to contribute in the open phase
    function buyAllPhases(
        BuyWithDiceNFTParams[] calldata _diceNFTParams,
        BuyWithAllowedNFTParams[] calldata _allowedNFTParams,
        AllowedWalletParams calldata _allowedWalletParams,
        bytes calldata _luckyPhaseSignature,
        uint256 _luckyPhaseAmount,
        uint256 _luckyPhaseMaxAmount,
        uint256 _openPhaseAmount
    ) external payable {
        uint256 ethContributed;

        uint256 diceNFTParamsLength = _diceNFTParams.length;
        for (uint256 i = 0; i < diceNFTParamsLength; i++) {
            BuyWithDiceNFTParams memory param = _diceNFTParams[i];
            _buyWithDiceNFT(param.amount, _diceNFTParams[i].proof, param.tokenId, param.maxAmount);
            ethContributed += param.amount;
        }

        uint256 allowedNFTParamsLength = _allowedNFTParams.length;
        for (uint256 i = 0; i < allowedNFTParamsLength; i++) {
            BuyWithAllowedNFTParams memory param = _allowedNFTParams[i];
            _buyWithAllowedNFT(
                param.amount, _allowedNFTParams[i].proof, param.collection, param.tokenId, param.maxAmount
            );
            ethContributed += param.amount;
        }

        if (_allowedWalletParams.proof.length > 0) {
            _buyWithAllowedWallet(
                _allowedWalletParams.amount, _allowedWalletParams.proof, _allowedWalletParams.maxAmount
            );
            ethContributed += _allowedWalletParams.amount;
        }

        if (_luckyPhaseSignature.length > 0) {
            _buyLuckyTier(_luckyPhaseAmount, _luckyPhaseMaxAmount, _luckyPhaseSignature);
            ethContributed += _luckyPhaseAmount;
        }

        if (_openPhaseAmount > 0) {
            _buyOpenPhase(_openPhaseAmount);
            ethContributed += _openPhaseAmount;
        }

        if (ethContributed != msg.value) {
            revert ContributionAmountMismatch();
        }
    }

    /*==============================================================
                            VIEW FUNCTIONS
    ==============================================================*/

    /// @notice Calculate the amount of tokens for a given amount and phase
    /// @param _amount Amount of ETH to calculate tokens for
    /// @param _phase Phase to calculate tokens for
    function calculateTokens(uint256 _amount, Phase _phase) public view returns (uint256 tokens) {
        if (_phase == Phase.Guaranteed) {
            tokens = _amount * guaranteedExchangeRate;
        } else {
            uint256 remainingETH = _amount;
            uint256 luckyRaisedETH_ = luckyRaisedETH;
            uint256 tier1Space = luckyAllocationTier1 > luckyRaisedETH_ ? luckyAllocationTier1 - luckyRaisedETH_ : 0;

            if (tier1Space > 0) {
                uint256 tier1ETH = remainingETH > tier1Space ? tier1Space : remainingETH;
                tokens = tier1ETH * luckyExchangeRate1;
                remainingETH -= tier1ETH;
                luckyRaisedETH_ += tier1Space;
            }

            if (remainingETH > 0) {
                uint256 tier2Space = luckyAllocationTier1 + luckyAllocationTier2 > luckyRaisedETH_
                    ? luckyAllocationTier1 + luckyAllocationTier2 - luckyRaisedETH_
                    : 0;

                if (tier2Space > 0) {
                    uint256 tier2ETH = remainingETH > tier2Space ? tier2Space : remainingETH;
                    tokens += tier2ETH * luckyExchangeRate2;
                    remainingETH -= tier2ETH;
                }
            }

            if (remainingETH > 0) {
                tokens += remainingETH * luckyExchangeRate3;
            }
        }
    }

    /// @notice Check if a phase is active
    /// @param _phase Phase to check
    /// @return bool
    function isPhaseActive(Phase _phase) public view returns (bool) {
        if (_phase == Phase.Guaranteed) {
            if (
                guaranteedRaisedETH > guaranteedAllocation
                    || (block.timestamp < guaranteedStart || block.timestamp > guaranteedEnd)
            ) {
                return false;
            }
        } else if (_phase == Phase.Lucky) {
            if (
                luckyRaisedETH > luckyAllocationTier1 + luckyAllocationTier2 + luckyAllocationTier3
                    || (block.timestamp < luckyStart || block.timestamp > luckyEnd)
            ) {
                return false;
            }
        } else if (_phase == Phase.Open) {
            if (block.timestamp < openStart || block.timestamp > openEnd) {
                return false;
            }
        }

        return true;
    }

    /*==============================================================
                       ADMIN FUNCTIONS
    ==============================================================*/

    /// @notice Set the merkle root for the allowed wallets
    /// @param _allowedWalletsMerkleRoot Merkle root for the allowed wallets
    function setAllowedWalletsMerkleRoot(bytes32 _allowedWalletsMerkleRoot) external onlyOwner onlyBeforePresale {
        allowedWalletsMerkleRoot = _allowedWalletsMerkleRoot;
        emit AllowedWalletsMerkleRootSet(_allowedWalletsMerkleRoot);
    }

    /// @notice Set the merkle root for the Dice NFTs
    /// @param _diceNFTsMerkleRoot Merkle root for the Dice NFTs
    function setDiceNFTsMerkleRoot(bytes32 _diceNFTsMerkleRoot) external onlyOwner onlyBeforePresale {
        diceNFTsMerkleRoot = _diceNFTsMerkleRoot;
        emit DiceNFTsMerkleRootSet(_diceNFTsMerkleRoot);
    }

    /// @notice Set the merkle root for the allowed NFTs
    /// @param _allowedNFTsMerkleRoot Merkle root for the allowed NFTs
    function setAllowedNFTsMerkleRoot(bytes32 _allowedNFTsMerkleRoot) external onlyOwner onlyBeforePresale {
        allowedNFTsMerkleRoot = _allowedNFTsMerkleRoot;
        emit AllowedNFTsMerkleRootSet(_allowedNFTsMerkleRoot);
    }

    /// @notice Set the merkle root for the Open phase allocations
    /// @param _openPhaseAllocationsMerkleRoot Merkle root for the Open phase allocations
    function setOpenPhaseAllocationsMerkleRoot(bytes32 _openPhaseAllocationsMerkleRoot)
        external
        onlyOwner
        onlyAfterPresale
    {
        openPhaseAllocationsMerkleRoot = _openPhaseAllocationsMerkleRoot;
        emit OpenPhaseAllocationsMerkleRootSet(_openPhaseAllocationsMerkleRoot);
    }

    /// @notice Sets the merkle root for the Dice NFTs tokens eligible
    /// @param _diceNFTsTokensEligibleMerkleRoot Merkle root for the Dice NFTs tokens eligible
    function setDiceNFTsTokensEligibleMerkleRoot(bytes32 _diceNFTsTokensEligibleMerkleRoot)
        external
        onlyOwner
        onlyAfterPresale
    {
        diceNFTsTokensEligibleMerkleRoot = _diceNFTsTokensEligibleMerkleRoot;
        emit DiceNFTsTokensEligibleMerkleRootSet(_diceNFTsTokensEligibleMerkleRoot);
    }

    /// @notice Set guaranteed phase start, end and allocation
    /// @param _guaranteedStart Start time of the guaranteed phase
    /// @param _guaranteedEnd End time of the guaranteed phase
    /// @param _guaranteedAllocation Allocation for the guaranteed phase
    function setGuaranteedPhase(uint256 _guaranteedStart, uint256 _guaranteedEnd, uint256 _guaranteedAllocation)
        external
        onlyOwner
        onlyBeforePresale
    {
        guaranteedStart = _guaranteedStart;
        guaranteedEnd = _guaranteedEnd;
        guaranteedAllocation = _guaranteedAllocation;

        emit GuaranteedPhaseSet(guaranteedStart, guaranteedEnd, guaranteedAllocation);
    }

    /// @notice Set Lucky phase start, end, allocations per tier, and wallet cap
    /// @param _luckyStart Start time of the Lucky phase
    /// @param _luckyEnd End time of the Lucky phase
    /// @param _luckyAllocationTier1 Allocation for the first lucky tier1
    /// @param _luckyAllocationTier2 Allocation for the second lucky tier2
    /// @param _luckyAllocationTier3 Allocation for the third lucky tier3
    /// @param _luckySigner Signer of lucky signatures
    function setLuckyPhase(
        uint256 _luckyStart,
        uint256 _luckyEnd,
        uint256 _luckyAllocationTier1,
        uint256 _luckyAllocationTier2,
        uint256 _luckyAllocationTier3,
        address _luckySigner
    ) external onlyOwner onlyBeforePresale {
        luckyStart = _luckyStart;
        luckyEnd = _luckyEnd;
        luckyAllocationTier1 = _luckyAllocationTier1;
        luckyAllocationTier2 = _luckyAllocationTier2;
        luckyAllocationTier3 = _luckyAllocationTier3;
        luckySigner = _luckySigner;

        emit LuckyPhaseSet(
            _luckyStart, _luckyEnd, _luckyAllocationTier1, _luckyAllocationTier2, _luckyAllocationTier3, _luckySigner
        );
    }

    /// @notice Set Open phase start, end and allocation
    /// @param _openStart Start time of the open phase
    /// @param _openEnd End time of the open phase
    function setOpenPhase(
        uint256 _openStart,
        uint256 _openEnd,
        uint256 _openPhaseWalletCap,
        uint256 _openPhaseContributionStep
    ) external onlyOwner onlyBeforePresale {
        openStart = _openStart;
        openEnd = _openEnd;
        openPhaseWalletCap = _openPhaseWalletCap;
        openPhaseContributionStep = _openPhaseContributionStep;

        emit OpenPhaseSet(_openStart, _openEnd, _openPhaseWalletCap, _openPhaseContributionStep);
    }

    /// @notice Set the exchange rates for each phase
    /// @param _guaranteedExchangeRate Exchange rate for the guaranteed phase
    /// @param _luckyExchangeRate1 Exchange rate for the first lucky phase
    /// @param _luckyExchangeRate2 Exchange rate for the second lucky phase
    /// @param _luckyExchangeRate3 Exchange rate for the third lucky phase
    function setExchangeRates(
        uint256 _guaranteedExchangeRate,
        uint256 _luckyExchangeRate1,
        uint256 _luckyExchangeRate2,
        uint256 _luckyExchangeRate3
    ) external onlyOwner onlyBeforePresale {
        guaranteedExchangeRate = _guaranteedExchangeRate;
        luckyExchangeRate1 = _luckyExchangeRate1;
        luckyExchangeRate2 = _luckyExchangeRate2;
        luckyExchangeRate3 = _luckyExchangeRate3;

        emit ExchangeRatesSet(_guaranteedExchangeRate, _luckyExchangeRate1, _luckyExchangeRate2, _luckyExchangeRate3);
    }

    /// @notice Pause the purchase functions, only owner can call this function
    function pause() external onlyOwner {
        _pause();
    }

    /// @notice Unpause the purchase functions, only owner can call this function
    function unpause() external onlyOwner {
        _unpause();
    }

    /*==============================================================
                       INTERNAL FUNCTIONS
    ==============================================================*/

    function _buyWithDiceNFT(uint256 _amount, bytes32[] calldata _proof, uint256 _tokenId, uint256 _maxAmount)
        internal
        nonReentrant
        whenNotPaused
        phaseGuard(Phase.Guaranteed)
    {
        _verifyTokenOwner(diceNFT, _tokenId);
        _verifyDiceNFTProof(_proof, _tokenId, _maxAmount);

        // check if contribution cap is not exceeded
        if (diceNFTContributed[_tokenId] + _amount > _maxAmount) {
            revert ExceededDiceNFTCap(_tokenId);
        }

        // check if total contribution cap is not exceeded
        if (guaranteedRaisedETH + _amount > guaranteedAllocation) {
            revert ExceededGuaranteedAllocation();
        }

        // update contributed amount, tokens eligible & total sold
        uint256 tokensAmount = calculateTokens(_amount, Phase.Guaranteed);
        tokensEligible[msg.sender] += tokensAmount;
        diceNFTContributed[_tokenId] += _amount;
        guaranteedRaisedETH += _amount;

        // transfer ETH to receiver wallet
        (bool success,) = treasury.call{value: _amount}("");
        if (!success) {
            revert TransferFailed(treasury, _amount);
        }

        emit ContributedDiceNFT(msg.sender, _amount, tokensAmount, _tokenId);
    }

    /// @notice Buy tokens with an allowlisted NFT
    /// @param _amount Amount of ETH to contribute
    /// @param _proof Merkle proof
    /// @param _collection Address of the NFT collection
    /// @param _tokenId ID of the NFT
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    function _buyWithAllowedNFT(
        uint256 _amount,
        bytes32[] calldata _proof,
        address _collection,
        uint256 _tokenId,
        uint256 _maxAmount
    ) internal nonReentrant whenNotPaused phaseGuard(Phase.Guaranteed) {
        _verifyTokenOwner(_collection, _tokenId);
        _verifyAllowedNFTProof(_proof, _collection, _maxAmount);

        // check if total contribution cap is not exceeded
        if (guaranteedRaisedETH + _amount > guaranteedAllocation) {
            revert ExceededGuaranteedAllocation();
        }

        // check if NFT cap is not exceeded
        if (allowedNFTContributed[_collection][_tokenId] + _amount > _maxAmount) {
            revert ExceededNFTCap(_collection, _tokenId);
        }

        // update contributed amount, tokens eligible & total sold
        uint256 tokensAmount = calculateTokens(_amount, Phase.Guaranteed);
        tokensEligible[msg.sender] += tokensAmount;
        allowedNFTContributed[_collection][_tokenId] += _amount;
        guaranteedRaisedETH += _amount;

        // transfer ETH to receiver wallet
        (bool success,) = treasury.call{value: _amount}("");
        if (!success) {
            revert TransferFailed(treasury, _amount);
        }

        emit ContributedAllowedNFT(msg.sender, _amount, tokensAmount, _collection, _tokenId);
    }

    /// @notice Buy tokens with an allowlisted wallet
    /// @param _amount Amount of ETH to contribute
    /// @param _proof Merkle proof
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    function _buyWithAllowedWallet(uint256 _amount, bytes32[] calldata _proof, uint256 _maxAmount)
        internal
        nonReentrant
        whenNotPaused
        phaseGuard(Phase.Guaranteed)
    {
        _verifyAllowedWalletProof(_proof, _maxAmount);

        // check if contribution cap is not exceeded
        if (allowedWalletContributed[msg.sender] + _amount > _maxAmount) {
            revert ExceededAllowedWalletCap(msg.sender);
        }

        // check if total contribution cap is not exceeded
        if (guaranteedRaisedETH + _amount > guaranteedAllocation) {
            revert ExceededGuaranteedAllocation();
        }

        // Update contributed amount, tokens eligible & total sold
        uint256 tokensAmount = calculateTokens(_amount, Phase.Guaranteed);
        tokensEligible[msg.sender] += tokensAmount;
        allowedWalletContributed[msg.sender] += _amount;
        guaranteedRaisedETH += _amount;

        // Transfer ETH to receiver wallet
        (bool success,) = treasury.call{value: _amount}("");
        if (!success) {
            revert TransferFailed(treasury, _amount);
        }

        emit ContributedAllowedWallet(msg.sender, _amount, tokensAmount);
    }

    /// @notice Buy tokens in the Lucky phase
    /// @param _amount Amount of ETH to contribute
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    /// @param _signature Signature of the maximum amount and the sender's address
    function _buyLuckyTier(uint256 _amount, uint256 _maxAmount, bytes calldata _signature)
        internal
        nonReentrant
        whenNotPaused
        phaseGuard(Phase.Lucky)
    {
        _verifyLuckySignature(_maxAmount, _signature);

        if (raffleTicketContributed[_signature] + _amount > _maxAmount) {
            revert ExceededRaffleTicketContribution();
        } else if (luckyRaisedETH + _amount > luckyAllocationTier1 + luckyAllocationTier2 + luckyAllocationTier3) {
            revert ExceededLuckyAllocation();
        }

        // update contributed amounts for raffle ticket and per-wallet, tokens eligible & total sold
        uint256 tokensAmount = calculateTokens(_amount, Phase.Lucky);
        tokensEligible[msg.sender] += tokensAmount;
        raffleTicketContributed[_signature] += _amount;
        luckyPhaseContributed[msg.sender] += _amount;
        luckyRaisedETH += _amount;

        // transfer ETH to receiver wallet
        (bool success,) = treasury.call{value: _amount}("");
        if (!success) {
            revert TransferFailed(treasury, _amount);
        }

        emit ContributedLuckyPhase(msg.sender, _amount, tokensAmount);
    }

    /// @notice Buy tokens in the Open phase
    /// @param _amount Amount of ETH to contribute
    function _buyOpenPhase(uint256 _amount) internal nonReentrant whenNotPaused phaseGuard(Phase.Open) {
        if (_amount % openPhaseContributionStep != 0) {
            revert IncorrectContributionStep();
        }

        if (openPhaseContributed[msg.sender] + _amount > openPhaseWalletCap) {
            revert ExceededOpenPhaseWalletCap(msg.sender);
        }

        openPhaseContributed[msg.sender] += _amount;
        openRaisedETH += _amount;

        // transfer ETH to receiver wallet
        (bool success,) = treasury.call{value: _amount}("");
        if (!success) {
            revert TransferFailed(treasury, _amount);
        }

        emit ContributedOpenPhase(msg.sender, _amount);
    }

    /// @notice Verifies if the sender is the owner of a given token or a valid delegate.
    /// @param _collection The address of the collection contract.
    /// @param _tokenId The token ID to verify ownership or delegation for.
    function _verifyTokenOwner(address _collection, uint256 _tokenId) internal view {
        address _tokenOwner = IERC721(_collection).ownerOf(_tokenId);

        // Check sender is owner
        if (_tokenOwner == msg.sender) {
            return;
        }

        // Check with delegate registry v2
        if (delegateRegistryV2.checkDelegateForERC721(msg.sender, _tokenOwner, _collection, _tokenId, "")) {
            return;
        }

        // Check with delegate registry v1
        if (delegateRegistryV1.checkDelegateForToken(msg.sender, _tokenOwner, _collection, _tokenId)) {
            return;
        }

        // Revert if not owner or delegate
        revert NotTokenOwner(_collection, _tokenId);
    }

    /// @notice Verify the signature of the maximum amount and the sender's address
    /// @param _maxAmount Maximum amount of ETH that can be contributed
    /// @param _signature Signature of the maximum amount and the sender's address
    function _verifyLuckySignature(uint256 _maxAmount, bytes calldata _signature) internal view {
        bytes32 signedMessageHash = MessageHashUtils.toEthSignedMessageHash(
            keccak256(abi.encode(_getDomainSeparator(), _maxAmount, msg.sender))
        );
        address recoveredOwner = ECDSA.recover(signedMessageHash, _signature);

        if (recoveredOwner != luckySigner) {
            revert InvalidSignature();
        }
    }

    /// @notice Verify the merkle proof
    /// @param _proof The merkle proof
    /// @param _maxAmount The amount to verify
    function _verifyAllowedWalletProof(bytes32[] calldata _proof, uint256 _maxAmount) internal view {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _maxAmount))));
        if (!MerkleProof.verify(_proof, allowedWalletsMerkleRoot, leaf)) {
            revert InvalidAllowedWalletProof();
        }
    }

    /// @notice Verify the merkle proof
    /// @param _proof The merkle proof
    /// @param _tokenId The token ID to verify
    /// @param _maxAmount The amount to verify
    function _verifyDiceNFTProof(bytes32[] calldata _proof, uint256 _tokenId, uint256 _maxAmount) internal view {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(_tokenId, _maxAmount))));
        if (!MerkleProof.verify(_proof, diceNFTsMerkleRoot, leaf)) {
            revert InvalidDiceNFTProof();
        }
    }

    /// @notice Verify the merkle proof
    /// @param _proof The merkle proof
    /// @param _collection Collection address
    /// @param _maxAmount The amount to verify
    function _verifyAllowedNFTProof(bytes32[] calldata _proof, address _collection, uint256 _maxAmount) internal view {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(_collection, _maxAmount))));
        if (!MerkleProof.verify(_proof, allowedNFTsMerkleRoot, leaf)) {
            revert InvalidAllowedNFTProof();
        }
    }

    /// @notice Get the domain separator
    function _getDomainSeparator() internal view returns (bytes32) {
        if (address(this) == cachedThis && block.chainid == cachedChainId) {
            return cachedDomainSeparator;
        } else {
            return _constructDomainSeparator();
        }
    }

    /// @notice Construct the domain separator
    function _constructDomainSeparator() internal view returns (bytes32) {
        return keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
    }

    /*==============================================================
                            EVENTS
    ==============================================================*/

    /// @notice Emitted when the merkle root for the allowed wallets is set
    /// @param allowedWalletsMerkleRoot The merkle root for the allowed wallets
    event AllowedWalletsMerkleRootSet(bytes32 indexed allowedWalletsMerkleRoot);

    /// @notice Emitted when the merkle root for the Dice NFTs is set
    /// @param diceNFTsMerkleRoot The merkle root for the Dice NFTs
    event DiceNFTsMerkleRootSet(bytes32 indexed diceNFTsMerkleRoot);

    /// @notice Emitted when the merkle root for the allowed NFTs is set
    /// @param allowedNFTsMerkleRoot The merkle root for the allowed NFTs
    event AllowedNFTsMerkleRootSet(bytes32 indexed allowedNFTsMerkleRoot);

    /// @notice Emitted when the merkle root for the open phase allocations is set
    /// @param openPhaseAllocationsMerkleRoot The merkle root for the open phase allocations
    event OpenPhaseAllocationsMerkleRootSet(bytes32 indexed openPhaseAllocationsMerkleRoot);

    /// @notice Emitted when the merkle root for the Dice NFTs tokens eligible is set
    /// @param diceNFTsTokensEligibleMerkleRootSet The merkle root for the Dice NFTs tokens eligible
    event DiceNFTsTokensEligibleMerkleRootSet(bytes32 indexed diceNFTsTokensEligibleMerkleRootSet);

    /// @notice Emitted when the exchange rates for each phase are set
    event ExchangeRatesSet(
        uint256 guaranteedExchangeRate,
        uint256 luckyExchangeRate1,
        uint256 luckyExchangeRate2,
        uint256 luckyExchangeRate3
    );

    /// @notice Emitted when the allocations for the guaranteed phase are set
    /// @param guaranteedStart Start time of the guaranteed phase
    /// @param guaranteedEnd End time of the guaranteed phase
    /// @param guaranteedAllocation Allocation for the guaranteed phase
    event GuaranteedPhaseSet(
        uint256 indexed guaranteedStart, uint256 indexed guaranteedEnd, uint256 indexed guaranteedAllocation
    );

    /// @notice Emitted when the allocations for the lucky phase are set
    /// @param luckyStart Start time of the Lucky phase
    /// @param luckyEnd End time of the Lucky phase
    /// @param luckyAllocationTier1 Allocation for the first lucky tier1
    /// @param luckyAllocationTier2 Allocation for the second lucky tier2
    /// @param luckyAllocationTier3 Allocation for the third lucky tier3
    /// @param luckySigner Signer of lucky signatures
    event LuckyPhaseSet(
        uint256 luckyStart,
        uint256 luckyEnd,
        uint256 luckyAllocationTier1,
        uint256 luckyAllocationTier2,
        uint256 luckyAllocationTier3,
        address luckySigner
    );

    /// @notice Emitted when the allocations for the open phase are set
    /// @param openStart Start time of the Open phase
    /// @param openEnd End time of the Open phase
    /// @param openPhaseWalletCap Wallet cap for the Open phase
    /// @param openPhaseContributionStep Contribution step for the Open phase
    event OpenPhaseSet(
        uint256 openStart, uint256 openEnd, uint256 openPhaseWalletCap, uint256 openPhaseContributionStep
    );

    /// @notice Emitted when a contributor buys tokens
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    event Contributed(address indexed contributor, uint256 indexed amount);

    /// @notice Emitted when a contributor buys tokens with allowed wallet
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    /// @param tokensAmount The amount of tokens contributed
    event ContributedAllowedWallet(address indexed contributor, uint256 indexed amount, uint256 indexed tokensAmount);

    /// @notice Emitted when a contributor buys tokens with a Dice NFT
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    /// @param tokensAmount The amount of tokens contributed
    /// @param tokenId The token ID contributed for
    event ContributedDiceNFT(
        address indexed contributor, uint256 indexed amount, uint256 indexed tokensAmount, uint256 tokenId
    );

    /// @notice Emitted when a contributor buys tokens with an allowlisted NFT
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    /// @param tokensAmount The amount of tokens contributed
    /// @param collection The address of the NFT collection
    /// @param tokenId The token ID contributed for
    event ContributedAllowedNFT(
        address indexed contributor,
        uint256 indexed amount,
        uint256 indexed tokensAmount,
        address collection,
        uint256 tokenId
    );

    /// @notice Emitted when a contributor buys tokens in the Lucky phase
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    /// @param tokensAmount The amount of tokens contributed
    event ContributedLuckyPhase(address indexed contributor, uint256 indexed amount, uint256 indexed tokensAmount);

    /// @notice Emitted when a contributor buys tokens in the Open phase
    /// @param contributor The address of the contributor
    /// @param amount The amount of ETH contributed
    event ContributedOpenPhase(address indexed contributor, uint256 indexed amount);

    /*==============================================================
                            ERRORS
    ==============================================================*/

    /// @notice Revert if the transfer of funds fails
    /// @param recipient The address of the recipient
    /// @param amount The amount of funds refunded
    error TransferFailed(address recipient, uint256 amount);

    /// @notice Reverse if the signature is invalid
    error InvalidSignature();

    /// @notice Revert if the merkle proof for allowed wallet is invalid
    error InvalidAllowedWalletProof();

    /// @notice Revert if the merkle proof for Dice NFT is invalid
    error InvalidDiceNFTProof();

    /// @notice Revert if the merkle proof for allowed NFT is invalid
    error InvalidAllowedNFTProof();

    /// @notice Revert if the Dice NFT cap is exceeded
    error ExceededDiceNFTCap(uint256 tokenId);

    /// @notice Revert if the NFT cap is exceeded
    error ExceededNFTCap(address collection, uint256 tokenId);

    /// @notice Revert if the guaranteed allocation is exceeded
    error ExceededGuaranteedAllocation();

    /// @notice Revert if the contribution amount is incorrect
    error ContributionAmountMismatch();

    /// @notice Revert if the lucky allocation is exceeded
    error ExceededLuckyAllocation();

    /// @notice Revert if the wallet cap is exceeded
    error ExceededAllowedWalletCap(address wallet);

    /// @notice Revert if the raffle ticket contribution is exceeded
    error ExceededOpenPhaseWalletCap(address wallet);

    /// @notice Revert if the contribution is not divisible by the step
    error IncorrectContributionStep();

    /// @notice Revert if the raffle ticket contribution is exceeded
    error ExceededRaffleTicketContribution();

    /// @notice Revert if the phase is not active
    error PhaseNotActive(Phase phase);

    /// @notice Revert if the caller is not the owner of the token or a valid delegate.
    /// @param collection The address of the collection contract.
    /// @param tokenId The token ID to verify ownership or delegation for.
    error NotTokenOwner(address collection, uint256 tokenId);

    /// @notice Revert if presale has started
    error OnlyBeforePresale();

    /// @notice Revert if presale has ended
    error OnlyAfterPresale();
}
Vesting.sol 304 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import {IDelegateRegistry} from "src/lib/IDelegateRegistry.sol";
import {IDelegationRegistry} from "src/lib/IDelegationRegistry.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {MerkleProof} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import {ReentrancyGuard} from "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import {Presale} from "src/Presale.sol";

/// @title Vesting
/// @notice Block Games vesting contract
/// @author karooolis
contract Vesting is ReentrancyGuard {
    /*==============================================================
                      CONSTANTS & IMMUTABLES
    ==============================================================*/

    /// @notice The presale contract address
    Presale public immutable presale;

    /// @notice The Dice NFT contract address
    address public immutable diceNFT;

    /// @notice The Dice token address
    address public immutable diceToken;

    /// @notice The vesting start timestamp
    uint256 public immutable vestingStart;

    /// @notice The vesting end timestamp
    uint256 public immutable vestingEnd;

    /// @notice The immediate vested tokens in percentage (10_000 basis points)
    uint256 public immutable immediateVestedPct;

    /// @notice The delegate registry v2 contract address
    IDelegateRegistry public immutable delegateRegistryV2;

    /// @notice The delegate registry v1 contract address
    IDelegationRegistry public immutable delegateRegistryV1;

    /*==============================================================
                       STORAGE VARIABLES
    ==============================================================*/

    /// @notice Already claimed tokens when user contributed
    mapping(address => uint256) public contributedClaimed;

    /// @notice Already claimed tokens when user claimed for a specific Dice NFT token
    mapping(uint256 => uint256) public diceNFTClaimed;

    /*==============================================================
                            FUNCTIONS
    ==============================================================*/

    /// @notice Vesting contract constructor
    /// @param _presale The presale contract address
    /// @param _diceNFT The Dice NFT contract address
    /// @param _diceToken The Dice token address
    /// @param _vestingStart The vesting start timestamp
    /// @param _vestingEnd The vesting end timestamp
    /// @param _immediateVestedPct The immediate vested tokens in percentage
    /// @param _delegateRegistryV1 The delegate registry v1 contract address
    /// @param _delegateRegistryV2 The delegate registry v2 contract address
    constructor(
        address _presale,
        address _diceNFT,
        address _diceToken,
        uint256 _vestingStart,
        uint256 _vestingEnd,
        uint256 _immediateVestedPct,
        address _delegateRegistryV1,
        address _delegateRegistryV2
    ) {
        presale = Presale(_presale);
        diceNFT = _diceNFT;
        diceToken = _diceToken;
        vestingStart = _vestingStart;
        vestingEnd = _vestingEnd;
        immediateVestedPct = _immediateVestedPct;
        delegateRegistryV1 = IDelegationRegistry(_delegateRegistryV1);
        delegateRegistryV2 = IDelegateRegistry(_delegateRegistryV2);
    }

    /// @notice Claim vested tokens for contributed tokens.
    function claimContributed() external nonReentrant {
        (,, uint256 vestedTokens) = getVestedContributed();
        _claimContributed(vestedTokens);
    }

    /// @notice Claim vested tokens for contributed tokens.
    /// @dev The vestable tokens are calculated based on the total tokens contributed, plus Open phase allocation.
    /// @param _proof The merkle proof
    /// @param _ethAmount The Open phase allocation (ETH)
    /// @param _tokensAmount The Open phase allocation (tokens)
    function claimContributed(bytes32[] calldata _proof, uint256 _ethAmount, uint256 _tokensAmount)
        external
        nonReentrant
    {
        (,, uint256 vestedTokens) = getVestedContributed(_proof, _ethAmount, _tokensAmount);
        _claimContributed(vestedTokens);
    }

    /// @notice Claim vested tokens for a specific Dice NFT token.
    /// @param _proof The Merkle proof for the Dice NFT token.
    /// @param _tokenId The token ID to claim vested tokens for.
    /// @param _totalTokens The maximum amount of tokens to claim.
    function claimDiceNFT(bytes32[] calldata _proof, uint256 _tokenId, uint256 _totalTokens)
        external
        nonReentrant
    {
        _verifyTokenOwner(diceNFT, _tokenId);

        (, uint256 vestedTokens) = getVestedDiceNFT(_proof, _tokenId, _totalTokens);

        // Check if there is anything to claim
        if (vestedTokens == 0) {
            revert NoTokensToClaim();
        }

        // Update claimed
        diceNFTClaimed[_tokenId] += vestedTokens;

        // Transfer vested tokens
        IERC20(diceToken).transfer(msg.sender, vestedTokens);

        emit DiceNFTClaimed(msg.sender, _tokenId, vestedTokens);
    }

    /// @notice Get the vested tokens for contributed tokens.
    /// @return totalTokens The total amount of tokens contributed.
    /// @return claimedTokens The amount of tokens already claimed.
    /// @return vestedTokens The amount of vested tokens available for claiming.
    function getVestedContributed()
        public
        view
        returns (uint256 totalTokens, uint256 claimedTokens, uint256 vestedTokens)
    {
        totalTokens = presale.tokensEligible(msg.sender);
        claimedTokens = contributedClaimed[msg.sender];
        vestedTokens = _getVestedTokens(totalTokens, claimedTokens);
    }

    /// @notice Get the vested tokens for contributed tokens if included in Open phase allocations.
    /// @param _proof The merkle proof
    /// @param _ethAmount The Open phase allocation (ETH)
    /// @param _tokensAmount The Open phase allocation (tokens)
    /// @return totalTokens The total amount of tokens contributed.
    /// @return claimedTokens The amount of tokens already claimed.
    /// @return vestedTokens The amount of vested tokens available for claiming.
    function getVestedContributed(bytes32[] calldata _proof, uint256 _ethAmount, uint256 _tokensAmount)
        public
        view
        returns (uint256 totalTokens, uint256 claimedTokens, uint256 vestedTokens)
    {
        _verifyOpenTierAllocation(_proof, _ethAmount, _tokensAmount);
        totalTokens = presale.tokensEligible(msg.sender) + _tokensAmount;
        claimedTokens = contributedClaimed[msg.sender];
        vestedTokens = _getVestedTokens(totalTokens, claimedTokens);
    }

    /// @notice Get the vested tokens for a specific Dice NFT token.
    /// @param _proof The Merkle proof for the Dice NFT token.
    /// @param _tokenId The token ID to claim vested tokens for.
    /// @param _totalTokens The maximum amount of tokens to claim.
    /// @return claimedTokens The amount of tokens already claimed.
    /// @return vestedTokens The amount of vested tokens available for claiming.
    function getVestedDiceNFT(bytes32[] calldata _proof, uint256 _tokenId, uint256 _totalTokens)
        public
        view
        returns (uint256 claimedTokens, uint256 vestedTokens)
    {
        _verifyDiceNFTVesting(_proof, _tokenId, _totalTokens);
        claimedTokens = diceNFTClaimed[_tokenId];
        vestedTokens = _getVestedTokens(_totalTokens, diceNFTClaimed[_tokenId]);
    }

    /*==============================================================
                       INTERNAL FUNCTIONS
    ==============================================================*/

    /// @notice Claim vested tokens for contributed tokens.
    /// @param _vestedTokens The amount of vested tokens to claim.
    function _claimContributed(uint256 _vestedTokens) internal {
        // Check if there is anything to claim
        if (_vestedTokens == 0) {
            revert NoTokensToClaim();
        }

        // Update claimed
        contributedClaimed[msg.sender] += _vestedTokens;

        // Transfer vested tokens
        IERC20(diceToken).transfer(msg.sender, _vestedTokens);

        emit ContributedClaimed(msg.sender, _vestedTokens);
    }

    /// @notice Returns the amount of vested tokens available for claiming.
    /// @param _totalTokens The total amount of tokens vestable over time.
    /// @param _claimedTokens The amount of tokens already claimed.
    /// @return The amount of vested tokens available for claiming.
    function _getVestedTokens(uint256 _totalTokens, uint256 _claimedTokens) internal view returns (uint256) {
        uint256 immediateVested = _totalTokens * immediateVestedPct / 10_000;
        if (block.timestamp < vestingStart) {
            if (immediateVested < _claimedTokens) {
                return 0;
            }
            return immediateVested - _claimedTokens;
        }

        uint256 totalVestable = _totalTokens - immediateVested;
        uint256 timestamp = block.timestamp > vestingEnd ? vestingEnd : block.timestamp;
        uint256 totalVested = (timestamp - vestingStart) * totalVestable / (vestingEnd - vestingStart) + immediateVested;
        return totalVested - _claimedTokens;
    }

    /// @notice Verify the merkle proof
    /// @param _proof The merkle proof
    /// @param _tokenId The token ID to claim vested tokens for.
    /// @param _totalTokens The total amount of tokens vestable over time.
    function _verifyDiceNFTVesting(bytes32[] calldata _proof, uint256 _tokenId, uint256 _totalTokens) internal view {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(_tokenId, _totalTokens))));
        bytes32 root = presale.diceNFTsTokensEligibleMerkleRoot();
        if (!MerkleProof.verify(_proof, root, leaf)) {
            revert InvalidDiceNFTVestingProof();
        }
    }

    /// @notice Verify the merkle proof
    /// @param _proof The merkle proof
    /// @param _ethAmount The amount to verify
    /// @param _tokensAmount The amount to verify
    function _verifyOpenTierAllocation(bytes32[] calldata _proof, uint256 _ethAmount, uint256 _tokensAmount)
        internal
        view
    {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, _ethAmount, _tokensAmount))));
        bytes32 root = presale.openPhaseAllocationsMerkleRoot();
        if (!MerkleProof.verify(_proof, root, leaf)) {
            revert InvalidOpenTierAllocationsMerkleProof();
        }
    }

    /// @notice Verifies if the caller is the owner of a given token or a valid delegate.
    /// @param _collection The address of the collection contract.
    /// @param _tokenId The token ID to verify ownership or delegation for.
    function _verifyTokenOwner(address _collection, uint256 _tokenId) internal view {
        address _tokenOwner = IERC721(_collection).ownerOf(_tokenId);

        // Check sender is owner
        if (_tokenOwner == msg.sender) {
            return;
        }

        // Check with delegate registry v2
        if (delegateRegistryV2.checkDelegateForERC721(msg.sender, _tokenOwner, _collection, _tokenId, "")) {
            return;
        }

        // Check with delegate registry v1
        if (delegateRegistryV1.checkDelegateForToken(msg.sender, _tokenOwner, _collection, _tokenId)) {
            return;
        }

        // Revert if not owner or delegate
        revert NotTokenOwner(_collection, _tokenId);
    }

    /*==============================================================
                            EVENTS
    ==============================================================*/

    /// @notice Emitted when tokens are claimed for contributed tokens.
    /// @param claimer The address of the claimer.
    /// @param tokensAmount The amount of tokens claimed.
    event ContributedClaimed(address indexed claimer, uint256 indexed tokensAmount);

    /// @notice Emitted when tokens are claimed for a specific Dice NFT token.
    /// @param claimer The address of the claimer.
    /// @param tokenId The token ID claimed for.
    /// @param tokensAmount The amount of tokens claimed.
    event DiceNFTClaimed(address indexed claimer, uint256 indexed tokenId, uint256 indexed tokensAmount);

    /*==============================================================
                            ERRORS
    ==============================================================*/

    /// @notice Revert if there are no tokens to claim.
    error NoTokensToClaim();

    /// @notice Revert if the caller is not the owner of the token or a valid delegate.
    /// @param collection The address of the collection contract.
    /// @param tokenId The token ID to verify ownership or delegation for.
    error NotTokenOwner(address collection, uint256 tokenId);

    /// @notice Revert if the merkle proof is invalid.
    error InvalidDiceNFTVestingProof();

    /// @notice Revert if the merkle proof for Open phase allocations is invalid
    error InvalidOpenTierAllocationsMerkleProof();
}
IDelegateRegistry.sol 221 lines
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;

/**
 * @title IDelegateRegistry
 * @custom:version 2.0
 * @custom:author foobar (0xfoobar)
 * @notice A standalone immutable registry storing delegated permissions from one address to another
 */
interface IDelegateRegistry {
    /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
    enum DelegationType {
        NONE,
        ALL,
        CONTRACT,
        ERC721,
        ERC20,
        ERC1155
    }

    /// @notice Struct for returning delegations
    struct Delegation {
        DelegationType type_;
        address to;
        address from;
        bytes32 rights;
        address contract_;
        uint256 tokenId;
        uint256 amount;
    }

    /// @notice Emitted when an address delegates or revokes rights for their entire wallet
    event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for a contract address
    event DelegateContract(address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
    event DelegateERC721(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
    event DelegateERC20(address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount);

    /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
    event DelegateERC1155(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount);

    /// @notice Thrown if multicall calldata is malformed
    error MulticallFailed();

    /**
     * -----------  WRITE -----------
     */

    /**
     * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
     * @param data The encoded function data for each of the calls to make to this contract
     * @return results The results from each of the calls passed in via data
     */
    function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
     * @param to The address to act as delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address for the fungible token contract
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address of the contract that holds the token
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);

    /**
     * ----------- CHECKS -----------
     */

    /**
     * @notice Check if `to` is a delegate of `from` for the entire wallet
     * @param to The potential delegate address
     * @param from The potential address who delegated rights
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on the from's behalf
     */
    function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
     */
    function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param tokenId The token id for the token to delegating
     * @param from The wallet that issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
     */
    function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (bool);

    /**
     * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256);

    /**
     * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param tokenId The token id to check the delegated amount of
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256);

    /**
     * ----------- ENUMERATIONS -----------
     */

    /**
     * @notice Returns all enabled delegations a given delegate has received
     * @param to The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all enabled delegations an address has given out
     * @param from The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has received
     * @param to The address to retrieve incoming delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has given out
     * @param from The address to retrieve outgoing delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns the delegations for a given array of delegation hashes
     * @param delegationHashes is an array of hashes that correspond to delegations
     * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
     */
    function getDelegationsFromHashes(bytes32[] calldata delegationHashes) external view returns (Delegation[] memory delegations);

    /**
     * ----------- STORAGE ACCESS -----------
     */

    /**
     * @notice Allows external contracts to read arbitrary storage slots
     */
    function readSlot(bytes32 location) external view returns (bytes32);

    /**
     * @notice Allows external contracts to read an arbitrary array of storage slots
     */
    function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
}
IDelegationRegistry.sol 219 lines
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.17;

/**
 * @title An immutable registry contract to be deployed as a standalone primitive
 * @dev See EIP-5639, new project launches can read previous cold wallet -> hot wallet delegations
 * from here and integrate those permissions into their flow
 */
interface IDelegationRegistry {
    /// @notice Delegation type
    enum DelegationType {
        NONE,
        ALL,
        CONTRACT,
        TOKEN
    }

    /// @notice Info about a single delegation, used for onchain enumeration
    struct DelegationInfo {
        DelegationType type_;
        address vault;
        address delegate;
        address contract_;
        uint256 tokenId;
    }

    /// @notice Info about a single contract-level delegation
    struct ContractDelegation {
        address contract_;
        address delegate;
    }

    /// @notice Info about a single token-level delegation
    struct TokenDelegation {
        address contract_;
        uint256 tokenId;
        address delegate;
    }

    /// @notice Emitted when a user delegates their entire wallet
    event DelegateForAll(address vault, address delegate, bool value);

    /// @notice Emitted when a user delegates a specific contract
    event DelegateForContract(
        address vault,
        address delegate,
        address contract_,
        bool value
    );

    /// @notice Emitted when a user delegates a specific token
    event DelegateForToken(
        address vault,
        address delegate,
        address contract_,
        uint256 tokenId,
        bool value
    );

    /// @notice Emitted when a user revokes all delegations
    event RevokeAllDelegates(address vault);

    /// @notice Emitted when a user revoes all delegations for a given delegate
    event RevokeDelegate(address vault, address delegate);

    /**
     * -----------  WRITE -----------
     */

    /**
     * @notice Allow the delegate to act on your behalf for all contracts
     * @param delegate The hotwallet to act on your behalf
     * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
     */
    function delegateForAll(address delegate, bool value) external;

    /**
     * @notice Allow the delegate to act on your behalf for a specific contract
     * @param delegate The hotwallet to act on your behalf
     * @param contract_ The address for the contract you're delegating
     * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
     */
    function delegateForContract(
        address delegate,
        address contract_,
        bool value
    ) external;

    /**
     * @notice Allow the delegate to act on your behalf for a specific token
     * @param delegate The hotwallet to act on your behalf
     * @param contract_ The address for the contract you're delegating
     * @param tokenId The token id for the token you're delegating
     * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking
     */
    function delegateForToken(
        address delegate,
        address contract_,
        uint256 tokenId,
        bool value
    ) external;

    /**
     * @notice Revoke all delegates
     */
    function revokeAllDelegates() external;

    /**
     * @notice Revoke a specific delegate for all their permissions
     * @param delegate The hotwallet to revoke
     */
    function revokeDelegate(address delegate) external;

    /**
     * @notice Remove yourself as a delegate for a specific vault
     * @param vault The vault which delegated to the msg.sender, and should be removed
     */
    function revokeSelf(address vault) external;

    /**
     * -----------  READ -----------
     */

    /**
     * @notice Returns all active delegations a given delegate is able to claim on behalf of
     * @param delegate The delegate that you would like to retrieve delegations for
     * @return info Array of DelegationInfo structs
     */
    function getDelegationsByDelegate(
        address delegate
    ) external view returns (DelegationInfo[] memory);

    /**
     * @notice Returns an array of wallet-level delegates for a given vault
     * @param vault The cold wallet who issued the delegation
     * @return addresses Array of wallet-level delegates for a given vault
     */
    function getDelegatesForAll(
        address vault
    ) external view returns (address[] memory);

    /**
     * @notice Returns an array of contract-level delegates for a given vault and contract
     * @param vault The cold wallet who issued the delegation
     * @param contract_ The address for the contract you're delegating
     * @return addresses Array of contract-level delegates for a given vault and contract
     */
    function getDelegatesForContract(
        address vault,
        address contract_
    ) external view returns (address[] memory);

    /**
     * @notice Returns an array of contract-level delegates for a given vault's token
     * @param vault The cold wallet who issued the delegation
     * @param contract_ The address for the contract holding the token
     * @param tokenId The token id for the token you're delegating
     * @return addresses Array of contract-level delegates for a given vault's token
     */
    function getDelegatesForToken(
        address vault,
        address contract_,
        uint256 tokenId
    ) external view returns (address[] memory);

    /**
     * @notice Returns all contract-level delegations for a given vault
     * @param vault The cold wallet who issued the delegations
     * @return delegations Array of ContractDelegation structs
     */
    function getContractLevelDelegations(
        address vault
    ) external view returns (ContractDelegation[] memory delegations);

    /**
     * @notice Returns all token-level delegations for a given vault
     * @param vault The cold wallet who issued the delegations
     * @return delegations Array of TokenDelegation structs
     */
    function getTokenLevelDelegations(
        address vault
    ) external view returns (TokenDelegation[] memory delegations);

    /**
     * @notice Returns true if the address is delegated to act on the entire vault
     * @param delegate The hotwallet to act on your behalf
     * @param vault The cold wallet who issued the delegation
     */
    function checkDelegateForAll(
        address delegate,
        address vault
    ) external view returns (bool);

    /**
     * @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault
     * @param delegate The hotwallet to act on your behalf
     * @param contract_ The address for the contract you're delegating
     * @param vault The cold wallet who issued the delegation
     */
    function checkDelegateForContract(
        address delegate,
        address vault,
        address contract_
    ) external view returns (bool);

    /**
     * @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault
     * @param delegate The hotwallet to act on your behalf
     * @param contract_ The address for the contract you're delegating
     * @param tokenId The token id for the token you're delegating
     * @param vault The cold wallet who issued the delegation
     */
    function checkDelegateForToken(
        address delegate,
        address vault,
        address contract_,
        uint256 tokenId
    ) external view returns (bool);
}
IDelegateRegistry.sol 221 lines
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;

/**
 * @title IDelegateRegistry
 * @custom:version 2.0
 * @custom:author foobar (0xfoobar)
 * @notice A standalone immutable registry storing delegated permissions from one address to another
 */
interface IDelegateRegistry {
    /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
    enum DelegationType {
        NONE,
        ALL,
        CONTRACT,
        ERC721,
        ERC20,
        ERC1155
    }

    /// @notice Struct for returning delegations
    struct Delegation {
        DelegationType type_;
        address to;
        address from;
        bytes32 rights;
        address contract_;
        uint256 tokenId;
        uint256 amount;
    }

    /// @notice Emitted when an address delegates or revokes rights for their entire wallet
    event DelegateAll(address indexed from, address indexed to, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for a contract address
    event DelegateContract(address indexed from, address indexed to, address indexed contract_, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
    event DelegateERC721(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, bool enable);

    /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
    event DelegateERC20(address indexed from, address indexed to, address indexed contract_, bytes32 rights, uint256 amount);

    /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
    event DelegateERC1155(address indexed from, address indexed to, address indexed contract_, uint256 tokenId, bytes32 rights, uint256 amount);

    /// @notice Thrown if multicall calldata is malformed
    error MulticallFailed();

    /**
     * -----------  WRITE -----------
     */

    /**
     * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
     * @param data The encoded function data for each of the calls to make to this contract
     * @return results The results from each of the calls passed in via data
     */
    function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
     * @param to The address to act as delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateAll(address to, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateContract(address to, address contract_, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC721(address to, address contract_, uint256 tokenId, bytes32 rights, bool enable) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address for the fungible token contract
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC20(address to, address contract_, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address of the contract that holds the token
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC1155(address to, address contract_, uint256 tokenId, bytes32 rights, uint256 amount) external payable returns (bytes32 delegationHash);

    /**
     * ----------- CHECKS -----------
     */

    /**
     * @notice Check if `to` is a delegate of `from` for the entire wallet
     * @param to The potential delegate address
     * @param from The potential address who delegated rights
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on the from's behalf
     */
    function checkDelegateForAll(address to, address from, bytes32 rights) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
     */
    function checkDelegateForContract(address to, address from, address contract_, bytes32 rights) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param tokenId The token id for the token to delegating
     * @param from The wallet that issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
     */
    function checkDelegateForERC721(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (bool);

    /**
     * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights) external view returns (uint256);

    /**
     * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param tokenId The token id to check the delegated amount of
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC1155(address to, address from, address contract_, uint256 tokenId, bytes32 rights) external view returns (uint256);

    /**
     * ----------- ENUMERATIONS -----------
     */

    /**
     * @notice Returns all enabled delegations a given delegate has received
     * @param to The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all enabled delegations an address has given out
     * @param from The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getOutgoingDelegations(address from) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has received
     * @param to The address to retrieve incoming delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getIncomingDelegationHashes(address to) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has given out
     * @param from The address to retrieve outgoing delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getOutgoingDelegationHashes(address from) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns the delegations for a given array of delegation hashes
     * @param delegationHashes is an array of hashes that correspond to delegations
     * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
     */
    function getDelegationsFromHashes(bytes32[] calldata delegationHashes) external view returns (Delegation[] memory delegations);

    /**
     * ----------- STORAGE ACCESS -----------
     */

    /**
     * @notice Allows external contracts to read arbitrary storage slots
     */
    function readSlot(bytes32 location) external view returns (bytes32);

    /**
     * @notice Allows external contracts to read an arbitrary array of storage slots
     */
    function readSlots(bytes32[] calldata locations) external view returns (bytes32[] memory);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

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

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
Strings.sol 94 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

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

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

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

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

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

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

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
Pausable.sol 119 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
Math.sol 415 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
            // works in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
     * towards zero.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
Ownable2Step.sol 59 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
IERC721.sol 135 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
ReentrancyGuard.sol 84 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

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

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

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

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

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

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

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

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
SignedMath.sol 43 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}
ECDSA.sol 174 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError, bytes32) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
MerkleProof.sol 232 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.20;

/**
 * @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.
 */
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.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     */
    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 leafs & pre-images are assumed to be sorted.
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(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}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    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.
     *
     * 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).
     */
    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 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 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[](totalHashes);
        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 < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

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

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
     */
    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 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        if (leavesLen + proofLen != totalHashes + 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[](totalHashes);
        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 < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

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

    /**
     * @dev Sorts the pair (a, b) and hashes the result.
     */
    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    /**
     * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
     */
    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}
MessageHashUtils.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
 *
 * The library provides methods for generating a hash of a message that conforms to the
 * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
 * specifications.
 */
library MessageHashUtils {
    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing a bytes32 `messageHash` with
     * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
     * keccak256, although any bytes32 value can be safely used because the final digest will
     * be re-hashed.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
            mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
            digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
        }
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x45` (`personal_sign` messages).
     *
     * The digest is calculated by prefixing an arbitrary `message` with
     * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
     * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
     *
     * See {ECDSA-recover}.
     */
    function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
        return
            keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-191 signed data with version
     * `0x00` (data with intended validator).
     *
     * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
     * `validator` address. Then hashing the result.
     *
     * See {ECDSA-recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(hex"19_00", validator, data));
    }

    /**
     * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
     *
     * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
     * `\x19\x01` and hashing the result. It corresponds to the hash signed by the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
     *
     * See {ECDSA-recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, hex"19_01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            digest := keccak256(ptr, 0x42)
        }
    }
}

Read Contract

contributedClaimed 0x86f3b7ae → uint256
delegateRegistryV1 0x856ed8c9 → address
delegateRegistryV2 0xdb584d25 → address
diceNFT 0x94483cfb → address
diceNFTClaimed 0x9588968e → uint256
diceToken 0x8ab9ff71 → address
getVestedContributed 0xd3580ad8 → uint256, uint256, uint256
getVestedContributed 0xececf4ef → uint256, uint256, uint256
getVestedDiceNFT 0xeb00b76e → uint256, uint256
immediateVestedPct 0xe06e6cd8 → uint256
presale 0xfdea8e0b → address
vestingEnd 0x84a1931f → uint256
vestingStart 0x254800d4 → uint256

Write Contract 3 functions

These functions modify contract state and require a wallet transaction to execute.

claimContributed 0x30b90b2c
No parameters
claimContributed 0x7921f9d4
bytes32[] _proof
uint256 _ethAmount
uint256 _tokensAmount
claimDiceNFT 0x4b88904c
bytes32[] _proof
uint256 _tokenId
uint256 _totalTokens

Recent Transactions

No transactions found for this address