ETH Price: $3,901.18 (+0.44%)

Contract

0x23C886396CFbaDB0F3bAC4b728150e8A59dC0E10
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
InitialETHCrowdfund

Compiler Version
v0.8.20+commit.a1b79de6

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 35 : InitialETHCrowdfund.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "./ETHCrowdfundBase.sol";
import "../utils/LibAddress.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../tokens/ERC721Receiver.sol";
import "../party/Party.sol";
import "../crowdfund/Crowdfund.sol";
import "../gatekeepers/IGateKeeper.sol";

/// @notice A crowdfund for raising the initial funds for new parties.
///         Unlike other crowdfunds that are started for the purpose of
///         acquiring NFT(s), this crowdfund simply bootstraps a party with
///         funds and lets its members coordinate on what to do with it after.
contract InitialETHCrowdfund is ETHCrowdfundBase {
    using LibRawResult for bytes;
    using LibSafeCast for uint256;
    using LibAddress for address payable;

    // Options to be passed into `initialize()` when the crowdfund is created.
    struct InitialETHCrowdfundOptions {
        address payable initialContributor;
        address initialDelegate;
        uint96 minContribution;
        uint96 maxContribution;
        bool disableContributingForExistingCard;
        uint96 minTotalContributions;
        uint96 maxTotalContributions;
        uint16 exchangeRateBps;
        uint16 fundingSplitBps;
        address payable fundingSplitRecipient;
        uint40 duration;
        IGateKeeper gateKeeper;
        bytes12 gateKeeperId;
    }

    struct ETHPartyOptions {
        string name;
        string symbol;
        uint256 customizationPresetId;
        Crowdfund.FixedGovernanceOpts governanceOpts;
        ProposalStorage.ProposalEngineOpts proposalEngineOpts;
        IERC721[] preciousTokens;
        uint256[] preciousTokenIds;
        uint40 rageQuitTimestamp;
    }

    struct BatchContributeArgs {
        // IDs of cards to credit the contributions to. When set to 0, it means
        uint256[] tokenIds;
        // The address to which voting power will be delegated for all contributions.
        address delegate;
        // The contribution amounts in wei. The length of this array must be
        // equal to the length of `tokenIds`.
        uint96[] values;
        // The data required to be validated by the `gatekeeper`, if set. If no
        // `gatekeeper` is set, this can be empty.
        bytes[] gateDatas;
    }

    struct BatchContributeForArgs {
        // IDs of cards to credit the contributions to. When set to 0, it means
        // a new one should be minted.
        uint256[] tokenIds;
        // Addresses of to credit the contributions under. Each contribution
        // amount in `values` corresponds to a recipient in this array.
        address payable[] recipients;
        // The delegate to set for each recipient if they have not delegated
        // before.
        address[] initialDelegates;
        // The contribution amounts in wei. The length of this array must be
        // equal to the length of `recipients`.
        uint96[] values;
        // The data required to be validated by the `gatekeeper`, if set. If no
        // `gatekeeper` is set, this can be empty.
        bytes[] gateDatas;
        // Whether to revert if any individual contribution fails or continue.
        bool revertOnFailure;
    }

    event Refunded(address indexed contributor, uint256 indexed tokenId, uint256 amount);

    // Set the `Globals` contract.
    constructor(IGlobals globals) ETHCrowdfundBase(globals) {}

    /// @notice Initializer to be delegatecalled by `Proxy` constructor. Will
    ///         revert if called outside the constructor.
    /// @param crowdfundOpts Options to initialize the crowdfund with.
    /// @param partyOpts Options to initialize the party with.
    function initialize(
        InitialETHCrowdfundOptions memory crowdfundOpts,
        ETHPartyOptions memory partyOpts
    ) external payable onlyConstructor {
        // Create party the initial crowdfund will be for.
        Party party_ = _createParty(partyOpts);

        // Initialize the crowdfund.
        _initialize(
            ETHCrowdfundOptions({
                party: party_,
                initialContributor: crowdfundOpts.initialContributor,
                initialDelegate: crowdfundOpts.initialDelegate,
                minContribution: crowdfundOpts.minContribution,
                maxContribution: crowdfundOpts.maxContribution,
                disableContributingForExistingCard: crowdfundOpts
                    .disableContributingForExistingCard,
                minTotalContributions: crowdfundOpts.minTotalContributions,
                maxTotalContributions: crowdfundOpts.maxTotalContributions,
                exchangeRateBps: crowdfundOpts.exchangeRateBps,
                fundingSplitBps: crowdfundOpts.fundingSplitBps,
                fundingSplitRecipient: crowdfundOpts.fundingSplitRecipient,
                duration: crowdfundOpts.duration,
                gateKeeper: crowdfundOpts.gateKeeper,
                gateKeeperId: crowdfundOpts.gateKeeperId
            })
        );

        // If the deployer passed in some ETH during deployment, credit them
        // for the initial contribution.
        uint96 initialContribution = msg.value.safeCastUint256ToUint96();
        if (initialContribution > 0) {
            // If this contract has ETH, either passed in during deployment or
            // pre-existing, credit it to the `initialContributor`.
            _contribute(
                crowdfundOpts.initialContributor,
                crowdfundOpts.initialDelegate,
                initialContribution,
                0,
                ""
            );
        }

        // Set up gatekeeper after initial contribution (initial always gets in).
        gateKeeper = crowdfundOpts.gateKeeper;
        gateKeeperId = crowdfundOpts.gateKeeperId;
    }

    /// @notice Contribute ETH to this crowdfund on behalf of a contributor.
    /// @param delegate The address to which voting power will be delegated to
    ///                 during the governance phase.
    /// @param gateData Data to pass to the gatekeeper to prove eligibility.
    /// @return votingPower The voting power the contributor receives for their
    ///                     contribution.
    function contribute(
        address delegate,
        bytes memory gateData
    ) public payable onlyDelegateCall returns (uint96 votingPower) {
        return
            _contribute(
                payable(msg.sender),
                delegate,
                msg.value.safeCastUint256ToUint96(),
                0, // Mint a new party card for the contributor.
                gateData
            );
    }

    /// @notice Contribute ETH to this crowdfund on behalf of a contributor.
    /// @param tokenId The ID of the card the contribution is being made towards.
    /// @param delegate The address to which voting power will be delegated to
    ///                 during the governance phase.
    /// @param gateData Data to pass to the gatekeeper to prove eligibility.
    /// @return votingPower The voting power the contributor receives for their
    ///                     contribution.
    function contribute(
        uint256 tokenId,
        address delegate,
        bytes memory gateData
    ) public payable onlyDelegateCall returns (uint96 votingPower) {
        return
            _contribute(
                payable(msg.sender),
                delegate,
                msg.value.safeCastUint256ToUint96(),
                tokenId,
                gateData
            );
    }

    /// @notice `contribute()` in batch form.
    ///         May not revert if any individual contribution fails.
    /// @param args The arguments to pass to each `contribute()` call.
    /// @return votingPowers The voting power received for each contribution.
    function batchContribute(
        BatchContributeArgs calldata args
    ) external payable onlyDelegateCall returns (uint96[] memory votingPowers) {
        uint256 numContributions = args.tokenIds.length;
        votingPowers = new uint96[](numContributions);

        uint256 ethAvailable = msg.value;
        for (uint256 i; i < numContributions; ++i) {
            ethAvailable -= args.values[i];

            votingPowers[i] = _contribute(
                payable(msg.sender),
                args.delegate,
                args.values[i],
                args.tokenIds[i],
                args.gateDatas[i]
            );
        }

        // Refund any unused ETH.
        if (ethAvailable > 0) payable(msg.sender).transfer(ethAvailable);
    }

    /// @notice Contribute to this crowdfund on behalf of another address.
    /// @param tokenId The ID of the token to credit the contribution to, or
    ///                zero to mint a new party card for the recipient
    /// @param recipient The address to record the contribution under
    /// @param initialDelegate The address to delegate to for the governance
    ///                        phase if recipient hasn't delegated
    /// @param gateData Data to pass to the gatekeeper to prove eligibility
    /// @return votingPower The voting power received for the contribution
    function contributeFor(
        uint256 tokenId,
        address payable recipient,
        address initialDelegate,
        bytes memory gateData
    ) external payable onlyDelegateCall returns (uint96 votingPower) {
        return
            _contribute(
                recipient,
                initialDelegate,
                msg.value.safeCastUint256ToUint96(),
                tokenId,
                gateData
            );
    }

    /// @notice `contributeFor()` in batch form.
    ///         May not revert if any individual contribution fails.
    /// @param args The arguments for the batched `contributeFor()` calls.
    /// @return votingPowers The voting power received for each contribution.
    function batchContributeFor(
        BatchContributeForArgs calldata args
    ) external payable onlyDelegateCall returns (uint96[] memory votingPowers) {
        uint256 numContributions = args.recipients.length;
        votingPowers = new uint96[](numContributions);

        uint256 ethAvailable = msg.value;
        for (uint256 i; i < numContributions; ++i) {
            (bool s, bytes memory r) = address(this).call{ value: args.values[i] }(
                abi.encodeCall(
                    this.contributeFor,
                    (
                        args.tokenIds[i],
                        args.recipients[i],
                        args.initialDelegates[i],
                        args.gateDatas[i]
                    )
                )
            );

            if (!s) {
                if (args.revertOnFailure) {
                    r.rawRevert();
                }
            } else {
                votingPowers[i] = abi.decode(r, (uint96));
                ethAvailable -= args.values[i];
            }
        }

        // Refund any unused ETH.
        if (ethAvailable > 0) payable(msg.sender).transfer(ethAvailable);
    }

    function _contribute(
        address payable contributor,
        address delegate,
        uint96 amount,
        uint256 tokenId,
        bytes memory gateData
    ) private returns (uint96 votingPower) {
        // Require a non-null delegate.
        if (delegate == address(0)) {
            revert InvalidDelegateError();
        }

        // Must not be blocked by gatekeeper.
        IGateKeeper _gateKeeper = gateKeeper;
        if (_gateKeeper != IGateKeeper(address(0))) {
            if (!_gateKeeper.isAllowed(msg.sender, gateKeeperId, gateData)) {
                revert NotAllowedByGateKeeperError(
                    contributor,
                    _gateKeeper,
                    gateKeeperId,
                    gateData
                );
            }
        }

        votingPower = _processContribution(contributor, delegate, amount);

        // OK to contribute with zero just to update delegate.
        if (amount == 0) return 0;

        if (tokenId == 0) {
            // Mint contributor a new party card.
            party.mint(contributor, votingPower, delegate);
        } else if (disableContributingForExistingCard) {
            revert ContributingForExistingCardDisabledError();
        } else if (party.ownerOf(tokenId) == contributor) {
            // Increase voting power of contributor's existing party card.
            party.addVotingPower(tokenId, votingPower);
        } else {
            revert NotOwnerError(tokenId);
        }
    }

    /// @notice Refund the owner of a party card and burn it. Only available if
    ///         the crowdfund lost. Can be called to refund for self or on
    ///         another's behalf.
    /// @param tokenId The ID of the party card to refund the owner of then burn.
    /// @return amount The amount of ETH refunded to the contributor.
    function refund(uint256 tokenId) external returns (uint96 amount) {
        // Check crowdfund lifecycle.
        {
            CrowdfundLifecycle lc = getCrowdfundLifecycle();
            if (lc != CrowdfundLifecycle.Lost) {
                revert WrongLifecycleError(lc);
            }
        }

        // Get amount to refund.
        uint96 votingPower = party.votingPowerByTokenId(tokenId).safeCastUint256ToUint96();
        amount = convertVotingPowerToContribution(votingPower);

        if (amount > 0) {
            // Get contributor to refund.
            address payable contributor = payable(party.ownerOf(tokenId));

            // Burn contributor's party card.
            party.burn(tokenId);

            // Refund contributor.
            contributor.transferEth(amount);

            emit Refunded(contributor, tokenId, amount);
        }
    }

    /// @notice `refund()` in batch form.
    ///         May not revert if any individual refund fails.
    /// @param tokenIds The IDs of the party cards to burn and refund the owners of.
    /// @param revertOnFailure If true, revert if any refund fails.
    /// @return amounts The amounts of ETH refunded for each refund.
    function batchRefund(
        uint256[] calldata tokenIds,
        bool revertOnFailure
    ) external returns (uint96[] memory amounts) {
        uint256 numRefunds = tokenIds.length;
        amounts = new uint96[](numRefunds);

        for (uint256 i; i < numRefunds; ++i) {
            (bool s, bytes memory r) = address(this).call(
                abi.encodeCall(this.refund, (tokenIds[i]))
            );

            if (!s) {
                if (revertOnFailure) {
                    r.rawRevert();
                }
            } else {
                amounts[i] = abi.decode(r, (uint96));
            }
        }
    }

    function _createParty(ETHPartyOptions memory opts) private returns (Party) {
        address[] memory authorities = new address[](1);
        authorities[0] = address(this);

        return
            opts.governanceOpts.partyFactory.createParty(
                opts.governanceOpts.partyImpl,
                authorities,
                Party.PartyOptions({
                    name: opts.name,
                    symbol: opts.symbol,
                    customizationPresetId: opts.customizationPresetId,
                    governance: PartyGovernance.GovernanceOpts({
                        hosts: opts.governanceOpts.hosts,
                        voteDuration: opts.governanceOpts.voteDuration,
                        executionDelay: opts.governanceOpts.executionDelay,
                        passThresholdBps: opts.governanceOpts.passThresholdBps,
                        totalVotingPower: 0,
                        feeBps: opts.governanceOpts.feeBps,
                        feeRecipient: opts.governanceOpts.feeRecipient
                    }),
                    proposalEngine: opts.proposalEngineOpts
                }),
                opts.preciousTokens,
                opts.preciousTokenIds,
                opts.rageQuitTimestamp
            );
    }
}

File 2 of 35 : ETHCrowdfundBase.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/LibAddress.sol";
import "../utils/LibSafeCast.sol";
import "../party/Party.sol";
import "../gatekeepers/IGateKeeper.sol";

contract ETHCrowdfundBase is Implementation {
    using LibRawResult for bytes;
    using LibSafeCast for uint256;
    using LibAddress for address payable;

    enum CrowdfundLifecycle {
        // In practice, this state is never used. If the crowdfund is ever in
        // this stage, something is wrong (e.g. crowdfund was never initialized).
        Invalid,
        // Ready to accept contributions to reach contribution targets
        // until a deadline or the minimum contribution target is reached and
        // host finalizes.
        Active,
        // Expired and the minimum contribution target was not reached.
        Lost,
        // The crowdfund has expired and reached the minimum contribution
        // target. It is now ready to finalize.
        Won,
        // A won crowdfund has been finalized, with funds transferred to the
        // party and voting power successfully updated.
        Finalized
    }

    // Options to be passed into `initialize()` when the crowdfund is created.
    struct ETHCrowdfundOptions {
        Party party;
        address payable initialContributor;
        address initialDelegate;
        uint96 minContribution;
        uint96 maxContribution;
        bool disableContributingForExistingCard;
        uint96 minTotalContributions;
        uint96 maxTotalContributions;
        uint16 exchangeRateBps;
        uint16 fundingSplitBps;
        address payable fundingSplitRecipient;
        uint40 duration;
        IGateKeeper gateKeeper;
        bytes12 gateKeeperId;
    }

    error WrongLifecycleError(CrowdfundLifecycle lc);
    error NotAllowedByGateKeeperError(
        address contributor,
        IGateKeeper gateKeeper,
        bytes12 gateKeeperId,
        bytes gateData
    );
    error OnlyPartyHostError();
    error OnlyPartyDaoError(address notDao);
    error OnlyPartyDaoOrHostError(address notDao);
    error NotOwnerError(uint256 tokenId);
    error OnlyWhenEmergencyActionsAllowedError();
    error InvalidDelegateError();
    error NotEnoughContributionsError(uint96 totalContribution, uint96 minTotalContributions);
    error MinGreaterThanMaxError(uint96 min, uint96 max);
    error MaxTotalContributionsCannotBeZeroError(uint96 maxTotalContributions);
    error BelowMinimumContributionsError(uint96 contributions, uint96 minContributions);
    error AboveMaximumContributionsError(uint96 contributions, uint96 maxContributions);
    error InvalidExchangeRateError(uint16 exchangeRateBps);
    error ContributingForExistingCardDisabledError();
    error ZeroVotingPowerError();
    error FundingSplitAlreadyPaidError();
    error FundingSplitNotConfiguredError();

    event Contributed(
        address indexed sender,
        address indexed contributor,
        uint256 amount,
        address delegate
    );
    event Finalized();
    event FundingSplitSent(address indexed fundingSplitRecipient, uint256 amount);
    event EmergencyExecuteDisabled();
    event EmergencyExecute(address target, bytes data, uint256 amountEth);

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice The address of the `Party` contract instance associated
    ///         with the crowdfund.
    Party public party;
    /// @notice The minimum amount of ETH that a contributor can send to
    ///         participate in the crowdfund.
    uint96 public minContribution;
    /// @notice The maximum amount of ETH that a contributor can send to
    ///         participate in the crowdfund per address.
    uint96 public maxContribution;
    /// @notice A boolean flag that determines whether contributors are allowed
    ///         to increase the voting power of their existing party cards.
    bool public disableContributingForExistingCard;
    /// @notice Whether the funding split has been claimed by the funding split
    ///         recipient.
    bool public fundingSplitPaid;
    /// @notice Whether the DAO has emergency powers for this crowdfund.
    bool public emergencyExecuteDisabled;
    /// @notice The minimum amount of total ETH contributions required for the
    ///         crowdfund to be considered successful.
    uint96 public minTotalContributions;
    /// @notice The maximum amount of total ETH contributions allowed for the
    ///         crowdfund.
    uint96 public maxTotalContributions;
    /// @notice The total amount of ETH contributed to the crowdfund so far.
    uint96 public totalContributions;
    /// @notice The timestamp at which the crowdfund will end or ended. If 0, the
    ///         crowdfund has finalized.
    uint40 public expiry;
    /// @notice The exchange rate to use for converting ETH contributions to
    ///         voting power in basis points (e.g. 10000 = 1:1).
    uint16 public exchangeRateBps;
    /// @notice The portion of contributions to send to the funding recipient in
    ///         basis points (e.g. 100 = 1%).
    uint16 public fundingSplitBps;
    /// @notice The address to which a portion of the contributions is sent to.
    address payable public fundingSplitRecipient;
    /// @notice The gatekeeper contract used to restrict who can contribute to the party.
    IGateKeeper public gateKeeper;
    /// @notice The ID of the gatekeeper to use for restricting contributions to the party.
    bytes12 public gateKeeperId;
    /// @notice The address a contributor is delegating their voting power to.
    mapping(address => address) public delegationsByContributor;

    // Set the `Globals` contract.
    constructor(IGlobals globals) {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts, credit initial contribution (if
    // any), and setup gatekeeper.
    function _initialize(ETHCrowdfundOptions memory opts) internal {
        // Set the minimum and maximum contribution amounts.
        if (opts.minContribution > opts.maxContribution) {
            revert MinGreaterThanMaxError(opts.minContribution, opts.maxContribution);
        }
        minContribution = opts.minContribution;
        maxContribution = opts.maxContribution;
        // Set the min total contributions.
        if (opts.minTotalContributions > opts.maxTotalContributions) {
            revert MinGreaterThanMaxError(opts.minTotalContributions, opts.maxTotalContributions);
        }
        minTotalContributions = opts.minTotalContributions;
        // Set the max total contributions.
        if (opts.maxTotalContributions == 0) {
            // Prevent this because when `maxTotalContributions` is 0 the
            // crowdfund is invalid in `getCrowdfundLifecycle()` meaning it has
            // never been initialized.
            revert MaxTotalContributionsCannotBeZeroError(opts.maxTotalContributions);
        }
        maxTotalContributions = opts.maxTotalContributions;
        // Set the party crowdfund is for.
        party = opts.party;
        // Set the crowdfund start and end timestamps.
        expiry = uint40(block.timestamp + opts.duration);
        // Set the exchange rate.
        if (opts.exchangeRateBps == 0) revert InvalidExchangeRateError(opts.exchangeRateBps);
        exchangeRateBps = opts.exchangeRateBps;
        // Set the funding split and its recipient.
        fundingSplitBps = opts.fundingSplitBps;
        fundingSplitRecipient = opts.fundingSplitRecipient;
        // Set whether to disable contributing for existing card.
        disableContributingForExistingCard = opts.disableContributingForExistingCard;
    }

    /// @notice Get the current lifecycle of the crowdfund.
    function getCrowdfundLifecycle() public view returns (CrowdfundLifecycle lifecycle) {
        if (maxTotalContributions == 0) {
            return CrowdfundLifecycle.Invalid;
        }

        uint256 expiry_ = expiry;
        if (expiry_ == 0) {
            return CrowdfundLifecycle.Finalized;
        }

        if (block.timestamp >= expiry_) {
            if (totalContributions >= minTotalContributions) {
                return CrowdfundLifecycle.Won;
            } else {
                return CrowdfundLifecycle.Lost;
            }
        }

        return CrowdfundLifecycle.Active;
    }

    function _processContribution(
        address payable contributor,
        address delegate,
        uint96 amount
    ) internal returns (uint96 votingPower) {
        address oldDelegate = delegationsByContributor[contributor];
        if (msg.sender == contributor || oldDelegate == address(0)) {
            // Update delegate.
            delegationsByContributor[contributor] = delegate;
        } else {
            // Prevent changing another's delegate if already delegated.
            delegate = oldDelegate;
        }

        emit Contributed(msg.sender, contributor, amount, delegate);

        // OK to contribute with zero just to update delegate.
        if (amount == 0) return 0;

        // Only allow contributions while the crowdfund is active.
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc != CrowdfundLifecycle.Active) {
            revert WrongLifecycleError(lc);
        }

        // Check that the contribution amount is at or below the maximum.
        uint96 maxContribution_ = maxContribution;
        if (amount > maxContribution_) {
            revert AboveMaximumContributionsError(amount, maxContribution_);
        }

        uint96 newTotalContributions = totalContributions + amount;
        uint96 maxTotalContributions_ = maxTotalContributions;
        if (newTotalContributions >= maxTotalContributions_) {
            totalContributions = maxTotalContributions_;

            // Finalize the crowdfund.
            // This occurs before refunding excess contribution to act as a
            // reentrancy guard.
            _finalize(maxTotalContributions_);

            // Refund excess contribution.
            uint96 refundAmount = newTotalContributions - maxTotalContributions;
            if (refundAmount > 0) {
                amount -= refundAmount;
                payable(msg.sender).transferEth(refundAmount);
            }
        } else {
            totalContributions = newTotalContributions;
        }

        // Check that the contribution amount is at or above the minimum. This
        // is done after `amount` is potentially reduced if refunding excess
        // contribution. There is a case where this prevents a crowdfunds from
        // reaching `maxTotalContributions` if the `minContribution` is greater
        // than the difference between `maxTotalContributions` and the current
        // `totalContributions`. In this scenario users will have to wait until
        // the crowdfund expires or a host finalizes after
        // `minTotalContribution` has been reached by calling `finalize()`.
        uint96 minContribution_ = minContribution;
        if (amount < minContribution_) {
            revert BelowMinimumContributionsError(amount, minContribution_);
        }

        // Subtract fee from contribution amount if applicable.
        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
            // Removes funding split from contribution amount in a way that
            // avoids rounding errors for very small contributions <1e4 wei.
            amount = (amount * (1e4 - fundingSplitBps_)) / 1e4;
        }

        // Calculate voting power.
        votingPower = (amount * exchangeRateBps) / 1e4;

        if (votingPower == 0) revert ZeroVotingPowerError();
    }

    /// @notice Calculate the contribution amount from the given voting power.
    /// @param votingPower The voting power to convert to a contribution amount.
    /// @return amount The contribution amount.
    function convertVotingPowerToContribution(
        uint96 votingPower
    ) public view returns (uint96 amount) {
        amount = (votingPower * 1e4) / exchangeRateBps;

        // Add back funding split to contribution amount if applicable.
        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
            amount = (amount * 1e4) / (1e4 - fundingSplitBps_);
        }
    }

    function finalize() external {
        uint96 totalContributions_ = totalContributions;

        // Check that the crowdfund is not already finalized.
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc == CrowdfundLifecycle.Active) {
            // Allow host to finalize crowdfund early if it has reached its minimum goal.
            if (!party.isHost(msg.sender)) revert OnlyPartyHostError();

            // Check that the crowdfund has reached its minimum goal.
            uint96 minTotalContributions_ = minTotalContributions;
            if (totalContributions_ < minTotalContributions_) {
                revert NotEnoughContributionsError(totalContributions_, minTotalContributions_);
            }
        } else {
            // Otherwise only allow finalization if the crowdfund has expired
            // and been won. Can be finalized by anyone.
            if (lc != CrowdfundLifecycle.Won) {
                revert WrongLifecycleError(lc);
            }
        }

        // Finalize the crowdfund.
        _finalize(totalContributions_);
    }

    function _finalize(uint96 totalContributions_) internal {
        // Finalize the crowdfund.
        delete expiry;

        // Transfer funding split to recipient if applicable.
        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ != address(0) && fundingSplitBps_ > 0) {
            totalContributions_ -= (totalContributions_ * fundingSplitBps_) / 1e4;
        }

        // Update the party's total voting power.
        uint96 newVotingPower = (totalContributions_ * exchangeRateBps) / 1e4;
        party.increaseTotalVotingPower(newVotingPower);

        // Transfer ETH to the party.
        payable(address(party)).transferEth(totalContributions_);

        emit Finalized();
    }

    /// @notice Send the funding split to the recipient if applicable.
    function sendFundingSplit() external returns (uint96 splitAmount) {
        // Check that the crowdfund is finalized.
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc != CrowdfundLifecycle.Finalized) revert WrongLifecycleError(lc);

        if (fundingSplitPaid) revert FundingSplitAlreadyPaidError();

        address payable fundingSplitRecipient_ = fundingSplitRecipient;
        uint16 fundingSplitBps_ = fundingSplitBps;
        if (fundingSplitRecipient_ == address(0) || fundingSplitBps_ == 0) {
            revert FundingSplitNotConfiguredError();
        }

        fundingSplitPaid = true;

        // Transfer funding split to recipient.
        splitAmount = (totalContributions * fundingSplitBps_) / 1e4;
        payable(fundingSplitRecipient_).transferEth(splitAmount);

        emit FundingSplitSent(fundingSplitRecipient_, splitAmount);
    }

    /// @notice As the DAO, execute an arbitrary function call from this contract.
    /// @dev Emergency actions must not be revoked for this to work.
    /// @param targetAddress The contract to call.
    /// @param targetCallData The data to pass to the contract.
    /// @param amountEth The amount of ETH to send to the contract.
    function emergencyExecute(
        address targetAddress,
        bytes calldata targetCallData,
        uint256 amountEth
    ) external payable {
        // Must be called by the DAO.
        if (_GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET) != msg.sender) {
            revert OnlyPartyDaoError(msg.sender);
        }
        // Must not be disabled by DAO or host.
        if (emergencyExecuteDisabled) {
            revert OnlyWhenEmergencyActionsAllowedError();
        }
        (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData);
        if (!success) {
            res.rawRevert();
        }
        emit EmergencyExecute(targetAddress, targetCallData, amountEth);
    }

    /// @notice Revoke the DAO's ability to call emergencyExecute().
    /// @dev Either the DAO or the party host can call this.
    function disableEmergencyExecute() external {
        // Only the DAO or a host can call this.
        if (
            !party.isHost(msg.sender) &&
            _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET) != msg.sender
        ) {
            revert OnlyPartyDaoOrHostError(msg.sender);
        }
        emergencyExecuteDisabled = true;
        emit EmergencyExecuteDisabled();
    }
}

File 3 of 35 : LibAddress.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibAddress {
    error EthTransferFailed(address receiver, bytes errData);

    // Transfer ETH with full gas stipend.
    function transferEth(address payable receiver, uint256 amount) internal {
        if (amount == 0) return;

        (bool s, bytes memory r) = receiver.call{ value: amount }("");
        if (!s) {
            revert EthTransferFailed(receiver, r);
        }
    }
}

File 4 of 35 : LibRawResult.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibRawResult {
    // Revert with the data in `b`.
    function rawRevert(bytes memory b) internal pure {
        assembly {
            revert(add(b, 32), mload(b))
        }
    }

    // Return with the data in `b`.
    function rawReturn(bytes memory b) internal pure {
        assembly {
            return(add(b, 32), mload(b))
        }
    }
}

File 5 of 35 : LibSafeCast.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

library LibSafeCast {
    error Uint256ToUint96CastOutOfRange(uint256 v);
    error Uint256ToInt192CastOutOfRange(uint256 v);
    error Int192ToUint96CastOutOfRange(int192 i192);
    error Uint256ToInt128CastOutOfRangeError(uint256 u256);
    error Uint256ToUint128CastOutOfRangeError(uint256 u256);
    error Uint256ToUint40CastOutOfRangeError(uint256 u256);

    function safeCastUint256ToUint96(uint256 v) internal pure returns (uint96) {
        if (v > uint256(type(uint96).max)) {
            revert Uint256ToUint96CastOutOfRange(v);
        }
        return uint96(v);
    }

    function safeCastUint256ToUint128(uint256 v) internal pure returns (uint128) {
        if (v > uint256(type(uint128).max)) {
            revert Uint256ToUint128CastOutOfRangeError(v);
        }
        return uint128(v);
    }

    function safeCastUint256ToInt192(uint256 v) internal pure returns (int192) {
        if (v > uint256(uint192(type(int192).max))) {
            revert Uint256ToInt192CastOutOfRange(v);
        }
        return int192(uint192(v));
    }

    function safeCastUint96ToInt192(uint96 v) internal pure returns (int192) {
        return int192(uint192(v));
    }

    function safeCastInt192ToUint96(int192 i192) internal pure returns (uint96) {
        if (i192 < 0 || i192 > int192(uint192(type(uint96).max))) {
            revert Int192ToUint96CastOutOfRange(i192);
        }
        return uint96(uint192(i192));
    }

    function safeCastUint256ToInt128(uint256 x) internal pure returns (int128) {
        if (x > uint256(uint128(type(int128).max))) {
            revert Uint256ToInt128CastOutOfRangeError(x);
        }
        return int128(uint128(x));
    }

    function safeCastUint256ToUint40(uint256 x) internal pure returns (uint40) {
        if (x > uint256(type(uint40).max)) {
            revert Uint256ToUint40CastOutOfRangeError(x);
        }
        return uint40(x);
    }
}

File 6 of 35 : ERC721Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

import "./IERC721Receiver.sol";
import "../utils/EIP165.sol";
import "../vendor/solmate/ERC721.sol";

/// @notice Mixin for contracts that want to receive ERC721 tokens.
/// @dev Use this instead of solmate's ERC721TokenReceiver because the
///      compiler has issues when overriding EIP165/IERC721Receiver functions.
abstract contract ERC721Receiver is IERC721Receiver, EIP165, ERC721TokenReceiver {
    /// @inheritdoc IERC721Receiver
    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) public virtual override(IERC721Receiver, ERC721TokenReceiver) returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    /// @inheritdoc EIP165
    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        return
            EIP165.supportsInterface(interfaceId) ||
            interfaceId == type(IERC721Receiver).interfaceId;
    }
}

File 7 of 35 : Party.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

import "./PartyGovernanceNFT.sol";
import "./PartyGovernance.sol";

/// @notice The governance contract that also custodies the precious NFTs. This
///         is also the Governance NFT 721 contract.
contract Party is PartyGovernanceNFT {
    // Arguments used to initialize the party.
    struct PartyOptions {
        PartyGovernance.GovernanceOpts governance;
        ProposalStorage.ProposalEngineOpts proposalEngine;
        string name;
        string symbol;
        uint256 customizationPresetId;
    }

    // Arguments used to initialize the `PartyGovernanceNFT`.
    struct PartyInitData {
        PartyOptions options;
        IERC721[] preciousTokens;
        uint256[] preciousTokenIds;
        address[] authorities;
        uint40 rageQuitTimestamp;
    }

    /// @notice Version ID of the party implementation contract.
    uint16 public constant VERSION_ID = 1;

    // Set the `Globals` contract.
    constructor(IGlobals globals) PartyGovernanceNFT(globals) {}

    /// @notice Initializer to be delegatecalled by `Proxy` constructor. Will
    ///         revert if called outside the constructor.
    /// @param initData Options used to initialize the party governance.
    function initialize(PartyInitData memory initData) external onlyConstructor {
        PartyGovernanceNFT._initialize(
            initData.options.name,
            initData.options.symbol,
            initData.options.customizationPresetId,
            initData.options.governance,
            initData.options.proposalEngine,
            initData.preciousTokens,
            initData.preciousTokenIds,
            initData.authorities,
            initData.rageQuitTimestamp
        );
    }

    receive() external payable {}
}

File 8 of 35 : Crowdfund.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/LibAddress.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../tokens/ERC721Receiver.sol";
import "../party/Party.sol";
import "../globals/IGlobals.sol";
import "../gatekeepers/IGateKeeper.sol";
import "../renderers/RendererStorage.sol";

import "./CrowdfundNFT.sol";

// Base contract for AuctionCrowdfund/BuyCrowdfund.
// Holds post-win/loss logic. E.g., burning contribution NFTs and creating a
// party after winning.
abstract contract Crowdfund is Implementation, ERC721Receiver, CrowdfundNFT {
    using LibRawResult for bytes;
    using LibSafeCast for uint256;
    using LibAddress for address payable;

    enum CrowdfundLifecycle {
        Invalid,
        Active,
        Expired,
        Busy, // Temporary. mid-settlement state
        Lost,
        Won
    }

    // PartyGovernance options that must be known and fixed at crowdfund creation.
    // This is a subset of PartyGovernance.GovernanceOpts.
    struct FixedGovernanceOpts {
        // The implementation of the party to be created.
        Party partyImpl;
        // The factory to use to create the party.
        IPartyFactory partyFactory;
        // Address of initial party hosts.
        address[] hosts;
        // How long people can vote on a proposal.
        uint40 voteDuration;
        // How long to wait after a proposal passes before it can be
        // executed.
        uint40 executionDelay;
        // Minimum ratio of accept votes to consider a proposal passed,
        // in bps, where 10,000 == 100%.
        uint16 passThresholdBps;
        // Fee bps for governance distributions.
        uint16 feeBps;
        // Fee recipeint for governance distributions.
        address payable feeRecipient;
    }

    // Options to be passed into `_initialize()` when the crowdfund is created.
    struct CrowdfundOptions {
        string name;
        string symbol;
        uint256 customizationPresetId;
        address payable splitRecipient;
        uint16 splitBps;
        address initialContributor;
        address initialDelegate;
        uint96 minContribution;
        uint96 maxContribution;
        IGateKeeper gateKeeper;
        bytes12 gateKeeperId;
        FixedGovernanceOpts governanceOpts;
        ProposalStorage.ProposalEngineOpts proposalEngineOpts;
    }

    // A record of a single contribution made by a user.
    // Stored in `_contributionsByContributor`.
    struct Contribution {
        // The value of `Crowdfund.totalContributions` when this contribution was made.
        uint96 previousTotalContributions;
        // How much was this contribution.
        uint96 amount;
    }

    // A record of the refund and governance NFT owed to a contributor if it
    // could not be received by them from `burn()`.
    struct Claim {
        uint256 refund;
        uint256 governanceTokenId;
    }

    error PartyAlreadyExistsError(Party party);
    error WrongLifecycleError(CrowdfundLifecycle lc);
    error InvalidGovernanceOptionsError();
    error InvalidDelegateError();
    error InvalidContributorError();
    error NoPartyError();
    error NotAllowedByGateKeeperError(
        address contributor,
        IGateKeeper gateKeeper,
        bytes12 gateKeeperId,
        bytes gateData
    );
    error SplitRecipientAlreadyBurnedError();
    error InvalidBpsError(uint16 bps);
    error ExceedsTotalContributionsError(uint96 value, uint96 totalContributions);
    error NothingToClaimError();
    error OnlyPartyHostError();
    error OnlyContributorError();
    error MissingHostsError();
    error OnlyPartyDaoError(address notDao);
    error OnlyPartyDaoOrHostError(address notDao);
    error OnlyWhenEmergencyActionsAllowedError();
    error BelowMinimumContributionsError(uint96 contributions, uint96 minContributions);
    error AboveMaximumContributionsError(uint96 contributions, uint96 maxContributions);

    event Burned(address contributor, uint256 ethUsed, uint256 ethOwed, uint256 votingPower);
    event Contributed(
        address sender,
        address contributor,
        uint256 amount,
        address delegate,
        uint256 previousTotalContributions
    );
    event EmergencyExecute(address target, bytes data, uint256 amountEth);
    event EmergencyExecuteDisabled();

    uint40 private constant DISABLE_RAGEQUIT_PERMANENTLY = 0xab2cb21860; // uint40(uint256(keccak256("DISABLE_RAGEQUIT_PERMANENTLY")))

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice The party instance created by `_createParty()`, if any after a
    ///         successful crowdfund.
    Party public party;
    /// @notice The total (recorded) ETH contributed to this crowdfund.
    uint96 public totalContributions;
    /// @notice The gatekeeper contract to use (if non-null) to restrict who can
    ///         contribute to the party.
    IGateKeeper public gateKeeper;
    /// @notice The ID of the gatekeeper strategy to use.
    bytes12 public gateKeeperId;
    /// @notice Who will receive a reserved portion of governance power when
    ///         the governance party is created.
    address payable public splitRecipient;
    /// @notice How much governance power to reserve for `splitRecipient`,
    ///         in bps, where 10,000 = 100%.
    uint16 public splitBps;
    // Whether the share for split recipient has been claimed through `burn()`.
    bool private _splitRecipientHasBurned;
    /// @notice Hash of party options passed into `initialize()`.
    ///         Used to check whether the options passed into `_createParty()`
    ///         matches.
    bytes32 public partyOptsHash;
    /// @notice Who a contributor last delegated to.
    mapping(address => address) public delegationsByContributor;
    // Array of contributions by a contributor.
    // One is created for every nonzero contribution made.
    // `internal` for testing purposes only.
    mapping(address => Contribution[]) internal _contributionsByContributor;
    /// @notice Stores the amount of ETH owed back to a contributor and governance NFT
    ///         that should be minted to them if it could not be transferred to
    ///         them with `burn()`.
    mapping(address => Claim) public claims;
    /// @notice Minimum amount of ETH that can be contributed to this crowdfund per address.
    uint96 public minContribution;
    /// @notice Maximum amount of ETH that can be contributed to this crowdfund per address.
    uint96 public maxContribution;
    /// @notice Whether the DAO has emergency powers for this party.
    bool public emergencyExecuteDisabled;

    // Set the `Globals` contract.
    constructor(IGlobals globals) CrowdfundNFT(globals) {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts, credit initial contribution (if
    // any), and setup gatekeeper.
    function _initialize(CrowdfundOptions memory opts) internal {
        CrowdfundNFT._initialize(opts.name, opts.symbol, opts.customizationPresetId);
        // Check that BPS values do not exceed the max.
        if (opts.governanceOpts.feeBps > 1e4) {
            revert InvalidBpsError(opts.governanceOpts.feeBps);
        }
        if (opts.governanceOpts.passThresholdBps > 1e4) {
            revert InvalidBpsError(opts.governanceOpts.passThresholdBps);
        }
        if (opts.splitBps > 1e4) {
            revert InvalidBpsError(opts.splitBps);
        }
        partyOptsHash = _hashOpts(opts.governanceOpts, opts.proposalEngineOpts);
        splitRecipient = opts.splitRecipient;
        splitBps = opts.splitBps;
        // Set the minimum and maximum contribution amounts.
        minContribution = opts.minContribution;
        maxContribution = opts.maxContribution;
        // If the deployer passed in some ETH during deployment, credit them
        // for the initial contribution.
        uint96 initialContribution = msg.value.safeCastUint256ToUint96();
        if (initialContribution > 0) {
            _setDelegate(opts.initialContributor, opts.initialDelegate);
            // If this ETH is passed in, credit it to the `initialContributor`.
            _contribute(opts.initialContributor, opts.initialDelegate, initialContribution, 0, "");
        }
        // Set up gatekeeper after initial contribution (initial always gets in).
        gateKeeper = opts.gateKeeper;
        gateKeeperId = opts.gateKeeperId;
    }

    /// @notice As the DAO, execute an arbitrary function call from this contract.
    /// @dev Emergency actions must not be revoked for this to work.
    /// @param targetAddress The contract to call.
    /// @param targetCallData The data to pass to the contract.
    /// @param amountEth The amount of ETH to send to the contract.
    function emergencyExecute(
        address targetAddress,
        bytes calldata targetCallData,
        uint256 amountEth
    ) external payable onlyDelegateCall {
        // Must be called by the DAO.
        if (!_isPartyDao(msg.sender)) {
            revert OnlyPartyDaoError(msg.sender);
        }
        // Must not be disabled by DAO or host.
        if (emergencyExecuteDisabled) {
            revert OnlyWhenEmergencyActionsAllowedError();
        }
        (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData);
        if (!success) {
            res.rawRevert();
        }
        emit EmergencyExecute(targetAddress, targetCallData, amountEth);
    }

    /// @notice Revoke the DAO's ability to call emergencyExecute().
    /// @dev Either the DAO or the party host can call this.
    /// @param governanceOpts The fixed governance opts the crowdfund was created with.
    /// @param proposalEngineOpts The options used to initialize the proposal
    ///                           engine in the `Party` instance.
    /// @param hostIndex The index of the party host (caller).
    function disableEmergencyExecute(
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        uint256 hostIndex
    ) external onlyDelegateCall {
        // Only the DAO or a host can call this.
        if (
            !_isHost(msg.sender, governanceOpts, proposalEngineOpts, hostIndex) &&
            !_isPartyDao(msg.sender)
        ) {
            revert OnlyPartyDaoOrHostError(msg.sender);
        }
        emergencyExecuteDisabled = true;
        emit EmergencyExecuteDisabled();
    }

    /// @notice Burn the participation NFT for `contributor`, potentially
    ///         minting voting power and/or refunding unused ETH. `contributor`
    ///         may also be the split recipient, regardless of whether they are
    ///         also a contributor or not. This can be called by anyone on a
    ///         contributor's behalf to unlock their voting power in the
    ///         governance stage ensuring delegates receive their voting
    ///         power and governance is not stalled.
    /// @param contributor The contributor whose NFT to burn for.
    function burn(address payable contributor) public {
        return _burn(contributor, getCrowdfundLifecycle(), party);
    }

    /// @dev Alias for `burn()`.
    function activateOrRefund(address payable contributor) external {
        burn(contributor);
    }

    /// @notice `burn()` in batch form.
    ///         Will not revert if any individual burn fails.
    /// @param contributors The contributors whose NFT to burn for.
    /// @param revertOnFailure If true, revert if any burn fails.
    function batchBurn(address payable[] calldata contributors, bool revertOnFailure) public {
        for (uint256 i = 0; i < contributors.length; ++i) {
            (bool s, bytes memory r) = address(this).delegatecall(
                abi.encodeCall(this.burn, (contributors[i]))
            );
            if (revertOnFailure && !s) {
                r.rawRevert();
            }
        }
    }

    /// @dev Alias for `batchBurn()`.
    function batchActivateOrRefund(
        address payable[] calldata contributors,
        bool revertOnFailure
    ) external {
        batchBurn(contributors, revertOnFailure);
    }

    /// @notice Claim a governance NFT or refund that is owed back but could not be
    ///         given due to error in `_burn()` (e.g. a contract that does not
    ///         implement `onERC721Received()` or cannot receive ETH). Only call
    ///         this if refund and governance NFT minting could not be returned
    ///         with `burn()`.
    /// @param receiver The address to receive the NFT or refund.
    function claim(address payable receiver) external {
        Claim memory claimInfo = claims[msg.sender];
        delete claims[msg.sender];

        if (claimInfo.refund == 0 && claimInfo.governanceTokenId == 0) {
            revert NothingToClaimError();
        }

        if (claimInfo.refund != 0) {
            receiver.transferEth(claimInfo.refund);
        }

        if (claimInfo.governanceTokenId != 0) {
            party.safeTransferFrom(address(this), receiver, claimInfo.governanceTokenId);
        }
    }

    /// @notice Contribute to this crowdfund and/or update your delegation for the
    ///         governance phase should the crowdfund succeed.
    ///         For restricted crowdfunds, `gateData` can be provided to prove
    ///         membership to the gatekeeper.
    /// @param delegate The address to delegate to for the governance phase.
    /// @param gateData Data to pass to the gatekeeper to prove eligibility.
    function contribute(address delegate, bytes memory gateData) external payable onlyDelegateCall {
        _setDelegate(msg.sender, delegate);

        _contribute(
            msg.sender,
            delegate,
            msg.value.safeCastUint256ToUint96(),
            // We cannot use `address(this).balance - msg.value` as the previous
            // total contributions in case someone forces (suicides) ETH into this
            // contract. This wouldn't be such a big deal for open crowdfunds
            // but private ones (protected by a gatekeeper) could be griefed
            // because it would ultimately result in governance power that
            // is unattributed/unclaimable, meaning that party will never be
            // able to reach 100% consensus.
            totalContributions,
            gateData
        );
    }

    /// @notice Contribute to this crowdfund on behalf of another address.
    /// @param recipient The address to record the contribution under.
    /// @param initialDelegate The address to delegate to for the governance phase if recipient hasn't delegated.
    /// @param gateData Data to pass to the gatekeeper to prove eligibility.
    function contributeFor(
        address recipient,
        address initialDelegate,
        bytes memory gateData
    ) external payable onlyDelegateCall {
        _setDelegate(recipient, initialDelegate);

        _contribute(
            recipient,
            initialDelegate,
            msg.value.safeCastUint256ToUint96(),
            totalContributions,
            gateData
        );
    }

    /// @notice `contributeFor()` in batch form.
    ///         May not revert if any individual contribution fails.
    /// @param recipients The addresses to record the contributions under.
    /// @param initialDelegates The addresses to delegate to for each recipient.
    /// @param values The ETH to contribute for each recipient.
    /// @param gateDatas Data to pass to the gatekeeper to prove eligibility.
    /// @param revertOnFailure If true, revert if any contribution fails.
    function batchContributeFor(
        address[] memory recipients,
        address[] memory initialDelegates,
        uint256[] memory values,
        bytes[] memory gateDatas,
        bool revertOnFailure
    ) external payable {
        for (uint256 i; i < recipients.length; ++i) {
            (bool s, bytes memory r) = address(this).call{ value: values[i] }(
                abi.encodeCall(
                    this.contributeFor,
                    (recipients[i], initialDelegates[i], gateDatas[i])
                )
            );
            if (revertOnFailure && !s) {
                r.rawRevert();
            }
        }
    }

    /// @inheritdoc EIP165
    function supportsInterface(
        bytes4 interfaceId
    ) public pure override(ERC721Receiver, CrowdfundNFT) returns (bool) {
        return
            ERC721Receiver.supportsInterface(interfaceId) ||
            CrowdfundNFT.supportsInterface(interfaceId);
    }

    /// @notice Retrieve info about a participant's contributions.
    /// @dev This will only be called off-chain so doesn't have to be optimal.
    /// @param contributor The contributor to retrieve contributions for.
    /// @return ethContributed The total ETH contributed by `contributor`.
    /// @return ethUsed The total ETH used by `contributor` to acquire the NFT.
    /// @return ethOwed The total ETH refunded back to `contributor`.
    /// @return votingPower The total voting power minted to `contributor`.
    function getContributorInfo(
        address contributor
    )
        external
        view
        returns (uint256 ethContributed, uint256 ethUsed, uint256 ethOwed, uint256 votingPower)
    {
        CrowdfundLifecycle lc = getCrowdfundLifecycle();
        if (lc == CrowdfundLifecycle.Won || lc == CrowdfundLifecycle.Lost) {
            (ethUsed, ethOwed, votingPower) = _getFinalContribution(contributor);
            ethContributed = ethUsed + ethOwed;
        } else {
            Contribution[] memory contributions = _contributionsByContributor[contributor];
            uint256 numContributions = contributions.length;
            for (uint256 i; i < numContributions; ++i) {
                ethContributed += contributions[i].amount;
            }
        }
    }

    /// @notice Get the current lifecycle of the crowdfund.
    function getCrowdfundLifecycle() public view virtual returns (CrowdfundLifecycle lifecycle);

    // Get the final sale price of the bought assets. This will also be the total
    // voting power of the governance party.
    function _getFinalPrice() internal view virtual returns (uint256);

    // Assert that `who` is a host at `governanceOpts.hosts[hostIndex]` and,
    // if so, assert that the governance opts is the same as the crowdfund
    // was created with.
    // Return true if `governanceOpts` was validated in the process.
    function _assertIsHost(
        address who,
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        uint256 hostIndex
    ) internal view {
        if (!_isHost(who, governanceOpts, proposalEngineOpts, hostIndex)) {
            revert OnlyPartyHostError();
        }
    }

    // Check if `who` is a host at `hostIndex` index. Validates governance opts if so.
    function _isHost(
        address who,
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        uint256 hostIndex
    ) private view returns (bool isHost) {
        if (hostIndex < governanceOpts.hosts.length && who == governanceOpts.hosts[hostIndex]) {
            // Validate governance opts if the host was found.
            _assertValidOpts(governanceOpts, proposalEngineOpts);
            return true;
        }
        return false;
    }

    function _isPartyDao(address who) private view returns (bool isPartyDao) {
        return who == _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
    }

    // Assert that `who` is a contributor to the crowdfund.
    function _assertIsContributor(address who) internal view {
        if (_contributionsByContributor[who].length == 0) {
            revert OnlyContributorError();
        }
    }

    // Can be called after a party has won.
    // Deploys and initializes a `Party` instance via the `PartyFactory`
    // and transfers the bought NFT to it.
    // After calling this, anyone can burn CF tokens on a contributor's behalf
    // with the `burn()` function.
    function _createParty(
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        bool governanceOptsAlreadyValidated,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal returns (Party party_) {
        if (party != Party(payable(0))) {
            revert PartyAlreadyExistsError(party);
        }
        // If the governance opts haven't already been validated, make sure that
        // it hasn't been tampered with.
        if (!governanceOptsAlreadyValidated) {
            _assertValidOpts(governanceOpts, proposalEngineOpts);
        }
        // Get options used to create the party.
        RendererStorage rendererStorage = RendererStorage(
            _GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE)
        );
        address[] memory authorities = new address[](1);
        authorities[0] = address(this);
        // Create a party.
        party = party_ = governanceOpts.partyFactory.createParty(
            governanceOpts.partyImpl,
            authorities,
            Party.PartyOptions({
                name: name,
                symbol: symbol,
                customizationPresetId: rendererStorage.getPresetFor(address(this)),
                governance: PartyGovernance.GovernanceOpts({
                    hosts: governanceOpts.hosts,
                    voteDuration: governanceOpts.voteDuration,
                    executionDelay: governanceOpts.executionDelay,
                    passThresholdBps: governanceOpts.passThresholdBps,
                    totalVotingPower: _getFinalPrice().safeCastUint256ToUint96(),
                    feeBps: governanceOpts.feeBps,
                    feeRecipient: governanceOpts.feeRecipient
                }),
                proposalEngine: proposalEngineOpts
            }),
            preciousTokens,
            preciousTokenIds,
            // Ragequit is not applicable for NFT parties which primarily own
            // non-fungible assets (which cannot be split) and can perform
            // distributions without needing a vote.
            DISABLE_RAGEQUIT_PERMANENTLY
        );
        // Transfer the acquired NFTs to the new party.
        for (uint256 i; i < preciousTokens.length; ++i) {
            preciousTokens[i].transferFrom(address(this), address(party_), preciousTokenIds[i]);
        }
    }

    // Overloaded single token wrapper for _createParty()
    function _createParty(
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        bool governanceOptsAlreadyValidated,
        IERC721 preciousToken,
        uint256 preciousTokenId
    ) internal returns (Party party_) {
        IERC721[] memory tokens = new IERC721[](1);
        tokens[0] = preciousToken;
        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = preciousTokenId;
        return
            _createParty(
                governanceOpts,
                proposalEngineOpts,
                governanceOptsAlreadyValidated,
                tokens,
                tokenIds
            );
    }

    // Assert that the hash of `opts` matches the hash this crowdfund was initialized with.
    function _assertValidOpts(
        FixedGovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
    ) private view {
        bytes32 partyOptsHash_ = _hashOpts(governanceOpts, proposalEngineOpts);
        if (partyOptsHash_ != partyOptsHash) {
            revert InvalidGovernanceOptionsError();
        }
    }

    function _hashOpts(
        FixedGovernanceOpts memory govOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
    ) internal pure returns (bytes32 h) {
        return keccak256(abi.encode(govOpts, proposalEngineOpts));
    }

    function _getFinalContribution(
        address contributor
    ) internal view returns (uint256 ethUsed, uint256 ethOwed, uint256 votingPower) {
        uint256 totalEthUsed = _getFinalPrice();
        {
            Contribution[] memory contributions = _contributionsByContributor[contributor];
            uint256 numContributions = contributions.length;
            for (uint256 i; i < numContributions; ++i) {
                Contribution memory c = contributions[i];
                if (c.previousTotalContributions >= totalEthUsed) {
                    // This entire contribution was not used.
                    ethOwed += c.amount;
                } else if (c.previousTotalContributions + c.amount <= totalEthUsed) {
                    // This entire contribution was used.
                    ethUsed += c.amount;
                } else {
                    // This contribution was partially used.
                    uint256 partialEthUsed = totalEthUsed - c.previousTotalContributions;
                    ethUsed += partialEthUsed;
                    ethOwed = c.amount - partialEthUsed;
                }
            }
        }
        // one SLOAD with optimizer on
        address splitRecipient_ = splitRecipient;
        uint256 splitBps_ = splitBps;
        if (splitRecipient_ == address(0)) {
            splitBps_ = 0;
        }
        votingPower = ((1e4 - splitBps_) * ethUsed) / 1e4;
        if (splitRecipient_ == contributor) {
            // Split recipient is also the contributor so just add the split
            // voting power.
            votingPower += (splitBps_ * totalEthUsed + (1e4 - 1)) / 1e4; // round up
        }
    }

    function _setDelegate(address contributor, address delegate) private {
        if (delegate == address(0)) revert InvalidDelegateError();

        // Only need to update delegate if there was a change.
        address oldDelegate = delegationsByContributor[contributor];
        if (oldDelegate == delegate) return;

        // Only allow setting delegate on another's behalf if the delegate is unset.
        if (msg.sender != contributor && oldDelegate != address(0)) return;

        // Update delegate.
        delegationsByContributor[contributor] = delegate;
    }

    /// @dev While it is not necessary to pass in `delegate` to this because the
    ///      function does not require it, it is here to emit in the
    ///      `Contribute` event so that the PartyBid frontend can access it more
    ///      easily.
    function _contribute(
        address contributor,
        address delegate,
        uint96 amount,
        uint96 previousTotalContributions,
        bytes memory gateData
    ) private {
        if (contributor == address(this)) revert InvalidContributorError();

        if (amount == 0) return;

        // Must not be blocked by gatekeeper.
        {
            IGateKeeper _gateKeeper = gateKeeper;
            if (_gateKeeper != IGateKeeper(address(0))) {
                if (!_gateKeeper.isAllowed(msg.sender, gateKeeperId, gateData)) {
                    revert NotAllowedByGateKeeperError(
                        msg.sender,
                        _gateKeeper,
                        gateKeeperId,
                        gateData
                    );
                }
            }
        }
        // Only allow contributions while the crowdfund is active.
        {
            CrowdfundLifecycle lc = getCrowdfundLifecycle();
            if (lc != CrowdfundLifecycle.Active) {
                revert WrongLifecycleError(lc);
            }
        }
        // Increase total contributions.
        totalContributions += amount;
        // Create contributions entry for this contributor.
        Contribution[] storage contributions = _contributionsByContributor[contributor];
        uint256 numContributions = contributions.length;
        uint96 ethContributed;
        for (uint256 i; i < numContributions; ++i) {
            ethContributed += contributions[i].amount;
        }
        // Check contribution is greater than minimum contribution.
        if (ethContributed + amount < minContribution) {
            revert BelowMinimumContributionsError(ethContributed + amount, minContribution);
        }
        // Check contribution is less than maximum contribution.
        if (ethContributed + amount > maxContribution) {
            revert AboveMaximumContributionsError(ethContributed + amount, maxContribution);
        }

        emit Contributed(msg.sender, contributor, amount, delegate, previousTotalContributions);

        if (numContributions >= 1) {
            Contribution memory lastContribution = contributions[numContributions - 1];
            // If no one else (other than this contributor) has contributed since,
            // we can just reuse this contributor's last entry.
            uint256 totalContributionsAmountForReuse = lastContribution.previousTotalContributions +
                lastContribution.amount;
            if (totalContributionsAmountForReuse == previousTotalContributions) {
                lastContribution.amount += amount;
                contributions[numContributions - 1] = lastContribution;
                return;
            }
        }
        // Add a new contribution entry.
        contributions.push(
            Contribution({ previousTotalContributions: previousTotalContributions, amount: amount })
        );
        // Mint a participation NFT if this is their first contribution.
        if (numContributions == 0) {
            _mint(contributor);
        }
    }

    function _burn(address payable contributor, CrowdfundLifecycle lc, Party party_) private {
        // If the CF has won, a party must have been created prior.
        if (lc == CrowdfundLifecycle.Won) {
            if (party_ == Party(payable(0))) {
                revert NoPartyError();
            }
        } else if (lc != CrowdfundLifecycle.Lost) {
            // Otherwise it must have lost.
            revert WrongLifecycleError(lc);
        }
        // Split recipient can burn even if they don't have a token.
        {
            address splitRecipient_ = splitRecipient;
            if (contributor == splitRecipient_) {
                if (_splitRecipientHasBurned) {
                    revert SplitRecipientAlreadyBurnedError();
                }
                _splitRecipientHasBurned = true;
            }
            // Revert if already burned or does not exist.
            if (splitRecipient_ != contributor || _doesTokenExistFor(contributor)) {
                CrowdfundNFT._burn(contributor);
            }
        }
        // Compute the contributions used and owed to the contributor, along
        // with the voting power they'll have in the governance stage.
        (uint256 ethUsed, uint256 ethOwed, uint256 votingPower) = _getFinalContribution(
            contributor
        );
        if (votingPower > 0) {
            // Get the address to delegate voting power to. If null, delegate to self.
            address delegate = delegationsByContributor[contributor];
            if (delegate == address(0)) {
                // Delegate can be unset for the split recipient if they never
                // contribute. Self-delegate if this occurs.
                delegate = contributor;
            }
            // Mint governance NFT for the contributor.
            try party_.mint(contributor, votingPower, delegate) returns (uint256) {
                // OK
            } catch {
                // Mint to the crowdfund itself to escrow for contributor to
                // come claim later on.
                uint256 tokenId = party_.mint(address(this), votingPower, delegate);
                claims[contributor].governanceTokenId = tokenId;
            }
        }
        // Refund any ETH owed back to the contributor.
        (bool s, ) = contributor.call{ value: ethOwed }("");
        if (!s) {
            // If the transfer fails, the contributor can still come claim it
            // from the crowdfund.
            claims[contributor].refund = ethOwed;
        }
        emit Burned(contributor, ethUsed, ethOwed, votingPower);
    }
}

function _hashFixedGovernanceOpts(
    Crowdfund.FixedGovernanceOpts memory opts
) pure returns (bytes32 h) {
    // Hash in place.
    assembly {
        // Replace the address[] hosts field with its hash temporarily.
        let oldHostsFieldValue := mload(opts)
        mstore(opts, keccak256(add(oldHostsFieldValue, 0x20), mul(mload(oldHostsFieldValue), 32)))
        // Hash the entire struct.
        h := keccak256(opts, 0xC0)
        // Restore old hosts field value.
        mstore(opts, oldHostsFieldValue)
    }
}

File 9 of 35 : IGateKeeper.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

// Interface for a gatekeeper contract used for private crowdfund instances.
interface IGateKeeper {
    /// @notice Check if a participant is eligible to participate in a crowdfund.
    /// @param participant The address of the participant.
    /// @param id The ID of the gate to eligibility against.
    /// @param userData The data used to check eligibility.
    /// @return `true` if the participant is allowed to participate, `false` otherwise.
    function isAllowed(
        address participant,
        bytes12 id,
        bytes memory userData
    ) external view returns (bool);
}

File 10 of 35 : IERC721Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes memory data
    ) external returns (bytes4);
}

File 11 of 35 : EIP165.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

abstract contract EIP165 {
    /// @notice Query if a contract implements an interface.
    /// @param interfaceId The interface identifier, as specified in ERC-165
    /// @return `true` if the contract implements `interfaceId` and
    ///         `interfaceId` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
        return interfaceId == this.supportsInterface.selector;
    }
}

File 12 of 35 : ERC721.sol
// SPDX-License-Identifier: AGPL-3.0-only
// Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0)
//  @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol
// Only modified to inherit IERC721 and EIP165.
pragma solidity >=0.8.0;

// NOTE: Only modified to inherit IERC20 and EIP165
import "../../tokens/IERC721.sol";
import "../../utils/EIP165.sol";

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 is IERC721, EIP165 {
    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id /* view */) public virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(address from, address to, uint256 id) public virtual {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from ||
                isApprovedForAll[from][msg.sender] ||
                msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(address from, address to, uint256 id) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        // NOTE: modified from original to call super.
        return
            super.supportsInterface(interfaceId) ||
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}

File 13 of 35 : IERC721.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC721 interface.
interface IERC721 {
    event Transfer(address indexed owner, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed operator, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function transferFrom(address from, address to, uint256 tokenId) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    function approve(address operator, uint256 tokenId) external;

    function setApprovalForAll(address operator, bool isApproved) external;

    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function getApproved(uint256 tokenId) external view returns (address);

    function isApprovedForAll(address owner, address operator) external view returns (bool);

    function ownerOf(uint256 tokenId) external view returns (address);

    function balanceOf(address owner) external view returns (uint256);
}

File 14 of 35 : PartyGovernanceNFT.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/ReadOnlyDelegateCall.sol";
import "../utils/LibSafeCast.sol";
import "../utils/LibAddress.sol";
import "openzeppelin/contracts/interfaces/IERC2981.sol";
import "../globals/IGlobals.sol";
import "../tokens/IERC721.sol";
import "../vendor/solmate/ERC721.sol";
import "./PartyGovernance.sol";
import "../renderers/RendererStorage.sol";

/// @notice ERC721 functionality built on top of `PartyGovernance`.
contract PartyGovernanceNFT is PartyGovernance, ERC721, IERC2981 {
    using LibSafeCast for uint256;
    using LibSafeCast for uint96;
    using LibERC20Compat for IERC20;
    using LibAddress for address payable;

    error OnlyAuthorityError();
    error OnlySelfError();
    error UnauthorizedToBurnError();
    error FixedRageQuitTimestampError(uint40 rageQuitTimestamp);
    error CannotRageQuitError(uint40 rageQuitTimestamp);
    error CannotDisableRageQuitAfterInitializationError();
    error InvalidTokenOrderError();
    error BelowMinWithdrawAmountError(uint256 amount, uint256 minAmount);
    error NothingToBurnError();

    event AuthorityAdded(address indexed authority);
    event AuthorityRemoved(address indexed authority);
    event RageQuitSet(uint40 oldRageQuitTimestamp, uint40 newRageQuitTimestamp);
    event Burn(address caller, uint256 tokenId, uint256 votingPower);
    event RageQuit(address caller, uint256[] tokenIds, IERC20[] withdrawTokens, address receiver);

    uint40 private constant ENABLE_RAGEQUIT_PERMANENTLY = 0x6b5b567bfe; // uint40(uint256(keccak256("ENABLE_RAGEQUIT_PERMANENTLY")))
    uint40 private constant DISABLE_RAGEQUIT_PERMANENTLY = 0xab2cb21860; // uint40(uint256(keccak256("DISABLE_RAGEQUIT_PERMANENTLY")))

    // Token address used to indicate ETH.
    address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and its address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice The number of tokens that have been minted.
    uint96 public tokenCount;
    /// @notice The total minted voting power.
    ///         Capped to `_governanceValues.totalVotingPower` unless minting
    ///         party cards for initial crowdfund.
    uint96 public mintedVotingPower;
    /// @notice The timestamp until which ragequit is enabled. Can be set to the
    ///         `ENABLE_RAGEQUIT_PERMANENTLY`/`DISABLE_RAGEQUIT_PERMANENTLY`
    ///         values to enable/disable ragequit permanently.
    ///         `DISABLE_RAGEQUIT_PERMANENTLY` can only be set during
    ///         initialization.
    uint40 public rageQuitTimestamp;
    /// @notice The voting power of `tokenId`.
    mapping(uint256 => uint256) public votingPowerByTokenId;
    /// @notice Address with authority to mint cards and update voting power for the party.
    mapping(address => bool) public isAuthority;

    modifier onlyAuthority() {
        if (!isAuthority[msg.sender]) {
            revert OnlyAuthorityError();
        }
        _;
    }

    modifier onlySelf() {
        if (msg.sender != address(this)) {
            revert OnlySelfError();
        }
        _;
    }

    // Set the `Globals` contract. The name or symbol of ERC721 does not matter;
    // it will be set in `_initialize()`.
    constructor(IGlobals globals) payable PartyGovernance(globals) ERC721("", "") {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts.
    function _initialize(
        string memory name_,
        string memory symbol_,
        uint256 customizationPresetId,
        PartyGovernance.GovernanceOpts memory governanceOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        address[] memory authorities,
        uint40 rageQuitTimestamp_
    ) internal {
        PartyGovernance._initialize(
            governanceOpts,
            proposalEngineOpts,
            preciousTokens,
            preciousTokenIds
        );
        name = name_;
        symbol = symbol_;
        rageQuitTimestamp = rageQuitTimestamp_;
        unchecked {
            for (uint256 i; i < authorities.length; ++i) {
                isAuthority[authorities[i]] = true;
            }
        }
        if (customizationPresetId != 0) {
            RendererStorage(_GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE))
                .useCustomizationPreset(customizationPresetId);
        }
    }

    /// @inheritdoc ERC721
    function ownerOf(uint256 tokenId) public view override returns (address owner) {
        return ERC721.ownerOf(tokenId);
    }

    /// @inheritdoc EIP165
    function supportsInterface(
        bytes4 interfaceId
    ) public pure override(PartyGovernance, ERC721, IERC165) returns (bool) {
        return
            PartyGovernance.supportsInterface(interfaceId) ||
            ERC721.supportsInterface(interfaceId) ||
            interfaceId == type(IERC2981).interfaceId;
    }

    /// @inheritdoc ERC721
    function tokenURI(uint256) public view override returns (string memory) {
        _delegateToRenderer();
        return ""; // Just to make the compiler happy.
    }

    /// @notice Returns a URI for the storefront-level metadata for your contract.
    function contractURI() external view returns (string memory) {
        _delegateToRenderer();
        return ""; // Just to make the compiler happy.
    }

    /// @notice Called with the sale price to determine how much royalty
    //          is owed and to whom.
    function royaltyInfo(uint256, uint256) external view returns (address, uint256) {
        _delegateToRenderer();
        return (address(0), 0); // Just to make the compiler happy.
    }

    /// @notice Return the distribution share amount of a token. Included as an alias
    ///         for `votePowerByTokenId` for backwards compatibility with old
    ///         `TokenDistributor` implementations.
    /// @param tokenId The token ID to query.
    /// @return share The distribution shares of `tokenId`.
    function getDistributionShareOf(uint256 tokenId) public view returns (uint256) {
        return votingPowerByTokenId[tokenId];
    }

    /// @notice Return the voting power share of a token. Denominated
    ///         fractions of 1e18. I.e., 1e18 = 100%.
    /// @param tokenId The token ID to query.
    /// @return share The voting power percentage of `tokenId`.
    function getVotingPowerShareOf(uint256 tokenId) public view returns (uint256) {
        uint256 totalVotingPower = _governanceValues.totalVotingPower;
        return
            totalVotingPower == 0 ? 0 : (votingPowerByTokenId[tokenId] * 1e18) / totalVotingPower;
    }

    /// @notice Mint a governance NFT for `owner` with `votingPower` and
    ///         immediately delegate voting power to `delegate.` Only callable
    ///         by an authority.
    /// @param owner The owner of the NFT.
    /// @param votingPower The voting power of the NFT.
    /// @param delegate The address to delegate voting power to.
    function mint(
        address owner,
        uint256 votingPower,
        address delegate
    ) external onlyAuthority returns (uint256 tokenId) {
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _governanceValues.totalVotingPower;

        // Cap voting power to remaining unminted voting power supply.
        uint96 votingPower_ = votingPower.safeCastUint256ToUint96();
        // Allow minting past total voting power if minting party cards for
        // initial crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) {
            unchecked {
                votingPower_ = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        unchecked {
            tokenId = ++tokenCount;
        }
        mintedVotingPower += votingPower_;
        votingPowerByTokenId[tokenId] = votingPower_;

        // Use delegate from party over the one set during crowdfund.
        address delegate_ = delegationsByVoter[owner];
        if (delegate_ != address(0)) {
            delegate = delegate_;
        }

        _adjustVotingPower(owner, votingPower_.safeCastUint96ToInt192(), delegate);
        _safeMint(owner, tokenId);
    }

    /// @notice Add voting power to an existing NFT. Only callable by an
    ///         authority.
    /// @param tokenId The ID of the NFT to add voting power to.
    /// @param votingPower The amount of voting power to add.
    function addVotingPower(uint256 tokenId, uint256 votingPower) external onlyAuthority {
        uint96 mintedVotingPower_ = mintedVotingPower;
        uint96 totalVotingPower = _governanceValues.totalVotingPower;
        // Cap voting power to remaining unminted voting power supply.
        uint96 votingPower_ = votingPower.safeCastUint256ToUint96();
        // Allow minting past total voting power if minting party cards for
        // initial crowdfund when there is no total voting power.
        if (totalVotingPower != 0 && totalVotingPower - mintedVotingPower_ < votingPower_) {
            unchecked {
                votingPower_ = totalVotingPower - mintedVotingPower_;
            }
        }

        // Update state.
        mintedVotingPower += votingPower_;
        votingPowerByTokenId[tokenId] += votingPower_;

        _adjustVotingPower(ownerOf(tokenId), votingPower_.safeCastUint96ToInt192(), address(0));
    }

    /// @notice Update the total voting power of the party. Only callable by
    ///         an authority.
    /// @param newVotingPower The new total voting power to add.
    function increaseTotalVotingPower(uint96 newVotingPower) external onlyAuthority {
        _governanceValues.totalVotingPower += newVotingPower;
    }

    /// @notice Burn governance NFTs and remove their voting power. Can only
    ///         be called by an authority before the party has started.
    /// @param tokenIds The IDs of the governance NFTs to burn.
    function burn(uint256[] memory tokenIds) public onlyAuthority {
        // Authority needs to be able to burn cards during the initial
        // crowdfund to process refunds but not after the party has started.
        if (_governanceValues.totalVotingPower != 0) revert UnauthorizedToBurnError();

        // Used to update voting power state of party at the end.
        _burnAndUpdateVotingPower(tokenIds, false);
    }

    function _burnAndUpdateVotingPower(
        uint256[] memory tokenIds,
        bool checkIfAuthorizedToBurn
    ) private returns (uint96 totalVotingPowerBurned) {
        for (uint256 i; i < tokenIds.length; ++i) {
            uint256 tokenId = tokenIds[i];

            // Check if caller is authorized to burn the token.
            address owner = ownerOf(tokenId);
            if (checkIfAuthorizedToBurn) {
                if (
                    msg.sender != owner &&
                    getApproved[tokenId] != msg.sender &&
                    !isApprovedForAll[owner][msg.sender]
                ) {
                    revert UnauthorizedToBurnError();
                }
            }

            // Must be retrieved before updating voting power for token to be burned.
            uint96 votingPower = votingPowerByTokenId[tokenId].safeCastUint256ToUint96();

            totalVotingPowerBurned += votingPower;

            // Update voting power for token to be burned.
            delete votingPowerByTokenId[tokenId];
            _adjustVotingPower(owner, -votingPower.safeCastUint96ToInt192(), address(0));

            // Burn token.
            _burn(tokenId);

            emit Burn(msg.sender, tokenId, votingPower);
        }

        // Update minted voting power.
        mintedVotingPower -= totalVotingPowerBurned;
    }

    /// @notice Burn governance NFT and remove its voting power. Can only be
    ///         called by an authority before the party has started.
    /// @param tokenId The ID of the governance NFTs to burn.
    function burn(uint256 tokenId) external {
        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = tokenId;
        burn(tokenIds);
    }

    /// @notice Set the timestamp until which ragequit is enabled.
    /// @param newRageQuitTimestamp The new ragequit timestamp.
    function setRageQuit(uint40 newRageQuitTimestamp) external onlyHost {
        // Prevent disabling ragequit after initialization.
        if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) {
            revert CannotDisableRageQuitAfterInitializationError();
        }

        uint40 oldRageQuitTimestamp = rageQuitTimestamp;

        // Prevent setting timestamp if it is permanently enabled/disabled.
        if (
            oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY ||
            oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY
        ) {
            revert FixedRageQuitTimestampError(oldRageQuitTimestamp);
        }

        emit RageQuitSet(oldRageQuitTimestamp, rageQuitTimestamp = newRageQuitTimestamp);
    }

    /// @notice Burn a governance NFT and withdraw a fair share of fungible tokens from the party.
    /// @param tokenIds The IDs of the governance NFTs to burn.
    /// @param withdrawTokens The fungible tokens to withdraw. Specify the
    ///                       `ETH_ADDRESS` value to withdraw ETH.
    /// @param minWithdrawAmounts The minimum amount of to withdraw for each token.
    /// @param receiver The address to receive the withdrawn tokens.
    function rageQuit(
        uint256[] calldata tokenIds,
        IERC20[] calldata withdrawTokens,
        uint256[] calldata minWithdrawAmounts,
        address receiver
    ) external {
        if (tokenIds.length == 0) revert NothingToBurnError();

        // Check if ragequit is allowed.
        uint40 currentRageQuitTimestamp = rageQuitTimestamp;
        if (currentRageQuitTimestamp != ENABLE_RAGEQUIT_PERMANENTLY) {
            if (
                currentRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY ||
                currentRageQuitTimestamp < block.timestamp
            ) {
                revert CannotRageQuitError(currentRageQuitTimestamp);
            }
        }

        // Used as a reentrancy guard. Will be updated back after ragequit.
        rageQuitTimestamp = DISABLE_RAGEQUIT_PERMANENTLY;

        // Update last rage quit timestamp.
        lastRageQuitTimestamp = uint40(block.timestamp);

        // Sum up total amount of each token to withdraw.
        uint256[] memory withdrawAmounts = new uint256[](withdrawTokens.length);
        {
            IERC20 prevToken;
            for (uint256 i; i < withdrawTokens.length; ++i) {
                IERC20 token = withdrawTokens[i];

                // Check if order of tokens to transfer is valid.
                // Prevent null and duplicate transfers.
                if (prevToken >= token) revert InvalidTokenOrderError();

                prevToken = token;

                // Check token's balance.
                uint256 balance = address(token) == ETH_ADDRESS
                    ? address(this).balance
                    : token.balanceOf(address(this));

                // Add fair share of tokens from the party to total.
                for (uint256 j; j < tokenIds.length; ++j) {
                    // Must be retrieved before burning the token.
                    uint256 shareOfVotingPower = getVotingPowerShareOf(tokenIds[j]);

                    withdrawAmounts[i] += (balance * shareOfVotingPower) / 1e18;
                }
            }
        }
        {
            // Burn caller's party cards. This will revert if caller is not the
            // the owner or approved for any of the card they are attempting to
            // burn or if there are duplicate token IDs.
            uint96 totalVotingPowerBurned = _burnAndUpdateVotingPower(tokenIds, true);

            // Update total voting power of party.
            _governanceValues.totalVotingPower -= totalVotingPowerBurned;
        }
        {
            uint16 feeBps_ = feeBps;
            for (uint256 i; i < withdrawTokens.length; ++i) {
                IERC20 token = withdrawTokens[i];
                uint256 amount = withdrawAmounts[i];

                // Take fee from amount.
                uint256 fee = (amount * feeBps_) / 1e4;

                if (fee > 0) {
                    amount -= fee;

                    // Transfer fee to fee recipient.
                    if (address(token) == ETH_ADDRESS) {
                        payable(feeRecipient).transferEth(fee);
                    } else {
                        token.compatTransfer(feeRecipient, fee);
                    }
                }

                if (amount > 0) {
                    uint256 minAmount = minWithdrawAmounts[i];

                    // Check amount is at least minimum.
                    if (amount < minAmount) {
                        revert BelowMinWithdrawAmountError(amount, minAmount);
                    }

                    // Transfer token from party to recipient.
                    if (address(token) == ETH_ADDRESS) {
                        payable(receiver).transferEth(amount);
                    } else {
                        token.compatTransfer(receiver, amount);
                    }
                }
            }
        }

        // Update ragequit timestamp back to before.
        rageQuitTimestamp = currentRageQuitTimestamp;

        emit RageQuit(msg.sender, tokenIds, withdrawTokens, receiver);
    }

    /// @inheritdoc ERC721
    function transferFrom(address owner, address to, uint256 tokenId) public override {
        // Transfer voting along with token.
        _transferVotingPower(owner, to, votingPowerByTokenId[tokenId]);
        super.transferFrom(owner, to, tokenId);
    }

    /// @inheritdoc ERC721
    function safeTransferFrom(address owner, address to, uint256 tokenId) public override {
        // super.safeTransferFrom() will call transferFrom() first which will
        // transfer voting power.
        super.safeTransferFrom(owner, to, tokenId);
    }

    /// @inheritdoc ERC721
    function safeTransferFrom(
        address owner,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) public override {
        // super.safeTransferFrom() will call transferFrom() first which will
        // transfer voting power.
        super.safeTransferFrom(owner, to, tokenId, data);
    }

    /// @notice Add a new authority.
    /// @dev Used in `AddAuthorityProposal`. Only the party itself can add
    ///      authorities to prevent it from being used anywhere else.
    function addAuthority(address authority) external onlySelf {
        isAuthority[authority] = true;

        emit AuthorityAdded(authority);
    }

    /// @notice Relinquish the authority role.
    function abdicateAuthority() external onlyAuthority {
        delete isAuthority[msg.sender];

        emit AuthorityRemoved(msg.sender);
    }

    function _delegateToRenderer() private view {
        _readOnlyDelegateCall(
            // Instance of IERC721Renderer.
            _GLOBALS.getAddress(LibGlobals.GLOBAL_GOVERNANCE_NFT_RENDER_IMPL),
            msg.data
        );
        assert(false); // Will not be reached.
    }
}

File 15 of 35 : PartyGovernance.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../distribution/ITokenDistributor.sol";
import "../utils/ReadOnlyDelegateCall.sol";
import "../tokens/IERC721.sol";
import "../tokens/IERC20.sol";
import "../tokens/IERC1155.sol";
import "../tokens/ERC721Receiver.sol";
import "../tokens/ERC1155Receiver.sol";
import "../utils/LibERC20Compat.sol";
import "../utils/LibRawResult.sol";
import "../utils/LibSafeCast.sol";
import "../globals/IGlobals.sol";
import "../globals/LibGlobals.sol";
import "../proposals/IProposalExecutionEngine.sol";
import "../proposals/LibProposal.sol";
import "../proposals/ProposalStorage.sol";

import "./Party.sol";
import "./IPartyFactory.sol";

/// @notice Base contract for a Party encapsulating all governance functionality.
abstract contract PartyGovernance is
    ERC721Receiver,
    ERC1155Receiver,
    ProposalStorage,
    Implementation,
    ReadOnlyDelegateCall
{
    using LibERC20Compat for IERC20;
    using LibRawResult for bytes;
    using LibSafeCast for uint256;
    using LibSafeCast for int192;
    using LibSafeCast for uint96;

    // States a proposal can be in.
    enum ProposalStatus {
        // The proposal does not exist.
        Invalid,
        // The proposal has been proposed (via `propose()`), has not been vetoed
        // by a party host, and is within the voting window. Members can vote on
        // the proposal and party hosts can veto the proposal.
        Voting,
        // The proposal has either exceeded its voting window without reaching
        // `passThresholdBps` of votes or was vetoed by a party host.
        Defeated,
        // The proposal reached at least `passThresholdBps` of votes but is still
        // waiting for `executionDelay` to pass before it can be executed. Members
        // can continue to vote on the proposal and party hosts can veto at this time.
        Passed,
        // Same as `Passed` but now `executionDelay` has been satisfied. Any member
        // may execute the proposal via `execute()`, unless `maxExecutableTime`
        // has arrived.
        Ready,
        // The proposal has been executed at least once but has further steps to
        // complete so it needs to be executed again. No other proposals may be
        // executed while a proposal is in the `InProgress` state. No voting or
        // vetoing of the proposal is allowed, however it may be forcibly cancelled
        // via `cancel()` if the `cancelDelay` has passed since being first executed.
        InProgress,
        // The proposal was executed and completed all its steps. No voting or
        // vetoing can occur and it cannot be cancelled nor executed again.
        Complete,
        // The proposal was executed at least once but did not complete before
        // `cancelDelay` seconds passed since the first execute and was forcibly cancelled.
        Cancelled
    }

    struct GovernanceOpts {
        // Address of initial party hosts.
        address[] hosts;
        // How long people can vote on a proposal.
        uint40 voteDuration;
        // How long to wait after a proposal passes before it can be
        // executed.
        uint40 executionDelay;
        // Minimum ratio of accept votes to consider a proposal passed,
        // in bps, where 10,000 == 100%.
        uint16 passThresholdBps;
        // Total voting power of governance NFTs.
        uint96 totalVotingPower;
        // Fee bps for distributions.
        uint16 feeBps;
        // Fee recipeint for distributions.
        address payable feeRecipient;
    }

    // Subset of `GovernanceOpts` that are commonly read together for
    // efficiency.
    struct GovernanceValues {
        uint40 voteDuration;
        uint40 executionDelay;
        uint16 passThresholdBps;
        uint96 totalVotingPower;
    }

    // A snapshot of voting power for a member.
    struct VotingPowerSnapshot {
        // The timestamp when the snapshot was taken.
        uint40 timestamp;
        // Voting power that was delegated to this user by others.
        uint96 delegatedVotingPower;
        // The intrinsic (not delegated from someone else) voting power of this user.
        uint96 intrinsicVotingPower;
        // Whether the user was delegated to another at this snapshot.
        bool isDelegated;
    }

    // Proposal details chosen by proposer.
    struct Proposal {
        // Time beyond which the proposal can no longer be executed.
        // If the proposal has already been executed, and is still InProgress,
        // this value is ignored.
        uint40 maxExecutableTime;
        // The minimum seconds this proposal can remain in the InProgress status
        // before it can be cancelled.
        uint40 cancelDelay;
        // Encoded proposal data. The first 4 bytes are the proposal type, followed
        // by encoded proposal args specific to the proposal type. See
        // ProposalExecutionEngine for details.
        bytes proposalData;
    }

    // Accounting and state tracking values for a proposal.
    struct ProposalStateValues {
        // When the proposal was proposed.
        uint40 proposedTime;
        // When the proposal passed the vote.
        uint40 passedTime;
        // When the proposal was first executed.
        uint40 executedTime;
        // When the proposal completed.
        uint40 completedTime;
        // Number of accept votes.
        uint96 votes; // -1 == vetoed
        // Number of total voting power at time proposal created.
        uint96 totalVotingPower;
    }

    // Storage states for a proposal.
    struct ProposalState {
        // Accounting and state tracking values.
        ProposalStateValues values;
        // Hash of the proposal.
        bytes32 hash;
        // Whether a member has voted for (accepted) this proposal already.
        mapping(address => bool) hasVoted;
    }

    event Proposed(uint256 proposalId, address proposer, Proposal proposal);
    event ProposalAccepted(uint256 proposalId, address voter, uint256 weight);
    event EmergencyExecute(address target, bytes data, uint256 amountEth);

    event ProposalPassed(uint256 indexed proposalId);
    event ProposalVetoed(uint256 indexed proposalId, address host);
    event ProposalExecuted(uint256 indexed proposalId, address executor, bytes nextProgressData);
    event ProposalCancelled(uint256 indexed proposalId);
    event DistributionCreated(
        ITokenDistributor.TokenType tokenType,
        address token,
        uint256 tokenId
    );
    event VotingPowerDelegated(address indexed owner, address indexed delegate);
    event HostStatusTransferred(address oldHost, address newHost);
    event EmergencyExecuteDisabled();

    error MismatchedPreciousListLengths();
    error BadProposalStatusError(ProposalStatus status);
    error BadProposalHashError(bytes32 proposalHash, bytes32 actualHash);
    error ExecutionTimeExceededError(uint40 maxExecutableTime, uint40 timestamp);
    error OnlyPartyHostError();
    error OnlyActiveMemberError();
    error InvalidDelegateError();
    error BadPreciousListError();
    error OnlyPartyDaoError(address notDao, address partyDao);
    error OnlyPartyDaoOrHostError(address notDao, address partyDao);
    error OnlyWhenEmergencyActionsAllowedError();
    error OnlyWhenEnabledError();
    error AlreadyVotedError(address voter);
    error InvalidNewHostError();
    error ProposalCannotBeCancelledYetError(uint40 currentTime, uint40 cancelTime);
    error InvalidBpsError(uint16 bps);
    error DistributionsRequireVoteError();
    error PartyNotStartedError();
    error CannotRageQuitAndAcceptError();

    uint256 private constant UINT40_HIGH_BIT = 1 << 39;
    uint96 private constant VETO_VALUE = type(uint96).max;

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice Whether the DAO has emergency powers for this party.
    bool public emergencyExecuteDisabled;
    /// @notice Distribution fee bps.
    uint16 public feeBps;
    /// @notice Distribution fee recipient.
    address payable public feeRecipient;
    /// @notice The timestamp of the last time `rageQuit()` was called.
    uint40 public lastRageQuitTimestamp;
    /// @notice The hash of the list of precious NFTs guarded by the party.
    bytes32 public preciousListHash;
    /// @notice The last proposal ID that was used. 0 means no proposals have been made.
    uint256 public lastProposalId;
    /// @notice Whether an address is a party host.
    mapping(address => bool) public isHost;
    /// @notice The last person a voter delegated its voting power to.
    mapping(address => address) public delegationsByVoter;
    // Governance parameters for this party.
    GovernanceValues internal _governanceValues;
    // ProposalState by proposal ID.
    mapping(uint256 => ProposalState) private _proposalStateByProposalId;
    // Snapshots of voting power per user, each sorted by increasing time.
    mapping(address => VotingPowerSnapshot[]) private _votingPowerSnapshotsByVoter;

    modifier onlyHost() {
        if (!isHost[msg.sender]) {
            revert OnlyPartyHostError();
        }
        _;
    }

    // Caller must have voting power at the current time.
    modifier onlyActiveMember() {
        {
            VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
            // Must have either delegated voting power or intrinsic voting power.
            if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) {
                revert OnlyActiveMemberError();
            }
        }
        _;
    }

    // Only the party DAO multisig can call.
    modifier onlyPartyDao() {
        {
            address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
            if (msg.sender != partyDao) {
                revert OnlyPartyDaoError(msg.sender, partyDao);
            }
        }
        _;
    }

    // Only the party DAO multisig or a party host can call.
    modifier onlyPartyDaoOrHost() {
        address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
        if (msg.sender != partyDao && !isHost[msg.sender]) {
            revert OnlyPartyDaoOrHostError(msg.sender, partyDao);
        }
        _;
    }

    // Only if `emergencyExecuteDisabled` is not true.
    modifier onlyWhenEmergencyExecuteAllowed() {
        if (emergencyExecuteDisabled) {
            revert OnlyWhenEmergencyActionsAllowedError();
        }
        _;
    }

    modifier onlyWhenNotGloballyDisabled() {
        if (_GLOBALS.getBool(LibGlobals.GLOBAL_DISABLE_PARTY_ACTIONS)) {
            revert OnlyWhenEnabledError();
        }
        _;
    }

    // Set the `Globals` contract.
    constructor(IGlobals globals) {
        _GLOBALS = globals;
    }

    // Initialize storage for proxy contracts and initialize the proposal execution engine.
    function _initialize(
        GovernanceOpts memory govOpts,
        ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal virtual {
        // Check BPS are valid.
        if (govOpts.feeBps > 1e4) {
            revert InvalidBpsError(govOpts.feeBps);
        }
        if (govOpts.passThresholdBps > 1e4) {
            revert InvalidBpsError(govOpts.passThresholdBps);
        }
        // Initialize the proposal execution engine.
        _initProposalImpl(
            IProposalExecutionEngine(_GLOBALS.getAddress(LibGlobals.GLOBAL_PROPOSAL_ENGINE_IMPL)),
            abi.encode(proposalEngineOpts)
        );
        // Set the governance parameters.
        _governanceValues = GovernanceValues({
            voteDuration: govOpts.voteDuration,
            executionDelay: govOpts.executionDelay,
            passThresholdBps: govOpts.passThresholdBps,
            totalVotingPower: govOpts.totalVotingPower
        });
        // Set fees.
        feeBps = govOpts.feeBps;
        feeRecipient = govOpts.feeRecipient;
        // Set the precious list.
        _setPreciousList(preciousTokens, preciousTokenIds);
        // Set the party hosts.
        for (uint256 i = 0; i < govOpts.hosts.length; ++i) {
            isHost[govOpts.hosts[i]] = true;
        }
    }

    /// @dev Forward all unknown read-only calls to the proposal execution engine.
    ///      Initial use case is to facilitate eip-1271 signatures.
    fallback() external {
        _readOnlyDelegateCall(address(_getSharedProposalStorage().engineImpl), msg.data);
    }

    /// @inheritdoc EIP165
    /// @dev Combined logic for `ERC721Receiver` and `ERC1155Receiver`.
    function supportsInterface(
        bytes4 interfaceId
    ) public pure virtual override(ERC721Receiver, ERC1155Receiver) returns (bool) {
        return
            ERC721Receiver.supportsInterface(interfaceId) ||
            ERC1155Receiver.supportsInterface(interfaceId);
    }

    /// @notice Get the current `ProposalExecutionEngine` instance.
    function getProposalExecutionEngine() external view returns (IProposalExecutionEngine) {
        return _getSharedProposalStorage().engineImpl;
    }

    /// @notice Get the current `ProposalEngineOpts` options.
    function getProposalEngineOpts() external view returns (ProposalEngineOpts memory) {
        return _getSharedProposalStorage().opts;
    }

    /// @notice Get the total voting power of `voter` at a `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the voting power at.
    /// @return votingPower The total voting power of `voter` at `timestamp`.
    function getVotingPowerAt(
        address voter,
        uint40 timestamp
    ) external view returns (uint96 votingPower) {
        return getVotingPowerAt(voter, timestamp, type(uint256).max);
    }

    /// @notice Get the total voting power of `voter` at a snapshot `snapIndex`, with checks to
    ///         make sure it is the latest voting snapshot =< `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the voting power at.
    /// @param snapIndex The index of the snapshot to get the voting power at.
    /// @return votingPower The total voting power of `voter` at `timestamp`.
    function getVotingPowerAt(
        address voter,
        uint40 timestamp,
        uint256 snapIndex
    ) public view returns (uint96 votingPower) {
        VotingPowerSnapshot memory snap = _getVotingPowerSnapshotAt(voter, timestamp, snapIndex);
        return (snap.isDelegated ? 0 : snap.intrinsicVotingPower) + snap.delegatedVotingPower;
    }

    /// @notice Get the state of a proposal.
    /// @param proposalId The ID of the proposal.
    /// @return status The status of the proposal.
    /// @return values The state of the proposal.
    function getProposalStateInfo(
        uint256 proposalId
    ) external view returns (ProposalStatus status, ProposalStateValues memory values) {
        values = _proposalStateByProposalId[proposalId].values;
        status = _getProposalStatus(values);
    }

    /// @notice Retrieve fixed governance parameters.
    /// @return gv The governance parameters of this party.
    function getGovernanceValues() external view returns (GovernanceValues memory gv) {
        return _governanceValues;
    }

    /// @notice Get the hash of a proposal.
    /// @dev Proposal details are not stored on-chain so the hash is used to enforce
    ///      consistency between calls.
    /// @param proposal The proposal to hash.
    /// @return proposalHash The hash of the proposal.
    function getProposalHash(Proposal memory proposal) public pure returns (bytes32 proposalHash) {
        // Hash the proposal in-place. Equivalent to:
        // keccak256(abi.encode(
        //   proposal.maxExecutableTime,
        //   proposal.cancelDelay,
        //   keccak256(proposal.proposalData)
        // ))
        bytes32 dataHash = keccak256(proposal.proposalData);
        assembly {
            // Overwrite the data field with the hash of its contents and then
            // hash the struct.
            let dataPos := add(proposal, 0x40)
            let t := mload(dataPos)
            mstore(dataPos, dataHash)
            proposalHash := keccak256(proposal, 0x60)
            // Restore the data field.
            mstore(dataPos, t)
        }
    }

    /// @notice Get the index of the most recent voting power snapshot <= `timestamp`.
    /// @param voter The address of the voter.
    /// @param timestamp The timestamp to get the snapshot index at.
    /// @return index The index of the snapshot.
    function findVotingPowerSnapshotIndex(
        address voter,
        uint40 timestamp
    ) public view returns (uint256 index) {
        VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter];

        // Derived from Open Zeppelin binary search
        // ref: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Checkpoints.sol#L39
        uint256 high = snaps.length;
        uint256 low = 0;
        while (low < high) {
            uint256 mid = (low + high) / 2;
            if (snaps[mid].timestamp > timestamp) {
                // Entry is too recent.
                high = mid;
            } else {
                // Entry is older. This is our best guess for now.
                low = mid + 1;
            }
        }

        // Return `type(uint256).max` if no valid voting snapshots found.
        return high == 0 ? type(uint256).max : high - 1;
    }

    /// @notice Pledge your intrinsic voting power to a new delegate, removing it from
    ///         the old one (if any).
    /// @param delegate The address to delegating voting power to.
    function delegateVotingPower(address delegate) external {
        _adjustVotingPower(msg.sender, 0, delegate);
        emit VotingPowerDelegated(msg.sender, delegate);
    }

    /// @notice Transfer party host status to another.
    /// @param newPartyHost The address of the new host.
    function abdicateHost(address newPartyHost) external onlyHost {
        // 0 is a special case burn address.
        if (newPartyHost != address(0)) {
            // Cannot transfer host status to an existing host.
            if (isHost[newPartyHost]) {
                revert InvalidNewHostError();
            }
            isHost[newPartyHost] = true;
        }
        isHost[msg.sender] = false;
        emit HostStatusTransferred(msg.sender, newPartyHost);
    }

    /// @notice Create a token distribution by moving the party's entire balance
    ///         to the `TokenDistributor` contract and immediately creating a
    ///         distribution governed by this party.
    /// @dev The `feeBps` and `feeRecipient` this party was created with will be
    ///      propagated to the distribution. Party members are entitled to a
    ///      share of the distribution's tokens proportionate to their relative
    ///      voting power in this party (less the fee).
    /// @dev Allow this to be called by the party itself for `FractionalizeProposal`.
    /// @param tokenType The type of token to distribute.
    /// @param token The address of the token to distribute.
    /// @param tokenId The ID of the token to distribute. Currently unused but
    ///                may be used in the future to support other distribution types.
    /// @return distInfo The information about the created distribution.
    function distribute(
        uint256 amount,
        ITokenDistributor.TokenType tokenType,
        address token,
        uint256 tokenId
    )
        external
        onlyWhenNotGloballyDisabled
        returns (ITokenDistributor.DistributionInfo memory distInfo)
    {
        // Ignore if the party is calling functions on itself, like with
        // `FractionalizeProposal` and `DistributionProposal`.
        if (msg.sender != address(this)) {
            // Must not require a vote to create a distribution, otherwise
            // distributions can only be created through a distribution
            // proposal.
            if (_getSharedProposalStorage().opts.distributionsRequireVote) {
                revert DistributionsRequireVoteError();
            }
            // Must be an active member.
            VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
            if (snap.intrinsicVotingPower == 0 && snap.delegatedVotingPower == 0) {
                revert OnlyActiveMemberError();
            }
        }
        // Prevent creating a distribution if the party has not started.
        if (_governanceValues.totalVotingPower == 0) {
            revert PartyNotStartedError();
        }
        // Get the address of the token distributor.
        ITokenDistributor distributor = ITokenDistributor(
            _GLOBALS.getAddress(LibGlobals.GLOBAL_TOKEN_DISTRIBUTOR)
        );
        emit DistributionCreated(tokenType, token, tokenId);
        // Create a native token distribution.
        address payable feeRecipient_ = feeRecipient;
        uint16 feeBps_ = feeBps;
        if (tokenType == ITokenDistributor.TokenType.Native) {
            return
                distributor.createNativeDistribution{ value: amount }(
                    Party(payable(address(this))),
                    feeRecipient_,
                    feeBps_
                );
        }
        // Otherwise must be an ERC20 token distribution.
        assert(tokenType == ITokenDistributor.TokenType.Erc20);
        IERC20(token).compatTransfer(address(distributor), amount);
        return
            distributor.createErc20Distribution(
                IERC20(token),
                Party(payable(address(this))),
                feeRecipient_,
                feeBps_
            );
    }

    /// @notice Make a proposal for members to vote on and cast a vote to accept it
    ///         as well.
    /// @dev Only an active member (has voting power) can call this.
    ///      Afterwards, members can vote to support it with `accept()` or a party
    ///      host can unilaterally reject the proposal with `veto()`.
    /// @param proposal The details of the proposal.
    /// @param latestSnapIndex The index of the caller's most recent voting power
    ///                        snapshot before the proposal was created. Should
    ///                        be retrieved off-chain and passed in.
    function propose(
        Proposal memory proposal,
        uint256 latestSnapIndex
    ) external onlyActiveMember returns (uint256 proposalId) {
        proposalId = ++lastProposalId;
        // Store the time the proposal was created and the proposal hash.
        (
            _proposalStateByProposalId[proposalId].values,
            _proposalStateByProposalId[proposalId].hash
        ) = (
            ProposalStateValues({
                proposedTime: uint40(block.timestamp),
                passedTime: 0,
                executedTime: 0,
                completedTime: 0,
                votes: 0,
                totalVotingPower: _governanceValues.totalVotingPower
            }),
            getProposalHash(proposal)
        );
        emit Proposed(proposalId, msg.sender, proposal);
        accept(proposalId, latestSnapIndex);
    }

    /// @notice Vote to support a proposed proposal.
    /// @dev The voting power cast will be the effective voting power of the caller
    ///      just before `propose()` was called (see `getVotingPowerAt()`).
    ///      If the proposal reaches `passThresholdBps` acceptance ratio then the
    ///      proposal will be in the `Passed` state and will be executable after
    ///      the `executionDelay` has passed, putting it in the `Ready` state.
    /// @param proposalId The ID of the proposal to accept.
    /// @param snapIndex The index of the caller's last voting power snapshot
    ///                  before the proposal was created. Should be retrieved
    ///                  off-chain and passed in.
    /// @return totalVotes The total votes cast on the proposal.
    function accept(uint256 proposalId, uint256 snapIndex) public returns (uint256 totalVotes) {
        // Get the information about the proposal.
        ProposalState storage info = _proposalStateByProposalId[proposalId];
        ProposalStateValues memory values = info.values;

        // Can only vote in certain proposal statuses.
        {
            ProposalStatus status = _getProposalStatus(values);
            // Allow voting even if the proposal is passed/ready so it can
            // potentially reach 100% consensus, which unlocks special
            // behaviors for certain proposal types.
            if (
                status != ProposalStatus.Voting &&
                status != ProposalStatus.Passed &&
                status != ProposalStatus.Ready
            ) {
                revert BadProposalStatusError(status);
            }
        }

        // Prevent voting in the same block as the last rage quit timestamp.
        // This is to prevent an exploit where a member can rage quit to reduce
        // the total voting power of the party, then propose and vote in the
        // same block since `getVotingPowerAt()` uses `values.proposedTime - 1`.
        // This would allow them to use the voting power snapshot just before
        // their card was burned to vote, potentially passing a proposal that
        // would have otherwise not passed.
        if (lastRageQuitTimestamp == block.timestamp) {
            revert CannotRageQuitAndAcceptError();
        }

        // Cannot vote twice.
        if (info.hasVoted[msg.sender]) {
            revert AlreadyVotedError(msg.sender);
        }
        // Mark the caller as having voted.
        info.hasVoted[msg.sender] = true;

        // Increase the total votes that have been cast on this proposal.
        uint96 votingPower = getVotingPowerAt(msg.sender, values.proposedTime - 1, snapIndex);
        values.votes += votingPower;
        info.values = values;
        emit ProposalAccepted(proposalId, msg.sender, votingPower);

        // Update the proposal status if it has reached the pass threshold.
        if (
            values.passedTime == 0 &&
            _areVotesPassing(
                values.votes,
                values.totalVotingPower,
                _governanceValues.passThresholdBps
            )
        ) {
            info.values.passedTime = uint40(block.timestamp);
            emit ProposalPassed(proposalId);
        }
        return values.votes;
    }

    /// @notice As a party host, veto a proposal, unilaterally rejecting it.
    /// @dev The proposal will never be executable and cannot be voted on anymore.
    ///      A proposal that has been already executed at least once (in the `InProgress` status)
    ///      cannot be vetoed.
    /// @param proposalId The ID of the proposal to veto.
    function veto(uint256 proposalId) external onlyHost {
        // Setting `votes` to -1 indicates a veto.
        ProposalState storage info = _proposalStateByProposalId[proposalId];
        ProposalStateValues memory values = info.values;

        {
            ProposalStatus status = _getProposalStatus(values);
            // Proposal must be in one of the following states.
            if (
                status != ProposalStatus.Voting &&
                status != ProposalStatus.Passed &&
                status != ProposalStatus.Ready
            ) {
                revert BadProposalStatusError(status);
            }
        }

        // -1 indicates veto.
        info.values.votes = VETO_VALUE;
        emit ProposalVetoed(proposalId, msg.sender);
    }

    /// @notice Executes a proposal that has passed governance.
    /// @dev The proposal must be in the `Ready` or `InProgress` status.
    ///      A `ProposalExecuted` event will be emitted with a non-empty `nextProgressData`
    ///      if the proposal has extra steps (must be executed again) to carry out,
    ///      in which case `nextProgressData` should be passed into the next `execute()` call.
    ///      The `ProposalExecutionEngine` enforces that only one `InProgress` proposal
    ///      is active at a time, so that proposal must be completed or cancelled via `cancel()`
    ///      in order to execute a different proposal.
    ///      `extraData` is optional, off-chain data a proposal might need to execute a step.
    /// @param proposalId The ID of the proposal to execute.
    /// @param proposal The details of the proposal.
    /// @param preciousTokens The tokens that the party considers precious.
    /// @param preciousTokenIds The token IDs associated with each precious token.
    /// @param progressData The data returned from the last `execute()` call, if any.
    /// @param extraData Off-chain data a proposal might need to execute a step.
    function execute(
        uint256 proposalId,
        Proposal memory proposal,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        bytes calldata progressData,
        bytes calldata extraData
    ) external payable onlyActiveMember onlyWhenNotGloballyDisabled onlyDelegateCall {
        // Get information about the proposal.
        ProposalState storage proposalState = _proposalStateByProposalId[proposalId];
        // Proposal details must remain the same from `propose()`.
        _validateProposalHash(proposal, proposalState.hash);
        ProposalStateValues memory values = proposalState.values;
        ProposalStatus status = _getProposalStatus(values);
        // The proposal must be executable or have already been executed but still
        // has more steps to go.
        if (status != ProposalStatus.Ready && status != ProposalStatus.InProgress) {
            revert BadProposalStatusError(status);
        }
        if (status == ProposalStatus.Ready) {
            // If the proposal has not been executed yet, make sure it hasn't
            // expired. Note that proposals that have been executed
            // (but still have more steps) ignore `maxExecutableTime`.
            if (proposal.maxExecutableTime < block.timestamp) {
                revert ExecutionTimeExceededError(
                    proposal.maxExecutableTime,
                    uint40(block.timestamp)
                );
            }
            proposalState.values.executedTime = uint40(block.timestamp);
        }
        // Check that the precious list is valid.
        if (!_isPreciousListCorrect(preciousTokens, preciousTokenIds)) {
            revert BadPreciousListError();
        }
        // Preemptively set the proposal to completed to avoid it being executed
        // again in a deeper call.
        proposalState.values.completedTime = uint40(block.timestamp);
        // Execute the proposal.
        bool completed = _executeProposal(
            proposalId,
            proposal,
            preciousTokens,
            preciousTokenIds,
            _getProposalFlags(values),
            progressData,
            extraData
        );
        if (!completed) {
            // Proposal did not complete.
            proposalState.values.completedTime = 0;
        }
    }

    /// @notice Cancel a (probably stuck) InProgress proposal.
    /// @dev `proposal.cancelDelay` seconds must have passed since it was first
    ///      executed for this to be valid. The currently active proposal will
    ///      simply be yeeted out of existence so another proposal can execute.
    ///      This is intended to be a last resort and can leave the party in a
    ///      broken state. Whenever possible, active proposals should be
    ///      allowed to complete their lifecycle.
    /// @param proposalId The ID of the proposal to cancel.
    /// @param proposal The details of the proposal to cancel.
    function cancel(uint256 proposalId, Proposal calldata proposal) external onlyActiveMember {
        // Get information about the proposal.
        ProposalState storage proposalState = _proposalStateByProposalId[proposalId];
        // Proposal details must remain the same from `propose()`.
        _validateProposalHash(proposal, proposalState.hash);
        ProposalStateValues memory values = proposalState.values;
        {
            // Must be `InProgress`.
            ProposalStatus status = _getProposalStatus(values);
            if (status != ProposalStatus.InProgress) {
                revert BadProposalStatusError(status);
            }
        }
        {
            // Limit the `cancelDelay` to the global max and min cancel delay
            // to mitigate parties accidentally getting stuck forever by setting an
            // unrealistic `cancelDelay` or being reckless with too low a
            // cancel delay.
            uint256 cancelDelay = proposal.cancelDelay;
            uint256 globalMaxCancelDelay = _GLOBALS.getUint256(
                LibGlobals.GLOBAL_PROPOSAL_MAX_CANCEL_DURATION
            );
            uint256 globalMinCancelDelay = _GLOBALS.getUint256(
                LibGlobals.GLOBAL_PROPOSAL_MIN_CANCEL_DURATION
            );
            if (globalMaxCancelDelay != 0) {
                // Only if we have one set.
                if (cancelDelay > globalMaxCancelDelay) {
                    cancelDelay = globalMaxCancelDelay;
                }
            }
            if (globalMinCancelDelay != 0) {
                // Only if we have one set.
                if (cancelDelay < globalMinCancelDelay) {
                    cancelDelay = globalMinCancelDelay;
                }
            }
            uint256 cancelTime = values.executedTime + cancelDelay;
            // Must not be too early.
            if (block.timestamp < cancelTime) {
                revert ProposalCannotBeCancelledYetError(
                    uint40(block.timestamp),
                    uint40(cancelTime)
                );
            }
        }
        // Mark the proposal as cancelled by setting the completed time to the current
        // time with the high bit set.
        proposalState.values.completedTime = uint40(block.timestamp | UINT40_HIGH_BIT);
        {
            // Delegatecall into the proposal engine impl to perform the cancel.
            (bool success, bytes memory resultData) = (
                address(_getSharedProposalStorage().engineImpl)
            ).delegatecall(abi.encodeCall(IProposalExecutionEngine.cancelProposal, (proposalId)));
            if (!success) {
                resultData.rawRevert();
            }
        }
        emit ProposalCancelled(proposalId);
    }

    /// @notice As the DAO, execute an arbitrary function call from this contract.
    /// @dev Emergency actions must not be revoked for this to work.
    /// @param targetAddress The contract to call.
    /// @param targetCallData The data to pass to the contract.
    /// @param amountEth The amount of ETH to send to the contract.
    function emergencyExecute(
        address targetAddress,
        bytes calldata targetCallData,
        uint256 amountEth
    ) external payable onlyPartyDao onlyWhenEmergencyExecuteAllowed onlyDelegateCall {
        (bool success, bytes memory res) = targetAddress.call{ value: amountEth }(targetCallData);
        if (!success) {
            res.rawRevert();
        }
        emit EmergencyExecute(targetAddress, targetCallData, amountEth);
    }

    /// @notice Revoke the DAO's ability to call emergencyExecute().
    /// @dev Either the DAO or the party host can call this.
    function disableEmergencyExecute() external onlyPartyDaoOrHost {
        emergencyExecuteDisabled = true;
        emit EmergencyExecuteDisabled();
    }

    function _executeProposal(
        uint256 proposalId,
        Proposal memory proposal,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        uint256 flags,
        bytes memory progressData,
        bytes memory extraData
    ) private returns (bool completed) {
        // Setup the arguments for the proposal execution engine.
        IProposalExecutionEngine.ExecuteProposalParams
            memory executeParams = IProposalExecutionEngine.ExecuteProposalParams({
                proposalId: proposalId,
                proposalData: proposal.proposalData,
                progressData: progressData,
                extraData: extraData,
                preciousTokens: preciousTokens,
                preciousTokenIds: preciousTokenIds,
                flags: flags
            });
        // Get the progress data returned after the proposal is executed.
        bytes memory nextProgressData;
        {
            // Execute the proposal.
            (bool success, bytes memory resultData) = address(
                _getSharedProposalStorage().engineImpl
            ).delegatecall(
                    abi.encodeCall(IProposalExecutionEngine.executeProposal, (executeParams))
                );
            if (!success) {
                resultData.rawRevert();
            }
            nextProgressData = abi.decode(resultData, (bytes));
        }
        emit ProposalExecuted(proposalId, msg.sender, nextProgressData);
        // If the returned progress data is empty, then the proposal completed
        // and it should not be executed again.
        return nextProgressData.length == 0;
    }

    // Get the most recent voting power snapshot <= timestamp using `hintindex` as a "hint".
    function _getVotingPowerSnapshotAt(
        address voter,
        uint40 timestamp,
        uint256 hintIndex
    ) internal view returns (VotingPowerSnapshot memory snap) {
        VotingPowerSnapshot[] storage snaps = _votingPowerSnapshotsByVoter[voter];
        uint256 snapsLength = snaps.length;
        if (snapsLength != 0) {
            if (
                // Hint is within bounds.
                hintIndex < snapsLength &&
                // Snapshot is not too recent.
                snaps[hintIndex].timestamp <= timestamp &&
                // Snapshot is not too old.
                (hintIndex == snapsLength - 1 || snaps[hintIndex + 1].timestamp > timestamp)
            ) {
                return snaps[hintIndex];
            }

            // Hint was wrong, fallback to binary search to find snapshot.
            hintIndex = findVotingPowerSnapshotIndex(voter, timestamp);
            // Check that snapshot was found.
            if (hintIndex != type(uint256).max) {
                return snaps[hintIndex];
            }
        }

        // No snapshot found.
        return snap;
    }

    // Transfers some voting power of `from` to `to`. The total voting power of
    // their respective delegates will be updated as well.
    function _transferVotingPower(address from, address to, uint256 power) internal {
        int192 powerI192 = power.safeCastUint256ToInt192();
        _adjustVotingPower(from, -powerI192, address(0));
        _adjustVotingPower(to, powerI192, address(0));
    }

    // Increase `voter`'s intrinsic voting power and update their delegate if delegate is nonzero.
    function _adjustVotingPower(address voter, int192 votingPower, address delegate) internal {
        VotingPowerSnapshot memory oldSnap = _getLastVotingPowerSnapshotForVoter(voter);
        address oldDelegate = delegationsByVoter[voter];
        // If `oldDelegate` is zero and `voter` never delegated, then have
        // `voter` delegate to themself.
        oldDelegate = oldDelegate == address(0) ? voter : oldDelegate;
        // If the new `delegate` is zero, use the current (old) delegate.
        delegate = delegate == address(0) ? oldDelegate : delegate;

        VotingPowerSnapshot memory newSnap = VotingPowerSnapshot({
            timestamp: uint40(block.timestamp),
            delegatedVotingPower: oldSnap.delegatedVotingPower,
            intrinsicVotingPower: (oldSnap.intrinsicVotingPower.safeCastUint96ToInt192() +
                votingPower).safeCastInt192ToUint96(),
            isDelegated: delegate != voter
        });
        _insertVotingPowerSnapshot(voter, newSnap);
        delegationsByVoter[voter] = delegate;
        // Handle rebalancing delegates.
        _rebalanceDelegates(voter, oldDelegate, delegate, oldSnap, newSnap);
    }

    // Update the delegated voting power of the old and new delegates delegated to
    // by `voter` based on the snapshot change.
    function _rebalanceDelegates(
        address voter,
        address oldDelegate,
        address newDelegate,
        VotingPowerSnapshot memory oldSnap,
        VotingPowerSnapshot memory newSnap
    ) private {
        if (newDelegate == address(0) || oldDelegate == address(0)) {
            revert InvalidDelegateError();
        }
        if (oldDelegate != voter && oldDelegate != newDelegate) {
            // Remove past voting power from old delegate.
            VotingPowerSnapshot memory oldDelegateSnap = _getLastVotingPowerSnapshotForVoter(
                oldDelegate
            );
            VotingPowerSnapshot memory updatedOldDelegateSnap = VotingPowerSnapshot({
                timestamp: uint40(block.timestamp),
                delegatedVotingPower: oldDelegateSnap.delegatedVotingPower -
                    oldSnap.intrinsicVotingPower,
                intrinsicVotingPower: oldDelegateSnap.intrinsicVotingPower,
                isDelegated: oldDelegateSnap.isDelegated
            });
            _insertVotingPowerSnapshot(oldDelegate, updatedOldDelegateSnap);
        }
        if (newDelegate != voter) {
            // Not delegating to self.
            // Add new voting power to new delegate.
            VotingPowerSnapshot memory newDelegateSnap = _getLastVotingPowerSnapshotForVoter(
                newDelegate
            );
            uint96 newDelegateDelegatedVotingPower = newDelegateSnap.delegatedVotingPower +
                newSnap.intrinsicVotingPower;
            if (newDelegate == oldDelegate) {
                // If the old and new delegate are the same, subtract the old
                // intrinsic voting power of the voter, or else we will double
                // count a portion of it.
                newDelegateDelegatedVotingPower -= oldSnap.intrinsicVotingPower;
            }
            VotingPowerSnapshot memory updatedNewDelegateSnap = VotingPowerSnapshot({
                timestamp: uint40(block.timestamp),
                delegatedVotingPower: newDelegateDelegatedVotingPower,
                intrinsicVotingPower: newDelegateSnap.intrinsicVotingPower,
                isDelegated: newDelegateSnap.isDelegated
            });
            _insertVotingPowerSnapshot(newDelegate, updatedNewDelegateSnap);
        }
    }

    // Append a new voting power snapshot, overwriting the last one if possible.
    function _insertVotingPowerSnapshot(address voter, VotingPowerSnapshot memory snap) private {
        VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter];
        uint256 n = voterSnaps.length;
        // If same timestamp as last entry, overwrite the last snapshot, otherwise append.
        if (n != 0) {
            VotingPowerSnapshot memory lastSnap = voterSnaps[n - 1];
            if (lastSnap.timestamp == snap.timestamp) {
                voterSnaps[n - 1] = snap;
                return;
            }
        }
        voterSnaps.push(snap);
    }

    function _getLastVotingPowerSnapshotForVoter(
        address voter
    ) private view returns (VotingPowerSnapshot memory snap) {
        VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter];
        uint256 n = voterSnaps.length;
        if (n != 0) {
            snap = voterSnaps[n - 1];
        }
    }

    function _getProposalFlags(ProposalStateValues memory pv) private pure returns (uint256) {
        if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
            return LibProposal.PROPOSAL_FLAG_UNANIMOUS;
        }
        return 0;
    }

    function _getProposalStatus(
        ProposalStateValues memory pv
    ) private view returns (ProposalStatus status) {
        // Never proposed.
        if (pv.proposedTime == 0) {
            return ProposalStatus.Invalid;
        }
        // Executed at least once.
        if (pv.executedTime != 0) {
            if (pv.completedTime == 0) {
                return ProposalStatus.InProgress;
            }
            // completedTime high bit will be set if cancelled.
            if (pv.completedTime & UINT40_HIGH_BIT == UINT40_HIGH_BIT) {
                return ProposalStatus.Cancelled;
            }
            return ProposalStatus.Complete;
        }
        // Vetoed.
        if (pv.votes == type(uint96).max) {
            return ProposalStatus.Defeated;
        }
        uint40 t = uint40(block.timestamp);
        GovernanceValues memory gv = _governanceValues;
        if (pv.passedTime != 0) {
            // Ready.
            if (pv.passedTime + gv.executionDelay <= t) {
                return ProposalStatus.Ready;
            }
            // If unanimous, we skip the execution delay.
            if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
                return ProposalStatus.Ready;
            }
            // Passed.
            return ProposalStatus.Passed;
        }
        // Voting window expired.
        if (pv.proposedTime + gv.voteDuration <= t) {
            return ProposalStatus.Defeated;
        }
        return ProposalStatus.Voting;
    }

    function _isUnanimousVotes(
        uint96 totalVotes,
        uint96 totalVotingPower
    ) private pure returns (bool) {
        uint256 acceptanceRatio = (totalVotes * 1e4) / totalVotingPower;
        // If >= 99.99% acceptance, consider it unanimous.
        // The minting formula for voting power is a bit lossy, so we check
        // for slightly less than 100%.
        return acceptanceRatio >= 0.9999e4;
    }

    function _areVotesPassing(
        uint96 voteCount,
        uint96 totalVotingPower,
        uint16 passThresholdBps
    ) private pure returns (bool) {
        return (uint256(voteCount) * 1e4) / uint256(totalVotingPower) >= uint256(passThresholdBps);
    }

    function _setPreciousList(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) private {
        if (preciousTokens.length != preciousTokenIds.length) {
            revert MismatchedPreciousListLengths();
        }
        preciousListHash = _hashPreciousList(preciousTokens, preciousTokenIds);
    }

    function _isPreciousListCorrect(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) private view returns (bool) {
        return preciousListHash == _hashPreciousList(preciousTokens, preciousTokenIds);
    }

    function _hashPreciousList(
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal pure returns (bytes32 h) {
        assembly {
            mstore(0x00, keccak256(add(preciousTokens, 0x20), mul(mload(preciousTokens), 0x20)))
            mstore(0x20, keccak256(add(preciousTokenIds, 0x20), mul(mload(preciousTokenIds), 0x20)))
            h := keccak256(0x00, 0x40)
        }
    }

    // Assert that the hash of a proposal matches expectedHash.
    function _validateProposalHash(Proposal memory proposal, bytes32 expectedHash) private pure {
        bytes32 actualHash = getProposalHash(proposal);
        if (expectedHash != actualHash) {
            revert BadProposalHashError(actualHash, expectedHash);
        }
    }
}

File 16 of 35 : IGlobals.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/Implementation.sol";

// Single registry of global values controlled by multisig.
// See `LibGlobals` for all valid keys.
interface IGlobals {
    function multiSig() external view returns (address);

    function getBytes32(uint256 key) external view returns (bytes32);

    function getUint256(uint256 key) external view returns (uint256);

    function getBool(uint256 key) external view returns (bool);

    function getAddress(uint256 key) external view returns (address);

    function getImplementation(uint256 key) external view returns (Implementation);

    function getIncludesBytes32(uint256 key, bytes32 value) external view returns (bool);

    function getIncludesUint256(uint256 key, uint256 value) external view returns (bool);

    function getIncludesAddress(uint256 key, address value) external view returns (bool);

    function setBytes32(uint256 key, bytes32 value) external;

    function setUint256(uint256 key, uint256 value) external;

    function setBool(uint256 key, bool value) external;

    function setAddress(uint256 key, address value) external;

    function setIncludesBytes32(uint256 key, bytes32 value, bool isIncluded) external;

    function setIncludesUint256(uint256 key, uint256 value, bool isIncluded) external;

    function setIncludesAddress(uint256 key, address value, bool isIncluded) external;
}

File 17 of 35 : RendererStorage.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "solmate/utils/SSTORE2.sol";
import "../utils/Multicall.sol";

contract RendererStorage is Multicall {
    error AlreadySetError();
    error NotOwnerError(address caller, address owner);

    event OwnershipTransferred(address previousOwner, address newOwner);

    uint256 constant CROWDFUND_CARD_DATA = 0;
    uint256 constant PARTY_CARD_DATA = 1;

    /// @notice Address allowed to store new data.
    address public owner;

    /// @notice Customization presets by ID, used for rendering cards. Begins at
    ///         1, 0 is reserved to indicate in `getPresetFor()` that a
    ///         party instance use the preset set by the crowdfund instance that
    ///         created it.
    mapping(uint256 => bytes) public customizationPresets;
    /// @notice Customization preset used by a crowdfund or party instance.
    mapping(address => uint256) public getPresetFor;
    /// @notice Addresses where URI data chunks are stored.
    mapping(uint256 => address) public files;

    modifier onlyOwner() {
        address owner_ = owner;
        if (msg.sender != owner_) {
            revert NotOwnerError(msg.sender, owner_);
        }

        _;
    }

    constructor(address _owner) {
        // Set the address allowed to write new data.
        owner = _owner;

        // Write URI data used by V1 of the renderers:

        files[CROWDFUND_CARD_DATA] = SSTORE2.write(
            bytes(
                '<path class="o" d="M118.4 419.5h5.82v1.73h-4.02v1.87h3.74v1.73h-3.74v1.94h4.11v1.73h-5.91v-9Zm9.93 1.76h-2.6v-1.76h7.06v1.76h-2.61v7.24h-1.85v-7.24Zm6.06-1.76h1.84v3.55h3.93v-3.55H142v9h-1.84v-3.67h-3.93v3.67h-1.84v-9Z"/><path class="o" d="M145 413a4 4 0 0 1 4 4v14a4 4 0 0 1-4 4H35a4 4 0 0 1-4-4v-14a4 4 0 0 1 4-4h110m0-1H35a5 5 0 0 0-5 5v14a5 5 0 0 0 5 5h110a5 5 0 0 0 5-5v-14a5 5 0 0 0-5-5Z"/><path d="M239.24 399.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.48-6.8a42.14 42.14 0 0 0-.75 6.01 43.12 43.12 0 0 0 5.58 2.35 42.54 42.54 0 0 0 5.58-2.35 45.32 45.32 0 0 0-.75-6.01c-.91-.79-2.6-2.21-4.83-3.66a42.5 42.5 0 0 0-4.83 3.66Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75ZM330 391.84l-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><use height="300" transform="matrix(1 0 0 .09 29.85 444)" width="300.15" xlink:href="#a"/><use height="21.15" transform="translate(30 446.92)" width="300" xlink:href="#b"/><g><path d="m191.54 428.67-28.09-24.34A29.98 29.98 0 0 0 143.8 397H30a15 15 0 0 0-15 15v98a15 15 0 0 0 15 15h300a15 15 0 0 0 15-15v-59a15 15 0 0 0-15-15H211.19a30 30 0 0 1-19.65-7.33Z" style="fill:url(#i)"/></g></svg>'
            )
        );

        files[PARTY_CARD_DATA] = SSTORE2.write(
            bytes(
                ' d="M188 444.3h2.4l2.6 8.2 2.7-8.2h2.3l-3.7 10.7h-2.8l-3.5-10.7zm10.5 5.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm8.7-6.7h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm6.9-2.1h2.2V455h-2.2v-10.7zm4.3 0h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm10.6 5.4c0-3.4 2.3-5.6 6-5.6 1.2 0 2.3.2 3.1.6v2.3c-.9-.6-1.9-.8-3.1-.8-2.4 0-3.8 1.3-3.8 3.5 0 2.1 1.3 3.4 3.5 3.4.5 0 .9-.1 1.3-.2v-2.2h-2.2v-1.9h4.3v5.6c-1 .5-2.2.8-3.4.8-3.5 0-5.7-2.2-5.7-5.5zm15.1-5.4h4.3c2.3 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm4.8.3c0-3.2 2.2-5.6 5.3-5.6 3.1 0 5.3 2.3 5.3 5.6 0 3.2-2.2 5.5-5.3 5.5-3.1.1-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.4 0-2.1-1.1-3.5-3-3.5s-3 1.3-3 3.5c0 2.1 1.1 3.4 3 3.4zm5.8-8.8h2.3l1.7 7.8 1.9-7.8h2.4l1.8 7.8 1.8-7.8h2.3l-2.7 10.7h-2.5l-1.9-8.2-1.8 8.2h-2.5l-2.8-10.7zm15.4 0h6.9v2.1H287v2.2h4.5v2.1H287v2.3h4.9v2.1h-7v-10.8zm9 0h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4 0-.9-.6-1.4-1.5-1.4h-2v2.9h2zM30 444.3h4.3c3 0 5.2 2.1 5.2 5.4s-2.1 5.4-5.2 5.4H30v-10.8zm4 8.6c2.1 0 3.2-1.2 3.2-3.2s-1.2-3.3-3.2-3.3h-1.8v6.5H34zm7.7-8.6h2.2V455h-2.2v-10.7zm4.8 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12-7.9h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4H66v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm6.1-4.8h2.2V455h-2.2v-10.7zm5 0h4.5c2 0 3.2 1.1 3.2 2.8 0 1.1-.5 1.9-1.4 2.3 1.1.3 1.8 1.3 1.8 2.5 0 1.9-1.3 3.1-3.5 3.1h-4.6v-10.7zm4.2 4.4c.9 0 1.4-.5 1.4-1.3s-.5-1.3-1.4-1.3h-2.1v2.5l2.1.1zm.3 4.4c.9 0 1.5-.5 1.5-1.3s-.6-1.3-1.5-1.3h-2.4v2.6h2.4zm5.7-2.5v-6.3h2.2v6.3c0 1.6.9 2.5 2.3 2.5s2.3-.9 2.3-2.5v-6.3h2.2v6.3c0 2.9-1.7 4.6-4.5 4.6s-4.6-1.7-4.5-4.6zm14.2-4.2h-3.1v-2.1h8.4v2.1h-3.1v8.6h-2.2v-8.6zm7.5-2.1h2.2V455h-2.2v-10.7zm4.5 5.3c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.5-8.8h2.9l4 8.2v-8.2h2.1V455h-2.9l-4-8.2v8.2h-2.1v-10.7zm11.7 10V452c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.6-1.5-2.6-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4a9.7 9.7 0 0 1-3.4-1.1zM30 259.3h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7H30v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.5-1.5-1.6-1.5h-1.9v2.9h1.9zm6.1-5h4.5c2 0 3.3 1.3 3.3 3.2 0 1.9-1.2 3.1-3 3.2l3.5 4.3h-2.7l-3.5-4.4v4.4h-2.1v-10.7zm4.1 4.8c1 0 1.5-.5 1.5-1.4s-.6-1.4-1.5-1.4h-2v2.9h2zm5.4.5c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.2-5.3-5.5zm5.3 3.5c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.6-8.8h4.3c2.2 0 3.7 1.3 3.7 3.5s-1.4 3.5-3.7 3.5h-2.1v3.7h-2.2v-10.7zm4.1 5c1.1 0 1.6-.5 1.6-1.5s-.6-1.5-1.6-1.5h-1.9v2.9h1.9zm5.4.4c0-3.2 2.2-5.6 5.3-5.6s5.3 2.3 5.3 5.6-2.2 5.5-5.3 5.5-5.3-2.3-5.3-5.5zm5.4 3.4c1.8 0 3-1.3 3-3.5s-1.2-3.5-3-3.5-3 1.3-3 3.5 1.1 3.5 3 3.5zm7.2 1.2V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm12.2-10h2.8l3.7 10.7h-2.3l-.8-2.5h-4l-.8 2.5h-2.2l3.6-10.7zm2.8 6.3-1.4-4.2-1.4 4.2h2.8zm5.7-6.3h2.2v8.6h4.7v2.1h-6.9v-10.7zm9.1 10V267c1 .7 2.1 1.1 3.2 1.1s1.7-.5 1.7-1.2-.4-1-1.2-1.2l-1.2-.3c-1.8-.5-2.7-1.5-2.7-3.1 0-2 1.5-3.2 3.9-3.2 1.1 0 2.1.2 2.9.7v2.3c-.9-.6-1.9-.8-3-.8-.9 0-1.6.4-1.6 1.1 0 .6.4.9 1.2 1.1l1.3.4c1.8.5 2.6 1.4 2.6 3.1 0 2.1-1.5 3.4-3.8 3.4-1.1-.2-2.3-.5-3.3-1.1zm-84.5-70h2.9l4 8.2v-8.2H39V210h-2.9l-4-8.2v8.2H30v-10.7zm14.7 0h2.8l3.7 10.7h-2.3l-.8-2.6h-4l-.8 2.6H41l3.7-10.7zm2.8 6.2-1.4-4.2-1.4 4.2h2.8zm5.7-6.2h3.3l2.5 8.2 2.5-8.2h3.3V210h-2v-8.6L60 210h-2.1l-2.7-8.5v8.5h-2v-10.7zm14.4 0h6.9v2.1h-4.8v2.2h4.4v2.1h-4.4v2.3h4.9v2.1h-7v-10.8z" /><path d="M239.24 24.83h3.04c1.7 0 2.82 1 2.82 2.55 0 2.1-1.27 3.32-3.57 3.32h-1.97l-.71 3.3h-1.56l1.96-9.17Zm2.34 4.38c1.23 0 1.88-.58 1.88-1.68 0-.73-.49-1.2-1.48-1.2h-1.51l-.6 2.88h1.7Zm3.57 1.86c0-2.27 1.44-3.83 3.57-3.83 1.82 0 3.06 1.25 3.06 3.09 0 2.28-1.43 3.83-3.57 3.83-1.82 0-3.06-1.25-3.06-3.09Zm3.13 1.74c1.19 0 1.93-1.02 1.93-2.52 0-1.06-.62-1.69-1.56-1.69-1.19 0-1.93 1.02-1.93 2.52 0 1.06.62 1.69 1.56 1.69Zm4.74-5.41h1.49l.28 4.73 2.25-4.73h1.64l.23 4.77 2.25-4.77h1.56l-3.3 6.61h-1.62l-.25-5.04-2.42 5.04h-1.63l-.48-6.61Zm9.54 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm3.46-2.59h1.55l-.28 1.28c.81-1.7 2.56-1.36 2.77-1.29l-.35 1.46c-.18-.06-2.3-.63-2.82 1.68l-.74 3.48h-1.55l1.42-6.61Zm3.91 3.66c0-2.27 1.45-3.81 3.6-3.81 2 0 3.05 1.58 2.33 3.92h-4.46c0 1.1.81 1.68 2.05 1.68.8 0 1.45-.2 2.1-.59l-.31 1.46a4.2 4.2 0 0 1-2.04.44c-2.06 0-3.26-1.19-3.26-3.11Zm4.7-1.07c.12-.86-.31-1.46-1.22-1.46s-1.57.61-1.82 1.46h3.05Zm2.25 1.36c0-2.44 1.36-4.1 3.26-4.1 1 0 1.76.53 2.05 1.31l.79-3.72h1.55l-1.96 9.17h-1.55l.2-.92a2.15 2.15 0 0 1-1.92 1.08c-1.49 0-2.43-1.18-2.43-2.82Zm3 1.51c.88 0 1.51-.58 1.73-1.56l.17-.81c.24-1.1-.31-1.93-1.36-1.93-1.19 0-1.94 1.08-1.94 2.59 0 1.06.55 1.71 1.4 1.71Zm9.6-.01-.25 1.16h-1.55l1.96-9.17h1.55l-.73 3.47a2.35 2.35 0 0 1 1.99-1.05c1.49 0 2.35 1.16 2.35 2.76 0 2.52-1.36 4.16-3.21 4.16-.98 0-1.81-.53-2.1-1.32Zm1.83.01c1.16 0 1.87-1.06 1.87-2.61 0-1.04-.5-1.69-1.39-1.69s-1.52.56-1.73 1.55l-.17.79c-.24 1.14.34 1.97 1.42 1.97Zm5.68 1.16-1.04-6.62h1.52l.66 4.75 2.66-4.75h1.69l-5.31 9.13h-1.73l1.55-2.51Zm23.47-6.8c.91-.79 2.6-2.21 4.83-3.66a42.5 42.5 0 0 1 4.83 3.66c.23 1.18.62 3.36.75 6.01a43.12 43.12 0 0 1-5.58 2.35 42.54 42.54 0 0 1-5.58-2.35c.14-2.65.53-4.83.75-6.01Zm13.07-7.95s.82-.29 1.76-.45a14.9 14.9 0 0 0-9.53-3.81c.66.71 1.28 1.67 1.84 2.75 1.84.22 4.07.7 5.92 1.51Zm-2.71 18.36c-2.06-.4-4.05-.97-5.53-1.51a38.65 38.65 0 0 1-5.53 1.51c.12 1.5.35 3.04.76 4.58 0 0 1.54 1.82 4.78 2.8 3.23-.98 4.78-2.8 4.78-2.8.4-1.53.64-3.08.76-4.58Zm-13.77-18.37a22.3 22.3 0 0 1 5.93-1.51 12.4 12.4 0 0 1 1.84-2.75 14.97 14.97 0 0 0-9.53 3.81c.95.16 1.76.45 1.76.45Zm-4.72 8.77a25.74 25.74 0 0 0 3.58 2.94 37.48 37.48 0 0 1 4.08-4.04c.27-1.56.77-3.57 1.46-5.55a25.24 25.24 0 0 0-4.34-1.63s-2.35.42-4.81 2.74c-.77 3.29.04 5.54.04 5.54Zm25.92 0s.81-2.25.04-5.54c-2.46-2.31-4.81-2.74-4.81-2.74-1.53.42-2.99.99-4.34 1.63a37.79 37.79 0 0 1 1.46 5.55 37.44 37.44 0 0 1 4.08 4.04 25.86 25.86 0 0 0 3.58-2.94Zm-26.38.2s-.66-.56-1.27-1.3c-.7 3.34-.27 6.93 1.46 10.16.28-.93.8-1.94 1.46-2.97a22.32 22.32 0 0 1-1.66-5.88Zm8.24 14.27a22.07 22.07 0 0 1-4.27-4.38c-1.22.06-2.36 0-3.3-.22a14.91 14.91 0 0 0 8.07 6.34c-.34-.9-.5-1.75-.5-1.75Zm18.6-14.27s.66-.56 1.27-1.3c.7 3.34.27 6.93-1.46 10.16-.28-.93-.8-1.94-1.46-2.97a22.32 22.32 0 0 0 1.66-5.88Zm-8.24 14.27a22.07 22.07 0 0 0 4.27-4.38c1.22.06 2.36 0 3.3-.22a14.91 14.91 0 0 1-8.07 6.34c.34-.9.5-1.75.5-1.75Zm-5.18-25.66-4.12 2.45 1.26 3.91h5.72l1.26-3.91-4.12-2.45Zm-11.4 19.74 4.18 2.35 2.75-3.05-2.86-4.95-4.02.86-.06 4.79Zm22.79 0-.06-4.79-4.02-.86-2.86 4.95 2.75 3.05 4.18-2.35Z" style="fill:#00c1fa"/><path d="M106.67 109.1a304.9 304.9 0 0 0-3.72-10.89c5.04-5.53 35.28-40.74 24.54-68.91 10.57 10.67 8.19 28.85 3.59 41.95-4.79 13.14-13.43 26.48-24.4 37.84Zm30.89 20.82c-5.87 6.12-20.46 17.92-21.67 18.77a99.37 99.37 0 0 0 7.94 6.02 133.26 133.26 0 0 0 20.09-18.48 353.47 353.47 0 0 0-6.36-6.31Zm-29.65-16.74a380.9 380.9 0 0 1 3.13 11.56c-4.8-1.37-8.66-2.53-12.36-3.82a123.4 123.4 0 0 1-21.16 13.21l15.84 5.47c14.83-8.23 28.13-20.82 37.81-34.68 0 0 8.56-12.55 12.42-23.68 2.62-7.48 4.46-16.57 3.49-24.89-2.21-12.27-6.95-15.84-9.32-17.66 6.16 5.72 3.25 27.8-2.79 39.89-6.08 12.16-15.73 24.27-27.05 34.59Zm59.05-37.86c-.03 7.72-3.05 15.69-6.44 22.69 1.7 2.2 3.18 4.36 4.42 6.49 7.97-16.51 3.74-26.67 2.02-29.18ZM61.18 128.51l12.5 4.3a101.45 101.45 0 0 0 21.42-13.19 163.26 163.26 0 0 1-10.61-4.51 101.28 101.28 0 0 1-23.3 13.4Zm87.78-42.73c.86.77 5.44 5.18 6.75 6.59 6.39-16.61.78-28.86-1.27-30.56.72 8.05-2.02 16.51-5.48 23.98Zm-14.29 40.62-2.47-15.18a142.42 142.42 0 0 1-35.74 29.45c6.81 2.36 12.69 4.4 15.45 5.38a115.98 115.98 0 0 0 22.75-19.66Zm-42.62 34.73c4.48 2.93 12.94 4.24 18.8 1.23 6.03-3.84-.6-8.34-8.01-9.88-9.8-2.03-16.82 1.22-13.4 6.21.41.6 1.19 1.5 2.62 2.44m-1.84.4c-3.56-2.37-6.77-7.2-.23-10.08 10.41-3.43 28.39 3.2 24.99 9.22-.58 1.04-1.46 1.6-2.38 2.19h-.03v.02h-.03v.02h-.03c-7.04 3.65-17.06 2.13-22.3-1.36m5.48-3.86a4.94 4.94 0 0 0 5.06.49l1.35-.74-4.68-2.38-1.47.79c-.38.22-1.53.88-.26 1.84m-1.7.59c-2.35-1.57-.78-2.61-.02-3.11 1.09-.57 2.19-1.15 3.28-1.77 6.95 3.67 7.22 3.81 13.19 6.17l-1.38.81c-1.93-.78-4.52-1.82-6.42-2.68.86 1.4 1.99 3.27 2.9 4.64l-1.68.87c-.75-1.28-1.76-2.99-2.47-4.29-3.19 2.06-6.99-.36-7.42-.64" style="fill:url(#f2)"/><path d="M159.13 52.37C143.51 24.04 119.45 15 103.6 15c-11.92 0-25.97 5.78-36.84 13.17 9.54 4.38 21.86 15.96 22.02 16.11-7.94-3.05-17.83-6.72-33.23-7.87a135.1 135.1 0 0 0-19.77 20.38c.77 7.66 2.88 15.68 2.88 15.68-6.28-4.75-11.02-4.61-18 9.45-5.4 12.66-6.93 24.25-4.65 33.18 0 0 4.72 26.8 36.23 40.07-1.3-4.61-1.58-9.91-.93-15.73a87.96 87.96 0 0 1-15.63-9.87c.79-6.61 2.79-13.82 6-21.36 4.42-10.66 4.35-15.14 4.35-15.19.03.07 5.48 12.43 12.95 22.08 4.23-8.84 9.46-16.08 13.67-21.83l-3.77-6.75a143.73 143.73 0 0 1 18.19-18.75c2.05 1.07 4.79 2.47 6.84 3.58 8.68-7.27 19.25-14.05 30.56-18.29-7-11.49-16.02-19.27-16.02-19.27s27.7 2.74 42.02 15.69a25.8 25.8 0 0 1 8.65 2.89ZM28.58 107.52a70.1 70.1 0 0 0-2.74 12.52 55.65 55.65 0 0 1-6.19-8.84 69.17 69.17 0 0 1 2.65-12.1c1.77-5.31 3.35-5.91 5.86-2.23v-.05c2.14 3.07 1.81 6.14.42 10.7ZM61.69 72.2l-.05.05a221.85 221.85 0 0 1-7.77-18.1l.14-.14a194.51 194.51 0 0 1 18.56 6.98 144.44 144.44 0 0 0-10.88 11.22Zm54.84-47.38c-4.42.7-9.02 1.95-13.67 3.72a65.03 65.03 0 0 0-7.81-5.31 66.04 66.04 0 0 1 13.02-3.54c1.53-.19 6.23-.79 10.32 2.42v-.05c2.47 1.91.14 2.37-1.86 2.75Z" style="fill:url(#h)"/>'
            )
        );
    }

    /// @notice Transfer ownership to a new owner.
    /// @param newOwner The address to transfer ownership to.
    function transferOwnership(address newOwner) external onlyOwner {
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// @notice Write data to be accessed by a given file key.
    /// @param key The key to access the written data.
    /// @param data The data to be written.
    function writeFile(uint256 key, string memory data) external onlyOwner {
        files[key] = SSTORE2.write(bytes(data));
    }

    /// @notice Read data using a given file key.
    /// @param key The key to access the stored data.
    /// @return data The data stored at the given key.
    function readFile(uint256 key) external view returns (string memory data) {
        return string(SSTORE2.read(files[key]));
    }

    /// @notice Create or set a customization preset for renderers to use.
    /// @param id The ID of the customization preset.
    /// @param customizationData Data decoded by renderers used to render the SVG according to the preset.
    function createCustomizationPreset(
        uint256 id,
        bytes memory customizationData
    ) external onlyOwner {
        customizationPresets[id] = customizationData;
    }

    /// @notice For crowdfund or party instances to set the customization preset they want to use.
    /// @param id The ID of the customization preset.
    function useCustomizationPreset(uint256 id) external {
        getPresetFor[msg.sender] = id;
    }
}

File 18 of 35 : CrowdfundNFT.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";
import "../utils/ReadOnlyDelegateCall.sol";
import "../utils/EIP165.sol";
import "../globals/IGlobals.sol";
import "../globals/LibGlobals.sol";
import "../renderers/RendererStorage.sol";

/// @notice NFT functionality for crowdfund types. This NFT is soulbound and read-only.
contract CrowdfundNFT is IERC721, EIP165, ReadOnlyDelegateCall {
    error AlreadyMintedError(address owner, uint256 tokenId);
    error AlreadyBurnedError(address owner, uint256 tokenId);
    error InvalidTokenError(uint256 tokenId);
    error InvalidAddressError();

    // The `Globals` contract storing global configuration values. This contract
    // is immutable and it’s address will never change.
    IGlobals private immutable _GLOBALS;

    /// @notice The name of the crowdfund. This will also carry over to the
    ///         governance party.
    string public name;
    /// @notice The token symbol for the crowdfund. This will also carry over to
    ///         the governance party.
    string public symbol;

    mapping(uint256 => address) private _owners;

    modifier alwaysRevert() {
        revert("ALWAYS FAILING");
        _; // Compiler requires this.
    }

    // Set the `Globals` contract.
    constructor(IGlobals globals) {
        _GLOBALS = globals;
    }

    // Initialize name and symbol for crowdfund.
    function _initialize(
        string memory name_,
        string memory symbol_,
        uint256 customizationPresetId
    ) internal virtual {
        name = name_;
        symbol = symbol_;
        if (customizationPresetId != 0) {
            RendererStorage(_GLOBALS.getAddress(LibGlobals.GLOBAL_RENDERER_STORAGE))
                .useCustomizationPreset(customizationPresetId);
        }
    }

    /// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always fail.
    function transferFrom(address, address, uint256) external pure alwaysRevert {}

    /// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always fail.
    function safeTransferFrom(address, address, uint256) external pure alwaysRevert {}

    /// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always fail.
    function safeTransferFrom(
        address,
        address,
        uint256,
        bytes calldata
    ) external pure alwaysRevert {}

    /// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always fail.
    function approve(address, uint256) external pure alwaysRevert {}

    /// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always fail.
    function setApprovalForAll(address, bool) external pure alwaysRevert {}

    /// @notice This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always return null.
    function getApproved(uint256) external pure returns (address) {
        return address(0);
    }

    /// @notice This is a soulbound NFT and cannot be transferred.
    ///         Attempting to call this function will always return false.
    function isApprovedForAll(address, address) external pure returns (bool) {
        return false;
    }

    /// @inheritdoc EIP165
    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        return
            super.supportsInterface(interfaceId) ||
            // ERC721 interface ID
            interfaceId == 0x80ac58cd;
    }

    /// @notice Returns a URI to render the NFT.
    function tokenURI(uint256) external view returns (string memory) {
        return _delegateToRenderer();
    }

    /// @notice Returns a URI for the storefront-level metadata for your contract.
    function contractURI() external view returns (string memory) {
        return _delegateToRenderer();
    }

    /// @inheritdoc IERC721
    function ownerOf(uint256 tokenId) external view returns (address owner) {
        owner = _owners[tokenId];
        if (owner == address(0)) {
            revert InvalidTokenError(tokenId);
        }
    }

    /// @inheritdoc IERC721
    function balanceOf(address owner) external view returns (uint256 numTokens) {
        return _doesTokenExistFor(owner) ? 1 : 0;
    }

    function _doesTokenExistFor(address owner) internal view returns (bool) {
        return _owners[uint256(uint160(owner))] != address(0);
    }

    function _mint(address owner) internal returns (uint256 tokenId) {
        if (owner == address(0)) revert InvalidAddressError();
        tokenId = uint256(uint160(owner));
        if (_owners[tokenId] != owner) {
            _owners[tokenId] = owner;
            emit Transfer(address(0), owner, tokenId);
        } else {
            revert AlreadyMintedError(owner, tokenId);
        }
    }

    function _burn(address owner) internal {
        uint256 tokenId = uint256(uint160(owner));
        if (_owners[tokenId] == owner) {
            _owners[tokenId] = address(0);
            emit Transfer(owner, address(0), tokenId);
            return;
        }
        revert AlreadyBurnedError(owner, tokenId);
    }

    function _delegateToRenderer() private view returns (string memory) {
        _readOnlyDelegateCall(
            // Instance of IERC721Renderer.
            _GLOBALS.getAddress(LibGlobals.GLOBAL_CF_NFT_RENDER_IMPL),
            msg.data
        );
        assert(false); // Will not be reached.
        return "";
    }
}

File 19 of 35 : ReadOnlyDelegateCall.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "./LibRawResult.sol";

interface IReadOnlyDelegateCall {
    // Marked `view` so that `_readOnlyDelegateCall` can be `view` as well.
    function delegateCallAndRevert(address impl, bytes memory callData) external view;
}

// Inherited by contracts to perform read-only delegate calls.
abstract contract ReadOnlyDelegateCall {
    using LibRawResult for bytes;

    // Delegatecall into implement and revert with the raw result.
    function delegateCallAndRevert(address impl, bytes memory callData) external {
        // Attempt to gate to only `_readOnlyDelegateCall()` invocations.
        require(msg.sender == address(this));
        (bool s, bytes memory r) = impl.delegatecall(callData);
        // Revert with success status and return data.
        abi.encode(s, r).rawRevert();
    }

    // Perform a `delegateCallAndRevert()` then return the raw result data.
    function _readOnlyDelegateCall(address impl, bytes memory callData) internal view {
        try IReadOnlyDelegateCall(address(this)).delegateCallAndRevert(impl, callData) {
            // Should never happen.
            assert(false);
        } catch (bytes memory r) {
            (bool success, bytes memory resultData) = abi.decode(r, (bool, bytes));
            if (!success) {
                resultData.rawRevert();
            }
            resultData.rawReturn();
        }
    }
}

File 20 of 35 : IERC2981.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)

pragma solidity ^0.8.0;

import "../utils/introspection/IERC165.sol";

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 *
 * _Available since v4.5._
 */
interface IERC2981 is IERC165 {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     */
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount);
}

File 21 of 35 : ITokenDistributor.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC20.sol";

import "../party/Party.sol";

/// @notice Creates token distributions for parties.
interface ITokenDistributor {
    enum TokenType {
        Native,
        Erc20
    }

    // Info on a distribution, created by createDistribution().
    struct DistributionInfo {
        // Type of distribution/token.
        TokenType tokenType;
        // ID of the distribution. Assigned by createDistribution().
        uint256 distributionId;
        // The party whose members can claim the distribution.
        Party party;
        // Who can claim `fee`.
        address payable feeRecipient;
        // The token being distributed.
        address token;
        // Total amount of `token` that can be claimed by party members.
        uint128 memberSupply;
        // Amount of `token` to be redeemed by `feeRecipient`.
        uint128 fee;
        // Total shares at time distribution was created.
        uint96 totalShares;
    }

    event DistributionCreated(Party indexed party, DistributionInfo info);
    event DistributionFeeClaimed(
        Party indexed party,
        address indexed feeRecipient,
        TokenType tokenType,
        address token,
        uint256 amount
    );
    event DistributionClaimedByPartyToken(
        Party indexed party,
        uint256 indexed partyTokenId,
        address indexed owner,
        TokenType tokenType,
        address token,
        uint256 amountClaimed
    );

    /// @notice Create a new distribution for an outstanding native token balance
    ///         governed by a party.
    /// @dev Native tokens should be transferred directly into this contract
    ///      immediately prior (same tx) to calling `createDistribution()` or
    ///      attached to the call itself.
    /// @param party The party whose members can claim the distribution.
    /// @param feeRecipient Who can claim `fee`.
    /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives.
    /// @return info Information on the created distribution.
    function createNativeDistribution(
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external payable returns (DistributionInfo memory info);

    /// @notice Create a new distribution for an outstanding ERC20 token balance
    ///         governed by a party.
    /// @dev ERC20 tokens should be transferred directly into this contract
    ///      immediately prior (same tx) to calling `createDistribution()` or
    ///      attached to the call itself.
    /// @param token The ERC20 token to distribute.
    /// @param party The party whose members can claim the distribution.
    /// @param feeRecipient Who can claim `fee`.
    /// @param feeBps Percentage (in bps) of the distribution `feeRecipient` receives.
    /// @return info Information on the created distribution.
    function createErc20Distribution(
        IERC20 token,
        Party party,
        address payable feeRecipient,
        uint16 feeBps
    ) external returns (DistributionInfo memory info);

    /// @notice Claim a portion of a distribution owed to a `partyTokenId` belonging
    ///         to the party that created the distribution. The caller
    ///         must own this token.
    /// @param info Information on the distribution being claimed.
    /// @param partyTokenId The ID of the party token to claim for.
    /// @return amountClaimed The amount of the distribution claimed.
    function claim(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) external returns (uint128 amountClaimed);

    /// @notice Claim the fee for a distribution. Only a distribution's `feeRecipient`
    ///         can call this.
    /// @param info Information on the distribution being claimed.
    /// @param recipient The address to send the fee to.
    function claimFee(DistributionInfo calldata info, address payable recipient) external;

    /// @notice Batch version of `claim()`.
    /// @param infos Information on the distributions being claimed.
    /// @param partyTokenIds The ID of the party tokens to claim for.
    /// @return amountsClaimed The amount of the distributions claimed.
    function batchClaim(
        DistributionInfo[] calldata infos,
        uint256[] calldata partyTokenIds
    ) external returns (uint128[] memory amountsClaimed);

    /// @notice Batch version of `claimFee()`.
    /// @param infos Information on the distributions to claim fees for.
    /// @param recipients The addresses to send the fees to.
    function batchClaimFee(
        DistributionInfo[] calldata infos,
        address payable[] calldata recipients
    ) external;

    /// @notice Compute the amount of a distribution's token are owed to a party
    ///         member, identified by the `partyTokenId`.
    /// @param info Information on the distribution being claimed.
    /// @param partyTokenId The ID of the party token to claim for.
    /// @return claimAmount The amount of the distribution owed to the party member.
    function getClaimAmount(
        DistributionInfo calldata info,
        uint256 partyTokenId
    ) external view returns (uint128);

    /// @notice Check whether the fee has been claimed for a distribution.
    /// @param party The party to use for checking whether the fee has been claimed.
    /// @param distributionId The ID of the distribution to check.
    /// @return feeClaimed Whether the fee has been claimed.
    function wasFeeClaimed(Party party, uint256 distributionId) external view returns (bool);

    /// @notice Check whether a `partyTokenId` has claimed their share of a distribution.
    /// @param party The party to use for checking whether the `partyTokenId` has claimed.
    /// @param partyTokenId The ID of the party token to check.
    /// @param distributionId The ID of the distribution to check.
    /// @return hasClaimed Whether the `partyTokenId` has claimed.
    function hasPartyTokenIdClaimed(
        Party party,
        uint256 partyTokenId,
        uint256 distributionId
    ) external view returns (bool);

    /// @notice Get how much unclaimed member tokens are left in a distribution.
    /// @param party The party to use for checking the unclaimed member tokens.
    /// @param distributionId The ID of the distribution to check.
    /// @return remainingMemberSupply The amount of distribution supply remaining.
    function getRemainingMemberSupply(
        Party party,
        uint256 distributionId
    ) external view returns (uint128);
}

File 22 of 35 : IERC20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC20 interface.
interface IERC20 {
    event Transfer(address indexed owner, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 allowance);

    function transfer(address to, uint256 amount) external returns (bool);

    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    function approve(address spender, uint256 allowance) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function balanceOf(address owner) external view returns (uint256);
}

File 23 of 35 : IERC1155.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

// Minimal ERC1155 interface.
interface IERC1155 {
    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function setApprovalForAll(address operator, bool approved) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;

    function balanceOf(address owner, uint256 tokenId) external view returns (uint256);

    function isApprovedForAll(address owner, address spender) external view returns (bool);

    function balanceOfBatch(
        address[] calldata owners,
        uint256[] calldata ids
    ) external view returns (uint256[] memory balances);
}

File 24 of 35 : ERC1155Receiver.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8;

import "../vendor/solmate/ERC1155.sol";
import "../utils/EIP165.sol";

abstract contract ERC1155Receiver is EIP165, ERC1155TokenReceiverBase {
    /// @inheritdoc EIP165
    function supportsInterface(bytes4 interfaceId) public pure virtual override returns (bool) {
        return
            super.supportsInterface(interfaceId) ||
            interfaceId == type(ERC1155TokenReceiverBase).interfaceId;
    }
}

File 25 of 35 : LibERC20Compat.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC20.sol";

// Compatibility helpers for ERC20s.
library LibERC20Compat {
    error NotATokenError(IERC20 token);
    error TokenTransferFailedError(IERC20 token, address to, uint256 amount);

    // Perform an `IERC20.transfer()` handling non-compliant implementations.
    function compatTransfer(IERC20 token, address to, uint256 amount) internal {
        (bool s, bytes memory r) = address(token).call(
            abi.encodeCall(IERC20.transfer, (to, amount))
        );
        if (s) {
            if (r.length == 0) {
                uint256 cs;
                assembly {
                    cs := extcodesize(token)
                }
                if (cs == 0) {
                    revert NotATokenError(token);
                }
                return;
            }
            if (abi.decode(r, (bool))) {
                return;
            }
        }
        revert TokenTransferFailedError(token, to, amount);
    }
}

File 26 of 35 : LibGlobals.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

// Valid keys in `IGlobals`. Append-only.
library LibGlobals {
    // The Globals commented out below were depreciated in 1.2; factories
    // can now choose the implementation address to deploy and no longer
    // deploy the latest implementation. They will no longer be updated
    // in future releases.
    //
    // See https://github.com/PartyDAO/party-migrations for
    // implementation addresses by release.

    uint256 internal constant GLOBAL_PARTY_IMPL = 1;
    uint256 internal constant GLOBAL_PROPOSAL_ENGINE_IMPL = 2;
    // uint256 internal constant GLOBAL_PARTY_FACTORY = 3;
    uint256 internal constant GLOBAL_GOVERNANCE_NFT_RENDER_IMPL = 4;
    uint256 internal constant GLOBAL_CF_NFT_RENDER_IMPL = 5;
    uint256 internal constant GLOBAL_OS_ZORA_AUCTION_TIMEOUT = 6;
    uint256 internal constant GLOBAL_OS_ZORA_AUCTION_DURATION = 7;
    // uint256 internal constant GLOBAL_AUCTION_CF_IMPL = 8;
    // uint256 internal constant GLOBAL_BUY_CF_IMPL = 9;
    // uint256 internal constant GLOBAL_COLLECTION_BUY_CF_IMPL = 10;
    uint256 internal constant GLOBAL_DAO_WALLET = 11;
    uint256 internal constant GLOBAL_TOKEN_DISTRIBUTOR = 12;
    uint256 internal constant GLOBAL_OPENSEA_CONDUIT_KEY = 13;
    uint256 internal constant GLOBAL_OPENSEA_ZONE = 14;
    uint256 internal constant GLOBAL_PROPOSAL_MAX_CANCEL_DURATION = 15;
    uint256 internal constant GLOBAL_ZORA_MIN_AUCTION_DURATION = 16;
    uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_DURATION = 17;
    uint256 internal constant GLOBAL_ZORA_MAX_AUCTION_TIMEOUT = 18;
    uint256 internal constant GLOBAL_OS_MIN_ORDER_DURATION = 19;
    uint256 internal constant GLOBAL_OS_MAX_ORDER_DURATION = 20;
    uint256 internal constant GLOBAL_DISABLE_PARTY_ACTIONS = 21;
    uint256 internal constant GLOBAL_RENDERER_STORAGE = 22;
    uint256 internal constant GLOBAL_PROPOSAL_MIN_CANCEL_DURATION = 23;
    // uint256 internal constant GLOBAL_ROLLING_AUCTION_CF_IMPL = 24;
    // uint256 internal constant GLOBAL_COLLECTION_BATCH_BUY_CF_IMPL = 25;
    uint256 internal constant GLOBAL_METADATA_REGISTRY = 26;
    // uint256 internal constant GLOBAL_CROWDFUND_FACTORY = 27;
    // uint256 internal constant GLOBAL_INITIAL_ETH_CF_IMPL = 28;
    // uint256 internal constant GLOBAL_RERAISE_ETH_CF_IMPL = 29;
    uint256 internal constant GLOBAL_SEAPORT = 30;
    uint256 internal constant GLOBAL_CONDUIT_CONTROLLER = 31;
}

File 27 of 35 : IProposalExecutionEngine.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

// Upgradeable proposals logic contract interface.
interface IProposalExecutionEngine {
    struct ExecuteProposalParams {
        uint256 proposalId;
        bytes proposalData;
        bytes progressData;
        bytes extraData;
        uint256 flags;
        IERC721[] preciousTokens;
        uint256[] preciousTokenIds;
    }

    function initialize(address oldImpl, bytes memory initData) external;

    /// @notice Execute a proposal.
    /// @dev Must be delegatecalled into by PartyGovernance.
    ///      If the proposal is incomplete, continues its next step (if possible).
    ///      If another proposal is incomplete, this will fail. Only one
    ///      incomplete proposal is allowed at a time.
    /// @param params The data needed to execute the proposal.
    /// @return nextProgressData Bytes to be passed into the next `execute()` call,
    ///         if the proposal execution is incomplete. Otherwise, empty bytes
    ///         to indicate the proposal is complete.
    function executeProposal(
        ExecuteProposalParams memory params
    ) external returns (bytes memory nextProgressData);

    /// @notice Forcibly cancel an incomplete proposal.
    /// @param proposalId The ID of the proposal to cancel.
    /// @dev This is intended to be a last resort as it can leave a party in a
    ///      broken step. Whenever possible, proposals should be allowed to
    ///      complete their entire lifecycle.
    function cancelProposal(uint256 proposalId) external;
}

File 28 of 35 : LibProposal.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../tokens/IERC721.sol";

library LibProposal {
    uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1;

    function isTokenPrecious(
        IERC721 token,
        IERC721[] memory preciousTokens
    ) internal pure returns (bool) {
        for (uint256 i; i < preciousTokens.length; ++i) {
            if (token == preciousTokens[i]) {
                return true;
            }
        }
        return false;
    }

    function isTokenIdPrecious(
        IERC721 token,
        uint256 tokenId,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds
    ) internal pure returns (bool) {
        for (uint256 i; i < preciousTokens.length; ++i) {
            if (token == preciousTokens[i] && tokenId == preciousTokenIds[i]) {
                return true;
            }
        }
        return false;
    }
}

File 29 of 35 : ProposalStorage.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "./IProposalExecutionEngine.sol";
import "../utils/LibRawResult.sol";

// The storage bucket shared by `PartyGovernance` and the `ProposalExecutionEngine`.
// Read this for more context on the pattern motivating this:
// https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/explicit-storage-buckets
abstract contract ProposalStorage {
    using LibRawResult for bytes;

    struct SharedProposalStorage {
        IProposalExecutionEngine engineImpl;
        ProposalEngineOpts opts;
    }

    struct ProposalEngineOpts {
        // Whether the party can add new authorities with the add authority proposal.
        bool enableAddAuthorityProposal;
        // Whether the party can spend ETH from the party's balance with
        // arbitrary call proposals.
        bool allowArbCallsToSpendPartyEth;
        // Whether operators can spend ETH from the party's balance with the
        // operator proposal.
        bool allowOperatorsToSpendPartyEth;
        // Whether distributions require a vote or can be executed by any active member.
        bool distributionsRequireVote;
    }

    uint256 internal constant PROPOSAL_FLAG_UNANIMOUS = 0x1;
    uint256 private constant SHARED_STORAGE_SLOT =
        uint256(keccak256("ProposalStorage.SharedProposalStorage"));

    function _initProposalImpl(IProposalExecutionEngine impl, bytes memory initData) internal {
        SharedProposalStorage storage stor = _getSharedProposalStorage();
        IProposalExecutionEngine oldImpl = stor.engineImpl;
        stor.engineImpl = impl;
        (bool s, bytes memory r) = address(impl).delegatecall(
            abi.encodeCall(IProposalExecutionEngine.initialize, (address(oldImpl), initData))
        );
        if (!s) {
            r.rawRevert();
        }
    }

    function _getSharedProposalStorage()
        internal
        pure
        returns (SharedProposalStorage storage stor)
    {
        uint256 s = SHARED_STORAGE_SLOT;
        assembly {
            stor.slot := s
        }
    }
}

File 30 of 35 : IPartyFactory.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../globals/IGlobals.sol";
import "../tokens/IERC721.sol";

import "./Party.sol";

// Creates generic Party instances.
interface IPartyFactory {
    event PartyCreated(
        Party indexed party,
        Party.PartyOptions opts,
        IERC721[] preciousTokens,
        uint256[] preciousTokenIds,
        address creator
    );

    /// @notice Deploy a new party instance.
    /// @param partyImpl The implementation of the party to deploy.
    /// @param authorities The addresses set as authorities for the party.
    /// @param opts Options used to initialize the party. These are fixed
    ///             and cannot be changed later.
    /// @param preciousTokens The tokens that are considered precious by the
    ///                       party.These are protected assets and are subject
    ///                       to extra restrictions in proposals vs other
    ///                       assets.
    /// @param preciousTokenIds The IDs associated with each token in `preciousTokens`.
    /// @param rageQuitTimestamp The timestamp until which ragequit is enabled.
    /// @return party The newly created `Party` instance.
    function createParty(
        Party partyImpl,
        address[] memory authorities,
        Party.PartyOptions calldata opts,
        IERC721[] memory preciousTokens,
        uint256[] memory preciousTokenIds,
        uint40 rageQuitTimestamp
    ) external returns (Party party);
}

File 31 of 35 : Implementation.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

// Base contract for all contracts intended to be delegatecalled into.
abstract contract Implementation {
    error OnlyDelegateCallError();
    error OnlyConstructorError();

    address public immutable IMPL;

    constructor() {
        IMPL = address(this);
    }

    // Reverts if the current function context is not inside of a delegatecall.
    modifier onlyDelegateCall() virtual {
        if (address(this) == IMPL) {
            revert OnlyDelegateCallError();
        }
        _;
    }

    // Reverts if the current function context is not inside of a constructor.
    modifier onlyConstructor() {
        if (address(this).code.length != 0) {
            revert OnlyConstructorError();
        }
        _;
    }
}

File 32 of 35 : SSTORE2.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
    uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called.

    /*//////////////////////////////////////////////////////////////
                               WRITE LOGIC
    //////////////////////////////////////////////////////////////*/

    function write(bytes memory data) internal returns (address pointer) {
        // Prefix the bytecode with a STOP opcode to ensure it cannot be called.
        bytes memory runtimeCode = abi.encodePacked(hex"00", data);

        bytes memory creationCode = abi.encodePacked(
            //---------------------------------------------------------------------------------------------------------------//
            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
            //---------------------------------------------------------------------------------------------------------------//
            // 0x60    |  0x600B             | PUSH1 11     | codeOffset                                                     //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
            // 0xf3    |  0xf3               | RETURN       |                                                                //
            //---------------------------------------------------------------------------------------------------------------//
            hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes.
            runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit.
        );

        /// @solidity memory-safe-assembly
        assembly {
            // Deploy a new contract with the generated creation code.
            // We start 32 bytes into the code to avoid copying the byte length.
            pointer := create(0, add(creationCode, 32), mload(creationCode))
        }

        require(pointer != address(0), "DEPLOYMENT_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                               READ LOGIC
    //////////////////////////////////////////////////////////////*/

    function read(address pointer) internal view returns (bytes memory) {
        return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET);
    }

    function read(address pointer, uint256 start) internal view returns (bytes memory) {
        start += DATA_OFFSET;

        return readBytecode(pointer, start, pointer.code.length - start);
    }

    function read(
        address pointer,
        uint256 start,
        uint256 end
    ) internal view returns (bytes memory) {
        start += DATA_OFFSET;
        end += DATA_OFFSET;

        require(pointer.code.length >= end, "OUT_OF_BOUNDS");

        return readBytecode(pointer, start, end - start);
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function readBytecode(
        address pointer,
        uint256 start,
        uint256 size
    ) private view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            data := mload(0x40)

            // Update the free memory pointer to prevent overriding our data.
            // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)).
            // Adding 31 to size and running the result through the logic above ensures
            // the memory pointer remains word-aligned, following the Solidity convention.
            mstore(0x40, add(data, and(add(add(size, 32), 31), not(31))))

            // Store the size of the data in the first 32 byte chunk of free memory.
            mstore(data, size)

            // Copy the code into memory right after the 32 bytes we used to store the size.
            extcodecopy(pointer, add(data, 32), start, size)
        }
    }
}

File 33 of 35 : Multicall.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

import "../utils/LibRawResult.sol";

abstract contract Multicall {
    using LibRawResult for bytes;

    /// @notice Perform multiple delegatecalls on ourselves.
    function multicall(bytes[] calldata multicallData) external {
        for (uint256 i; i < multicallData.length; ++i) {
            (bool s, bytes memory r) = address(this).delegatecall(multicallData[i]);
            if (!s) {
                r.rawRevert();
            }
        }
    }
}

File 34 of 35 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @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);
}

File 35 of 35 : ERC1155.sol
// SPDX-License-Identifier: AGPL-3.0-only
// Based on solmate commit 1681dc505f4897ef636f0435d01b1aa027fdafaf (v6.4.0)
//  @ https://github.com/Rari-Capital/solmate/blob/1681dc505f4897ef636f0435d01b1aa027fdafaf/src/tokens/ERC1155.sol
// Only modified to inherit IERC1155 and rename ERC1155TokenReceiver -> ERC1155TokenReceiverBase.
pragma solidity ^0.8;

import "../../tokens/IERC1155.sol";

/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 is IERC1155 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event URI(string value, uint256 indexed id);

    /*//////////////////////////////////////////////////////////////
                             ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                              ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public virtual {
        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155Received(
                    msg.sender,
                    from,
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        require(ids.length == amounts.length, "LENGTH_MISMATCH");

        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i; i < ids.length; ) {
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155BatchReceived(
                    msg.sender,
                    from,
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(
        address[] calldata owners,
        uint256[] calldata ids
    ) public view virtual returns (uint256[] memory balances) {
        require(owners.length == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i; i < owners.length; ++i) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155Received(
                    msg.sender,
                    address(0),
                    id,
                    amount,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiverBase(to).onERC1155BatchReceived(
                    msg.sender,
                    address(0),
                    ids,
                    amounts,
                    data
                ) == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

    function _burn(address from, uint256 id, uint256 amount) internal virtual {
        balanceOf[from][id] -= amount;

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiverBase {
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiverBase.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiverBase.onERC1155BatchReceived.selector;
    }
}

Settings
{
  "remappings": [
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/",
    "openzeppelin/=lib/openzeppelin-contracts/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "paris",
  "libraries": {},
  "viaIR": true
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IGlobals","name":"globals","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint96","name":"contributions","type":"uint96"},{"internalType":"uint96","name":"maxContributions","type":"uint96"}],"name":"AboveMaximumContributionsError","type":"error"},{"inputs":[{"internalType":"uint96","name":"contributions","type":"uint96"},{"internalType":"uint96","name":"minContributions","type":"uint96"}],"name":"BelowMinimumContributionsError","type":"error"},{"inputs":[],"name":"ContributingForExistingCardDisabledError","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"errData","type":"bytes"}],"name":"EthTransferFailed","type":"error"},{"inputs":[],"name":"FundingSplitAlreadyPaidError","type":"error"},{"inputs":[],"name":"FundingSplitNotConfiguredError","type":"error"},{"inputs":[],"name":"InvalidDelegateError","type":"error"},{"inputs":[{"internalType":"uint16","name":"exchangeRateBps","type":"uint16"}],"name":"InvalidExchangeRateError","type":"error"},{"inputs":[{"internalType":"uint96","name":"maxTotalContributions","type":"uint96"}],"name":"MaxTotalContributionsCannotBeZeroError","type":"error"},{"inputs":[{"internalType":"uint96","name":"min","type":"uint96"},{"internalType":"uint96","name":"max","type":"uint96"}],"name":"MinGreaterThanMaxError","type":"error"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"},{"internalType":"contract IGateKeeper","name":"gateKeeper","type":"address"},{"internalType":"bytes12","name":"gateKeeperId","type":"bytes12"},{"internalType":"bytes","name":"gateData","type":"bytes"}],"name":"NotAllowedByGateKeeperError","type":"error"},{"inputs":[{"internalType":"uint96","name":"totalContribution","type":"uint96"},{"internalType":"uint96","name":"minTotalContributions","type":"uint96"}],"name":"NotEnoughContributionsError","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NotOwnerError","type":"error"},{"inputs":[],"name":"OnlyConstructorError","type":"error"},{"inputs":[],"name":"OnlyDelegateCallError","type":"error"},{"inputs":[{"internalType":"address","name":"notDao","type":"address"}],"name":"OnlyPartyDaoError","type":"error"},{"inputs":[{"internalType":"address","name":"notDao","type":"address"}],"name":"OnlyPartyDaoOrHostError","type":"error"},{"inputs":[],"name":"OnlyPartyHostError","type":"error"},{"inputs":[],"name":"OnlyWhenEmergencyActionsAllowedError","type":"error"},{"inputs":[{"internalType":"uint256","name":"v","type":"uint256"}],"name":"Uint256ToUint96CastOutOfRange","type":"error"},{"inputs":[{"internalType":"enum ETHCrowdfundBase.CrowdfundLifecycle","name":"lc","type":"uint8"}],"name":"WrongLifecycleError","type":"error"},{"inputs":[],"name":"ZeroVotingPowerError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"delegate","type":"address"}],"name":"Contributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"amountEth","type":"uint256"}],"name":"EmergencyExecute","type":"event"},{"anonymous":false,"inputs":[],"name":"EmergencyExecuteDisabled","type":"event"},{"anonymous":false,"inputs":[],"name":"Finalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fundingSplitRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FundingSplitSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Refunded","type":"event"},{"inputs":[],"name":"IMPL","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"uint96[]","name":"values","type":"uint96[]"},{"internalType":"bytes[]","name":"gateDatas","type":"bytes[]"}],"internalType":"struct InitialETHCrowdfund.BatchContributeArgs","name":"args","type":"tuple"}],"name":"batchContribute","outputs":[{"internalType":"uint96[]","name":"votingPowers","type":"uint96[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"address payable[]","name":"recipients","type":"address[]"},{"internalType":"address[]","name":"initialDelegates","type":"address[]"},{"internalType":"uint96[]","name":"values","type":"uint96[]"},{"internalType":"bytes[]","name":"gateDatas","type":"bytes[]"},{"internalType":"bool","name":"revertOnFailure","type":"bool"}],"internalType":"struct InitialETHCrowdfund.BatchContributeForArgs","name":"args","type":"tuple"}],"name":"batchContributeFor","outputs":[{"internalType":"uint96[]","name":"votingPowers","type":"uint96[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"bool","name":"revertOnFailure","type":"bool"}],"name":"batchRefund","outputs":[{"internalType":"uint96[]","name":"amounts","type":"uint96[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegate","type":"address"},{"internalType":"bytes","name":"gateData","type":"bytes"}],"name":"contribute","outputs":[{"internalType":"uint96","name":"votingPower","type":"uint96"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"bytes","name":"gateData","type":"bytes"}],"name":"contribute","outputs":[{"internalType":"uint96","name":"votingPower","type":"uint96"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"address","name":"initialDelegate","type":"address"},{"internalType":"bytes","name":"gateData","type":"bytes"}],"name":"contributeFor","outputs":[{"internalType":"uint96","name":"votingPower","type":"uint96"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint96","name":"votingPower","type":"uint96"}],"name":"convertVotingPowerToContribution","outputs":[{"internalType":"uint96","name":"amount","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"delegationsByContributor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableContributingForExistingCard","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableEmergencyExecute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"targetAddress","type":"address"},{"internalType":"bytes","name":"targetCallData","type":"bytes"},{"internalType":"uint256","name":"amountEth","type":"uint256"}],"name":"emergencyExecute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"emergencyExecuteDisabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exchangeRateBps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"expiry","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"finalize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fundingSplitBps","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundingSplitPaid","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fundingSplitRecipient","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gateKeeper","outputs":[{"internalType":"contract IGateKeeper","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gateKeeperId","outputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCrowdfundLifecycle","outputs":[{"internalType":"enum ETHCrowdfundBase.CrowdfundLifecycle","name":"lifecycle","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address payable","name":"initialContributor","type":"address"},{"internalType":"address","name":"initialDelegate","type":"address"},{"internalType":"uint96","name":"minContribution","type":"uint96"},{"internalType":"uint96","name":"maxContribution","type":"uint96"},{"internalType":"bool","name":"disableContributingForExistingCard","type":"bool"},{"internalType":"uint96","name":"minTotalContributions","type":"uint96"},{"internalType":"uint96","name":"maxTotalContributions","type":"uint96"},{"internalType":"uint16","name":"exchangeRateBps","type":"uint16"},{"internalType":"uint16","name":"fundingSplitBps","type":"uint16"},{"internalType":"address payable","name":"fundingSplitRecipient","type":"address"},{"internalType":"uint40","name":"duration","type":"uint40"},{"internalType":"contract IGateKeeper","name":"gateKeeper","type":"address"},{"internalType":"bytes12","name":"gateKeeperId","type":"bytes12"}],"internalType":"struct InitialETHCrowdfund.InitialETHCrowdfundOptions","name":"crowdfundOpts","type":"tuple"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint256","name":"customizationPresetId","type":"uint256"},{"components":[{"internalType":"contract Party","name":"partyImpl","type":"address"},{"internalType":"contract IPartyFactory","name":"partyFactory","type":"address"},{"internalType":"address[]","name":"hosts","type":"address[]"},{"internalType":"uint40","name":"voteDuration","type":"uint40"},{"internalType":"uint40","name":"executionDelay","type":"uint40"},{"internalType":"uint16","name":"passThresholdBps","type":"uint16"},{"internalType":"uint16","name":"feeBps","type":"uint16"},{"internalType":"address payable","name":"feeRecipient","type":"address"}],"internalType":"struct Crowdfund.FixedGovernanceOpts","name":"governanceOpts","type":"tuple"},{"components":[{"internalType":"bool","name":"enableAddAuthorityProposal","type":"bool"},{"internalType":"bool","name":"allowArbCallsToSpendPartyEth","type":"bool"},{"internalType":"bool","name":"allowOperatorsToSpendPartyEth","type":"bool"},{"internalType":"bool","name":"distributionsRequireVote","type":"bool"}],"internalType":"struct ProposalStorage.ProposalEngineOpts","name":"proposalEngineOpts","type":"tuple"},{"internalType":"contract IERC721[]","name":"preciousTokens","type":"address[]"},{"internalType":"uint256[]","name":"preciousTokenIds","type":"uint256[]"},{"internalType":"uint40","name":"rageQuitTimestamp","type":"uint40"}],"internalType":"struct InitialETHCrowdfund.ETHPartyOptions","name":"partyOpts","type":"tuple"}],"name":"initialize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"maxContribution","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTotalContributions","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minContribution","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minTotalContributions","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"party","outputs":[{"internalType":"contract Party","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"refund","outputs":[{"internalType":"uint96","name":"amount","type":"uint96"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sendFundingSplit","outputs":[{"internalType":"uint96","name":"splitAmount","type":"uint96"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalContributions","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"}]

60c0346100a357601f620030a638819003918201601f19168301916001600160401b038311848410176100a8578084926020946040528339810103126100a357516001600160a01b03811681036100a3573060805260a052604051612fe79081620000bf823960805181818161040801528181610bbc01528181610cdb01528181610f6d015281816116830152611747015260a051818181610753015261186b0152f35b600080fd5b634e487b7160e01b600052604160045260246000fdfe6080604052600436101561001257600080fd5b60003560e01c806314b182f9146102075780631840f5c2146102025780631a8ae87d146101fd578063278ecde1146101f85780632a12a8a8146101f3578063305a0721146101ee578063354284f2146101e957806337c08923146101e45780633a6ba242146101df5780633d2f3caa146101da57806345d61ded146101d55780634816a870146101d05780634bb278f3146101cb57806356973ee5146101c65780635cc4f009146101c15780636984dfad146101bc5780637c0899d0146101b75780637ea2f4c5146101b25780638d3d6576146101ad5780639bd7308b146101a8578063a15352fa146101a3578063aa15f2d91461019e578063aaf15a7b14610199578063aaffadf314610194578063ad6f10d51461018f578063e184c9be1461018a578063e6f26d9a14610185578063e7a7905714610180578063f26224661461017b578063fd5f1553146101765763fea44a561461017157600080fd5b61178f565b61170c565b6116e5565b61163c565b611616565b6115ee565b6114f9565b611163565b611141565b61108b565b610f3b565b610f15565b610eee565b610ca8565b610c42565b610c18565b610beb565b610ba6565b610a70565b610944565b61091b565b6108f3565b61089b565b610871565b610848565b6106d7565b6106b1565b6104a4565b61047a565b6103cd565b610231565b6001600160a01b0381160361021d57565b600080fd5b6024359061022f8261020c565b565b3461021d57602036600319011261021d5760206004356102508161020c565b60018060a01b038091166000526005825260406000205416604051908152f35b634e487b7160e01b600052604160045260246000fd5b6001600160401b03811161029957604052565b610270565b90601f801991011681019081106001600160401b0382111761029957604052565b6040519061010082018281106001600160401b0382111761029957604052565b604051906101a082018281106001600160401b0382111761029957604052565b604051906101c082018281106001600160401b0382111761029957604052565b6040519060e082018281106001600160401b0382111761029957604052565b6040519060a082018281106001600160401b0382111761029957604052565b6001600160401b03811161029957601f01601f191660200190565b9291926103848261035d565b91610392604051938461029e565b82948184528183011161021d578281602093846000960137010152565b9080601f8301121561021d578160206103ca93359101610378565b90565b604036600319011261021d576004356103e58161020c565b6024356001600160401b03811161021d576104049036906004016103af565b90307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161461045d5760209161044c9161044534612f85565b903361243e565b6001600160601b0360405191168152f35b60405163ea2cbbd560e01b8152600490fd5b600091031261021d57565b3461021d57600036600319011261021d576020600454604051906001600160601b0360a01b168152f35b3461021d5760208060031936011261021d57600435906104c26118e4565b6104cb816108c0565b600281036106905750600080549092906104f5906001600160a01b03165b6001600160a01b031690565b60405163caf6aa4b60e01b8152600481018390529093908381602481885afa90811561062e5761053591610530918491610663575b50612f85565b6119e9565b926001600160601b038416908161055f575b6040516001600160601b0386168152602090f35b0390f35b6040516331a9108f60e11b81526004810185905281816024818a5afa91821561062e578492610636575b50506001600160a01b031691853b1561063357604051630852cd8d60e31b815260048101859052958690602490829084905af190811561062e5761055b956105d792610615575b5082612f21565b6040516001600160601b03841681527f2dc8e290002f06fc0085bbca9dfb8b415cf4d1178950c72ff9ee8f4d8878ee6690602090a338808080610547565b8061062261062892610286565b8061046f565b386105d0565b611a68565b80fd5b6106559250803d1061065c575b61064d818361029e565b810190611bd3565b3880610589565b503d610643565b6106839150863d8811610689575b61067b818361029e565b81019061242f565b3861052a565b503d610671565b604051638847aaed60e01b81529081906106ad90600483016108e0565b0390fd5b3461021d57600036600319011261021d57602060ff60015460681c166040519015158152f35b606036600319011261021d576004356106ef8161020c565b6024356001600160401b039182821161021d573660238301121561021d57816004013592831161021d576024820191602484369201011161021d57604051635c9fcd8560e11b8152600b600482015260443593906001600160a01b036020826024817f000000000000000000000000000000000000000000000000000000000000000085165afa91821561062e57600092610828575b50339116036108105760015460701c60ff166107fe57600080604051806107ad818689611be8565b039087865af16107bb611c19565b90156107f957507f66cf5924183d7d44caa75d9268a169d7c1422fef43848743d5e1ba32d5833acf936107f49160405194859485611c6a565b0390a1005b612f7d565b6040516304e71fd360e31b8152600490fd5b604051639f3df03d60e01b8152336004820152602490fd5b61084191925060203d811161065c5761064d818361029e565b9038610785565b3461021d57600036600319011261021d576000546040516001600160a01b039091168152602090f35b3461021d57600036600319011261021d5760206001600160601b0360025460601c16604051908152f35b3461021d57600036600319011261021d57602061ffff60025460e81c16604051908152f35b600511156108ca57565b634e487b7160e01b600052602160045260246000fd5b9190602083019260058210156108ca5752565b3461021d57600036600319011261021d5761055b61090f6118e4565b604051918291826108e0565b3461021d57600036600319011261021d576004546040516001600160a01b039091168152602090f35b3461021d57600036600319011261021d5761095d6118e4565b610966816108c0565b600481036106905760015460681c60ff16610a5e5760035461ffff81169060101c6001600160a01b031680158015610a56575b610a44576001805460ff60681b1916600160681b17905561055b916109e1906109d09060025460601c6001600160601b0316611989565b6001600160601b0361271091160490565b906109f56001600160601b03831682612f21565b6040516001600160601b03831681527f161c34d8458db26e87706a1037c42bafe83f6af37ed3586801dbeda1e300611590602090a26040516001600160601b0390911681529081906020820190565b60405163cd42f85360e01b8152600490fd5b508115610999565b60405163ac03c55b60e01b8152600490fd5b3461021d576000806003193601126106335760025460601c6001600160601b0316610a996118e4565b610aa2816108c0565b60018103610b8b57508154610abf906001600160a01b03166104e9565b60405163eb2931b760e01b815233600482015290602090829060249082905afa90811561062e578391610b5d575b5015610b4b5760015460781c6001600160601b03166001600160601b0380821690831610610b225750610b1f90611a8d565b80f35b6040516341fcdd4d60e11b81526001600160601b03928316600482015291166024820152604490fd5b604051633641391960e21b8152600490fd5b610b7e915060203d8111610b84575b610b76818361029e565b810190611a53565b38610aed565b503d610b6c565b610b94816108c0565b600381036106905750610b1f90611a8d565b3461021d57600036600319011261021d576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b3461021d57600036600319011261021d5760035460405160109190911c6001600160a01b03168152602090f35b3461021d57600036600319011261021d5760206001600160601b0360015460781c16604051908152f35b3461021d57600036600319011261021d57602061ffff60035416604051908152f35b6020908160408183019282815285518094520193019160005b828110610c8b575050505090565b83516001600160601b031685529381019392810192600101610c7d565b6003196020368201811361021d57600435906001600160401b03821161021d5760c08260040193833603011261021d57307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161461045d5760249182810190610d198286612210565b929050610d2583612245565b9534956000906064850193604486019060848701935b888110610d81578b8b80158015610d5b575b6040518061055b8582610c64565b600080809381938290610d78575b3390f11561062e578180610d4d565b506108fc610d69565b80610d8c8888612210565b610d96929161229c565b610d9f906122b1565b6001600160601b031681610db38880612210565b610dbd929161229c565b3582610dc9858a612210565b610dd3929161229c565b610ddc906122c8565b908c84610de9888c612210565b610df3929161229c565b610dfc906122c8565b9185610e088b8d612210565b610e1292916122d2565b9060405194859485019663e7a7905760e01b88528c860194610e3395612334565b03601f1981018252610e45908261029e565b5130915a93600094938594f18c610e5a611c19565b91610e7f5750610e6c60a48a01612378565b6107f95750610e7a90612277565b610d3b565b9b610ee2610ed6610ed1859f958f610e7a97610eb383610ead8a85610ec196610ee89d518301019101612363565b92612320565b906001600160601b03169052565b610ecb8d8d612210565b9061229c565b6122b1565b6001600160601b031690565b906122bb565b9a612277565b3461021d57600036600319011261021d5760206001600160601b0360015416604051908152f35b3461021d57600036600319011261021d57602060ff60015460601c166040519015158152f35b60031960203682011261021d57600435906001600160401b03821161021d5760808260040191833603011261021d57307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161461045d5790610fa58280612210565b929050610fb183612245565b923492600092604482019160648101906024015b848610610fe457878780158015610d5b576040518061055b8582610c64565b90919293949561100561106a91610ee2610ed6610ed18b610ecb8b8b612210565b9661106561105b611015856122c8565b87611027610ed186610ecb8d85612210565b61105461104d876110478c61104083610ecb8980612210565b3596612210565b906122d2565b3691610378565b92336125a7565b610eb3838c612320565b612277565b9493929190610fc5565b8015150361021d57565b6084359061022f82611074565b3461021d57604036600319011261021d576004356001600160401b0380821161021d573660238301121561021d57816004013590811161021d573660248260051b8401011161021d5761055b916110f09160248035926110ea84611074565b01612afa565b60405191829182610c64565b6001600160601b0381160361021d57565b6044359061022f826110fc565b6064359061022f826110fc565b60a4359061022f826110fc565b60c4359061022f826110fc565b3461021d57602036600319011261021d57602061044c600435610530816110fc565b3461021d57600036600319011261021d57602060005460a01c604051908152f35b6004359061022f8261020c565b610124359061022f8261020c565b359061022f8261020c565b60e4359061ffff8216820361021d57565b610104359061ffff8216820361021d57565b359061ffff8216820361021d57565b610144359064ffffffffff8216820361021d57565b359064ffffffffff8216820361021d57565b610164359061022f8261020c565b61018435906001600160a01b03198216820361021d57565b6001600160401b0381116102995760051b60200190565b81601f8201121561021d5780359161125783611229565b92611265604051948561029e565b808452602092838086019260051b82010192831161021d578301905b82821061128f575050505090565b838091833561129d8161020c565b815201910190611281565b9190916101008184031261021d576112be6102bf565b926112c88261119f565b84526112d66020830161119f565b60208501526040820135916001600160401b03831161021d5761130060e09261134f948301611240565b6040860152611311606082016111f1565b6060860152611322608082016111f1565b608086015261133360a082016111cd565b60a086015261134460c082016111cd565b60c08601520161119f565b60e0830152565b919082608091031261021d57604051608081018181106001600160401b03821117610299576040526060808294803561138e81611074565b8452602081013561139e81611074565b602085015260408101356113b181611074565b60408501520135916113c283611074565b0152565b81601f8201121561021d578035916113dd83611229565b926113eb604051948561029e565b808452602092838086019260051b82010192831161021d578301905b828210611415575050505090565b81358152908301908301611407565b9190916101608184031261021d5761143a6102bf565b926001600160401b0391803583811161021d57826114599183016103af565b8552602081013583811161021d57826114739183016103af565b602086015260408101356040860152606081013583811161021d578261149a9183016112a8565b60608601526114ac8260808301611356565b608086015261010081013583811161021d57826114ca918301611240565b60a086015261012081013592831161021d576114ee6101409261134f9483016113c6565b60c0860152016111f1565b36600319016101c0811261021d576101a01361021d576115176102df565b61151f611184565b8152611529610222565b602082015261153661110d565b604082015261154361111a565b606082015261155061107e565b608082015261155d611127565b60a082015261156a611134565b60c08201526115776111aa565b60e08201526115846111bb565b610100820152611592611191565b6101208201526115a06111dc565b6101408201526115ae611203565b6101608201526115bc611211565b6101808201526101a435906001600160401b03821161021d576115e66115ec923690600401611424565b90611c95565b005b3461021d57600036600319011261021d57602064ffffffffff60025460c01c16604051908152f35b3461021d57600036600319011261021d57602060ff60015460701c166040519015158152f35b608036600319011261021d576024356116548161020c565b604435906116618261020c565b6064356001600160401b03811161021d576116809036906004016103af565b307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161461045d5761055b926116cb92600435916116c534612f85565b916125a7565b6040516001600160601b0390911681529081906020820190565b3461021d57600036600319011261021d5760206001600160601b0360025416604051908152f35b606036600319011261021d576024356117248161020c565b6044356001600160401b03811161021d576117439036906004016103af565b90307f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03161461045d5760209161044c916004359061178834612f85565b90336125a7565b3461021d576000806003193601126106335780546117b5906001600160a01b03166104e9565b60405163eb2931b760e01b81523360048201526020918290829060249082905afa90811561062e5783916118c7575b50159081611844575b5061182c576001805460ff60701b1916600160701b1790557f447e75484d6bdb571b4a92ae14018db7b6dd41f3f02360690c554e1d48f46f878180a180f35b604051636b7bc65960e01b8152336004820152602490fd5b604051635c9fcd8560e11b8152600b60048201529091506001600160a01b039082816024817f000000000000000000000000000000000000000000000000000000000000000086165afa92831561062e5784936118a8575b505016331415386117ed565b6118bf929350803d1061065c5761064d818361029e565b90388061189c565b6118de9150823d8411610b8457610b76818361029e565b386117e4565b6002546001600160601b038082161561194b5764ffffffffff8260c01c16801561194357421015611916575050600190565b61192d8160015460781c166001600160601b031690565b60609290921c161061193e57600390565b600290565b505050600490565b5050600090565b634e487b7160e01b600052601160045260246000fd5b906127106001600160601b038093160291821691820361198457565b611952565b9190916001600160601b038080941691160291821691820361198457565b906001600160601b038091169182156119bf57160490565b634e487b7160e01b600052601260045260246000fd5b9061ffff8092166127100391821161198457565b6119f290611968565b90611a0861ffff928360025460e81c16906119a7565b60035490928181169160101c6001600160a01b0316151580611a4a575b611a2d575050565b611a43611a3d6103ca9495611968565b926119d5565b16906119a7565b50811515611a25565b9081602091031261021d57516103ca81611074565b6040513d6000823e3d90fd5b6001600160601b03918216908216039190821161198457565b6002805464ffffffffff60c01b1916905560035461ffff81169060101c6001600160a01b0316151580611bca575b611bad575b50611ae86109d0611ae2611adb60025461ffff9060e81c1690565b61ffff1690565b83611989565b6000805490929190611b02906001600160a01b03166104e9565b90813b15611ba957604051633c8ff62b60e11b81526001600160601b0391909116600482015292919082908490602490829084905af190811561062e577f6823b073d48d6e3a7d385eeb601452d680e74bb46afe3255a7d778f3a9b1768193611b9292611b96575b5082546001600160601b0390611b8a906104e9906001600160a01b031681565b911690612f21565b80a1565b80610622611ba392610286565b38611b6a565b8380fd5b90611bbe6109d0611bc49383611989565b90611a74565b38611ac0565b50801515611abb565b9081602091031261021d57516103ca8161020c565b908092918237016000815290565b60405190602082018281106001600160401b038211176102995760405260008252565b3d15611c44573d90611c2a8261035d565b91611c38604051938461029e565b82523d6000602084013e565b606090565b908060209392818452848401376000828201840152601f01601f1916010190565b94939291604092611c909260018060a01b03168752606060208801526060870191611c49565b930152565b90303b611ee057611ca590612d90565b81516001600160a01b0316602083015183906001600160a01b031660408201516001600160601b031660608301516001600160601b03166080840151151560a08501516001600160601b031660c08601516001600160601b03169060e0870151611d109061ffff1690565b92610100880151611d229061ffff1690565b6101208901519095906001600160a01b0316966101408a0151611d499064ffffffffff1690565b6101608b01519099906001600160a01b0316610180909b01516001600160a01b0319169b611d756102ff565b6001600160a01b03909e168e528d6001600160a01b03909116602091909101526001600160a01b031660408d01526001600160601b031660608c01526001600160601b031660808b0152151560a08a01526001600160601b031660c08901526001600160601b031660e088015261ffff1661010087015261ffff166101208601526001600160a01b031661014085015264ffffffffff166101608401526001600160a01b03166101808301526001600160a01b0319166101a0820152611e3a90611eff565b611e4334612f85565b6001600160601b038116151561022f92611e8c9261018092611eaa575b50610160810151600480546001600160a01b039092166001600160a01b03199283161790559101511690565b6004546001600160a01b03166001600160a01b031990911617600455565b8151611ed991906001600160a01b031660208401519091906001600160a01b0316611ed3611bf6565b9261243e565b5038611e60565b6040516304a41c4d60e51b8152600490fd5b9190820180921161198457565b6060810180516001600160601b0316906080830190611f2582516001600160601b031690565b6001600160601b0393848216858216116121e7575050611fa191611f78611f56611f8593516001600160601b031690565b6000546001600160a01b031660a09190911b6001600160a01b03191617600055565b516001600160601b031690565b6001600160601b03166001600160601b03196001541617600155565b60c0820190611fb782516001600160601b031690565b9160e0840192611fce84516001600160601b031690565b90838216848216116121e75750505161201f906001600160601b0316600180546bffffffffffffffffffffffff60781b191660789290921b6bffffffffffffffffffffffff60781b16919091179055565b81516001600160601b0316908116156121c457505161205d906001600160601b03166001600160601b03166001600160601b03196002541617600255565b8051600080546001600160a01b0319166001600160a01b039092169190911790556120d46120ad61209d6120a761209d61016086015164ffffffffff1690565b64ffffffffff1690565b42611ef2565b6002805464ffffffffff60c01b191660c09290921b64ffffffffff60c01b16919091179055565b6101008101805161ffff1680156121a6575060a061022f9261211f6120fe612187945161ffff1690565b6002805461ffff60e81b191660e89290921b61ffff60e81b16919091179055565b61214461213261012083015161ffff1690565b61ffff1661ffff196003541617600355565b610140810151612180906001600160a01b03166003805462010000600160b01b03191660109290921b62010000600160b01b0316919091179055565b0151151590565b6001805460ff60601b191691151560601b60ff60601b16919091179055565b604051637f1ac8ef60e01b815261ffff919091166004820152602490fd5b604051636ab8fc7360e11b81526001600160601b03919091166004820152602490fd5b60405163a0de900f60e01b81526001600160601b03918216600482015291166024820152604490fd5b903590601e198136030182121561021d57018035906001600160401b03821161021d57602001918160051b3603831361021d57565b9061224f82611229565b61225c604051918261029e565b828152809261226d601f1991611229565b0190602036910137565b60001981146119845760010190565b634e487b7160e01b600052603260045260246000fd5b91908110156122ac5760051b0190565b612286565b356103ca816110fc565b9190820391821161198457565b356103ca8161020c565b91908110156122ac5760051b81013590601e198136030182121561021d5701908135916001600160401b03831161021d57602001823603811361021d579190565b8051156122ac5760200190565b80518210156122ac5760209160051b010190565b9081526001600160a01b039182166020820152911660408201526080606082018190526103ca93910191611c49565b9081602091031261021d57516103ca816110fc565b356103ca81611074565b919082519283825260005b8481106123ae575050826000602080949584010152601f8019910116010190565b60208183018101518483018201520161238d565b6001600160a01b0390911681526001600160a01b031990911660208201526060604082018190526103ca92910190612382565b6001600160a01b039182168152911660208201526001600160a01b031990911660408201526080606082018190526103ca92910190612382565b9081602091031261021d575190565b9193929091906001600160a01b039081841615612595576004549182169182612513575b5050506001600160601b0361247885848461281d565b94161561250b576124e09160209185600061249c6104e9825460018060a01b031690565b604051630d4d151360e01b81526001600160a01b0394851660048201526001600160601b039093166024840152929093166044820152938492839182906064820190565b03925af1801561062e576124f15750565b6125089060203d81116106895761067b818361029e565b50565b506000925050565b6001600160601b0360a01b1660405163267a0a0560e21b81526020818061253f868633600485016123c2565b0381875afa90811561062e57600091612577575b5061246257604051633921815160e21b8152945084936106ad9390600486016123f5565b61258f915060203d8111610b8457610b76818361029e565b38612553565b60405163e5ec2d2560e01b8152600490fd5b91949390929091906001600160a01b039081851615612595576004938454838116908161277f575b5050506001600160601b036125e588878661281d565b97161561277457806126535750506020918591600061260d6104e9825460018060a01b031690565b926124e060405197889687958694630d4d151360e01b86528501919392906040916001600160601b03606085019660018060a01b03809416865216602085015216910152565b9091929594935061266a60015460ff9060601c1690565b156126805760405163096c940760e31b81528690fd5b909192939460009361269b6104e9865460018060a01b031690565b93604051916331a9108f60e11b8352602083806126bf888883019190602083019252565b0381895afa92831561062e578793612752575b50811691161460001461273557823b15611ba95760408051631a2d593760e31b81529182019283526001600160601b0387166020840152909392849290918391859183910103925af1801561062e576127285750565b8061062261022f92610286565b60405163123d8bcf60e21b81529081019182529081906020010390fd5b8291935061276d9060203d811161065c5761064d818361029e565b92906126d2565b506000955050505050565b60405163267a0a0560e21b81526001600160a01b0319919091169190602081806127ad8787338e85016123c2565b0381855afa90811561062e576000916127e4575b506125cf57604051633921815160e21b8152958695506106ad94508588016123f5565b6127fc915060203d8111610b8457610b76818361029e565b386127c1565b9190916001600160601b038080941691160191821161198457565b92919061284c61283f8560018060a01b03166000526005602052604060002090565b546001600160a01b031690565b6001600160a01b039480861692913384148015612af0575b15612ae857506001600160a01b031660009081526005602052604090206128a690829080546001600160a01b0319166001600160a01b03909216919091179055565b604080516001600160601b03851681526001600160a01b0392909216602083015233917f9f3d681dbcf634bccf5fdd83844d63be101420f8cb4283a6cdab01d7202644489190a36001600160601b03928382168015612adf576129076118e4565b612910816108c0565b6001810361069057506001546001600160601b03169085821610612ab557506002548461294284828460601c16612802565b9116908581168211612a7c57600280546bffffffffffffffffffffffff60601b1916606084901b6bffffffffffffffffffffffff60601b1617905561299b9161298a90611a8d565b6002546001600160601b0316611bbe565b8481169081612a60575b50505b60005460a01c84811685841610612a3557506129f7916109d0916003549061ffff82169160101c16151580612a2c575b612a13575b506002546129f19060e81c61ffff16611adb565b90611989565b91821615612a0157565b6040516327366dc160e21b8152600490fd5b612a26916129f1611adb6109d0936119d5565b386129dd565b508015156129d8565b604051630352153b60e01b81526001600160601b038481166004830152919091166024820152604490fd5b612a6e90612a759294611a74565b9233612f21565b38806129a5565b600280546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055506129a8565b60405162f2e14960e61b81526001600160601b038481166004830152919091166024820152604490fd5b50600093505050565b9150506128a6565b5086811615612864565b929192612b0682612245565b93600092835b818110612b1a575050505050565b612b2581838661229c565b60405163278ecde160e01b602080830191825292356024808401919091528252859188918291612b5660448261029e565b519082305af191612b65611c19565b92612b7e57506107f95750612b7990612277565b612b0c565b61106591508281612b9992612b799695518301019101612363565b610eb3838b612320565b60405190604082018281106001600160401b03821117610299576040526001825260203681840137565b90815180825260208080930193019160005b828110612bed575050505090565b83516001600160a01b031685529381019392810192600101612bdf565b90815180825260208080930193019160005b828110612c2a575050505090565b835185529381019392810192600101612c1c565b96959360a093612d7491612c6e612d829561022f99956001808a1b03168c5260c060208d015260c08c0190612bcd565b8a810360408c015260e06080612d61612d4f8651612d0660c0612ca2610100808a52888551918b01526101e08a0190612bcd565b602084015164ffffffffff9081166101208b01526040850151166101408a0152606084015161ffff166101608a0152868401516001600160601b03166101808a015292808f015161ffff166101a08a015201516001600160a01b03166101c0880152565b612d3e602089015160208801906060809180511515845260208101511515602085015260408101511515604085015201511515910152565b60408801518682038d880152612382565b606087015185820360c0870152612382565b94015191015289820360608b0152612bcd565b908782036080890152612c0a565b94019064ffffffffff169052565b612d98612ba3565b9030612da383612313565b6001600160a01b03909116905260608101516020808201519093849290916001600160a01b03166001600160a01b031681519091906001600160a01b0316928551858701516040880151916040850151946060810151612e079064ffffffffff1690565b906080810151612e1b9064ffffffffff1690565b60a082015161ffff169060c0830151612e359061ffff1690565b60e0909301516001600160a01b031693612e4d61031f565b998a5264ffffffffff168c8a015264ffffffffff16604089015261ffff1660608801526000608088015261ffff1660a08701526001600160a01b031660c08601526080890151612e9b61033e565b9586528886015260408501526060840152608083015260a08601519360c08701519660e00151612ecf9064ffffffffff1690565b604051630211addb60e31b815297889687958695612ef09560048801612c3e565b03915a90600091f191821561062e57600092612f0b57505090565b6103ca9250803d1061065c5761064d818361029e565b8115612f79576001600160a01b031690600090819081908190855af190612f46611c19565b9115612f50575050565b6106ad60405192839263354db69760e01b84526004840152604060248401526044830190612382565b5050565b602081519101fd5b6001600160601b0390818111612f99571690565b60249060405190636f55e21560e11b82526004820152fdfea264697066735822122027ba3e6742f3e0aa0bf95629ebf6eea91ec1f654e1851be67552a42afc8e09b464736f6c634300081400330000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade

Deployed Bytecode

0x6080604052600436101561001257600080fd5b60003560e01c806314b182f9146102075780631840f5c2146102025780631a8ae87d146101fd578063278ecde1146101f85780632a12a8a8146101f3578063305a0721146101ee578063354284f2146101e957806337c08923146101e45780633a6ba242146101df5780633d2f3caa146101da57806345d61ded146101d55780634816a870146101d05780634bb278f3146101cb57806356973ee5146101c65780635cc4f009146101c15780636984dfad146101bc5780637c0899d0146101b75780637ea2f4c5146101b25780638d3d6576146101ad5780639bd7308b146101a8578063a15352fa146101a3578063aa15f2d91461019e578063aaf15a7b14610199578063aaffadf314610194578063ad6f10d51461018f578063e184c9be1461018a578063e6f26d9a14610185578063e7a7905714610180578063f26224661461017b578063fd5f1553146101765763fea44a561461017157600080fd5b61178f565b61170c565b6116e5565b61163c565b611616565b6115ee565b6114f9565b611163565b611141565b61108b565b610f3b565b610f15565b610eee565b610ca8565b610c42565b610c18565b610beb565b610ba6565b610a70565b610944565b61091b565b6108f3565b61089b565b610871565b610848565b6106d7565b6106b1565b6104a4565b61047a565b6103cd565b610231565b6001600160a01b0381160361021d57565b600080fd5b6024359061022f8261020c565b565b3461021d57602036600319011261021d5760206004356102508161020c565b60018060a01b038091166000526005825260406000205416604051908152f35b634e487b7160e01b600052604160045260246000fd5b6001600160401b03811161029957604052565b610270565b90601f801991011681019081106001600160401b0382111761029957604052565b6040519061010082018281106001600160401b0382111761029957604052565b604051906101a082018281106001600160401b0382111761029957604052565b604051906101c082018281106001600160401b0382111761029957604052565b6040519060e082018281106001600160401b0382111761029957604052565b6040519060a082018281106001600160401b0382111761029957604052565b6001600160401b03811161029957601f01601f191660200190565b9291926103848261035d565b91610392604051938461029e565b82948184528183011161021d578281602093846000960137010152565b9080601f8301121561021d578160206103ca93359101610378565b90565b604036600319011261021d576004356103e58161020c565b6024356001600160401b03811161021d576104049036906004016103af565b90307f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03161461045d5760209161044c9161044534612f85565b903361243e565b6001600160601b0360405191168152f35b60405163ea2cbbd560e01b8152600490fd5b600091031261021d57565b3461021d57600036600319011261021d576020600454604051906001600160601b0360a01b168152f35b3461021d5760208060031936011261021d57600435906104c26118e4565b6104cb816108c0565b600281036106905750600080549092906104f5906001600160a01b03165b6001600160a01b031690565b60405163caf6aa4b60e01b8152600481018390529093908381602481885afa90811561062e5761053591610530918491610663575b50612f85565b6119e9565b926001600160601b038416908161055f575b6040516001600160601b0386168152602090f35b0390f35b6040516331a9108f60e11b81526004810185905281816024818a5afa91821561062e578492610636575b50506001600160a01b031691853b1561063357604051630852cd8d60e31b815260048101859052958690602490829084905af190811561062e5761055b956105d792610615575b5082612f21565b6040516001600160601b03841681527f2dc8e290002f06fc0085bbca9dfb8b415cf4d1178950c72ff9ee8f4d8878ee6690602090a338808080610547565b8061062261062892610286565b8061046f565b386105d0565b611a68565b80fd5b6106559250803d1061065c575b61064d818361029e565b810190611bd3565b3880610589565b503d610643565b6106839150863d8811610689575b61067b818361029e565b81019061242f565b3861052a565b503d610671565b604051638847aaed60e01b81529081906106ad90600483016108e0565b0390fd5b3461021d57600036600319011261021d57602060ff60015460681c166040519015158152f35b606036600319011261021d576004356106ef8161020c565b6024356001600160401b039182821161021d573660238301121561021d57816004013592831161021d576024820191602484369201011161021d57604051635c9fcd8560e11b8152600b600482015260443593906001600160a01b036020826024817f0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade85165afa91821561062e57600092610828575b50339116036108105760015460701c60ff166107fe57600080604051806107ad818689611be8565b039087865af16107bb611c19565b90156107f957507f66cf5924183d7d44caa75d9268a169d7c1422fef43848743d5e1ba32d5833acf936107f49160405194859485611c6a565b0390a1005b612f7d565b6040516304e71fd360e31b8152600490fd5b604051639f3df03d60e01b8152336004820152602490fd5b61084191925060203d811161065c5761064d818361029e565b9038610785565b3461021d57600036600319011261021d576000546040516001600160a01b039091168152602090f35b3461021d57600036600319011261021d5760206001600160601b0360025460601c16604051908152f35b3461021d57600036600319011261021d57602061ffff60025460e81c16604051908152f35b600511156108ca57565b634e487b7160e01b600052602160045260246000fd5b9190602083019260058210156108ca5752565b3461021d57600036600319011261021d5761055b61090f6118e4565b604051918291826108e0565b3461021d57600036600319011261021d576004546040516001600160a01b039091168152602090f35b3461021d57600036600319011261021d5761095d6118e4565b610966816108c0565b600481036106905760015460681c60ff16610a5e5760035461ffff81169060101c6001600160a01b031680158015610a56575b610a44576001805460ff60681b1916600160681b17905561055b916109e1906109d09060025460601c6001600160601b0316611989565b6001600160601b0361271091160490565b906109f56001600160601b03831682612f21565b6040516001600160601b03831681527f161c34d8458db26e87706a1037c42bafe83f6af37ed3586801dbeda1e300611590602090a26040516001600160601b0390911681529081906020820190565b60405163cd42f85360e01b8152600490fd5b508115610999565b60405163ac03c55b60e01b8152600490fd5b3461021d576000806003193601126106335760025460601c6001600160601b0316610a996118e4565b610aa2816108c0565b60018103610b8b57508154610abf906001600160a01b03166104e9565b60405163eb2931b760e01b815233600482015290602090829060249082905afa90811561062e578391610b5d575b5015610b4b5760015460781c6001600160601b03166001600160601b0380821690831610610b225750610b1f90611a8d565b80f35b6040516341fcdd4d60e11b81526001600160601b03928316600482015291166024820152604490fd5b604051633641391960e21b8152600490fd5b610b7e915060203d8111610b84575b610b76818361029e565b810190611a53565b38610aed565b503d610b6c565b610b94816108c0565b600381036106905750610b1f90611a8d565b3461021d57600036600319011261021d576040517f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03168152602090f35b3461021d57600036600319011261021d5760035460405160109190911c6001600160a01b03168152602090f35b3461021d57600036600319011261021d5760206001600160601b0360015460781c16604051908152f35b3461021d57600036600319011261021d57602061ffff60035416604051908152f35b6020908160408183019282815285518094520193019160005b828110610c8b575050505090565b83516001600160601b031685529381019392810192600101610c7d565b6003196020368201811361021d57600435906001600160401b03821161021d5760c08260040193833603011261021d57307f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03161461045d5760249182810190610d198286612210565b929050610d2583612245565b9534956000906064850193604486019060848701935b888110610d81578b8b80158015610d5b575b6040518061055b8582610c64565b600080809381938290610d78575b3390f11561062e578180610d4d565b506108fc610d69565b80610d8c8888612210565b610d96929161229c565b610d9f906122b1565b6001600160601b031681610db38880612210565b610dbd929161229c565b3582610dc9858a612210565b610dd3929161229c565b610ddc906122c8565b908c84610de9888c612210565b610df3929161229c565b610dfc906122c8565b9185610e088b8d612210565b610e1292916122d2565b9060405194859485019663e7a7905760e01b88528c860194610e3395612334565b03601f1981018252610e45908261029e565b5130915a93600094938594f18c610e5a611c19565b91610e7f5750610e6c60a48a01612378565b6107f95750610e7a90612277565b610d3b565b9b610ee2610ed6610ed1859f958f610e7a97610eb383610ead8a85610ec196610ee89d518301019101612363565b92612320565b906001600160601b03169052565b610ecb8d8d612210565b9061229c565b6122b1565b6001600160601b031690565b906122bb565b9a612277565b3461021d57600036600319011261021d5760206001600160601b0360015416604051908152f35b3461021d57600036600319011261021d57602060ff60015460601c166040519015158152f35b60031960203682011261021d57600435906001600160401b03821161021d5760808260040191833603011261021d57307f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03161461045d5790610fa58280612210565b929050610fb183612245565b923492600092604482019160648101906024015b848610610fe457878780158015610d5b576040518061055b8582610c64565b90919293949561100561106a91610ee2610ed6610ed18b610ecb8b8b612210565b9661106561105b611015856122c8565b87611027610ed186610ecb8d85612210565b61105461104d876110478c61104083610ecb8980612210565b3596612210565b906122d2565b3691610378565b92336125a7565b610eb3838c612320565b612277565b9493929190610fc5565b8015150361021d57565b6084359061022f82611074565b3461021d57604036600319011261021d576004356001600160401b0380821161021d573660238301121561021d57816004013590811161021d573660248260051b8401011161021d5761055b916110f09160248035926110ea84611074565b01612afa565b60405191829182610c64565b6001600160601b0381160361021d57565b6044359061022f826110fc565b6064359061022f826110fc565b60a4359061022f826110fc565b60c4359061022f826110fc565b3461021d57602036600319011261021d57602061044c600435610530816110fc565b3461021d57600036600319011261021d57602060005460a01c604051908152f35b6004359061022f8261020c565b610124359061022f8261020c565b359061022f8261020c565b60e4359061ffff8216820361021d57565b610104359061ffff8216820361021d57565b359061ffff8216820361021d57565b610144359064ffffffffff8216820361021d57565b359064ffffffffff8216820361021d57565b610164359061022f8261020c565b61018435906001600160a01b03198216820361021d57565b6001600160401b0381116102995760051b60200190565b81601f8201121561021d5780359161125783611229565b92611265604051948561029e565b808452602092838086019260051b82010192831161021d578301905b82821061128f575050505090565b838091833561129d8161020c565b815201910190611281565b9190916101008184031261021d576112be6102bf565b926112c88261119f565b84526112d66020830161119f565b60208501526040820135916001600160401b03831161021d5761130060e09261134f948301611240565b6040860152611311606082016111f1565b6060860152611322608082016111f1565b608086015261133360a082016111cd565b60a086015261134460c082016111cd565b60c08601520161119f565b60e0830152565b919082608091031261021d57604051608081018181106001600160401b03821117610299576040526060808294803561138e81611074565b8452602081013561139e81611074565b602085015260408101356113b181611074565b60408501520135916113c283611074565b0152565b81601f8201121561021d578035916113dd83611229565b926113eb604051948561029e565b808452602092838086019260051b82010192831161021d578301905b828210611415575050505090565b81358152908301908301611407565b9190916101608184031261021d5761143a6102bf565b926001600160401b0391803583811161021d57826114599183016103af565b8552602081013583811161021d57826114739183016103af565b602086015260408101356040860152606081013583811161021d578261149a9183016112a8565b60608601526114ac8260808301611356565b608086015261010081013583811161021d57826114ca918301611240565b60a086015261012081013592831161021d576114ee6101409261134f9483016113c6565b60c0860152016111f1565b36600319016101c0811261021d576101a01361021d576115176102df565b61151f611184565b8152611529610222565b602082015261153661110d565b604082015261154361111a565b606082015261155061107e565b608082015261155d611127565b60a082015261156a611134565b60c08201526115776111aa565b60e08201526115846111bb565b610100820152611592611191565b6101208201526115a06111dc565b6101408201526115ae611203565b6101608201526115bc611211565b6101808201526101a435906001600160401b03821161021d576115e66115ec923690600401611424565b90611c95565b005b3461021d57600036600319011261021d57602064ffffffffff60025460c01c16604051908152f35b3461021d57600036600319011261021d57602060ff60015460701c166040519015158152f35b608036600319011261021d576024356116548161020c565b604435906116618261020c565b6064356001600160401b03811161021d576116809036906004016103af565b307f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03161461045d5761055b926116cb92600435916116c534612f85565b916125a7565b6040516001600160601b0390911681529081906020820190565b3461021d57600036600319011261021d5760206001600160601b0360025416604051908152f35b606036600319011261021d576024356117248161020c565b6044356001600160401b03811161021d576117439036906004016103af565b90307f00000000000000000000000023c886396cfbadb0f3bac4b728150e8a59dc0e106001600160a01b03161461045d5760209161044c916004359061178834612f85565b90336125a7565b3461021d576000806003193601126106335780546117b5906001600160a01b03166104e9565b60405163eb2931b760e01b81523360048201526020918290829060249082905afa90811561062e5783916118c7575b50159081611844575b5061182c576001805460ff60701b1916600160701b1790557f447e75484d6bdb571b4a92ae14018db7b6dd41f3f02360690c554e1d48f46f878180a180f35b604051636b7bc65960e01b8152336004820152602490fd5b604051635c9fcd8560e11b8152600b60048201529091506001600160a01b039082816024817f0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade86165afa92831561062e5784936118a8575b505016331415386117ed565b6118bf929350803d1061065c5761064d818361029e565b90388061189c565b6118de9150823d8411610b8457610b76818361029e565b386117e4565b6002546001600160601b038082161561194b5764ffffffffff8260c01c16801561194357421015611916575050600190565b61192d8160015460781c166001600160601b031690565b60609290921c161061193e57600390565b600290565b505050600490565b5050600090565b634e487b7160e01b600052601160045260246000fd5b906127106001600160601b038093160291821691820361198457565b611952565b9190916001600160601b038080941691160291821691820361198457565b906001600160601b038091169182156119bf57160490565b634e487b7160e01b600052601260045260246000fd5b9061ffff8092166127100391821161198457565b6119f290611968565b90611a0861ffff928360025460e81c16906119a7565b60035490928181169160101c6001600160a01b0316151580611a4a575b611a2d575050565b611a43611a3d6103ca9495611968565b926119d5565b16906119a7565b50811515611a25565b9081602091031261021d57516103ca81611074565b6040513d6000823e3d90fd5b6001600160601b03918216908216039190821161198457565b6002805464ffffffffff60c01b1916905560035461ffff81169060101c6001600160a01b0316151580611bca575b611bad575b50611ae86109d0611ae2611adb60025461ffff9060e81c1690565b61ffff1690565b83611989565b6000805490929190611b02906001600160a01b03166104e9565b90813b15611ba957604051633c8ff62b60e11b81526001600160601b0391909116600482015292919082908490602490829084905af190811561062e577f6823b073d48d6e3a7d385eeb601452d680e74bb46afe3255a7d778f3a9b1768193611b9292611b96575b5082546001600160601b0390611b8a906104e9906001600160a01b031681565b911690612f21565b80a1565b80610622611ba392610286565b38611b6a565b8380fd5b90611bbe6109d0611bc49383611989565b90611a74565b38611ac0565b50801515611abb565b9081602091031261021d57516103ca8161020c565b908092918237016000815290565b60405190602082018281106001600160401b038211176102995760405260008252565b3d15611c44573d90611c2a8261035d565b91611c38604051938461029e565b82523d6000602084013e565b606090565b908060209392818452848401376000828201840152601f01601f1916010190565b94939291604092611c909260018060a01b03168752606060208801526060870191611c49565b930152565b90303b611ee057611ca590612d90565b81516001600160a01b0316602083015183906001600160a01b031660408201516001600160601b031660608301516001600160601b03166080840151151560a08501516001600160601b031660c08601516001600160601b03169060e0870151611d109061ffff1690565b92610100880151611d229061ffff1690565b6101208901519095906001600160a01b0316966101408a0151611d499064ffffffffff1690565b6101608b01519099906001600160a01b0316610180909b01516001600160a01b0319169b611d756102ff565b6001600160a01b03909e168e528d6001600160a01b03909116602091909101526001600160a01b031660408d01526001600160601b031660608c01526001600160601b031660808b0152151560a08a01526001600160601b031660c08901526001600160601b031660e088015261ffff1661010087015261ffff166101208601526001600160a01b031661014085015264ffffffffff166101608401526001600160a01b03166101808301526001600160a01b0319166101a0820152611e3a90611eff565b611e4334612f85565b6001600160601b038116151561022f92611e8c9261018092611eaa575b50610160810151600480546001600160a01b039092166001600160a01b03199283161790559101511690565b6004546001600160a01b03166001600160a01b031990911617600455565b8151611ed991906001600160a01b031660208401519091906001600160a01b0316611ed3611bf6565b9261243e565b5038611e60565b6040516304a41c4d60e51b8152600490fd5b9190820180921161198457565b6060810180516001600160601b0316906080830190611f2582516001600160601b031690565b6001600160601b0393848216858216116121e7575050611fa191611f78611f56611f8593516001600160601b031690565b6000546001600160a01b031660a09190911b6001600160a01b03191617600055565b516001600160601b031690565b6001600160601b03166001600160601b03196001541617600155565b60c0820190611fb782516001600160601b031690565b9160e0840192611fce84516001600160601b031690565b90838216848216116121e75750505161201f906001600160601b0316600180546bffffffffffffffffffffffff60781b191660789290921b6bffffffffffffffffffffffff60781b16919091179055565b81516001600160601b0316908116156121c457505161205d906001600160601b03166001600160601b03166001600160601b03196002541617600255565b8051600080546001600160a01b0319166001600160a01b039092169190911790556120d46120ad61209d6120a761209d61016086015164ffffffffff1690565b64ffffffffff1690565b42611ef2565b6002805464ffffffffff60c01b191660c09290921b64ffffffffff60c01b16919091179055565b6101008101805161ffff1680156121a6575060a061022f9261211f6120fe612187945161ffff1690565b6002805461ffff60e81b191660e89290921b61ffff60e81b16919091179055565b61214461213261012083015161ffff1690565b61ffff1661ffff196003541617600355565b610140810151612180906001600160a01b03166003805462010000600160b01b03191660109290921b62010000600160b01b0316919091179055565b0151151590565b6001805460ff60601b191691151560601b60ff60601b16919091179055565b604051637f1ac8ef60e01b815261ffff919091166004820152602490fd5b604051636ab8fc7360e11b81526001600160601b03919091166004820152602490fd5b60405163a0de900f60e01b81526001600160601b03918216600482015291166024820152604490fd5b903590601e198136030182121561021d57018035906001600160401b03821161021d57602001918160051b3603831361021d57565b9061224f82611229565b61225c604051918261029e565b828152809261226d601f1991611229565b0190602036910137565b60001981146119845760010190565b634e487b7160e01b600052603260045260246000fd5b91908110156122ac5760051b0190565b612286565b356103ca816110fc565b9190820391821161198457565b356103ca8161020c565b91908110156122ac5760051b81013590601e198136030182121561021d5701908135916001600160401b03831161021d57602001823603811361021d579190565b8051156122ac5760200190565b80518210156122ac5760209160051b010190565b9081526001600160a01b039182166020820152911660408201526080606082018190526103ca93910191611c49565b9081602091031261021d57516103ca816110fc565b356103ca81611074565b919082519283825260005b8481106123ae575050826000602080949584010152601f8019910116010190565b60208183018101518483018201520161238d565b6001600160a01b0390911681526001600160a01b031990911660208201526060604082018190526103ca92910190612382565b6001600160a01b039182168152911660208201526001600160a01b031990911660408201526080606082018190526103ca92910190612382565b9081602091031261021d575190565b9193929091906001600160a01b039081841615612595576004549182169182612513575b5050506001600160601b0361247885848461281d565b94161561250b576124e09160209185600061249c6104e9825460018060a01b031690565b604051630d4d151360e01b81526001600160a01b0394851660048201526001600160601b039093166024840152929093166044820152938492839182906064820190565b03925af1801561062e576124f15750565b6125089060203d81116106895761067b818361029e565b50565b506000925050565b6001600160601b0360a01b1660405163267a0a0560e21b81526020818061253f868633600485016123c2565b0381875afa90811561062e57600091612577575b5061246257604051633921815160e21b8152945084936106ad9390600486016123f5565b61258f915060203d8111610b8457610b76818361029e565b38612553565b60405163e5ec2d2560e01b8152600490fd5b91949390929091906001600160a01b039081851615612595576004938454838116908161277f575b5050506001600160601b036125e588878661281d565b97161561277457806126535750506020918591600061260d6104e9825460018060a01b031690565b926124e060405197889687958694630d4d151360e01b86528501919392906040916001600160601b03606085019660018060a01b03809416865216602085015216910152565b9091929594935061266a60015460ff9060601c1690565b156126805760405163096c940760e31b81528690fd5b909192939460009361269b6104e9865460018060a01b031690565b93604051916331a9108f60e11b8352602083806126bf888883019190602083019252565b0381895afa92831561062e578793612752575b50811691161460001461273557823b15611ba95760408051631a2d593760e31b81529182019283526001600160601b0387166020840152909392849290918391859183910103925af1801561062e576127285750565b8061062261022f92610286565b60405163123d8bcf60e21b81529081019182529081906020010390fd5b8291935061276d9060203d811161065c5761064d818361029e565b92906126d2565b506000955050505050565b60405163267a0a0560e21b81526001600160a01b0319919091169190602081806127ad8787338e85016123c2565b0381855afa90811561062e576000916127e4575b506125cf57604051633921815160e21b8152958695506106ad94508588016123f5565b6127fc915060203d8111610b8457610b76818361029e565b386127c1565b9190916001600160601b038080941691160191821161198457565b92919061284c61283f8560018060a01b03166000526005602052604060002090565b546001600160a01b031690565b6001600160a01b039480861692913384148015612af0575b15612ae857506001600160a01b031660009081526005602052604090206128a690829080546001600160a01b0319166001600160a01b03909216919091179055565b604080516001600160601b03851681526001600160a01b0392909216602083015233917f9f3d681dbcf634bccf5fdd83844d63be101420f8cb4283a6cdab01d7202644489190a36001600160601b03928382168015612adf576129076118e4565b612910816108c0565b6001810361069057506001546001600160601b03169085821610612ab557506002548461294284828460601c16612802565b9116908581168211612a7c57600280546bffffffffffffffffffffffff60601b1916606084901b6bffffffffffffffffffffffff60601b1617905561299b9161298a90611a8d565b6002546001600160601b0316611bbe565b8481169081612a60575b50505b60005460a01c84811685841610612a3557506129f7916109d0916003549061ffff82169160101c16151580612a2c575b612a13575b506002546129f19060e81c61ffff16611adb565b90611989565b91821615612a0157565b6040516327366dc160e21b8152600490fd5b612a26916129f1611adb6109d0936119d5565b386129dd565b508015156129d8565b604051630352153b60e01b81526001600160601b038481166004830152919091166024820152604490fd5b612a6e90612a759294611a74565b9233612f21565b38806129a5565b600280546bffffffffffffffffffffffff60601b191660609290921b6bffffffffffffffffffffffff60601b16919091179055506129a8565b60405162f2e14960e61b81526001600160601b038481166004830152919091166024820152604490fd5b50600093505050565b9150506128a6565b5086811615612864565b929192612b0682612245565b93600092835b818110612b1a575050505050565b612b2581838661229c565b60405163278ecde160e01b602080830191825292356024808401919091528252859188918291612b5660448261029e565b519082305af191612b65611c19565b92612b7e57506107f95750612b7990612277565b612b0c565b61106591508281612b9992612b799695518301019101612363565b610eb3838b612320565b60405190604082018281106001600160401b03821117610299576040526001825260203681840137565b90815180825260208080930193019160005b828110612bed575050505090565b83516001600160a01b031685529381019392810192600101612bdf565b90815180825260208080930193019160005b828110612c2a575050505090565b835185529381019392810192600101612c1c565b96959360a093612d7491612c6e612d829561022f99956001808a1b03168c5260c060208d015260c08c0190612bcd565b8a810360408c015260e06080612d61612d4f8651612d0660c0612ca2610100808a52888551918b01526101e08a0190612bcd565b602084015164ffffffffff9081166101208b01526040850151166101408a0152606084015161ffff166101608a0152868401516001600160601b03166101808a015292808f015161ffff166101a08a015201516001600160a01b03166101c0880152565b612d3e602089015160208801906060809180511515845260208101511515602085015260408101511515604085015201511515910152565b60408801518682038d880152612382565b606087015185820360c0870152612382565b94015191015289820360608b0152612bcd565b908782036080890152612c0a565b94019064ffffffffff169052565b612d98612ba3565b9030612da383612313565b6001600160a01b03909116905260608101516020808201519093849290916001600160a01b03166001600160a01b031681519091906001600160a01b0316928551858701516040880151916040850151946060810151612e079064ffffffffff1690565b906080810151612e1b9064ffffffffff1690565b60a082015161ffff169060c0830151612e359061ffff1690565b60e0909301516001600160a01b031693612e4d61031f565b998a5264ffffffffff168c8a015264ffffffffff16604089015261ffff1660608801526000608088015261ffff1660a08701526001600160a01b031660c08601526080890151612e9b61033e565b9586528886015260408501526060840152608083015260a08601519360c08701519660e00151612ecf9064ffffffffff1690565b604051630211addb60e31b815297889687958695612ef09560048801612c3e565b03915a90600091f191821561062e57600092612f0b57505090565b6103ca9250803d1061065c5761064d818361029e565b8115612f79576001600160a01b031690600090819081908190855af190612f46611c19565b9115612f50575050565b6106ad60405192839263354db69760e01b84526004840152604060248401526044830190612382565b5050565b602081519101fd5b6001600160601b0390818111612f99571690565b60249060405190636f55e21560e11b82526004820152fdfea264697066735822122027ba3e6742f3e0aa0bf95629ebf6eea91ec1f654e1851be67552a42afc8e09b464736f6c63430008140033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade

-----Decoded View---------------
Arg [0] : globals (address): 0x1cA20040cE6aD406bC2A6c89976388829E7fbAde

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000001ca20040ce6ad406bc2a6c89976388829e7fbade


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.