// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"./AuctionCrowdfundBase.sol";
/// @notice A crowdfund that can repeatedly bid on an auction for a specific NFT/// (i.e. with a known token ID) until it wins.contractAuctionCrowdfundisAuctionCrowdfundBase{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
// Set the `Globals` contract.constructor(IGlobals globals) AuctionCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later.functioninitialize(AuctionCrowdfundOptions memory opts) externalpayableonlyInitialize{
AuctionCrowdfundBase._initialize(opts);
}
/// @notice Calls `finalize()` on the market adapter, which will claim the NFT/// (if necessary) if the crowdfund won, or recover the bid (if/// necessary) if lost. If won, a party will also be created./// @param governanceOpts The options used to initialize governance in the/// `Party` instance created if the crowdfund wins./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @return party_ Address of the `Party` instance created if successful.functionfinalize(
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
) externalonlyDelegateCallreturns (Party party_) {
// Check that the auction is still active and has not passed the `expiry` time.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active && lc != CrowdfundLifecycle.Expired) {
revert WrongLifecycleError(lc);
}
// Finalize the auction if it is not already finalized.uint96 lastBid_ = lastBid;
_finalizeAuction(lc, market, auctionId, lastBid_);
IERC721 nftContract_ = nftContract;
uint256 nftTokenId_ = nftTokenId;
// Are we now in possession of the NFT?if (nftContract_.safeOwnerOf(nftTokenId_) ==address(this) && lastBid_ !=0) {
// If we placed a bid before then consider it won for that price.// Create a governance party around the NFT.
party_ = _createParty(
governanceOpts,
proposalEngineOpts,
false,
nftContract_,
nftTokenId_
);
emit Won(lastBid_, party_);
} else {
// Otherwise we lost the auction or the NFT was gifted to us.// Clear `lastBid` so `_getFinalPrice()` is 0 and people can redeem their// full contributions when they burn their participation NFTs.
lastBid =0;
emit Lost();
}
_bidStatus = AuctionCrowdfundStatus.Finalized;
// Notify third-party platforms that the crowdfund NFT metadata has// updated for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
}
}
Contract Source Code
File 2 of 57: AuctionCrowdfundBase.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../party/Party.sol";
import"../utils/LibSafeERC721.sol";
import"../utils/LibRawResult.sol";
import"../globals/IGlobals.sol";
import"../gatekeepers/IGateKeeper.sol";
import"../market-wrapper/IMarketWrapper.sol";
import"./Crowdfund.sol";
abstractcontractAuctionCrowdfundBaseisCrowdfund{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
enumAuctionCrowdfundStatus {
// The crowdfund has been created and contributions can be made and// acquisition functions may be called.
Active,
// A temporary state set by the contract during complex operations to// act as a reentrancy guard.
Busy,
// The crowdfund is over and has either won or lost.
Finalized
}
structAuctionCrowdfundOptions {
// The name of the crowdfund.// This will also carry over to the governance party.string name;
// The token symbol for both the crowdfund and the governance NFTs.string symbol;
// Customization preset ID to use for the crowdfund and governance NFTs.uint256 customizationPresetId;
// The auction ID (specific to the IMarketWrapper).uint256 auctionId;
// IMarketWrapper contract that handles interactions with auction markets.
IMarketWrapper market;
// The ERC721 contract of the NFT being bought.
IERC721 nftContract;
// ID of the NFT being bought.uint256 nftTokenId;
// How long this crowdfund has to bid on the NFT, in seconds.uint40 duration;
// Maximum bid allowed.uint96 maximumBid;
// An address that receives a portion of the final voting power// when the party transitions into governance.addresspayable splitRecipient;
// What percentage (in bps) of the final total voting power `splitRecipient`// receives.uint16 splitBps;
// If ETH is attached during deployment, it will be interpreted// as a contribution. This is who gets credit for that contribution.address initialContributor;
// If there is an initial contribution, this is who they will delegate their// voting power to when the crowdfund transitions to governance.address initialDelegate;
// Minimum amount of ETH that can be contributed to this crowdfund per address.uint96 minContribution;
// Maximum amount of ETH that can be contributed to this crowdfund per address.uint96 maxContribution;
// The gatekeeper contract to use (if non-null) to restrict who can// contribute to this crowdfund. If used, only contributors or hosts can// call `bid()`.
IGateKeeper gateKeeper;
// The gate ID within the gateKeeper contract to use.bytes12 gateKeeperId;
// Whether the party is only allowing a host to call `bid()`.bool onlyHostCanBid;
// Fixed governance options (i.e. cannot be changed) that the governance// `Party` will be created with if the crowdfund succeeds.
FixedGovernanceOpts governanceOpts;
// Options for the proposal engine that the governance `Party` will be// created with if the crowdfund succeeds.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
}
eventBid(uint256 bidAmount);
eventWon(uint256 bid, Party party);
eventLost();
errorInvalidAuctionIdError();
errorAuctionFinalizedError(uint256 auctionId);
errorAlreadyHighestBidderError();
errorExceedsMaximumBidError(uint256 bidAmount, uint256 maximumBid);
errorMinimumBidExceedsMaximumBidError(uint256 bidAmount, uint256 maximumBid);
errorNoContributionsError();
errorAuctionNotExpiredError();
/// @notice The NFT contract to buy.
IERC721 public nftContract;
/// @notice The NFT token ID to buy.uint256public nftTokenId;
/// @notice An adapter for the auction market (Zora, OpenSea, etc)./// @dev This will be delegatecalled into to execute bids.
IMarketWrapper public market;
/// @notice The auction ID to identify the auction on the `market`.uint256public auctionId;
/// @notice The maximum possible bid this crowdfund can make.uint96public maximumBid;
/// @notice The last successful bid() amount.uint96public lastBid;
/// @notice When this crowdfund expires. If the NFT has not been bought/// by this time, participants can withdraw their contributions.uint40public expiry;
/// @notice Whether the party is only allowing a host to call `bid()`.boolpublic onlyHostCanBid;
// Track extra status of the crowdfund specific to bids.
AuctionCrowdfundStatus internal _bidStatus;
// Set the `Globals` contract.constructor(IGlobals globals) Crowdfund(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later.function_initialize(AuctionCrowdfundOptions memory opts) internal{
if (opts.onlyHostCanBid && opts.governanceOpts.hosts.length==0) {
revert MissingHostsError();
}
nftContract = opts.nftContract;
nftTokenId = opts.nftTokenId;
market = opts.market;
expiry = (opts.duration +block.timestamp).safeCastUint256ToUint40();
auctionId = opts.auctionId;
maximumBid = opts.maximumBid;
onlyHostCanBid = opts.onlyHostCanBid;
Crowdfund._initialize(
CrowdfundOptions({
name: opts.name,
symbol: opts.symbol,
customizationPresetId: opts.customizationPresetId,
splitRecipient: opts.splitRecipient,
splitBps: opts.splitBps,
initialContributor: opts.initialContributor,
initialDelegate: opts.initialDelegate,
minContribution: opts.minContribution,
maxContribution: opts.maxContribution,
gateKeeper: opts.gateKeeper,
gateKeeperId: opts.gateKeeperId,
governanceOpts: opts.governanceOpts,
proposalEngineOpts: opts.proposalEngineOpts
})
);
// Check that the auction can be bid on and is valid.
_validateAuction(opts.market, opts.auctionId, opts.nftContract, opts.nftTokenId);
// Check that the minimum bid is less than the maximum bid.uint256 minimumBid = opts.market.getMinimumBid(opts.auctionId);
if (minimumBid > opts.maximumBid) {
revert MinimumBidExceedsMaximumBidError(minimumBid, opts.maximumBid);
}
}
/// @notice Accept ETH, e.g., if an auction needs to return ETH to us.receive() externalpayable{}
/// @notice Place a bid on the NFT using the funds in this crowdfund,/// placing the minimum possible bid to be the highest bidder, up to/// `maximumBid`. Only callable by contributors if `onlyHostCanBid`/// is not enabled.functionbid() external{
if (onlyHostCanBid) revert OnlyPartyHostError();
// Bid with the minimum amount to be the highest bidder.
_bid(type(uint96).max);
}
/// @notice Place a bid on the NFT using the funds in this crowdfund,/// placing the minimum possible bid to be the highest bidder, up to/// `maximumBid`./// @param governanceOpts The governance options the crowdfund was created/// with. Only used for crowdfunds where only a host/// can bid to verify the caller is a host./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @param hostIndex If the caller is a host, this is the index of the caller in the/// `governanceOpts.hosts` array. Only used for crowdfunds where only/// a host can bid to verify the caller is a host.functionbid(
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
uint256 hostIndex
) external{
// This function can be optionally restricted in different ways.if (onlyHostCanBid) {
// Only a host can call this function.
_assertIsHost(msg.sender, governanceOpts, proposalEngineOpts, hostIndex);
} elseif (address(gateKeeper) !=address(0)) {
// `onlyHostCanBid` is false and we are using a gatekeeper.// Only a contributor can call this function.
_assertIsContributor(msg.sender);
}
// Bid with the minimum amount to be the highest bidder.
_bid(type(uint96).max);
}
/// @notice Place a bid on the NFT using the funds in this crowdfund,/// placing a bid, up to `maximumBid`. Only host can call this./// @param amount The amount to bid./// @param governanceOpts The governance options the crowdfund was created/// with. Used to verify the caller is a host./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @param hostIndex If the caller is a host, this is the index of the caller in the/// `governanceOpts.hosts` array. Used to verify the caller is a host.functionbid(uint96 amount,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
uint256 hostIndex
) external{
// Only a host can specify a custom bid amount.
_assertIsHost(msg.sender, governanceOpts, proposalEngineOpts, hostIndex);
_bid(amount);
}
function_bid(uint96 amount) privateonlyDelegateCall{
// Check that the auction is still active.
{
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active) {
revert WrongLifecycleError(lc);
}
}
// Mark as busy to prevent `burn()`, `bid()`, and `contribute()`// getting called because this will result in a `CrowdfundLifecycle.Busy`.
_bidStatus = AuctionCrowdfundStatus.Busy;
// Make sure the auction is not finalized.
IMarketWrapper market_ = market;
uint256 auctionId_ = auctionId;
if (market_.isFinalized(auctionId_)) {
revert AuctionFinalizedError(auctionId_);
}
// Only bid if we are not already the highest bidder.if (market_.getCurrentHighestBidder(auctionId_) ==address(this)) {
revert AlreadyHighestBidderError();
}
if (amount ==type(uint96).max) {
// Get the minimum necessary bid to be the highest bidder.
amount = market_.getMinimumBid(auctionId_).safeCastUint256ToUint96();
}
// Prevent unaccounted ETH from being used to inflate the bid and// create "ghost shares" in voting power.uint96 totalContributions_ = totalContributions;
if (amount > totalContributions_) {
revert ExceedsTotalContributionsError(amount, totalContributions_);
}
// Make sure the bid is less than the maximum bid.uint96 maximumBid_ = maximumBid;
if (amount > maximumBid_) {
revert ExceedsMaximumBidError(amount, maximumBid_);
}
lastBid = amount;
// No need to check that we have `amount` since this will attempt to// transfer `amount` ETH to the auction platform.
(bool s, bytesmemory r) =address(market_).delegatecall(
abi.encodeCall(IMarketWrapper.bid, (auctionId_, amount))
);
if (!s) {
r.rawRevert();
}
emit Bid(amount);
_bidStatus = AuctionCrowdfundStatus.Active;
}
/// @inheritdoc CrowdfundfunctiongetCrowdfundLifecycle() publicviewoverridereturns (CrowdfundLifecycle) {
// Do not rely on `market.isFinalized()` in case `auctionId` gets reused.
AuctionCrowdfundStatus status = _bidStatus;
if (status == AuctionCrowdfundStatus.Busy) {
// In the midst of finalizing/bidding (trying to reenter).return CrowdfundLifecycle.Busy;
}
if (status == AuctionCrowdfundStatus.Finalized) {
returnaddress(party) !=address(0) // If we're fully finalized and we have a party instance then we won.
? CrowdfundLifecycle.Won // Otherwise we lost.
: CrowdfundLifecycle.Lost;
}
if (block.timestamp>= expiry) {
// Expired. `finalize()` needs to be called.return CrowdfundLifecycle.Expired;
}
return CrowdfundLifecycle.Active;
}
function_getFinalPrice() internalviewoverridereturns (uint256 price) {
return lastBid;
}
function_validateAuction(
IMarketWrapper market_,
uint256 auctionId_,
IERC721 nftContract_,
uint256 nftTokenId_
) internalview{
if (!market_.auctionIdMatchesToken(auctionId_, address(nftContract_), nftTokenId_)) {
revert InvalidAuctionIdError();
}
}
function_finalizeAuction(
CrowdfundLifecycle lc,
IMarketWrapper market_,
uint256 auctionId_,
uint96 lastBid_
) internal{
// Mark as busy to prevent `burn()`, `bid()`, and `contribute()`// getting called because this will result in a `CrowdfundLifecycle.Busy`.
_bidStatus = AuctionCrowdfundStatus.Busy;
// If we've bid before or the CF is not expired, finalize the auction.if (lastBid_ !=0|| lc == CrowdfundLifecycle.Active) {
if (!market_.isFinalized(auctionId_)) {
// If the crowdfund has expired and we are not the highest// bidder, skip finalization because there is no chance of// winning the auction.if (
lc == CrowdfundLifecycle.Expired &&
market_.getCurrentHighestBidder(auctionId_) !=address(this)
) return;
(bool s, bytesmemory r) =address(market_).call(
abi.encodeCall(IMarketWrapper.finalize, auctionId_)
);
if (!s) {
r.rawRevert();
}
}
}
}
}
Contract Source Code
File 3 of 57: BuyCrowdfund.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../party/Party.sol";
import"../utils/LibSafeERC721.sol";
import"../globals/IGlobals.sol";
import"../gatekeepers/IGateKeeper.sol";
import"./BuyCrowdfundBase.sol";
/// @notice A crowdfund that purchases a specific NFT (i.e., with a known token/// ID) listing for a known price.contractBuyCrowdfundisBuyCrowdfundBase{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
structBuyCrowdfundOptions {
// The name of the crowdfund.// This will also carry over to the governance party.string name;
// The token symbol for both the crowdfund and the governance NFTs.string symbol;
// Customization preset ID to use for the crowdfund and governance NFTs.uint256 customizationPresetId;
// The ERC721 contract of the NFT being bought.
IERC721 nftContract;
// ID of the NFT being bought.uint256 nftTokenId;
// How long this crowdfund has to buy the NFT, in seconds.uint40 duration;
// Maximum amount this crowdfund will pay for the NFT.uint96 maximumPrice;
// An address that receives a portion of the final voting power// when the party transitions into governance.addresspayable splitRecipient;
// What percentage (in bps) of the final total voting power `splitRecipient`// receives.uint16 splitBps;
// If ETH is attached during deployment, it will be interpreted// as a contribution. This is who gets credit for that contribution.address initialContributor;
// If there is an initial contribution, this is who they will delegate their// voting power to when the crowdfund transitions to governance.address initialDelegate;
// Minimum amount of ETH that can be contributed to this crowdfund per address.uint96 minContribution;
// Maximum amount of ETH that can be contributed to this crowdfund per address.uint96 maxContribution;
// The gatekeeper contract to use (if non-null) to restrict who can// contribute to this crowdfund. If used, only contributors or hosts can// call `buy()`.
IGateKeeper gateKeeper;
// The gate ID within the gateKeeper contract to use.bytes12 gateKeeperId;
// Whether the party is only allowing a host to call `buy()`.bool onlyHostCanBuy;
// Fixed governance options (i.e. cannot be changed) that the governance// `Party` will be created with if the crowdfund succeeds.
FixedGovernanceOpts governanceOpts;
// Options for the proposal engine that the governance `Party` will be// created with if the crowdfund succeeds.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
}
/// @notice The NFT token ID to buy.uint256public nftTokenId;
/// @notice The NFT contract to buy.
IERC721 public nftContract;
/// @notice Whether the party is only allowing a host to call `buy()`.boolpublic onlyHostCanBuy;
// Set the `Globals` contract.constructor(IGlobals globals) BuyCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later.functioninitialize(BuyCrowdfundOptions memory opts) externalpayableonlyInitialize{
if (opts.onlyHostCanBuy && opts.governanceOpts.hosts.length==0) {
revert MissingHostsError();
}
BuyCrowdfundBase._initialize(
BuyCrowdfundBaseOptions({
name: opts.name,
symbol: opts.symbol,
customizationPresetId: opts.customizationPresetId,
duration: opts.duration,
maximumPrice: opts.maximumPrice,
splitRecipient: opts.splitRecipient,
splitBps: opts.splitBps,
initialContributor: opts.initialContributor,
initialDelegate: opts.initialDelegate,
minContribution: opts.minContribution,
maxContribution: opts.maxContribution,
gateKeeper: opts.gateKeeper,
gateKeeperId: opts.gateKeeperId,
governanceOpts: opts.governanceOpts,
proposalEngineOpts: opts.proposalEngineOpts
})
);
onlyHostCanBuy = opts.onlyHostCanBuy;
nftTokenId = opts.nftTokenId;
nftContract = opts.nftContract;
}
/// @notice Execute arbitrary calldata to perform a buy, creating a party/// if it successfully buys the NFT./// @param callTarget The target contract to call to buy the NFT./// @param callValue The amount of ETH to send with the call./// @param callData The calldata to execute./// @param governanceOpts The options used to initialize governance in the/// `Party` instance created if the buy was successful./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @param hostIndex If the caller is a host, this is the index of the caller in the/// `governanceOpts.hosts` array./// @return party_ Address of the `Party` instance created after its bought.functionbuy(addresspayable callTarget,
uint96 callValue,
bytesmemory callData,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
uint256 hostIndex
) externalonlyDelegateCallreturns (Party party_) {
// This function can be optionally restricted in different ways.bool isValidatedGovernanceOpts;
if (onlyHostCanBuy) {
// Only a host can call this function.
_assertIsHost(msg.sender, governanceOpts, proposalEngineOpts, hostIndex);
// If _assertIsHost() succeeded, the governance opts were validated.
isValidatedGovernanceOpts =true;
} elseif (address(gateKeeper) !=address(0)) {
// `onlyHostCanBuy` is false and we are using a gatekeeper.// Only a contributor can call this function.
_assertIsContributor(msg.sender);
}
{
// Ensure that the crowdfund is still active.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active) {
revert WrongLifecycleError(lc);
}
}
// Temporarily set to non-zero as a reentrancy guard.
settledPrice =type(uint96).max;
// Buy the NFT and check NFT is owned by the crowdfund.
(bool success, bytesmemory revertData) = _buy(
nftContract,
nftTokenId,
callTarget,
callValue,
callData
);
if (!success) {
if (revertData.length>0) {
revertData.rawRevert();
} else {
revert FailedToBuyNFTError(nftContract, nftTokenId);
}
}
return
_finalize(
nftContract,
nftTokenId,
callValue,
governanceOpts,
proposalEngineOpts,
isValidatedGovernanceOpts
);
}
}
Contract Source Code
File 4 of 57: BuyCrowdfundBase.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../party/Party.sol";
import"../utils/LibSafeERC721.sol";
import"../globals/IGlobals.sol";
import"../gatekeepers/IGateKeeper.sol";
import"./Crowdfund.sol";
// Base for BuyCrowdfund and CollectionBuyCrowdfundabstractcontractBuyCrowdfundBaseisCrowdfund{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
structBuyCrowdfundBaseOptions {
// The name of the crowdfund.// This will also carry over to the governance party.string name;
// The token symbol for both the crowdfund and the governance NFTs.string symbol;
// Customization preset ID to use for the crowdfund and governance NFTs.uint256 customizationPresetId;
// How long this crowdfund has to buy the NFT, in seconds.uint40 duration;
// Maximum amount this crowdfund will pay for the NFT.uint96 maximumPrice;
// An address that receives an extra share of the final voting power// when the party transitions into governance.addresspayable splitRecipient;
// What percentage (in bps) of the final total voting power `splitRecipient`// receives.uint16 splitBps;
// If ETH is attached during deployment, it will be interpreted// as a contribution. This is who gets credit for that contribution.address initialContributor;
// If there is an initial contribution, this is who they will delegate their// voting power to when the crowdfund transitions to governance.address initialDelegate;
// Minimum amount of ETH that can be contributed to this crowdfund per address.uint96 minContribution;
// Maximum amount of ETH that can be contributed to this crowdfund per address.uint96 maxContribution;
// The gatekeeper contract to use (if non-null) to restrict who can// contribute to this crowdfund.
IGateKeeper gateKeeper;
// The gatekeeper contract to use (if non-null).bytes12 gateKeeperId;
// Fixed governance options (i.e. cannot be changed) that the governance// `Party` will be created with if the crowdfund succeeds.
FixedGovernanceOpts governanceOpts;
// Options for the proposal engine that the governance `Party` will be// created with if the crowdfund succeeds.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
}
eventWon(Party party, IERC721[] tokens, uint256[] tokenIds, uint256 settledPrice);
eventLost();
errorMaximumPriceError(uint96 callValue, uint96 maximumPrice);
errorNoContributionsError();
errorCallProhibitedError(address target, bytes data);
errorFailedToBuyNFTError(IERC721 token, uint256 tokenId);
/// @notice When this crowdfund expires.uint40public expiry;
/// @notice Maximum amount this crowdfund will pay for the NFT.uint96public maximumPrice;
/// @notice What the NFT was actually bought for.uint96public settledPrice;
// Set the `Globals` contract.constructor(IGlobals globals) Crowdfund(globals) {}
// Initialize storage for proxy contracts.function_initialize(BuyCrowdfundBaseOptions memory opts) internal{
expiry = (opts.duration +block.timestamp).safeCastUint256ToUint40();
maximumPrice = opts.maximumPrice;
Crowdfund._initialize(
CrowdfundOptions({
name: opts.name,
symbol: opts.symbol,
customizationPresetId: opts.customizationPresetId,
splitRecipient: opts.splitRecipient,
splitBps: opts.splitBps,
initialContributor: opts.initialContributor,
initialDelegate: opts.initialDelegate,
minContribution: opts.minContribution,
maxContribution: opts.maxContribution,
gateKeeper: opts.gateKeeper,
gateKeeperId: opts.gateKeeperId,
governanceOpts: opts.governanceOpts,
proposalEngineOpts: opts.proposalEngineOpts
})
);
}
// Execute arbitrary calldata to perform a buy, creating a party// if it successfully buys the NFT.function_buy(
IERC721 token,
uint256 tokenId,
addresspayable callTarget,
uint96 callValue,
bytesmemory callData
) internalreturns (bool success, bytesmemory revertData) {
// Check that the call is not prohibited.if (!_isCallAllowed(callTarget, callData, token)) {
revert CallProhibitedError(callTarget, callData);
}
// Check that the call value is under the maximum price.
{
uint96 maximumPrice_ = maximumPrice;
if (callValue > maximumPrice_) {
revert MaximumPriceError(callValue, maximumPrice_);
}
}
// Execute the call to buy the NFT.
(bool s, bytesmemory r) = callTarget.call{ value: callValue }(callData);
if (!s) {
return (false, r);
}
// Return whether the NFT was successfully bought.return (token.safeOwnerOf(tokenId) ==address(this), "");
}
function_finalize(
IERC721[] memory tokens,
uint256[] memory tokenIds,
uint96 totalEthUsed,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
bool isValidatedGovernanceOpts
) internalreturns (Party party_) {
{
// Prevent unaccounted ETH from being used to inflate the price and// create "ghost shares" in voting power.uint96 totalContributions_ = totalContributions;
if (totalEthUsed > totalContributions_) {
revert ExceedsTotalContributionsError(totalEthUsed, totalContributions_);
}
}
if (totalEthUsed !=0) {
// Create a party around the newly bought NFTs and finalize a win.
settledPrice = totalEthUsed;
party_ = _createParty(
governanceOpts,
proposalEngineOpts,
isValidatedGovernanceOpts,
tokens,
tokenIds
);
emit Won(party_, tokens, tokenIds, totalEthUsed);
} else {
// If all NFTs were purchased for free or were all "gifted" to us,// refund all contributors by finalizing a loss.
settledPrice =0;
expiry =uint40(block.timestamp);
emit Lost();
}
// Notify third-party platforms that the crowdfund NFT metadata has// updated for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
}
function_finalize(
IERC721 token,
uint256 tokenId,
uint96 totalEthUsed,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
bool isValidatedGovernanceOpts
) internalreturns (Party party_) {
IERC721[] memory tokens =new IERC721[](1);
tokens[0] = token;
uint256[] memory tokenIds =newuint256[](1);
tokenIds[0] = tokenId;
return
_finalize(
tokens,
tokenIds,
totalEthUsed,
governanceOpts,
proposalEngineOpts,
isValidatedGovernanceOpts
);
}
/// @inheritdoc CrowdfundfunctiongetCrowdfundLifecycle() publicviewoverridereturns (CrowdfundLifecycle) {
// If there is a settled price then we tried to buy the NFT.if (settledPrice !=0) {
returnaddress(party) !=address(0)
? CrowdfundLifecycle.Won // If we have a party, then we succeeded buying the NFT.
: CrowdfundLifecycle.Busy; // Otherwise we're in the middle of the `buy()`.
}
if (block.timestamp>= expiry) {
// Expired, but nothing to do so skip straight to lost, or NFT was// acquired for free so refund contributors and trigger lost.return CrowdfundLifecycle.Lost;
}
return CrowdfundLifecycle.Active;
}
function_getFinalPrice() internalviewoverridereturns (uint256) {
return settledPrice;
}
function_isCallAllowed(addresspayable callTarget,
bytesmemory callData,
IERC721 token
) privateviewreturns (bool isAllowed) {
// Ensure the call target isn't trying to reenterif (callTarget ==address(this)) {
returnfalse;
}
if (callTarget ==address(token) && callData.length>=4) {
// Get the function selector of the call (first 4 bytes of calldata).bytes4 selector;
assembly {
selector :=and(
mload(add(callData, 32)),
0xffffffff00000000000000000000000000000000000000000000000000000000
)
}
// Prevent approving the NFT to be transferred out from the crowdfund.if (
selector == IERC721.approve.selector||
selector == IERC721.setApprovalForAll.selector
) {
returnfalse;
}
}
// All other calls are allowed.returntrue;
}
}
Contract Source Code
File 5 of 57: Clones.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)pragmasolidity ^0.8.19;/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/libraryClones{
/**
* @dev A clone instance deployment failed.
*/errorERC1167FailedCreateClone();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/functionclone(address implementation) internalreturns (address instance) {
/// @solidity memory-safe-assemblyassembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes// of the `implementation` address with the bytecode before the address.mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance :=create(0, 0x09, 0x37)
}
if (instance ==address(0)) {
revert ERC1167FailedCreateClone();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/functioncloneDeterministic(address implementation, bytes32 salt) internalreturns (address instance) {
/// @solidity memory-safe-assemblyassembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes// of the `implementation` address with the bytecode before the address.mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance :=create2(0, 0x09, 0x37, salt)
}
if (instance ==address(0)) {
revert ERC1167FailedCreateClone();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/functionpredictDeterministicAddress(address implementation,
bytes32 salt,
address deployer
) internalpurereturns (address predicted) {
/// @solidity memory-safe-assemblyassembly {
let ptr :=mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted :=keccak256(add(ptr, 0x43), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/functionpredictDeterministicAddress(address implementation,
bytes32 salt
) internalviewreturns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
}
Contract Source Code
File 6 of 57: CollectionBatchBuyCrowdfund.sol
// SPDX-License-Identifier: Beta Software// http://ipfs.io/ipfs/QmbGX2MFCaMAsMNMugRFND6DtYygRkwkvrqEyTKhTdBLo5pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../party/Party.sol";
import"../utils/LibSafeERC721.sol";
import"../globals/IGlobals.sol";
import"../gatekeepers/IGateKeeper.sol";
import"openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import"./BuyCrowdfundBase.sol";
/// @notice A highly customizable crowdfund that can be used to buying one or/// many NFTs in batch from a specific collection.contractCollectionBatchBuyCrowdfundisBuyCrowdfundBase{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
structCollectionBatchBuyCrowdfundOptions {
// The name of the crowdfund.// This will also carry over to the governance party.string name;
// The token symbol for both the crowdfund and the governance NFTs.string symbol;
// Customization preset ID to use for the crowdfund and governance NFTs.uint256 customizationPresetId;
// The ERC721 contract of the NFTs being bought.
IERC721 nftContract;
// The merkle root of the token IDs that can be bought. If null, any// token ID in the collection can be bought.bytes32 nftTokenIdsMerkleRoot;
// How long this crowdfund has to buy the NFTs, in seconds.uint40 duration;
// Maximum amount this crowdfund will pay for an NFT.uint96 maximumPrice;
// An address that receives a portion of the final voting power// when the party transitions into governance.addresspayable splitRecipient;
// What percentage (in bps) of the final total voting power `splitRecipient`// receives.uint16 splitBps;
// If ETH is attached during deployment, it will be interpreted// as a contribution. This is who gets credit for that contribution.address initialContributor;
// If there is an initial contribution, this is who they will delegate their// voting power to when the crowdfund transitions to governance.address initialDelegate;
// Minimum amount of ETH that can be contributed to this crowdfund per address.uint96 minContribution;
// Maximum amount of ETH that can be contributed to this crowdfund per address.uint96 maxContribution;
// The gatekeeper contract to use (if non-null) to restrict who can// contribute to this crowdfund.
IGateKeeper gateKeeper;
// The gate ID within the gateKeeper contract to use.bytes12 gateKeeperId;
// Fixed governance options (i.e. cannot be changed) that the governance// `Party` will be created with if the crowdfund succeeds.
FixedGovernanceOpts governanceOpts;
// Options for the proposal engine that the governance `Party` will be// created with if the crowdfund succeeds.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
}
structBatchBuyArgs {
uint256[] tokenIds;
addresspayable[] callTargets;
uint96[] callValues;
bytes[] callDatas;
bytes32[][] proofs;
uint256 minTokensBought;
uint256 minTotalEthUsed;
FixedGovernanceOpts governanceOpts;
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
uint256 hostIndex;
}
errorNothingBoughtError();
errorInvalidMinTokensBoughtError(uint256 minTokensBought);
errorInvalidTokenIdError();
errorContributionsSpentForFailedBuyError();
errorNotEnoughTokensBoughtError(uint256 tokensBought, uint256 minTokensBought);
errorNotEnoughEthUsedError(uint256 ethUsed, uint256 minTotalEthUsed);
errorMismatchedCallArgLengthsError();
/// @notice The contract of NFTs to buy.
IERC721 public nftContract;
/// @notice The merkle root of the token IDs that can be bought. If null,/// allow any token ID in the collection to be bought.bytes32public nftTokenIdsMerkleRoot;
// Set the `Globals` contract.constructor(IGlobals globals) BuyCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later.functioninitialize(
CollectionBatchBuyCrowdfundOptions memory opts
) externalpayableonlyInitialize{
if (opts.governanceOpts.hosts.length==0) {
revert MissingHostsError();
}
BuyCrowdfundBase._initialize(
BuyCrowdfundBaseOptions({
name: opts.name,
symbol: opts.symbol,
customizationPresetId: opts.customizationPresetId,
duration: opts.duration,
maximumPrice: opts.maximumPrice,
splitRecipient: opts.splitRecipient,
splitBps: opts.splitBps,
initialContributor: opts.initialContributor,
initialDelegate: opts.initialDelegate,
minContribution: opts.minContribution,
maxContribution: opts.maxContribution,
gateKeeper: opts.gateKeeper,
gateKeeperId: opts.gateKeeperId,
governanceOpts: opts.governanceOpts,
proposalEngineOpts: opts.proposalEngineOpts
})
);
nftContract = opts.nftContract;
nftTokenIdsMerkleRoot = opts.nftTokenIdsMerkleRoot;
}
/// @notice Execute arbitrary calldata to perform a batch buy, creating a party/// if it successfully buys the NFT. Only a host may call this./// @param args Arguments for the batch buy./// @return party_ Address of the `Party` instance created after its bought.functionbatchBuy(BatchBuyArgs memory args) externalonlyDelegateCallreturns (Party party_) {
// This function is restricted to hosts.
_assertIsHost(msg.sender, args.governanceOpts, args.proposalEngineOpts, args.hostIndex);
{
// Ensure that the crowdfund is still active.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active) {
revert WrongLifecycleError(lc);
}
}
if (args.minTokensBought ==0) {
// Must buy at least one token.revert InvalidMinTokensBoughtError(0);
}
// Check length of all arg arrays.if (
args.tokenIds.length!= args.callTargets.length||
args.tokenIds.length!= args.callValues.length||
args.tokenIds.length!= args.callDatas.length||
args.tokenIds.length!= args.proofs.length
) {
revert MismatchedCallArgLengthsError();
}
// Temporarily set to non-zero as a reentrancy guard.
settledPrice =type(uint96).max;
uint96 totalEthUsed;
uint256 tokensBought;
IERC721[] memory tokens =new IERC721[](args.tokenIds.length);
IERC721 token = nftContract;
bytes32 root = nftTokenIdsMerkleRoot;
for (uint256 i; i < args.tokenIds.length; ++i) {
if (root !=bytes32(0)) {
// Verify the token ID is in the merkle tree.
_verifyTokenId(args.tokenIds[i], root, args.proofs[i]);
}
// Used to ensure no ETH is spent if the call fails.uint256 balanceBefore =address(this).balance;
// Execute the call to buy the NFT.
(bool success, bytesmemory revertData) = _buy(
token,
args.tokenIds[i],
args.callTargets[i],
args.callValues[i],
args.callDatas[i]
);
if (!success) {
if (args.minTokensBought >= args.tokenIds.length) {
// If the call failed with revert data, revert with that data.if (revertData.length>0) {
revertData.rawRevert();
} else {
revert FailedToBuyNFTError(token, args.tokenIds[i]);
}
} else {
// If the call failed, ensure no ETH was spent and skip this NFT.if (address(this).balance!= balanceBefore) {
revert ContributionsSpentForFailedBuyError();
}
continue;
}
}
totalEthUsed += args.callValues[i];
++tokensBought;
tokens[tokensBought -1] = token;
args.tokenIds[tokensBought -1] = args.tokenIds[i];
}
// This is to prevent this crowdfund from finalizing a loss if nothing// was attempted to be bought (ie. `tokenIds` is empty) or all NFTs were// bought for free.if (totalEthUsed ==0) revert NothingBoughtError();
// Check number of tokens bought is not less than the minimum.if (tokensBought < args.minTokensBought) {
revert NotEnoughTokensBoughtError(tokensBought, args.minTokensBought);
}
// Check total ETH used is not less than the minimum.if (totalEthUsed < args.minTotalEthUsed) {
revert NotEnoughEthUsedError(totalEthUsed, args.minTotalEthUsed);
}
assembly {
// Update length of `tokens`mstore(tokens, tokensBought)
// Update length of `tokenIds`mstore(mload(args), tokensBought)
}
return
_finalize(
tokens,
args.tokenIds,
totalEthUsed,
args.governanceOpts,
args.proposalEngineOpts,
// If `_assertIsHost()` succeeded, the governance opts were validated.true
);
}
function_verifyTokenId(uint256 tokenId, bytes32 root, bytes32[] memory proof) privatepure{
bytes32 leaf;
assembly {
mstore(0x00, tokenId)
leaf :=keccak256(0x00, 0x20)
}
if (!MerkleProof.verify(proof, root, leaf)) revert InvalidTokenIdError();
}
}
Contract Source Code
File 7 of 57: CollectionBuyCrowdfund.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../party/Party.sol";
import"../utils/LibSafeERC721.sol";
import"../utils/LibRawResult.sol";
import"../globals/IGlobals.sol";
import"../gatekeepers/IGateKeeper.sol";
import"./BuyCrowdfundBase.sol";
/// @notice A crowdfund that purchases any NFT from a collection (i.e., any/// token ID) from a collection for a known price. Like `BuyCrowdfund`/// but allows any token ID to be bought.contractCollectionBuyCrowdfundisBuyCrowdfundBase{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
structCollectionBuyCrowdfundOptions {
// The name of the crowdfund.// This will also carry over to the governance party.string name;
// The token symbol for both the crowdfund and the governance NFTs.string symbol;
// Customization preset ID to use for the crowdfund and governance NFTs.uint256 customizationPresetId;
// The ERC721 contract of the NFT being bought.
IERC721 nftContract;
// How long this crowdfund has to buy the NFT, in seconds.uint40 duration;
// Maximum amount this crowdfund will pay for the NFT.uint96 maximumPrice;
// An address that receives a portion of the final voting power// when the party transitions into governance.addresspayable splitRecipient;
// What percentage (in bps) of the final total voting power `splitRecipient`// receives.uint16 splitBps;
// If ETH is attached during deployment, it will be interpreted// as a contribution. This is who gets credit for that contribution.address initialContributor;
// If there is an initial contribution, this is who they will delegate their// voting power to when the crowdfund transitions to governance.address initialDelegate;
// Minimum amount of ETH that can be contributed to this crowdfund per address.uint96 minContribution;
// Maximum amount of ETH that can be contributed to this crowdfund per address.uint96 maxContribution;
// The gatekeeper contract to use (if non-null) to restrict who can// contribute to this crowdfund.
IGateKeeper gateKeeper;
// The gate ID within the gateKeeper contract to use.bytes12 gateKeeperId;
// Fixed governance options (i.e. cannot be changed) that the governance// `Party` will be created with if the crowdfund succeeds.
FixedGovernanceOpts governanceOpts;
// Options for the proposal engine that the governance `Party` will be// created with if the crowdfund succeeds.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
}
/// @notice The NFT contract to buy.
IERC721 public nftContract;
// Set the `Globals` contract.constructor(IGlobals globals) BuyCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later.functioninitialize(CollectionBuyCrowdfundOptions memory opts) externalpayableonlyInitialize{
if (opts.governanceOpts.hosts.length==0) {
revert MissingHostsError();
}
BuyCrowdfundBase._initialize(
BuyCrowdfundBaseOptions({
name: opts.name,
symbol: opts.symbol,
customizationPresetId: opts.customizationPresetId,
duration: opts.duration,
maximumPrice: opts.maximumPrice,
splitRecipient: opts.splitRecipient,
splitBps: opts.splitBps,
initialContributor: opts.initialContributor,
initialDelegate: opts.initialDelegate,
minContribution: opts.minContribution,
maxContribution: opts.maxContribution,
gateKeeper: opts.gateKeeper,
gateKeeperId: opts.gateKeeperId,
governanceOpts: opts.governanceOpts,
proposalEngineOpts: opts.proposalEngineOpts
})
);
nftContract = opts.nftContract;
}
/// @notice Execute arbitrary calldata to perform a buy, creating a party/// if it successfully buys the NFT. Only a host may call this./// @param tokenId The token ID of the NFT in the collection to buy./// @param callTarget The target contract to call to buy the NFT./// @param callValue The amount of ETH to send with the call./// @param callData The calldata to execute./// @param governanceOpts The options used to initialize governance in the/// `Party` instance created if the buy was successful./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @param hostIndex This is the index of the caller in the `governanceOpts.hosts` array./// @return party_ Address of the `Party` instance created after its bought.functionbuy(uint256 tokenId,
addresspayable callTarget,
uint96 callValue,
bytesmemory callData,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
uint256 hostIndex
) externalonlyDelegateCallreturns (Party party_) {
// This function is always restricted to hosts.
_assertIsHost(msg.sender, governanceOpts, proposalEngineOpts, hostIndex);
{
// Ensure that the crowdfund is still active.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active) {
revert WrongLifecycleError(lc);
}
}
// Temporarily set to non-zero as a reentrancy guard.
settledPrice =type(uint96).max;
// Buy the NFT and check NFT is owned by the crowdfund.
(bool success, bytesmemory revertData) = _buy(
nftContract,
tokenId,
callTarget,
callValue,
callData
);
if (!success) {
if (revertData.length>0) {
revertData.rawRevert();
} else {
revert FailedToBuyNFTError(nftContract, tokenId);
}
}
return
_finalize(
nftContract,
tokenId,
callValue,
governanceOpts,
proposalEngineOpts,
// If `_assertIsHost()` succeeded, the governance opts were validated.true
);
}
}
Contract Source Code
File 8 of 57: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)pragmasolidity ^0.8.20;/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
function_contextSuffixLength() internalviewvirtualreturns (uint256) {
return0;
}
}
Contract Source Code
File 9 of 57: Crowdfund.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 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"../party/IPartyFactory.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.abstractcontractCrowdfundisImplementation, ERC721Receiver, CrowdfundNFT{
usingLibRawResultforbytes;
usingLibSafeCastforuint256;
usingLibAddressforaddresspayable;
enumCrowdfundLifecycle {
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.structFixedGovernanceOpts {
// 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.addresspayable feeRecipient;
}
// Options to be passed into `_initialize()` when the crowdfund is created.structCrowdfundOptions {
string name;
string symbol;
uint256 customizationPresetId;
addresspayable 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`.structContribution {
// 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()`.structClaim {
uint256 refund;
uint256 governanceTokenId;
}
errorPartyAlreadyExistsError(Party party);
errorWrongLifecycleError(CrowdfundLifecycle lc);
errorInvalidGovernanceOptionsError();
errorInvalidDelegateError();
errorInvalidContributorError();
errorNoPartyError();
errorNotAllowedByGateKeeperError(address contributor,
IGateKeeper gateKeeper,
bytes12 gateKeeperId,
bytes gateData
);
errorSplitRecipientAlreadyBurnedError();
errorInvalidBpsError(uint16 bps);
errorExceedsTotalContributionsError(uint96 value, uint96 totalContributions);
errorNothingToClaimError();
errorOnlyPartyHostError();
errorOnlyContributorError();
errorMissingHostsError();
errorOnlyPartyDaoError(address notDao);
errorOnlyPartyDaoOrHostError(address notDao);
errorOnlyWhenEmergencyActionsAllowedError();
errorBelowMinimumContributionsError(uint96 contributions, uint96 minContributions);
errorAboveMaximumContributionsError(uint96 contributions, uint96 maxContributions);
errorInvalidMessageValue();
errorArityMismatch();
eventBurned(address contributor, uint256 ethUsed, uint256 ethOwed, uint256 votingPower);
eventContributed(address sender,
address contributor,
uint256 amount,
address delegate,
uint256 previousTotalContributions
);
eventEmergencyExecute(address target, bytes data, uint256 amountEth);
eventEmergencyExecuteDisabled();
uint40privateconstant 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 privateimmutable _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.uint96public 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.bytes12public gateKeeperId;
/// @notice Who will receive a reserved portion of governance power when/// the governance party is created.addresspayablepublic splitRecipient;
/// @notice How much governance power to reserve for `splitRecipient`,/// in bps, where 10,000 = 100%.uint16public splitBps;
// Whether the share for split recipient has been claimed through `burn()`.boolprivate _splitRecipientHasBurned;
/// @notice Hash of party options passed into `initialize()`./// Used to check whether the options passed into `_createParty()`/// matches.bytes32public 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.uint96public minContribution;
/// @notice Maximum amount of ETH that can be contributed to this crowdfund per address.uint96public maxContribution;
/// @notice Whether the DAO has emergency powers for this party.boolpublic 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 creator passed in some ETH during initialization, 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.functionemergencyExecute(address targetAddress,
bytescalldata targetCallData,
uint256 amountEth
) externalpayableonlyDelegateCall{
// 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, bytesmemory 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).functiondisableEmergencyExecute(
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts,
uint256 hostIndex
) externalonlyDelegateCall{
// 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.functionburn(addresspayable contributor) public{
return _burn(contributor, getCrowdfundLifecycle(), party);
}
/// @dev Alias for `burn()`.functionactivateOrRefund(addresspayable 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.functionbatchBurn(addresspayable[] calldata contributors, bool revertOnFailure) public{
for (uint256 i =0; i < contributors.length; ++i) {
(bool s, bytesmemory r) =address(this).delegatecall(
abi.encodeCall(this.burn, (contributors[i]))
);
if (revertOnFailure &&!s) {
r.rawRevert();
}
}
}
/// @dev Alias for `batchBurn()`.functionbatchActivateOrRefund(addresspayable[] 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.functionclaim(addresspayable 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.functioncontribute(address delegate, bytesmemory gateData) externalpayableonlyDelegateCall{
_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.functioncontributeFor(address recipient,
address initialDelegate,
bytesmemory gateData
) externalpayableonlyDelegateCall{
_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.functionbatchContributeFor(address[] memory recipients,
address[] memory initialDelegates,
uint96[] memory values,
bytes[] memory gateDatas
) externalpayable{
uint256 numRecipients = recipients.length;
if (
numRecipients != initialDelegates.length||
numRecipients != values.length||
numRecipients != gateDatas.length
) {
revert ArityMismatch();
}
uint256 valuesSum;
for (uint256 i; i < recipients.length; ++i) {
_setDelegate(recipients[i], initialDelegates[i]);
_contribute(
recipients[i],
initialDelegates[i],
values[i],
totalContributions,
gateDatas[i]
);
valuesSum += values[i];
}
if (msg.value!= valuesSum) {
revert InvalidMessageValue();
}
}
/// @inheritdoc EIP165functionsupportsInterface(bytes4 interfaceId
) publicpureoverride(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`.functiongetContributorInfo(address contributor
)
externalviewreturns (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.functiongetCrowdfundLifecycle() publicviewvirtualreturns (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() internalviewvirtualreturns (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
) internalview{
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
) privateviewreturns (bool isHost) {
if (hostIndex < governanceOpts.hosts.length&& who == governanceOpts.hosts[hostIndex]) {
// Validate governance opts if the host was found.
_assertValidOpts(governanceOpts, proposalEngineOpts);
returntrue;
}
returnfalse;
}
function_isPartyDao(address who) privateviewreturns (bool isPartyDao) {
return who == _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
}
// Assert that `who` is a contributor to the crowdfund.function_assertIsContributor(address who) internalview{
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
) internalreturns (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 =newaddress[](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
) internalreturns (Party party_) {
IERC721[] memory tokens =new IERC721[](1);
tokens[0] = preciousToken;
uint256[] memory tokenIds =newuint256[](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
) privateview{
bytes32 partyOptsHash_ = _hashOpts(governanceOpts, proposalEngineOpts);
if (partyOptsHash_ != partyOptsHash) {
revert InvalidGovernanceOptionsError();
}
}
function_hashOpts(
FixedGovernanceOpts memory govOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
) internalpurereturns (bytes32 h) {
returnkeccak256(abi.encode(govOpts, proposalEngineOpts));
}
function_getFinalContribution(address contributor
) internalviewreturns (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;
} elseif (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 onaddress 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,
bytesmemory 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))) {
// Checking msg.sender here instead of contributor is intentional to// allow someone who's allowed by a gatekeeper to invite others// into the Party. For example, to allow another contract, and// only that contract, to process contributions on behalf of// contributors.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);
// Notify third-party platforms that the crowdfund NFT metadata has updated.emit MetadataUpdate(uint256(uint160(contributor)));
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(addresspayable 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();
}
} elseif (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
) purereturns (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)
}
}
Contract Source Code
File 10 of 57: CrowdfundFactory.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { Clones } from"openzeppelin/contracts/proxy/Clones.sol";
import { LibRawResult } from"../utils/LibRawResult.sol";
import { IGateKeeper } from"../gatekeepers/IGateKeeper.sol";
import { AuctionCrowdfund, AuctionCrowdfundBase } from"./AuctionCrowdfund.sol";
import { BuyCrowdfund } from"./BuyCrowdfund.sol";
import { CollectionBuyCrowdfund } from"./CollectionBuyCrowdfund.sol";
import { RollingAuctionCrowdfund } from"./RollingAuctionCrowdfund.sol";
import { CollectionBatchBuyCrowdfund } from"./CollectionBatchBuyCrowdfund.sol";
import { InitialETHCrowdfund, ETHCrowdfundBase } from"./InitialETHCrowdfund.sol";
import { ERC20LaunchCrowdfund } from"./ERC20LaunchCrowdfund.sol";
import { MetadataProvider } from"../renderers/MetadataProvider.sol";
import { Party } from"../party/Party.sol";
/// @notice Factory used to deploys new proxified `Crowdfund` instances.contractCrowdfundFactory{
usingClonesforaddress;
usingLibRawResultforbytes;
eventBuyCrowdfundCreated(addressindexed creator,
BuyCrowdfund indexed crowdfund,
BuyCrowdfund.BuyCrowdfundOptions opts
);
eventAuctionCrowdfundCreated(addressindexed creator,
AuctionCrowdfund indexed crowdfund,
AuctionCrowdfundBase.AuctionCrowdfundOptions opts
);
eventCollectionBuyCrowdfundCreated(addressindexed creator,
CollectionBuyCrowdfund indexed crowdfund,
CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions opts
);
eventRollingAuctionCrowdfundCreated(addressindexed creator,
RollingAuctionCrowdfund indexed crowdfund,
AuctionCrowdfundBase.AuctionCrowdfundOptions opts,
bytes32 allowedAuctionsMerkleRoot
);
eventCollectionBatchBuyCrowdfundCreated(addressindexed creator,
CollectionBatchBuyCrowdfund indexed crowdfund,
CollectionBatchBuyCrowdfund.CollectionBatchBuyCrowdfundOptions opts
);
eventInitialETHCrowdfundCreated(addressindexed creator,
InitialETHCrowdfund indexed crowdfund,
Party indexed party,
InitialETHCrowdfund.InitialETHCrowdfundOptions crowdfundOpts,
InitialETHCrowdfund.ETHPartyOptions partyOpts
);
eventERC20LaunchCrowdfundCreated(addressindexed creator,
ERC20LaunchCrowdfund indexed crowdfund,
Party indexed party,
ERC20LaunchCrowdfund.InitialETHCrowdfundOptions crowdfundOpts,
ERC20LaunchCrowdfund.ETHPartyOptions partyOpts,
ERC20LaunchCrowdfund.ERC20LaunchOptions tokenOpts
);
/// @notice Create a new crowdfund to purchase a specific NFT (i.e., with a/// known token ID) listing for a known price./// @param crowdfundImpl The implementation contract of the crowdfund to create./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to/// create the crowdfund if one is specified in `opts`.functioncreateBuyCrowdfund(
BuyCrowdfund crowdfundImpl,
BuyCrowdfund.BuyCrowdfundOptions memory opts,
bytesmemory createGateCallData
) externalpayablereturns (BuyCrowdfund inst) {
opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData);
inst = BuyCrowdfund(address(crowdfundImpl).clone());
inst.initialize{ value: msg.value }(opts);
emit BuyCrowdfundCreated(msg.sender, inst, opts);
}
/// @notice Create a new crowdfund to bid on an auction for a specific NFT/// (i.e. with a known token ID)./// @param crowdfundImpl The implementation contract of the crowdfund to create./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateAuctionCrowdfund(
AuctionCrowdfund crowdfundImpl,
AuctionCrowdfundBase.AuctionCrowdfundOptions memory opts,
bytesmemory createGateCallData
) externalpayablereturns (AuctionCrowdfund inst) {
opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData);
inst = AuctionCrowdfund(payable(address(crowdfundImpl).clone()));
inst.initialize{ value: msg.value }(opts);
emit AuctionCrowdfundCreated(msg.sender, inst, opts);
}
/// @notice Create a new crowdfund to bid on an auctions for an NFT from a collection/// on a market (e.g. Nouns)./// @param crowdfundImpl The implementation contract of the crowdfund to create./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateRollingAuctionCrowdfund(
RollingAuctionCrowdfund crowdfundImpl,
AuctionCrowdfundBase.AuctionCrowdfundOptions memory opts,
bytes32 allowedAuctionsMerkleRoot,
bytesmemory createGateCallData
) externalpayablereturns (RollingAuctionCrowdfund inst) {
opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData);
inst = RollingAuctionCrowdfund(payable(address(crowdfundImpl).clone()));
inst.initialize{ value: msg.value }(opts, allowedAuctionsMerkleRoot);
emit RollingAuctionCrowdfundCreated(msg.sender, inst, opts, allowedAuctionsMerkleRoot);
}
/// @notice Create a new crowdfund to purchases any NFT from a collection/// (i.e. any token ID) from a collection for a known price./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateCollectionBuyCrowdfund(
CollectionBuyCrowdfund crowdfundImpl,
CollectionBuyCrowdfund.CollectionBuyCrowdfundOptions memory opts,
bytesmemory createGateCallData
) externalpayablereturns (CollectionBuyCrowdfund inst) {
opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData);
inst = CollectionBuyCrowdfund(address(crowdfundImpl).clone());
inst.initialize{ value: msg.value }(opts);
emit CollectionBuyCrowdfundCreated(msg.sender, inst, opts);
}
/// @notice Create a new crowdfund to purchase multiple NFTs from a collection/// (i.e. any token ID) from a collection for known prices./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateCollectionBatchBuyCrowdfund(
CollectionBatchBuyCrowdfund crowdfundImpl,
CollectionBatchBuyCrowdfund.CollectionBatchBuyCrowdfundOptions memory opts,
bytesmemory createGateCallData
) externalpayablereturns (CollectionBatchBuyCrowdfund inst) {
opts.gateKeeperId = _prepareGate(opts.gateKeeper, opts.gateKeeperId, createGateCallData);
inst = CollectionBatchBuyCrowdfund(address(crowdfundImpl).clone());
inst.initialize{ value: msg.value }(opts);
emit CollectionBatchBuyCrowdfundCreated(msg.sender, inst, opts);
}
/// @notice Create a new crowdfund to raise ETH for a new party./// @param crowdfundImpl The implementation contract of the crowdfund to create./// @param crowdfundOpts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param partyOpts Options used to initialize the party created by the crowdfund./// These are fixed and cannot be changed later./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateInitialETHCrowdfund(
InitialETHCrowdfund crowdfundImpl,
InitialETHCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts,
InitialETHCrowdfund.ETHPartyOptions memory partyOpts,
bytesmemory createGateCallData
) externalpayablereturns (InitialETHCrowdfund inst) {
return
createInitialETHCrowdfundWithMetadata(
crowdfundImpl,
crowdfundOpts,
partyOpts,
MetadataProvider(address(0)),
"",
createGateCallData
);
}
/// @notice Create a new crowdfund to raise ETH for a new party./// @param crowdfundImpl The implementation contract of the crowdfund to create./// @param crowdfundOpts Options used to initialize the crowdfund./// @param partyOpts Options used to initialize the party created by the crowdfund./// @param customMetadataProvider A custom metadata provider to use for the party./// @param customMetadata Custom metadata to use for the party./// @param createGateCallData Encoded calldata used by `createGate()` to create/// the crowdfund if one is specified in `opts`.functioncreateInitialETHCrowdfundWithMetadata(
InitialETHCrowdfund crowdfundImpl,
InitialETHCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts,
InitialETHCrowdfund.ETHPartyOptions memory partyOpts,
MetadataProvider customMetadataProvider,
bytesmemory customMetadata,
bytesmemory createGateCallData
) publicpayablereturns (InitialETHCrowdfund inst) {
crowdfundOpts.gateKeeperId = _prepareGate(
crowdfundOpts.gateKeeper,
crowdfundOpts.gateKeeperId,
createGateCallData
);
inst = InitialETHCrowdfund(address(crowdfundImpl).clone());
inst.initialize{ value: msg.value }(
crowdfundOpts,
partyOpts,
customMetadataProvider,
customMetadata
);
emit InitialETHCrowdfundCreated(msg.sender, inst, inst.party(), crowdfundOpts, partyOpts);
}
functioncreateERC20LaunchCrowdfund(
ERC20LaunchCrowdfund crowdfundImpl,
ERC20LaunchCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts,
ERC20LaunchCrowdfund.ETHPartyOptions memory partyOpts,
ERC20LaunchCrowdfund.ERC20LaunchOptions memory tokenOpts,
bytesmemory createGateCallData
) externalpayablereturns (ERC20LaunchCrowdfund inst) {
return
createERC20LaunchCrowdfundWithMetadata(
crowdfundImpl,
crowdfundOpts,
partyOpts,
tokenOpts,
MetadataProvider(address(0)),
"",
createGateCallData
);
}
functioncreateERC20LaunchCrowdfundWithMetadata(
ERC20LaunchCrowdfund crowdfundImpl,
ERC20LaunchCrowdfund.InitialETHCrowdfundOptions memory crowdfundOpts,
ERC20LaunchCrowdfund.ETHPartyOptions memory partyOpts,
ERC20LaunchCrowdfund.ERC20LaunchOptions memory tokenOpts,
MetadataProvider customMetadataProvider,
bytesmemory customMetadata,
bytesmemory createGateCallData
) publicpayablereturns (ERC20LaunchCrowdfund inst) {
crowdfundOpts.gateKeeperId = _prepareGate(
crowdfundOpts.gateKeeper,
crowdfundOpts.gateKeeperId,
createGateCallData
);
inst = ERC20LaunchCrowdfund(address(crowdfundImpl).clone());
inst.initialize{ value: msg.value }(
crowdfundOpts,
partyOpts,
tokenOpts,
customMetadataProvider,
customMetadata
);
emit ERC20LaunchCrowdfundCreated(
msg.sender,
inst,
inst.party(),
crowdfundOpts,
partyOpts,
tokenOpts
);
}
function_prepareGate(
IGateKeeper gateKeeper,
bytes12 gateKeeperId,
bytesmemory createGateCallData
) privatereturns (bytes12 newGateKeeperId) {
if (address(gateKeeper) ==address(0) || gateKeeperId !=bytes12(0)) {
// Using an existing gate on the gatekeeper// or not using a gate at all.return gateKeeperId;
}
// Call the gate creation function on the gatekeeper.
(bool s, bytesmemory r) =address(gateKeeper).call(createGateCallData);
if (!s) {
r.rawRevert();
}
// Result is always a bytes12.returnabi.decode(r, (bytes12));
}
}
Contract Source Code
File 11 of 57: CrowdfundNFT.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"../utils/ReadOnlyDelegateCall.sol";
import"../utils/EIP165.sol";
import"../utils/IERC4906.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.abstractcontractCrowdfundNFTisIERC721, IERC4906, EIP165, ReadOnlyDelegateCall{
errorAlreadyMintedError(address owner, uint256 tokenId);
errorAlreadyBurnedError(address owner, uint256 tokenId);
errorInvalidTokenError(uint256 tokenId);
errorInvalidAddressError();
// The `Globals` contract storing global configuration values. This contract// is immutable and it’s address will never change.
IGlobals privateimmutable _GLOBALS;
/// @notice The name of the crowdfund. This will also carry over to the/// governance party.stringpublic name;
/// @notice The token symbol for the crowdfund. This will also carry over to/// the governance party.stringpublic symbol;
mapping(uint256=>address) private _owners;
modifieralwaysRevert() {
revert("ALWAYS FAILING");
_; // Compiler requires this.
}
// Set the `Globals` contract.constructor(IGlobals globals) {
_GLOBALS = globals;
}
// Initialize name and symbol for crowdfund.function_initialize(stringmemory name_,
stringmemory symbol_,
uint256 customizationPresetId
) internalvirtual{
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.functiontransferFrom(address, address, uint256) externalpurealwaysRevert{}
/// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always fail.functionsafeTransferFrom(address, address, uint256) externalpurealwaysRevert{}
/// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always fail.functionsafeTransferFrom(address,
address,
uint256,
bytescalldata) externalpurealwaysRevert{}
/// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always fail.functionapprove(address, uint256) externalpurealwaysRevert{}
/// @notice DO NOT CALL. This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always fail.functionsetApprovalForAll(address, bool) externalpurealwaysRevert{}
/// @notice This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always return null.functiongetApproved(uint256) externalpurereturns (address) {
returnaddress(0);
}
/// @notice This is a soulbound NFT and cannot be transferred./// Attempting to call this function will always return false.functionisApprovedForAll(address, address) externalpurereturns (bool) {
returnfalse;
}
/// @inheritdoc EIP165functionsupportsInterface(bytes4 interfaceId) publicpurevirtualoverridereturns (bool) {
returnsuper.supportsInterface(interfaceId) ||// ERC721 interface ID
interfaceId ==0x80ac58cd;
}
/// @notice Returns a URI to render the NFT.functiontokenURI(uint256) externalviewreturns (stringmemory) {
return _delegateToRenderer();
}
/// @notice Returns a URI for the storefront-level metadata for your contract.functioncontractURI() externalviewreturns (stringmemory) {
return _delegateToRenderer();
}
/// @inheritdoc IERC721functionownerOf(uint256 tokenId) externalviewreturns (address owner) {
owner = _owners[tokenId];
if (owner ==address(0)) {
revert InvalidTokenError(tokenId);
}
}
/// @inheritdoc IERC721functionbalanceOf(address owner) externalviewreturns (uint256 numTokens) {
return _doesTokenExistFor(owner) ? 1 : 0;
}
function_doesTokenExistFor(address owner) internalviewreturns (bool) {
return _owners[uint256(uint160(owner))] !=address(0);
}
function_mint(address owner) internalreturns (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() privateviewreturns (stringmemory) {
_readOnlyDelegateCall(
// Instance of IERC721Renderer.
_GLOBALS.getAddress(LibGlobals.GLOBAL_CF_NFT_RENDER_IMPL),
msg.data
);
assert(false); // Will not be reached.return"";
}
}
Contract Source Code
File 12 of 57: EIP165.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;abstractcontractEIP165{
/// @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` otherwisefunctionsupportsInterface(bytes4 interfaceId) publicpurevirtualreturns (bool) {
return interfaceId ==this.supportsInterface.selector;
}
}
Contract Source Code
File 13 of 57: ERC1155.sol
// SPDX-License-Identifier: AGPL-3.0-only// Only modified to inherit IERC1155 and rename ERC1155TokenReceiver -> ERC1155TokenReceiverBase.pragmasolidity ^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)abstractcontractERC1155isIERC1155{
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/eventURI(string value, uint256indexed id);
/*//////////////////////////////////////////////////////////////
ERC1155 STORAGE
//////////////////////////////////////////////////////////////*/mapping(address=>mapping(uint256=>uint256)) public balanceOf;
mapping(address=>mapping(address=>bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
METADATA LOGIC
//////////////////////////////////////////////////////////////*/functionuri(uint256 id) publicviewvirtualreturns (stringmemory);
/*//////////////////////////////////////////////////////////////
ERC1155 LOGIC
//////////////////////////////////////////////////////////////*/functionsetApprovalForAll(address operator, bool approved) publicvirtual{
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
functionsafeTransferFrom(addressfrom,
address to,
uint256 id,
uint256 amount,
bytescalldata data
) publicvirtual{
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"
);
}
functionsafeBatchTransferFrom(addressfrom,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytescalldata data
) publicvirtual{
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"
);
}
functionbalanceOfBatch(address[] calldata owners,
uint256[] calldata ids
) publicviewvirtualreturns (uint256[] memory balances) {
require(owners.length== ids.length, "LENGTH_MISMATCH");
balances =newuint256[](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
//////////////////////////////////////////////////////////////*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (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, bytesmemory data) internalvirtual{
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,
bytesmemory data
) internalvirtual{
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(addressfrom,
uint256[] memory ids,
uint256[] memory amounts
) internalvirtual{
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(addressfrom, uint256 id, uint256 amount) internalvirtual{
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)abstractcontractERC1155TokenReceiverBase{
functiononERC1155Received(address,
address,
uint256,
uint256,
bytescalldata) externalvirtualreturns (bytes4) {
return ERC1155TokenReceiverBase.onERC1155Received.selector;
}
functiononERC1155BatchReceived(address,
address,
uint256[] calldata,
uint256[] calldata,
bytescalldata) externalvirtualreturns (bytes4) {
return ERC1155TokenReceiverBase.onERC1155BatchReceived.selector;
}
}
Contract Source Code
File 14 of 57: ERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol)pragmasolidity ^0.8.20;import {IERC20} from"./IERC20.sol";
import {IERC20Metadata} from"./extensions/IERC20Metadata.sol";
import {Context} from"../../utils/Context.sol";
import {IERC20Errors} from"../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/abstractcontractERC20isContext, IERC20, IERC20Metadata, IERC20Errors{
mapping(address account =>uint256) private _balances;
mapping(address account =>mapping(address spender =>uint256)) private _allowances;
uint256private _totalSupply;
stringprivate _name;
stringprivate _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/constructor(stringmemory name_, stringmemory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/functionname() publicviewvirtualreturns (stringmemory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/functionsymbol() publicviewvirtualreturns (stringmemory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/functiondecimals() publicviewvirtualreturns (uint8) {
return18;
}
/**
* @dev See {IERC20-totalSupply}.
*/functiontotalSupply() publicviewvirtualreturns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/functionbalanceOf(address account) publicviewvirtualreturns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/functiontransfer(address to, uint256 value) publicvirtualreturns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
returntrue;
}
/**
* @dev See {IERC20-allowance}.
*/functionallowance(address owner, address spender) publicviewvirtualreturns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionapprove(address spender, uint256 value) publicvirtualreturns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
returntrue;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/functiontransferFrom(addressfrom, address to, uint256 value) publicvirtualreturns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
returntrue;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/function_transfer(addressfrom, address to, uint256 value) internal{
if (from==address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to ==address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/function_update(addressfrom, address to, uint256 value) internalvirtual{
if (from==address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to ==address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/function_mint(address account, uint256 value) internal{
if (account ==address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/function_burn(address account, uint256 value) internal{
if (account ==address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/function_approve(address owner, address spender, uint256 value) internal{
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/function_approve(address owner, address spender, uint256 value, bool emitEvent) internalvirtual{
if (owner ==address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender ==address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/function_spendAllowance(address owner, address spender, uint256 value) internalvirtual{
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance !=type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
Contract Source Code
File 15 of 57: ERC20LaunchCrowdfund.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { InitialETHCrowdfund } from"./InitialETHCrowdfund.sol";
import { Party } from"../party/Party.sol";
import { MetadataProvider } from"../renderers/MetadataProvider.sol";
import { IGlobals } from"../globals/IGlobals.sol";
import { IERC20Creator, TokenConfiguration, ERC20 } from"../utils/IERC20Creator.sol";
/// @notice A crowdfund for launching ERC20 tokens./// Unlike other crowdfunds that are started for the purpose of/// acquiring NFT(s), this crowdfund bootstraps an ERC20 token/// and sends a share of the total supply to the new party.contractERC20LaunchCrowdfundisInitialETHCrowdfund{
structERC20LaunchOptions {
// The name of the ERC20 token launched.string name;
// The symbol of the ERC20 token launched.string symbol;
// An arbitrary address to receive ERC20 tokens.address recipient;
// The total supply to mint for the ERC20 token.uint256 totalSupply;
// The number of tokens to distribute to the party.uint256 numTokensForDistribution;
// The number of tokens to send to an arbitrary recipient.uint256 numTokensForRecipient;
// The number of tokens to use for the Uniswap LP position.uint256 numTokensForLP;
// The address that receives fees from the Uniswap LP position.address lpFeeRecipient;
}
errorInvalidTokenDistribution();
errorTokenAlreadyLaunched();
addresspublicconstant PARTY_ADDRESS_KEY =address(uint160(uint256(keccak256(abi.encode("party address")))));
IERC20Creator publicimmutable ERC20_CREATOR;
ERC20LaunchOptions public tokenOpts;
boolpublic isTokenLaunched;
constructor(IGlobals globals, IERC20Creator erc20Creator) InitialETHCrowdfund(globals) {
ERC20_CREATOR = erc20Creator;
}
/// @notice Initializer to be called prior to using the contract./// @param crowdfundOpts Options to initialize the crowdfund with./// @param partyOpts Options to initialize the party with./// @param customMetadataProvider Optional provider to use for the party for/// rendering custom metadata./// @param customMetadata Optional custom metadata to use for the party.functioninitialize(
InitialETHCrowdfundOptions memory crowdfundOpts,
ETHPartyOptions memory partyOpts,
ERC20LaunchOptions memory _tokenOpts,
MetadataProvider customMetadataProvider,
bytesmemory customMetadata
) externalpayable{
uint16 feeBasisPoints =5e3; // Max possible feeuint256 minTotalSpendableEth = ((((uint256(crowdfundOpts.minTotalContributions) *
(1e4- crowdfundOpts.fundingSplitBps)) /1e4) * (1e4- feeBasisPoints)) /1e4);
uint256 numTokensForLP =uint256(_tokenOpts.numTokensForLP);
if (
_tokenOpts.numTokensForDistribution +
_tokenOpts.numTokensForRecipient +
_tokenOpts.numTokensForLP !=
_tokenOpts.totalSupply ||
_tokenOpts.totalSupply >type(uint112).max||
_tokenOpts.numTokensForLP <1e4||
crowdfundOpts.fundingSplitBps >5e3||
crowdfundOpts.minTotalContributions <1e4||
crowdfundOpts.maxTotalContributions >= numTokensForLP *1e18||
_tokenOpts.numTokensForLP >= minTotalSpendableEth *1e18
) {
revert InvalidTokenDistribution();
}
tokenOpts = _tokenOpts;
InitialETHCrowdfund.initialize(
crowdfundOpts,
partyOpts,
customMetadataProvider,
customMetadata
);
}
/// @notice Launch the ERC20 token for the Party.functionlaunchToken() publicreturns (ERC20 token) {
if (isTokenLaunched) revert TokenAlreadyLaunched();
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Finalized) revert WrongLifecycleError(lc);
isTokenLaunched =true;
// Update the party's total voting poweruint96 totalContributions_ = totalContributions;
uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ >0) {
// Assuming fundingSplitBps_ <= 1e4, this cannot overflow uint96
totalContributions_ -=uint96((uint256(totalContributions_) * fundingSplitBps_) /1e4);
}
address tokenRecipient = tokenOpts.recipient;
if (tokenRecipient == PARTY_ADDRESS_KEY) {
tokenRecipient =address(party);
}
address lpFeeRecipient = tokenOpts.lpFeeRecipient;
if (lpFeeRecipient == PARTY_ADDRESS_KEY) {
lpFeeRecipient =address(party);
}
// Create the ERC20 token.
ERC20LaunchOptions memory _tokenOpts = tokenOpts;
token = ERC20_CREATOR.createToken{ value: totalContributions_ }(
address(party),
lpFeeRecipient,
_tokenOpts.name,
_tokenOpts.symbol,
TokenConfiguration({
totalSupply: _tokenOpts.totalSupply,
numTokensForDistribution: _tokenOpts.numTokensForDistribution,
numTokensForRecipient: _tokenOpts.numTokensForRecipient,
numTokensForLP: _tokenOpts.numTokensForLP
}),
tokenRecipient
);
}
/// @notice Finalize the crowdfund and launch the ERC20 token.functionfinalize() publicoverride{
super.finalize();
launchToken();
}
function_finalize(uint96 totalContributions_) internaloverride{
// Finalize the crowdfund.delete expiry;
// Transfer funding split to recipient if applicable.uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ >0) {
// Assuming fundingSplitBps_ <= 1e4, this cannot overflow uint96
totalContributions_ -=uint96((uint256(totalContributions_) * fundingSplitBps_) /1e4);
}
// Update the party's total voting power.uint96 newVotingPower = _calculateContributionToVotingPower(totalContributions_);
party.increaseTotalVotingPower(newVotingPower);
emit Finalized();
}
}
Contract Source Code
File 16 of 57: ERC721.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;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)/// @dev Modified from SolmateabstractcontractERC721isIERC721, EIP165{
errorNotMinted();
errorZeroAddress();
errorUnsafeRecipient();
errorUnauthorized();
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/stringpublic name;
stringpublic symbol;
functiontokenURI(uint256 id /* view */) publicvirtualreturns (stringmemory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/mapping(uint256=>address) internal _ownerOf;
mapping(address=>uint256) internal _balanceOf;
functionownerOf(uint256 id) publicviewvirtualreturns (address owner) {
owner = _ownerOf[id];
if (owner ==address(0)) {
revert NotMinted();
}
}
functionbalanceOf(address owner) publicviewvirtualreturns (uint256) {
if (owner ==address(0)) {
revert ZeroAddress();
}
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/mapping(uint256=>address) public getApproved;
mapping(address=>mapping(address=>bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/constructor(stringmemory _name, stringmemory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/functionapprove(address spender, uint256 id) publicvirtual{
address owner = _ownerOf[id];
if (msg.sender!= owner &&!isApprovedForAll[owner][msg.sender]) {
revert Unauthorized();
}
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
functionsetApprovalForAll(address operator, bool approved) publicvirtual{
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
functiontransferFrom(addressfrom, address to, uint256 id) publicvirtual{
if (from!= _ownerOf[id]) {
revert Unauthorized();
}
if (to ==address(0)) {
revert ZeroAddress();
}
if (
msg.sender!=from&&!isApprovedForAll[from][msg.sender] &&msg.sender!= getApproved[id]
) {
revert Unauthorized();
}
// 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);
}
functionsafeTransferFrom(addressfrom, address to, uint256 id) publicvirtual{
transferFrom(from, to, id);
if (
to.code.length!=0&&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) {
revert UnsafeRecipient();
}
}
functionsafeTransferFrom(addressfrom,
address to,
uint256 id,
bytescalldata data
) publicvirtual{
transferFrom(from, to, id);
if (
to.code.length!=0&&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) {
revert UnsafeRecipient();
}
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/functionsupportsInterface(bytes4 interfaceId) publicpurevirtualoverridereturns (bool) {
// NOTE: modified from original to call super.returnsuper.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) internalvirtual{
if (to ==address(0)) {
revert ZeroAddress();
}
if (_ownerOf[id] !=address(0)) {
revert Unauthorized();
}
// Counter overflow is incredibly unrealistic.unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function_burn(uint256 id) internalvirtual{
address owner = _ownerOf[id];
if (owner ==address(0)) {
revert Unauthorized();
}
// 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) internalvirtual{
_mint(to, id);
if (
to.code.length!=0&&
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) {
revert UnsafeRecipient();
}
}
function_safeMint(address to, uint256 id, bytesmemory data) internalvirtual{
_mint(to, id);
if (
to.code.length!=0&&
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) {
revert UnsafeRecipient();
}
}
}
/// @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)abstractcontractERC721TokenReceiver{
functiononERC721Received(address,
address,
uint256,
bytescalldata) externalvirtualreturns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
Contract Source Code
File 17 of 57: ERC721Receiver.sol
// SPDX-License-Identifier: Apache-2.0pragmasolidity ^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.abstractcontractERC721ReceiverisIERC721Receiver, EIP165, ERC721TokenReceiver{
/// @inheritdoc IERC721ReceiverfunctiononERC721Received(address,
address,
uint256,
bytesmemory) publicvirtualoverride(IERC721Receiver, ERC721TokenReceiver) returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
/// @inheritdoc EIP165functionsupportsInterface(bytes4 interfaceId) publicpurevirtualoverridereturns (bool) {
return
EIP165.supportsInterface(interfaceId) ||
interfaceId ==type(IERC721Receiver).interfaceId;
}
}
Contract Source Code
File 18 of 57: ETHCrowdfundBase.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../utils/LibAddress.sol";
import"../utils/LibSafeCast.sol";
import"../party/Party.sol";
import"../gatekeepers/IGateKeeper.sol";
import { FixedPointMathLib } from"solmate/utils/FixedPointMathLib.sol";
abstractcontractETHCrowdfundBaseisImplementation{
usingFixedPointMathLibforuint96;
usingLibRawResultforbytes;
usingLibSafeCastforuint96;
usingLibSafeCastforuint256;
usingLibAddressforaddresspayable;
enumCrowdfundLifecycle {
// 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.structETHCrowdfundOptions {
Party party;
addresspayable initialContributor;
address initialDelegate;
uint96 minContribution;
uint96 maxContribution;
bool disableContributingForExistingCard;
uint96 minTotalContributions;
uint96 maxTotalContributions;
uint160 exchangeRate;
uint16 fundingSplitBps;
addresspayable fundingSplitRecipient;
uint40 duration;
IGateKeeper gateKeeper;
bytes12 gateKeeperId;
}
errorWrongLifecycleError(CrowdfundLifecycle lc);
errorNotAllowedByGateKeeperError(address contributor,
IGateKeeper gateKeeper,
bytes12 gateKeeperId,
bytes gateData
);
errorOnlyPartyHostError();
errorOnlyPartyDaoError(address notDao);
errorOnlyPartyDaoOrHostError(address notDao);
errorNotOwnerError(uint256 tokenId);
errorOnlyWhenEmergencyActionsAllowedError();
errorInvalidDelegateError();
errorNotEnoughContributionsError(uint96 totalContribution, uint96 minTotalContributions);
errorMinGreaterThanMaxError(uint96 min, uint96 max);
errorMinMaxDifferenceTooSmall(uint96 min, uint96 max);
errorMaxTotalContributionsCannotBeZeroError(uint96 maxTotalContributions);
errorBelowMinimumContributionsError(uint96 contributions, uint96 minContributions);
errorAboveMaximumContributionsError(uint96 contributions, uint96 maxContributions);
errorExceedsRemainingContributionsError(uint96 amount, uint96 remaining);
errorInvalidExchangeRateError(uint160 exchangeRate);
errorInvalidFundingSplitRecipient();
errorContributingForExistingCardDisabledError();
errorZeroVotingPowerError();
errorFundingSplitAlreadyPaidError();
errorFundingSplitNotConfiguredError();
errorInvalidMessageValue();
errorArityMismatch();
eventContributed(addressindexed sender,
addressindexed contributor,
uint256 amount,
address delegate
);
eventFinalized();
eventFundingSplitSent(addressindexed fundingSplitRecipient, uint256 amount);
eventEmergencyExecuteDisabled();
eventEmergencyExecute(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 privateimmutable _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.uint96public minContribution;
/// @notice The maximum amount of ETH that a contributor can send to/// participate in the crowdfund per address.uint96public maxContribution;
/// @notice A boolean flag that determines whether contributors are allowed/// to increase the voting power of their existing party cards.boolpublic disableContributingForExistingCard;
/// @notice Whether the funding split has been claimed by the funding split/// recipient.boolpublic fundingSplitPaid;
/// @notice Whether the DAO has emergency powers for this crowdfund.boolpublic emergencyExecuteDisabled;
/// @notice The minimum amount of total ETH contributions required for the/// crowdfund to be considered successful.uint96public minTotalContributions;
/// @notice The maximum amount of total ETH contributions allowed for the/// crowdfund.uint96public maxTotalContributions;
/// @notice The total amount of ETH contributed to the crowdfund so far.uint96public totalContributions;
/// @notice The timestamp at which the crowdfund will end or ended. If 0, the/// crowdfund has finalized.uint40public expiry;
/// @notice The exchange rate from contribution amount to voting power where/// 100% = 1e18. May be greater than 1e18 (100%).uint160public exchangeRate;
/// @notice The portion of contributions to send to the funding recipient in/// basis points (e.g. 100 = 1%).uint16public fundingSplitBps;
/// @notice The address to which a portion of the contributions is sent to.addresspayablepublic 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.bytes12public 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 contractfunction_initialize(ETHCrowdfundOptions memory opts) internal{
if (opts.minContribution > opts.maxContribution) {
revert MinGreaterThanMaxError(opts.minContribution, opts.maxContribution);
}
if (opts.maxTotalContributions - opts.minTotalContributions +1< opts.minContribution) {
revert MinMaxDifferenceTooSmall(opts.minTotalContributions, opts.maxTotalContributions);
}
// Set the minimum and maximum contribution amounts.
minContribution = opts.minContribution;
maxContribution = opts.maxContribution;
// Set the min total contributions.
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 = (block.timestamp+ opts.duration).safeCastUint256ToUint40();
// Set the exchange rate.if (opts.exchangeRate ==0) revert InvalidExchangeRateError(opts.exchangeRate);
exchangeRate = opts.exchangeRate;
// Set the funding split and its recipient.
fundingSplitBps = opts.fundingSplitBps;
fundingSplitRecipient = opts.fundingSplitRecipient;
if (opts.fundingSplitBps >0&& opts.fundingSplitRecipient ==address(0)) {
revert InvalidFundingSplitRecipient();
}
// Set whether to disable contributing for existing card.
disableContributingForExistingCard = opts.disableContributingForExistingCard;
// Check that the voting power that one receives from a contribution of// size minContribution is not equal to zeroif (convertContributionToVotingPower(opts.minContribution) ==0) {
revert ZeroVotingPowerError();
}
}
/// @notice Get the current lifecycle of the crowdfund.functiongetCrowdfundLifecycle() publicviewreturns (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(addresspayable contributor,
address delegate,
uint96 contribution
) internalreturns (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, contribution, delegate);
// OK to contribute with zero just to update delegate.if (contribution ==0) return0;
// 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 above the minimum.uint96 minContribution_ = minContribution;
if (contribution < minContribution_) {
revert BelowMinimumContributionsError(contribution, minContribution_);
}
// Check that the contribution amount is at or below the maximum.uint96 maxContribution_ = maxContribution;
if (contribution > maxContribution_) {
revert AboveMaximumContributionsError(contribution, maxContribution_);
}
uint96 newTotalContributions = totalContributions + contribution;
uint96 maxTotalContributions_ = maxTotalContributions;
if (newTotalContributions > maxTotalContributions_) {
revert ExceedsRemainingContributionsError(
contribution,
maxTotalContributions_ - totalContributions
);
} else {
totalContributions = newTotalContributions;
if (
maxTotalContributions_ == newTotalContributions ||
minContribution_ > maxTotalContributions_ - newTotalContributions
) {
_finalize(newTotalContributions);
}
}
// Calculate voting power.
votingPower = convertContributionToVotingPower(contribution);
if (votingPower ==0) revert ZeroVotingPowerError();
}
/// @notice Calculate the voting power amount that would be received from/// the given contribution./// @param contribution The contribution amount./// @return votingPower The voting power amount that would be received from/// the contribution.functionconvertContributionToVotingPower(uint96 contribution
) publicviewreturns (uint96 votingPower) {
contribution = _removeFundingSplitFromContribution(contribution);
votingPower = _calculateContributionToVotingPower(contribution);
}
/// @notice Calculate the contribution amount from the given voting power./// @param votingPower The voting power to convert to a contribution amount./// @return contribution The contribution amount.functionconvertVotingPowerToContribution(uint96 votingPower
) publicviewreturns (uint96 contribution) {
contribution = _calculateVotingPowerToContribution(votingPower);
contribution = _addFundingSplitToContribution(contribution);
}
function_calculateContributionToVotingPower(uint96 contribution
) internalviewreturns (uint96) {
return contribution.mulDivDown(exchangeRate, 1e18).safeCastUint256ToUint96();
}
function_calculateVotingPowerToContribution(uint96 votingPower
) internalviewreturns (uint96) {
return votingPower.mulDivUp(1e18, exchangeRate).safeCastUint256ToUint96();
}
function_addFundingSplitToContribution(uint96 contribution) internalviewreturns (uint96) {
uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ >0) {
// Downcast is safe since `contribution` cannot exceed// type(uint96).max. When the contribution is made, it cannot exceed// type(uint96).max, neither can `totalContributions` exceed it.
contribution =uint96((uint256(contribution) *1e4) / (1e4- fundingSplitBps_));
}
return contribution;
}
function_removeFundingSplitFromContribution(uint96 contribution
) internalviewreturns (uint96) {
uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ >0) {
// Safe since contribution initially fits into uint96 and cannot get bigger
contribution =uint96((uint256(contribution) * (1e4- fundingSplitBps_)) /1e4);
}
return contribution;
}
/// @notice Finalize the crowdfund and transfer the funds to the Party.functionfinalize() publicvirtual{
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_) internalvirtual{
// Finalize the crowdfund.delete expiry;
// Transfer funding split to recipient if applicable.uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ >0) {
// Assuming fundingSplitBps_ <= 1e4, this cannot overflow uint96
totalContributions_ -=uint96((uint256(totalContributions_) * fundingSplitBps_) /1e4);
}
// Update the party's total voting power.uint96 newVotingPower = _calculateContributionToVotingPower(totalContributions_);
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.functionsendFundingSplit() externalreturns (uint96 splitAmount) {
// Check that the crowdfund is finalized.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Finalized) revert WrongLifecycleError(lc);
if (fundingSplitPaid) revert FundingSplitAlreadyPaidError();
uint16 fundingSplitBps_ = fundingSplitBps;
if (fundingSplitBps_ ==0) {
revert FundingSplitNotConfiguredError();
}
fundingSplitPaid =true;
// Transfer funding split to recipient.// Assuming fundingSplitBps_ <= 1e4, this cannot overflow uint96addresspayable fundingSplitRecipient_ = fundingSplitRecipient;
splitAmount =uint96((uint256(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.functionemergencyExecute(address targetAddress,
bytescalldata targetCallData,
uint256 amountEth
) externalpayable{
// 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, bytesmemory 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.functiondisableEmergencyExecute() 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();
}
}
Contract Source Code
File 19 of 57: FixedPointMathLib.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=0.8.0;/// @notice Arithmetic library with operations for fixed-point numbers./// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)libraryFixedPointMathLib{
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/uint256internalconstant MAX_UINT256 =2**256-1;
uint256internalconstant WAD =1e18; // The scalar of ETH and most ERC20s.functionmulWadDown(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
functionmulWadUp(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
functiondivWadDown(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
functiondivWadUp(uint256 x, uint256 y) internalpurereturns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/functionmulDivDown(uint256 x,
uint256 y,
uint256 denominator
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))ifiszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z :=div(mul(x, y), denominator)
}
}
functionmulDivUp(uint256 x,
uint256 y,
uint256 denominator
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))ifiszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,// 1 is added to round up the division of x * y by the denominator.
z :=add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
functionrpow(uint256 x,
uint256 n,
uint256 scalar
) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
switch x
case0 {
switch n
case0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z :=0
}
}
default {
switchmod(n, 2)
case0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.let half :=shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n :=shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n :=shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.// Equivalent to iszero(eq(div(xx, x), x)) here.ifshr(128, x) {
revert(0, 0)
}
// Store x squared.let xx :=mul(x, x)
// Round to the nearest number.let xxRound :=add(xx, half)
// Revert if xx + half overflowed.iflt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x :=div(xxRound, scalar)
// If n is even:ifmod(n, 2) {
// Compute z * x.let zx :=mul(z, x)
// If z * x overflowed:ifiszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.ifiszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.let zxRound :=add(zx, half)
// Revert if zx + half overflowed.iflt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z :=div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/functionsqrt(uint256 x) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
let y := x // We start y at x, which will help us make our initial estimate.
z :=181// The "correct" value is 1, but this saves a multiplication later.// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.// We check y >= 2^(k + 8) but shift right by k bits// each branch to ensure that if x >= 256, then y >= 256.ifiszero(lt(y, 0x10000000000000000000000000000000000)) {
y :=shr(128, y)
z :=shl(64, z)
}
ifiszero(lt(y, 0x1000000000000000000)) {
y :=shr(64, y)
z :=shl(32, z)
}
ifiszero(lt(y, 0x10000000000)) {
y :=shr(32, y)
z :=shl(16, z)
}
ifiszero(lt(y, 0x1000000)) {
y :=shr(16, y)
z :=shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could// get y in a tighter range. Currently, we will have y in [256, 256*2^16).// We ensured y >= 256 so that the relative difference between y and y+1 is small.// That's not possible if x < 256 but we can just verify those cases exhaustively.// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.// There is no overflow risk here since y < 2^136 after the first branch above.
z :=shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
z :=shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z :=sub(z, lt(div(x, z), z))
}
}
functionunsafeMod(uint256 x, uint256 y) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Mod x by y. Note this will return// 0 instead of reverting if y is zero.
z :=mod(x, y)
}
}
functionunsafeDiv(uint256 x, uint256 y) internalpurereturns (uint256 r) {
/// @solidity memory-safe-assemblyassembly {
// Divide x by y. Note this will return// 0 instead of reverting if y is zero.
r :=div(x, y)
}
}
functionunsafeDivUp(uint256 x, uint256 y) internalpurereturns (uint256 z) {
/// @solidity memory-safe-assemblyassembly {
// Add 1 to x * y if x % y > 0. Note this will// return 0 instead of reverting if y is zero.
z :=add(gt(mod(x, y), 0), div(x, y))
}
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)pragmasolidity ^0.8.19;/**
* @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}.
*/interfaceIERC165{
/**
* @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.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)pragmasolidity ^0.8.20;import {IERC20} from"../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/interfaceIERC20MetadataisIERC20{
/**
* @dev Returns the name of the token.
*/functionname() externalviewreturns (stringmemory);
/**
* @dev Returns the symbol of the token.
*/functionsymbol() externalviewreturns (stringmemory);
/**
* @dev Returns the decimals places of the token.
*/functiondecimals() externalviewreturns (uint8);
}
Contract Source Code
File 25 of 57: IERC2981.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC2981.sol)pragmasolidity ^0.8.19;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._
*/interfaceIERC2981isIERC165{
/**
* @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.
*/functionroyaltyInfo(uint256 tokenId,
uint256 salePrice
) externalviewreturns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;// Interface for a gatekeeper contract used for private crowdfund instances.interfaceIGateKeeper{
/// @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.functionisAllowed(address participant,
bytes12 id,
bytesmemory userData
) externalviewreturns (bool);
}
// SPDX-License-Identifier: Apache-2.0pragmasolidity 0.8.20;/**
* @title IMarketWrapper
* @author Anna Carroll
* @notice IMarketWrapper provides a common interface for
* interacting with NFT auction markets.
* Contracts can abstract their interactions with
* different NFT markets using IMarketWrapper.
* NFT markets can become compatible with any contract
* using IMarketWrapper by deploying a MarketWrapper contract
* that implements this interface using the logic of their Market.
*
* WARNING: MarketWrapper contracts should NEVER write to storage!
* When implementing a MarketWrapper, exercise caution; a poorly implemented
* MarketWrapper contract could permanently lose access to the NFT or user funds.
*/interfaceIMarketWrapper{
/**
* @notice Given the auctionId, nftContract, and tokenId, check that:
* 1. the auction ID matches the token
* referred to by tokenId + nftContract
* 2. the auctionId refers to an *ACTIVE* auction
* (e.g. an auction that will accept bids)
* within this market contract
* 3. any additional validation to ensure that
* a PartyBid can bid on this auction
* (ex: if the market allows arbitrary bidding currencies,
* check that the auction currency is ETH)
* Note: This function probably should have been named "isValidAuction"
* @dev Called in PartyBid.sol in `initialize` at line 174
* @return TRUE if the auction is valid
*/functionauctionIdMatchesToken(uint256 auctionId,
address nftContract,
uint256 tokenId
) externalviewreturns (bool);
/**
* @notice Calculate the minimum next bid for this auction.
* PartyBid contracts always submit the minimum possible
* bid that will be accepted by the Market contract.
* usually, this is either the reserve price (if there are no bids)
* or a certain percentage increase above the current highest bid
* @dev Called in PartyBid.sol in `bid` at line 251
* @return minimum bid amount
*/functiongetMinimumBid(uint256 auctionId) externalviewreturns (uint256);
/**
* @notice Query the current highest bidder for this auction
* It is assumed that there is always 1 winning highest bidder for an auction
* This is used to ensure that PartyBid cannot outbid itself if it is already winning
* @dev Called in PartyBid.sol in `bid` at line 241
* @return highest bidder
*/functiongetCurrentHighestBidder(uint256 auctionId) externalviewreturns (address);
/**
* @notice Submit bid to Market contract
* @dev Called in PartyBid.sol in `bid` at line 259
*/functionbid(uint256 auctionId, uint256 bidAmount) external;
/**
* @notice Determine whether the auction has been finalized
* Used to check if it is still possible to bid
* And to determine whether the PartyBid should finalize the auction
* @dev Called in PartyBid.sol in `bid` at line 247
* @dev and in `finalize` at line 288
* @return TRUE if the auction has been finalized
*/functionisFinalized(uint256 auctionId) externalviewreturns (bool);
/**
* @notice Finalize the results of the auction
* on the Market contract
* It is assumed that this operation is performed once for each auction,
* that after it is done the auction is over and the NFT has been
* transferred to the auction winner.
* @dev Called in PartyBid.sol in `finalize` at line 289
*/functionfinalize(uint256 auctionId) external;
}
Contract Source Code
File 32 of 57: IMetadataProvider.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8;interfaceIMetadataProvider{
/// @notice Whether or not the metadata provider supports registrars that can/// set metadata for other instances./// @dev See `MetadataRegistry` for more information on the registrar role.functionsupportsRegistrars() externalviewreturns (bool);
/// @notice Get the metadata for a Party instance./// @param instance The address of the instance./// @param tokenId The ID of the token to get the metadata for./// @return metadata The encoded metadata.functiongetMetadata(address instance,
uint256 tokenId
) externalviewreturns (bytesmemory metadata);
}
Contract Source Code
File 33 of 57: IPartyFactory.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { Party } from"../party/Party.sol";
import { IERC721 } from"../tokens/IERC721.sol";
import { MetadataProvider } from"../renderers/MetadataProvider.sol";
// Creates generic Party instances.interfaceIPartyFactory{
eventPartyCreated(
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.functioncreateParty(
Party partyImpl,
address[] memory authorities,
Party.PartyOptions calldata opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
uint40 rageQuitTimestamp
) externalreturns (Party party);
/// @notice Deploy a new party instance with custom metadata./// @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./// @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./// @param provider The metadata provider to use for the party./// @param metadata The metadata to use for the party./// @return party The newly created `Party` instance.functioncreatePartyWithMetadata(
Party partyImpl,
address[] memory authorities,
Party.PartyOptions memory opts,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
uint40 rageQuitTimestamp,
MetadataProvider provider,
bytesmemory metadata
) externalreturns (Party party);
}
Contract Source Code
File 34 of 57: IProposalExecutionEngine.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
// Upgradeable proposals logic contract interface.interfaceIProposalExecutionEngine{
structExecuteProposalParams {
uint256 proposalId;
bytes proposalData;
bytes progressData;
bytes extraData;
uint256 flags;
IERC721[] preciousTokens;
uint256[] preciousTokenIds;
}
functioninitialize(address oldImpl, bytesmemory 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.functionexecuteProposal(
ExecuteProposalParams memory params
) externalpayablereturns (bytesmemory 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.functioncancelProposal(uint256 proposalId) external;
}
Contract Source Code
File 35 of 57: ITokenDistributor.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC20.sol";
import"../party/Party.sol";
/// @notice Creates token distributions for parties.interfaceITokenDistributor{
enumTokenType {
Native,
Erc20
}
// Info on a distribution, created by createDistribution().structDistributionInfo {
// 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`.addresspayable 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;
}
eventDistributionCreated(Party indexed party, DistributionInfo info);
eventDistributionFeeClaimed(
Party indexed party,
addressindexed feeRecipient,
TokenType tokenType,
address token,
uint256 amount
);
eventDistributionClaimedByPartyToken(
Party indexed party,
uint256indexed partyTokenId,
addressindexed 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.functioncreateNativeDistribution(
Party party,
addresspayable feeRecipient,
uint16 feeBps
) externalpayablereturns (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.functioncreateErc20Distribution(
IERC20 token,
Party party,
addresspayable feeRecipient,
uint16 feeBps
) externalreturns (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.functionclaim(
DistributionInfo calldata info,
uint256 partyTokenId
) externalreturns (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.functionclaimFee(DistributionInfo calldata info, addresspayable 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.functionbatchClaim(
DistributionInfo[] calldata infos,
uint256[] calldata partyTokenIds
) externalreturns (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.functionbatchClaimFee(
DistributionInfo[] calldata infos,
addresspayable[] 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.functiongetClaimAmount(
DistributionInfo calldata info,
uint256 partyTokenId
) externalviewreturns (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.functionwasFeeClaimed(Party party, uint256 distributionId) externalviewreturns (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.functionhasPartyTokenIdClaimed(
Party party,
uint256 partyTokenId,
uint256 distributionId
) externalviewreturns (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.functiongetRemainingMemberSupply(
Party party,
uint256 distributionId
) externalviewreturns (uint128);
}
Contract Source Code
File 36 of 57: Implementation.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;// Base contract for all contracts intended to be delegatecalled into.abstractcontractImplementation{
eventInitialized();
errorAlreadyInitialized();
errorOnlyDelegateCallError();
/// @notice The address of the implementation contract.addresspublicimmutable implementation;
/// @notice Whether or not the implementation has been initialized.boolpublic initialized;
constructor() {
implementation =address(this);
}
// Reverts if the current function context is not inside of a delegatecall.modifieronlyDelegateCall() virtual{
if (address(this) == implementation) {
revert OnlyDelegateCallError();
}
_;
}
modifieronlyInitialize() {
if (initialized) revert AlreadyInitialized();
initialized =true;
emit Initialized();
_;
}
/// @notice The address of the implementation contract./// @dev This is an alias for `implementation` for backwards compatibility.functionIMPL() externalviewreturns (address) {
return implementation;
}
}
Contract Source Code
File 37 of 57: InitialETHCrowdfund.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { ETHCrowdfundBase } from"./ETHCrowdfundBase.sol";
import { ProposalStorage } from"../proposals/ProposalStorage.sol";
import { LibAddress } from"../utils/LibAddress.sol";
import { LibRawResult } from"../utils/LibRawResult.sol";
import { LibSafeCast } from"../utils/LibSafeCast.sol";
import { Party, PartyGovernance } from"../party/Party.sol";
import { Crowdfund } from"../crowdfund/Crowdfund.sol";
import { MetadataProvider } from"../renderers/MetadataProvider.sol";
import { IGateKeeper } from"../gatekeepers/IGateKeeper.sol";
import { IGlobals } from"../globals/IGlobals.sol";
import { IERC721 } from"../tokens/IERC721.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.contractInitialETHCrowdfundisETHCrowdfundBase{
usingLibRawResultforbytes;
usingLibSafeCastforuint256;
usingLibAddressforaddresspayable;
// Options to be passed into `initialize()` when the crowdfund is created.structInitialETHCrowdfundOptions {
addresspayable initialContributor;
address initialDelegate;
uint96 minContribution;
uint96 maxContribution;
bool disableContributingForExistingCard;
uint96 minTotalContributions;
uint96 maxTotalContributions;
uint160 exchangeRate;
uint16 fundingSplitBps;
addresspayable fundingSplitRecipient;
uint40 duration;
IGateKeeper gateKeeper;
bytes12 gateKeeperId;
}
structETHPartyOptions {
// Name of the party.string name;
// Symbol of the party.string symbol;
// The ID of the customization preset to use for the party card.uint256 customizationPresetId;
// Options to initialize party governance with.
Crowdfund.FixedGovernanceOpts governanceOpts;
// Options to initialize party proposal engine with.
ProposalStorage.ProposalEngineOpts proposalEngineOpts;
// The tokens that are considered precious by the party.These are// protected assets and are subject to extra restrictions in proposals// vs other assets.
IERC721[] preciousTokens;
// The IDs associated with each token in `preciousTokens`.uint256[] preciousTokenIds;
// The timestamp until which ragequit is enabled.uint40 rageQuitTimestamp;
// Initial authorities to set on the partyaddress[] authorities;
}
structBatchContributeArgs {
// IDs of cards to credit the contributions to. When set to 0, it means// a new one should be minted.uint256[] tokenIds;
// The address to which voting power will be delegated for all// contributions. This will be ignored if recipient has already set a// delegate.address initialDelegate;
// 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;
}
structBatchContributeForArgs {
// 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.addresspayable[] recipients;
// The delegate to set for each recipient. This will be ignored if// recipient has already set a delegate.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;
}
eventRefunded(addressindexed contributor, uint256indexed tokenId, uint256 amount);
// Set the `Globals` contract.constructor(IGlobals globals) ETHCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param crowdfundOpts Options to initialize the crowdfund with./// @param partyOpts Options to initialize the party with./// @param customMetadataProvider Optional provider to use for the party for/// rendering custom metadata./// @param customMetadata Optional custom metadata to use for the party.functioninitialize(
InitialETHCrowdfundOptions memory crowdfundOpts,
ETHPartyOptions memory partyOpts,
MetadataProvider customMetadataProvider,
bytesmemory customMetadata
) publicpayableonlyInitialize{
// Create party the initial crowdfund will be for.
Party party_ = _createParty(partyOpts, customMetadataProvider, customMetadata);
// 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,
exchangeRate: crowdfundOpts.exchangeRate,
fundingSplitBps: crowdfundOpts.fundingSplitBps,
fundingSplitRecipient: crowdfundOpts.fundingSplitRecipient,
duration: crowdfundOpts.duration,
gateKeeper: crowdfundOpts.gateKeeper,
gateKeeperId: crowdfundOpts.gateKeeperId
})
);
// If the creator passed in some ETH during initialization, credit them// for the initial contribution.uint96 initialContribution =msg.value.safeCastUint256ToUint96();
if (initialContribution >0) {
_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./// @param initialDelegate The address to which voting power will be delegated to/// during the governance phase. This will be ignored/// if recipient has already set a delegate./// @param gateData Data to pass to the gatekeeper to prove eligibility./// @return votingPower The voting power the contributor receives for their/// contribution.functioncontribute(address initialDelegate,
bytesmemory gateData
) publicpayableonlyDelegateCallreturns (uint96 votingPower) {
return
_contribute(
payable(msg.sender),
initialDelegate,
msg.value.safeCastUint256ToUint96(),
0, // Mint a new party card for the contributor.
gateData
);
}
/// @notice Contribute ETH to this crowdfund./// @param tokenId The ID of the card the contribution is being made towards./// @param initialDelegate The address to which voting power will be delegated to/// during the governance phase. This will be ignored/// if recipient has already set a delegate./// @param gateData Data to pass to the gatekeeper to prove eligibility./// @return votingPower The voting power the contributor receives for their/// contribution.functioncontribute(uint256 tokenId,
address initialDelegate,
bytesmemory gateData
) publicpayableonlyDelegateCallreturns (uint96 votingPower) {
return
_contribute(
payable(msg.sender),
initialDelegate,
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.functionbatchContribute(
BatchContributeArgs calldata args
) externalpayableonlyDelegateCallreturns (uint96[] memory votingPowers) {
uint256 numContributions = args.tokenIds.length;
if (numContributions != args.values.length|| numContributions != args.gateDatas.length) {
revert ArityMismatch();
}
votingPowers =newuint96[](numContributions);
uint256 valuesSum;
for (uint256 i; i < numContributions; ++i) {
votingPowers[i] = _contribute(
payable(msg.sender),
args.initialDelegate,
args.values[i],
args.tokenIds[i],
args.gateDatas[i]
);
valuesSum += args.values[i];
}
if (msg.value!= valuesSum) {
revert InvalidMessageValue();
}
}
/// @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 which voting power will be delegated to/// during the governance phase. This will be ignored/// if recipient has already set a delegate./// @param gateData Data to pass to the gatekeeper to prove eligibility/// @return votingPower The voting power received for the contributionfunctioncontributeFor(uint256 tokenId,
addresspayable recipient,
address initialDelegate,
bytesmemory gateData
) externalpayableonlyDelegateCallreturns (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.functionbatchContributeFor(
BatchContributeForArgs calldata args
) externalpayableonlyDelegateCallreturns (uint96[] memory votingPowers) {
uint256 numContributions = args.tokenIds.length;
if (
numContributions != args.values.length||
numContributions != args.gateDatas.length||
numContributions != args.recipients.length
) {
revert ArityMismatch();
}
votingPowers =newuint96[](numContributions);
uint256 valuesSum;
for (uint256 i; i < numContributions; ++i) {
votingPowers[i] = _contribute(
args.recipients[i],
args.initialDelegates[i],
args.values[i],
args.tokenIds[i],
args.gateDatas[i]
);
valuesSum += args.values[i];
}
if (msg.value!= valuesSum) {
revert InvalidMessageValue();
}
}
function_contribute(addresspayable contributor,
address delegate,
uint96 amount,
uint256 tokenId,
bytesmemory gateData
) privatereturns (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))) {
// Checking msg.sender here instead of contributor is intentional to// allow someone who's allowed by a gatekeeper to invite others// into the Party. For example, to allow another contract, and// only that contract, to process contributions on behalf of// contributors.if (!_gateKeeper.isAllowed(msg.sender, gateKeeperId, gateData)) {
revert NotAllowedByGateKeeperError(msg.sender, _gateKeeper, gateKeeperId, gateData);
}
}
votingPower = _processContribution(contributor, delegate, amount);
// OK to contribute with zero just to update delegate.if (amount ==0) return0;
if (tokenId ==0) {
// Mint contributor a new party card.
party.mint(contributor, votingPower, delegate);
} elseif (disableContributingForExistingCard) {
revert ContributingForExistingCardDisabledError();
} elseif (party.ownerOf(tokenId) == contributor) {
// Increase voting power of contributor's existing party card.
party.increaseVotingPower(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.functionrefund(uint256 tokenId) externalreturns (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.addresspayable 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.functionbatchRefund(uint256[] calldata tokenIds,
bool revertOnFailure
) externalreturns (uint96[] memory amounts) {
uint256 numRefunds = tokenIds.length;
amounts =newuint96[](numRefunds);
for (uint256 i; i < numRefunds; ++i) {
(bool s, bytesmemory 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,
MetadataProvider customMetadataProvider,
bytesmemory customMetadata
) privatereturns (Party) {
uint256 authoritiesLength = opts.authorities.length+1;
address[] memory authorities =newaddress[](authoritiesLength);
for (uint i =0; i < authoritiesLength -1; ++i) {
authorities[i] = opts.authorities[i];
}
authorities[authoritiesLength -1] =address(this);
if (address(customMetadataProvider) ==address(0)) {
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
);
} else {
return
opts.governanceOpts.partyFactory.createPartyWithMetadata(
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,
customMetadataProvider,
customMetadata
);
}
}
}
Contract Source Code
File 38 of 57: LibAddress.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;libraryLibAddress{
errorEthTransferFailed(address receiver, bytes errData);
// Transfer ETH with full gas stipend.functiontransferEth(addresspayable receiver, uint256 amount) internal{
if (amount ==0) return;
(bool s, bytesmemory r) = receiver.call{ value: amount }("");
if (!s) {
revert EthTransferFailed(receiver, r);
}
}
}
Contract Source Code
File 39 of 57: LibERC20Compat.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC20.sol";
// Compatibility helpers for ERC20s.libraryLibERC20Compat{
errorNotATokenError(IERC20 token);
errorTokenTransferFailedError(IERC20 token, address to, uint256 amount);
errorTokenApprovalFailed(IERC20 token, address spender, uint256 amount);
// Perform an `IERC20.transfer()` handling non-compliant implementations.functioncompatTransfer(IERC20 token, address to, uint256 amount) internal{
(bool s, bytesmemory 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);
}
// Perform an `IERC20.transferFrom()` handling non-compliant implementations.functioncompatTransferFrom(IERC20 token, addressfrom, address to, uint256 amount) internal{
(bool s, bytesmemory r) =address(token).call(
abi.encodeCall(IERC20.transferFrom, (from, 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);
}
functioncompatApprove(IERC20 token, address spender, uint256 amount) internal{
(bool s, bytesmemory r) =address(token).call(
abi.encodeCall(IERC20.approve, (spender, 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 TokenApprovalFailed(token, spender, amount);
}
}
Contract Source Code
File 40 of 57: LibGlobals.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;// Valid keys in `IGlobals`. Append-only.libraryLibGlobals{
// 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.uint256internalconstant GLOBAL_PARTY_IMPL =1;
uint256internalconstant GLOBAL_PROPOSAL_ENGINE_IMPL =2;
uint256internalconstant GLOBAL_PARTY_FACTORY =3;
uint256internalconstant GLOBAL_GOVERNANCE_NFT_RENDER_IMPL =4;
uint256internalconstant GLOBAL_CF_NFT_RENDER_IMPL =5;
uint256internalconstant GLOBAL_OS_ZORA_AUCTION_TIMEOUT =6;
uint256internalconstant 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;uint256internalconstant GLOBAL_DAO_WALLET =11;
uint256internalconstant GLOBAL_TOKEN_DISTRIBUTOR =12;
uint256internalconstant GLOBAL_OPENSEA_CONDUIT_KEY =13;
uint256internalconstant GLOBAL_OPENSEA_ZONE =14;
uint256internalconstant GLOBAL_PROPOSAL_MAX_CANCEL_DURATION =15;
uint256internalconstant GLOBAL_ZORA_MIN_AUCTION_DURATION =16;
uint256internalconstant GLOBAL_ZORA_MAX_AUCTION_DURATION =17;
uint256internalconstant GLOBAL_ZORA_MAX_AUCTION_TIMEOUT =18;
uint256internalconstant GLOBAL_OS_MIN_ORDER_DURATION =19;
uint256internalconstant GLOBAL_OS_MAX_ORDER_DURATION =20;
uint256internalconstant GLOBAL_DISABLE_PARTY_ACTIONS =21;
uint256internalconstant GLOBAL_RENDERER_STORAGE =22;
uint256internalconstant 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;uint256internalconstant 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;uint256internalconstant GLOBAL_SEAPORT =30;
uint256internalconstant GLOBAL_CONDUIT_CONTROLLER =31;
uint256internalconstant GLOBAL_OFF_CHAIN_SIGNATURE_VALIDATOR =32;
}
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;libraryLibRawResult{
// Revert with the data in `b`.functionrawRevert(bytesmemory b) internalpure{
assembly {
revert(add(b, 32), mload(b))
}
}
// Return with the data in `b`.functionrawReturn(bytesmemory b) internalpure{
assembly {
return(add(b, 32), mload(b))
}
}
}
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../tokens/IERC721.sol";
import"./LibRawResult.sol";
libraryLibSafeERC721{
usingLibRawResultforbytes;
// Call `IERC721.ownerOf()` without reverting and return `address(0)` if:// - `tokenID` does not exist.// - `token` is an EOA// - `token` is an empty contract// - `token` is a "bad" implementation of ERC721 that returns nothing for// `ownerOf()`functionsafeOwnerOf(IERC721 token, uint256 tokenId) internalviewreturns (address owner) {
(bool s, bytesmemory r) =address(token).staticcall(
abi.encodeCall(token.ownerOf, (tokenId))
);
if (!s || r.length<32) {
returnaddress(0);
}
returnabi.decode(r, (address));
}
}
Contract Source Code
File 45 of 57: MerkleProof.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)pragmasolidity ^0.8.19;/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/libraryMerkleProof{
/**
*@dev The multiproof provided is not valid.
*/errorMerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/functionverify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internalpurereturns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/functionverifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internalpurereturns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/functionprocessProof(bytes32[] memory proof, bytes32 leaf) internalpurereturns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i =0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/functionprocessProofCalldata(bytes32[] calldata proof, bytes32 leaf) internalpurereturns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i =0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/functionmultiProofVerify(bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internalpurereturns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/functionmultiProofVerifyCalldata(bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internalpurereturns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* _Available since v4.7._
*/functionprocessMultiProof(bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internalpurereturns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of// the merkle tree.uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.if (leavesLen + proofLen -1!= totalHashes) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".bytes32[] memory hashes =newbytes32[](totalHashes);
uint256 leafPos =0;
uint256 hashPos =0;
uint256 proofPos =0;
// At each step, we compute the next hash using two values:// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we// get the next hash.// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the// `proof` array.for (uint256 i =0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes >0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes -1];
}
} elseif (leavesLen >0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/functionprocessMultiProofCalldata(bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internalpurereturns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of// the merkle tree.uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.if (leavesLen + proofLen -1!= totalHashes) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".bytes32[] memory hashes =newbytes32[](totalHashes);
uint256 leafPos =0;
uint256 hashPos =0;
uint256 proofPos =0;
// At each step, we compute the next hash using two values:// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we// get the next hash.// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the// `proof` array.for (uint256 i =0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes >0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes -1];
}
} elseif (leavesLen >0) {
return leaves[0];
} else {
return proof[0];
}
}
function_hashPair(bytes32 a, bytes32 b) privatepurereturns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function_efficientHash(bytes32 a, bytes32 b) privatepurereturns (bytes32 value) {
/// @solidity memory-safe-assemblyassembly {
mstore(0x00, a)
mstore(0x20, b)
value :=keccak256(0x00, 0x40)
}
}
}
Contract Source Code
File 46 of 57: MetadataProvider.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { Multicall } from"../utils/Multicall.sol";
import { MetadataRegistry } from"./MetadataRegistry.sol";
import { IMetadataProvider } from"./IMetadataProvider.sol";
import { IGlobals } from"../globals/IGlobals.sol";
import { LibGlobals } from"../globals/LibGlobals.sol";
/// @notice A contract that provides custom metadata for Party Cards.contractMetadataProviderisIMetadataProvider, Multicall{
eventMetadataSet(addressindexed instance, bytes metadata);
errorNotAuthorized(address caller, address instance);
// The `Globals` contract storing global configuration values. This contract// is immutable and it’s address will never change.
IGlobals internalimmutable _GLOBALS;
/// @inheritdoc IMetadataProviderboolpublicconstant supportsRegistrars =true;
// The metadata for each Party instance.mapping(address instance =>bytes metadata) internal _metadata;
// Set the `Globals` contract.constructor(IGlobals globals) {
_GLOBALS = globals;
}
/// @inheritdoc IMetadataProviderfunctiongetMetadata(address instance,
uint256) externalviewvirtualoverridereturns (bytesmemory) {
return _metadata[instance];
}
/// @notice Set the metadata for a Party instance./// @param instance The address of the instance./// @param metadata The encoded metadata.functionsetMetadata(address instance, bytesmemory metadata) externalvirtual{
if (instance !=msg.sender) {
MetadataRegistry registry = MetadataRegistry(
_GLOBALS.getAddress(LibGlobals.GLOBAL_METADATA_REGISTRY)
);
// Check if the caller is authorized to set metadata for the instance.if (!registry.isRegistrar(msg.sender, instance)) {
revert NotAuthorized(msg.sender, instance);
}
}
_metadata[instance] = metadata;
emit MetadataSet(instance, metadata);
}
}
Contract Source Code
File 47 of 57: MetadataRegistry.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { IGlobals } from"../globals/IGlobals.sol";
import { LibGlobals } from"../globals/LibGlobals.sol";
import { IMetadataProvider } from"./IMetadataProvider.sol";
import { Multicall } from"../utils/Multicall.sol";
/// @notice A registry of custom metadata providers for Party Cards.contractMetadataRegistryisMulticall{
eventProviderSet(addressindexed instance, IMetadataProvider indexed provider);
eventRegistrarSet(addressindexed registrar, addressindexed instance, bool canSetData);
errorNotAuthorized(address caller, address instance);
// The `Globals` contract storing global configuration values. This contract// is immutable and it’s address will never change.
IGlobals privateimmutable _GLOBALS;
/// @notice Get the metadata provider for a Party instance.mapping(address instance => IMetadataProvider provider) public getProvider;
/// @notice Whether or not an address is a registar that can set the/// provider and metadata for another instance. If registrar is set/// true for `address(1)`, the address is a universal registar and/// can set data for any instance./// @dev Registrars' ability to set metadata for another instance must also be/// supported by the metadata provider used by that instance, indicated by/// `IMetadataProvider.supportsRegistrars()`.mapping(address registrar =>mapping(address instance =>bool canSetData)) private _isRegistrar;
/// @param globals The address of the `Globals` contract./// @param registrars The addresses of the initial universal registrars.constructor(IGlobals globals, address[] memory registrars) {
_GLOBALS = globals;
// Set the initial universal registrars.for (uint256 i =0; i < registrars.length; i++) {
_isRegistrar[registrars[i]][address(1)] =true;
}
}
/// @notice Set the metadata provider for a Party instance./// @param instance The address of the instance./// @param provider The address of the metadata provider.functionsetProvider(address instance, IMetadataProvider provider) external{
// Check if the caller is authorized to set the provider for the instance.if (!isRegistrar(msg.sender, instance)) revert NotAuthorized(msg.sender, instance);
getProvider[instance] = provider;
emit ProviderSet(instance, provider);
}
/// @notice Set whether or not an address can set metadata for a Party instance./// @param registrar The address of the possible registrar./// @param instance The address of the instance the registrar can set/// metadata for./// @param canSetData Whether or not the address can set data for the instance.functionsetRegistrar(address registrar, address instance, bool canSetData) external{
if (
msg.sender!= instance &&msg.sender!= _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET)
) {
revert NotAuthorized(msg.sender, instance);
}
_isRegistrar[registrar][instance] = canSetData;
emit RegistrarSet(registrar, instance, canSetData);
}
/// @notice Get whether or not an address can set metadata for a Party instance./// @param registrar The address of the possible registrar./// @param instance The address of the instance the registrar can set/// metadata for./// @return canSetData Whether or not the address can set data for the instance.functionisRegistrar(address registrar, address instance) publicviewreturns (bool) {
return
registrar == instance ||
_isRegistrar[registrar][address(1)] ||
_isRegistrar[registrar][instance];
}
/// @notice Get the metadata for a Party instance./// @param instance The address of the instance./// @param tokenId The ID of the token to get the metadata for./// @return metadata The encoded metadata.functiongetMetadata(address instance, uint256 tokenId) externalviewreturns (bytesmemory) {
IMetadataProvider provider = getProvider[instance];
returnaddress(provider) !=address(0) ? provider.getMetadata(instance, tokenId) : bytes("");
}
}
Contract Source Code
File 48 of 57: Multicall.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"../utils/LibRawResult.sol";
abstractcontractMulticall{
usingLibRawResultforbytes;
/// @notice Perform multiple delegatecalls on ourselves.functionmulticall(bytes[] calldata multicallData) external{
for (uint256 i; i < multicallData.length; ++i) {
(bool s, bytesmemory r) =address(this).delegatecall(multicallData[i]);
if (!s) {
r.rawRevert();
}
}
}
}
Contract Source Code
File 49 of 57: Party.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 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.contractPartyisPartyGovernanceNFT{
// Arguments used to initialize the party.structPartyOptions {
PartyGovernance.GovernanceOpts governance;
ProposalStorage.ProposalEngineOpts proposalEngine;
string name;
string symbol;
uint256 customizationPresetId;
}
// Arguments used to initialize the `PartyGovernanceNFT`.structPartyInitData {
PartyOptions options;
IERC721[] preciousTokens;
uint256[] preciousTokenIds;
address[] authorities;
uint40 rageQuitTimestamp;
}
/// @notice Version ID of the party implementation contract.uint16publicconstant VERSION_ID =2;
// Set the `Globals` contract.constructor(IGlobals globals) PartyGovernanceNFT(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param initData Options used to initialize the party governance.functioninitialize(PartyInitData memory initData) externalonlyInitialize{
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() externalpayable{}
}
Contract Source Code
File 50 of 57: PartyGovernance.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import { ITokenDistributor } from"../distribution/ITokenDistributor.sol";
import { ReadOnlyDelegateCall } from"../utils/ReadOnlyDelegateCall.sol";
import { IERC721 } from"../tokens/IERC721.sol";
import { IERC20 } from"../tokens/IERC20.sol";
import { IERC721Receiver } from"../tokens/IERC721Receiver.sol";
import { ERC1155TokenReceiverBase } from"../vendor/solmate/ERC1155.sol";
import { LibERC20Compat } from"../utils/LibERC20Compat.sol";
import { LibRawResult } from"../utils/LibRawResult.sol";
import { LibSafeCast } from"../utils/LibSafeCast.sol";
import { IERC4906 } from"../utils/IERC4906.sol";
import { IGlobals } from"../globals/IGlobals.sol";
import { LibGlobals } from"../globals/LibGlobals.sol";
import { IProposalExecutionEngine } from"../proposals/IProposalExecutionEngine.sol";
import { LibProposal } from"../proposals/LibProposal.sol";
import { ProposalStorage } from"../proposals/ProposalStorage.sol";
import { Implementation } from"../utils/Implementation.sol";
import { Party } from"./Party.sol";
/// @notice Base contract for a Party encapsulating all governance functionality./// @dev This contract uses IERC4906 however does not comply with the standard/// since it does emit metadata events when distributions are claimed or/// when a MetadaProvider changes its URI. This decision was made/// intentionally which is why ERC4906 is not included in `supportsInterface`.abstractcontractPartyGovernanceisProposalStorage,
Implementation,
IERC4906,
ReadOnlyDelegateCall{
usingLibERC20CompatforIERC20;
usingLibRawResultforbytes;
usingLibSafeCastforuint256;
usingLibSafeCastforint192;
usingLibSafeCastforuint96;
// States a proposal can be in.enumProposalStatus {
// 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
}
structGovernanceOpts {
// 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 recipient for distributions.addresspayable feeRecipient;
}
// A snapshot of voting power for a member.structVotingPowerSnapshot {
// 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.structProposal {
// 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.structProposalStateValues {
// 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;
// Number of hosts at time proposal createduint8 numHosts;
// Number of hosts that accepted proposaluint8 numHostsAccepted;
// Cached vote duration from proposal creation.uint40 voteDuration;
// Cached execution delay from proposal creation.uint40 executionDelay;
// Cached pass threshold bps from proposal creation.uint16 passThresholdBps;
}
// Storage states for a proposal.structProposalState {
// 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;
}
eventProposed(uint256 proposalId, address proposer, Proposal proposal);
eventProposalAccepted(uint256 proposalId, address voter, uint256 weight);
eventEmergencyExecute(address target, bytes data, uint256 amountEth);
eventProposalPassed(uint256indexed proposalId);
eventProposalVetoed(uint256indexed proposalId, address host);
eventProposalExecuted(uint256indexed proposalId, address executor, bytes nextProgressData);
eventProposalCancelled(uint256indexed proposalId);
eventDistributionCreated(
ITokenDistributor.TokenType tokenType,
address token,
uint256 tokenId
);
eventPartyDelegateUpdated(addressindexed owner, addressindexed delegate);
eventHostStatusTransferred(address oldHost, address newHost);
eventEmergencyExecuteDisabled();
eventPartyVotingSnapshotCreated(addressindexed voter,
uint40 timestamp,
uint96 delegatedVotingPower,
uint96 intrinsicVotingPower,
bool isDelegated
);
errorMismatchedPreciousListLengths();
errorBadProposalStatusError(ProposalStatus status);
errorBadProposalHashError(bytes32 proposalHash, bytes32 actualHash);
errorExecutionTimeExceededError(uint40 maxExecutableTime, uint40 timestamp);
errorNotAuthorized();
errorInvalidDelegateError();
errorBadPreciousListError();
errorOnlyWhenEmergencyActionsAllowedError();
errorOnlyWhenEnabledError();
errorAlreadyVotedError(address voter);
errorInvalidNewHostError();
errorProposalCannotBeCancelledYetError(uint40 currentTime, uint40 cancelTime);
errorInvalidBpsError(uint16 bps);
errorInvalidGovernanceParameter(uint256 value);
errorDistributionsRequireVoteError();
errorPartyNotStartedError();
errorCannotModifyTotalVotingPowerAndAcceptError();
errorTooManyHosts();
uint256privateconstant UINT40_HIGH_BIT =1<<39;
uint96privateconstant VETO_VALUE =type(uint96).max;
// The `Globals` contract storing global configuration values. This contract// is immutable and it’s address will never change.
IGlobals privateimmutable _GLOBALS;
/// @notice Whether the DAO has emergency powers for this party.boolpublic emergencyExecuteDisabled;
/// @notice Distribution fee bps.uint16public feeBps;
/// @notice Distribution fee recipient.addresspayablepublic feeRecipient;
/// @notice The timestamp of the last time total voting power changed in the party.uint40public lastTotalVotingPowerChangeTimestamp;
/// @notice The hash of the list of precious NFTs guarded by the party.bytes32public preciousListHash;
/// @notice The last proposal ID that was used. 0 means no proposals have been made.uint256public 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;
/// @notice Number of hosts for this partyuint8public numHosts;
/// @notice ProposalState by proposal ID.mapping(uint256=> ProposalState) private _proposalStateByProposalId;
/// @notice Snapshots of voting power per user, each sorted by increasing time.mapping(address=> VotingPowerSnapshot[]) private _votingPowerSnapshotsByVoter;
function_assertHost() internalview{
if (!isHost[msg.sender]) {
revert NotAuthorized();
}
}
function_assertActiveMember() internalview{
VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
// Must have either delegated voting power or intrinsic voting power.if (snap.intrinsicVotingPower ==0&& snap.delegatedVotingPower ==0) {
revert NotAuthorized();
}
}
// Only the party DAO multisig can call.modifieronlyPartyDao() {
{
address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
if (msg.sender!= partyDao) {
revert NotAuthorized();
}
}
_;
}
// Only the party DAO multisig or a party host can call.modifieronlyPartyDaoOrHost() {
address partyDao = _GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
if (msg.sender!= partyDao &&!isHost[msg.sender]) {
revert NotAuthorized();
}
_;
}
// Only if `emergencyExecuteDisabled` is not true.modifieronlyWhenEmergencyExecuteAllowed() {
if (emergencyExecuteDisabled) {
revert OnlyWhenEmergencyActionsAllowedError();
}
_;
}
function_assertNotGloballyDisabled() internalview{
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
) internalvirtual{
// Check BPS are valid.if (govOpts.feeBps >1e4) {
revert InvalidBpsError(govOpts.feeBps);
}
if (govOpts.voteDuration <1hours) {
revert InvalidGovernanceParameter(govOpts.voteDuration);
}
if (govOpts.passThresholdBps ==0|| govOpts.passThresholdBps >1e4) {
revert InvalidBpsError(govOpts.passThresholdBps);
}
if (govOpts.executionDelay ==0|| govOpts.executionDelay >30days) {
revert InvalidGovernanceParameter(govOpts.executionDelay);
}
// Initialize the proposal execution engine.
_initProposalImpl(
IProposalExecutionEngine(_GLOBALS.getAddress(LibGlobals.GLOBAL_PROPOSAL_ENGINE_IMPL)),
abi.encode(proposalEngineOpts)
);
// Set the governance parameters.
_getSharedProposalStorage().governanceValues = GovernanceValues({
voteDuration: govOpts.voteDuration,
executionDelay: govOpts.executionDelay,
passThresholdBps: govOpts.passThresholdBps,
totalVotingPower: govOpts.totalVotingPower
});
numHosts =uint8(govOpts.hosts.length);
// Set fees.
feeBps = govOpts.feeBps;
feeRecipient = govOpts.feeRecipient;
// Set the precious list.
_setPreciousList(preciousTokens, preciousTokenIds);
// Set the party hosts.if (govOpts.hosts.length>type(uint8).max) {
revert TooManyHosts();
}
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{
bytes4 functionSelector =bytes4(msg.data[0:4]);
if (
functionSelector == ERC1155TokenReceiverBase.onERC1155BatchReceived.selector||
functionSelector == ERC1155TokenReceiverBase.onERC1155Received.selector||
functionSelector == IERC721Receiver.onERC721Received.selector
) {
assembly {
let freeMem :=mload(0x40)
mstore(freeMem, functionSelector)
mstore(0x40, add(freeMem, 0x20))
return(freeMem, 0x20)
}
}
_readOnlyDelegateCall(address(_getSharedProposalStorage().engineImpl), msg.data);
}
/// @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` otherwisefunctionsupportsInterface(bytes4 interfaceId) publicpurevirtualreturns (bool) {
return
interfaceId ==type(IERC721Receiver).interfaceId||
interfaceId ==type(ERC1155TokenReceiverBase).interfaceId;
}
/// @notice Get the current `ProposalExecutionEngine` instance.functiongetProposalExecutionEngine() externalviewreturns (IProposalExecutionEngine) {
return _getSharedProposalStorage().engineImpl;
}
/// @notice Get the current `ProposalEngineOpts` options.functiongetProposalEngineOpts() externalviewreturns (ProposalEngineOpts memory) {
return _getSharedProposalStorage().opts;
}
/// @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`.functiongetVotingPowerAt(address voter,
uint40 timestamp,
uint256 snapIndex
) publicviewreturns (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.functiongetProposalStateInfo(uint256 proposalId
) externalviewreturns (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.functiongetGovernanceValues() externalviewreturns (GovernanceValues memory) {
return _getSharedProposalStorage().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.functiongetProposalHash(Proposal memory proposal) publicpurereturns (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.functionfindVotingPowerSnapshotIndex(address voter,
uint40 timestamp
) publicviewreturns (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#L39uint256 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.functiondelegateVotingPower(address delegate) external{
_adjustVotingPower(msg.sender, 0, delegate);
}
/// @notice Transfer party host status to another./// @param newPartyHost The address of the new host.functionabdicateHost(address newPartyHost) external{
_assertHost();
// 0 is a special case burn address.if (newPartyHost !=address(0)) {
// Can only abdicate hostrevert InvalidNewHostError();
} else {
// Burned the host status--numHosts;
}
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.functiondistribute(uint256 amount,
ITokenDistributor.TokenType tokenType,
address token,
uint256 tokenId
) externalreturns (ITokenDistributor.DistributionInfo memory distInfo) {
_assertNotGloballyDisabled();
// 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.distributionsConfig !=
DistributionsConfig.AllowedWithoutVote
) {
revert DistributionsRequireVoteError();
}
// Must be an active member.
VotingPowerSnapshot memory snap = _getLastVotingPowerSnapshotForVoter(msg.sender);
if (snap.intrinsicVotingPower ==0&& snap.delegatedVotingPower ==0) {
revert NotAuthorized();
}
}
// Prevent creating a distribution if the party has not started.if (_getSharedProposalStorage().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);
_emitMetadataUpdateEvent();
// Create a native token distribution.addresspayable 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.functionpropose(
Proposal memory proposal,
uint256 latestSnapIndex
) externalreturns (uint256 proposalId) {
_assertActiveMember();
proposalId =++lastProposalId;
ProposalStorage.GovernanceValues memory gv = _getSharedProposalStorage().governanceValues;
// 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: gv.totalVotingPower,
numHosts: numHosts,
numHostsAccepted: 0,
voteDuration: gv.voteDuration,
executionDelay: gv.executionDelay,
passThresholdBps: gv.passThresholdBps
}),
getProposalHash(proposal)
);
emit Proposed(proposalId, msg.sender, proposal);
accept(proposalId, latestSnapIndex);
_emitMetadataUpdateEvent();
}
/// @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.functionaccept(uint256 proposalId, uint256 snapIndex) publicreturns (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 total voting power// change. This is to prevent an exploit where a member can, for// example, 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 (lastTotalVotingPowerChangeTimestamp ==block.timestamp) {
revert CannotModifyTotalVotingPowerAndAcceptError();
}
// 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;
if (isHost[msg.sender]) {
++values.numHostsAccepted;
}
info.values = values;
emit ProposalAccepted(proposalId, msg.sender, votingPower);
// Update the proposal status if it has reached the pass threshold.if (
values.passedTime ==0&&
(uint256(values.votes) *1e4) /uint256(values.totalVotingPower) >=uint256(values.passThresholdBps)
) {
info.values.passedTime =uint40(block.timestamp);
emit ProposalPassed(proposalId);
_emitMetadataUpdateEvent();
}
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.functionveto(uint256 proposalId) external{
_assertHost();
// 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);
_emitMetadataUpdateEvent();
}
/// @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.functionexecute(uint256 proposalId,
Proposal memory proposal,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
bytescalldata progressData,
bytescalldata extraData
) externalpayable{
_assertNotGloballyDisabled();
_assertActiveMember();
// 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.functioncancel(uint256 proposalId, Proposal calldata proposal) external{
_assertActiveMember();
// 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, bytesmemory resultData) = (
address(_getSharedProposalStorage().engineImpl)
).delegatecall(abi.encodeCall(IProposalExecutionEngine.cancelProposal, (proposalId)));
if (!success) {
resultData.rawRevert();
}
}
emit ProposalCancelled(proposalId);
_emitMetadataUpdateEvent();
}
/// @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.functionemergencyExecute(address targetAddress,
bytescalldata targetCallData,
uint256 amountEth
) externalpayableonlyPartyDaoonlyWhenEmergencyExecuteAllowed{
(bool success, bytesmemory 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.functiondisableEmergencyExecute() externalonlyPartyDaoOrHost{
emergencyExecuteDisabled =true;
emit EmergencyExecuteDisabled();
}
function_executeProposal(uint256 proposalId,
Proposal memory proposal,
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds,
uint256 flags,
bytesmemory progressData,
bytesmemory extraData
) privatereturns (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.bytesmemory nextProgressData;
{
// Execute the proposal.
(bool success, bytesmemory 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);
_emitMetadataUpdateEvent();
// 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
) internalviewreturns (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(addressfrom, 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;
// This event is emitted even if the delegate did not change.emit PartyDelegateUpdated(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{
emit PartyVotingSnapshotCreated(
voter,
snap.timestamp,
snap.delegatedVotingPower,
snap.intrinsicVotingPower,
snap.isDelegated
);
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
) privateviewreturns (VotingPowerSnapshot memory snap) {
VotingPowerSnapshot[] storage voterSnaps = _votingPowerSnapshotsByVoter[voter];
uint256 n = voterSnaps.length;
if (n !=0) {
snap = voterSnaps[n -1];
}
}
function_getProposalFlags(ProposalStateValues memory pv) privatepurereturns (uint256) {
uint256 flags =0;
if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
flags = flags | LibProposal.PROPOSAL_FLAG_UNANIMOUS;
}
if (_hostsAccepted(pv.numHosts, pv.numHostsAccepted)) {
flags = flags | LibProposal.PROPOSAL_FLAG_HOSTS_ACCEPT;
}
return flags;
}
function_getProposalStatus(
ProposalStateValues memory pv
) privateviewreturns (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);
if (pv.passedTime !=0) {
// Ready.if (pv.passedTime + pv.executionDelay <= t) {
return ProposalStatus.Ready;
}
// If unanimous, we skip the execution delay.if (_isUnanimousVotes(pv.votes, pv.totalVotingPower)) {
return ProposalStatus.Ready;
}
// If all hosts voted, skip execution delayif (_hostsAccepted(pv.numHosts, pv.numHostsAccepted)) {
return ProposalStatus.Ready;
}
// Passed.return ProposalStatus.Passed;
}
// Voting window expired.if (pv.proposedTime + pv.voteDuration <= t) {
return ProposalStatus.Defeated;
}
return ProposalStatus.Voting;
}
function_isUnanimousVotes(uint96 totalVotes,
uint96 totalVotingPower
) privatepurereturns (bool) {
uint256 acceptanceRatio = (uint256(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_hostsAccepted(uint8 snapshotNumHosts,
uint8 numHostsAccepted
) privatepurereturns (bool) {
return snapshotNumHosts >0&& snapshotNumHosts == numHostsAccepted;
}
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
) privateviewreturns (bool) {
return preciousListHash == _hashPreciousList(preciousTokens, preciousTokenIds);
}
function_hashPreciousList(
IERC721[] memory preciousTokens,
uint256[] memory preciousTokenIds
) internalpurereturns (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)
}
}
function_emitMetadataUpdateEvent() internal{
emit BatchMetadataUpdate(0, type(uint256).max);
}
// Assert that the hash of a proposal matches expectedHash.function_validateProposalHash(Proposal memory proposal, bytes32 expectedHash) privatepure{
bytes32 actualHash = getProposalHash(proposal);
if (expectedHash != actualHash) {
revert BadProposalHashError(actualHash, expectedHash);
}
}
}
Contract Source Code
File 51 of 57: PartyGovernanceNFT.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;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`.abstractcontractPartyGovernanceNFTisPartyGovernance, ERC721, IERC2981{
usingLibSafeCastforuint256;
usingLibSafeCastforuint96;
usingLibERC20CompatforIERC20;
usingLibAddressforaddresspayable;
errorFixedRageQuitTimestampError(uint40 rageQuitTimestamp);
errorCannotRageQuitError(uint40 rageQuitTimestamp);
errorCannotDisableRageQuitAfterInitializationError();
errorCannotEnableRageQuitIfNotDistributionsRequireVoteError();
errorInvalidTokenOrderError();
errorBelowMinWithdrawAmountError(uint256 amount, uint256 minAmount);
errorNothingToBurnError();
eventAuthorityAdded(addressindexed authority);
eventAuthorityRemoved(addressindexed authority);
eventRageQuitSet(uint40 oldRageQuitTimestamp, uint40 newRageQuitTimestamp);
eventBurn(address caller, uint256 tokenId, uint256 votingPower);
eventRageQuit(address caller, uint256[] tokenIds, IERC20[] withdrawTokens, address receiver);
eventPartyCardIntrinsicVotingPowerSet(uint256indexed tokenId, uint256 intrinsicVotingPower);
uint40privateconstant ENABLE_RAGEQUIT_PERMANENTLY =0x6b5b567bfe; // uint40(uint256(keccak256("ENABLE_RAGEQUIT_PERMANENTLY")))uint40privateconstant DISABLE_RAGEQUIT_PERMANENTLY =0xab2cb21860; // uint40(uint256(keccak256("DISABLE_RAGEQUIT_PERMANENTLY")))// Token address used to indicate ETH.addressprivateconstant ETH_ADDRESS =0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// The `Globals` contract storing global configuration values. This contract// is immutable and its address will never change.
IGlobals privateimmutable _GLOBALS;
/// @notice The number of tokens that have been minted.uint96public tokenCount;
/// @notice The total minted voting power./// Capped to `_governanceValues.totalVotingPower` unless minting/// party cards for initial crowdfund.uint96public 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.uint40public 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;
function_assertAuthority() internalview{
if (!isAuthority[msg.sender]) {
revert NotAuthorized();
}
}
modifieronlySelf() {
if (msg.sender!=address(this)) {
revert NotAuthorized();
}
_;
}
// Set the `Globals` contract. The name or symbol of ERC721 does not matter;// it will be set in `_initialize()`.constructor(IGlobals globals) payablePartyGovernance(globals) ERC721("", "") {
_GLOBALS = globals;
}
// Initialize storage for proxy contracts.function_initialize(stringmemory name_,
stringmemory 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_;
if (rageQuitTimestamp_ !=0) {
if (proposalEngineOpts.distributionsConfig == DistributionsConfig.AllowedWithoutVote) {
revert CannotEnableRageQuitIfNotDistributionsRequireVoteError();
}
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 EIP165functionsupportsInterface(bytes4 interfaceId
) publicpureoverride(PartyGovernance, ERC721, IERC165) returns (bool) {
return
PartyGovernance.supportsInterface(interfaceId) ||
ERC721.supportsInterface(interfaceId) ||
interfaceId ==type(IERC2981).interfaceId;
}
/// @inheritdoc ERC721functiontokenURI(uint256) publicviewoverridereturns (stringmemory) {
_delegateToRenderer();
return""; // Just to make the compiler happy.
}
/// @notice Returns a URI for the storefront-level metadata for your contract.functioncontractURI() externalviewreturns (stringmemory) {
_delegateToRenderer();
return""; // Just to make the compiler happy.
}
/// @notice Called with the sale price to determine how much royalty// is owed and to whom.functionroyaltyInfo(uint256, uint256) externalviewreturns (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`.functiongetDistributionShareOf(uint256 tokenId) externalviewreturns (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`.functiongetVotingPowerShareOf(uint256 tokenId) publicviewreturns (uint256) {
uint256 totalVotingPower = _getSharedProposalStorage().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.functionmint(address owner,
uint256 votingPower,
address delegate
) externalreturns (uint256 tokenId) {
_assertAuthority();
uint96 mintedVotingPower_ = mintedVotingPower;
uint96 totalVotingPower = _getSharedProposalStorage().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_;
emit PartyCardIntrinsicVotingPowerSet(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.functionincreaseVotingPower(uint256 tokenId, uint96 votingPower) external{
_assertAuthority();
uint96 mintedVotingPower_ = mintedVotingPower;
uint96 totalVotingPower = _getSharedProposalStorage().governanceValues.totalVotingPower;
// Cap voting power to remaining unminted voting power supply. 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;
uint256 newIntrinsicVotingPower = votingPowerByTokenId[tokenId] + votingPower;
votingPowerByTokenId[tokenId] = newIntrinsicVotingPower;
emit PartyCardIntrinsicVotingPowerSet(tokenId, newIntrinsicVotingPower);
_adjustVotingPower(ownerOf(tokenId), votingPower.safeCastUint96ToInt192(), address(0));
// Notify third-party platforms that the party NFT metadata has updated.emit MetadataUpdate(tokenId);
}
/// @notice Remove voting power from an existing NFT. Only callable by an/// authority./// @param tokenId The ID of the NFT to remove voting power from./// @param votingPower The amount of voting power to remove.functiondecreaseVotingPower(uint256 tokenId, uint96 votingPower) external{
_assertAuthority();
mintedVotingPower -= votingPower;
votingPowerByTokenId[tokenId] -= votingPower;
_adjustVotingPower(ownerOf(tokenId), -votingPower.safeCastUint96ToInt192(), address(0));
// Notify third-party platforms that the party NFT metadata has updated.emit MetadataUpdate(tokenId);
}
/// @notice Increase the total voting power of the party. Only callable by/// an authority./// @param votingPower The new total voting power to add.functionincreaseTotalVotingPower(uint96 votingPower) external{
_assertAuthority();
_getSharedProposalStorage().governanceValues.totalVotingPower += votingPower;
lastTotalVotingPowerChangeTimestamp ==uint40(block.timestamp);
// Notify third-party platforms that the party NFT metadata has updated// for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
}
/// @notice Decrease the total voting power of the party. Only callable by/// an authority./// @param votingPower The new total voting power to add.functiondecreaseTotalVotingPower(uint96 votingPower) external{
_assertAuthority();
_getSharedProposalStorage().governanceValues.totalVotingPower -= votingPower;
lastTotalVotingPowerChangeTimestamp ==uint40(block.timestamp);
// Notify third-party platforms that the party NFT metadata has updated// for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
}
/// @notice Burn governance NFTs and remove their voting power./// @param tokenIds The IDs of the governance NFTs to burn.functionburn(uint256[] memory tokenIds) public{
_assertAuthority();
_burnAndUpdateVotingPower(tokenIds, false);
}
function_burnAndUpdateVotingPower(uint256[] memory tokenIds,
bool checkIfAuthorizedToBurn
) privatereturns (uint96 totalVotingPowerBurned) {
for (uint256 i; i < tokenIds.length; ++i) {
uint256 tokenId = tokenIds[i];
address owner = ownerOf(tokenId);
// Check if caller is authorized to burn the token.if (checkIfAuthorizedToBurn) {
if (
msg.sender!= owner &&
getApproved[tokenId] !=msg.sender&&!isApprovedForAll[owner][msg.sender]
) {
revert NotAuthorized();
}
}
// 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];
emit PartyCardIntrinsicVotingPowerSet(tokenId, 0);
_adjustVotingPower(owner, -votingPower.safeCastUint96ToInt192(), address(0));
// Burn token.
_burn(tokenId);
emit Burn(msg.sender, tokenId, votingPower);
}
// Update minted voting power.
mintedVotingPower -= totalVotingPowerBurned;
emit BatchMetadataUpdate(0, type(uint256).max);
}
/// @notice Burn governance NFT and remove its voting power./// @param tokenId The ID of the governance NFTs to burn.functionburn(uint256 tokenId) external{
uint256[] memory tokenIds =newuint256[](1);
tokenIds[0] = tokenId;
burn(tokenIds);
}
/// @notice Set the timestamp until which ragequit is enabled./// @param newRageQuitTimestamp The new ragequit timestamp.functionsetRageQuit(uint40 newRageQuitTimestamp) external{
_assertHost();
// Prevent disabling ragequit after initialization.if (newRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY) {
revert CannotDisableRageQuitAfterInitializationError();
}
// Prevent enabling ragequit if distributions can be created without a vote.if (
_getSharedProposalStorage().opts.distributionsConfig ==
DistributionsConfig.AllowedWithoutVote
) revert CannotEnableRageQuitIfNotDistributionsRequireVoteError();
uint40 oldRageQuitTimestamp = rageQuitTimestamp;
// Prevent setting timestamp if it is permanently enabled/disabled.if (
oldRageQuitTimestamp == ENABLE_RAGEQUIT_PERMANENTLY ||
oldRageQuitTimestamp == DISABLE_RAGEQUIT_PERMANENTLY
) {
revert FixedRageQuitTimestampError(oldRageQuitTimestamp);
}
rageQuitTimestamp = newRageQuitTimestamp;
emit RageQuitSet(oldRageQuitTimestamp, 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.functionrageQuit(uint256[] calldata tokenIds,
IERC20[] calldata withdrawTokens,
uint256[] calldata minWithdrawAmounts,
address receiver
) external{
if (tokenIds.length==0) revert NothingToBurnError();
// Check if called by an authority.bool isAuthority_ = isAuthority[msg.sender];
// Check if ragequit is allowed.uint40 currentRageQuitTimestamp = rageQuitTimestamp;
if (!isAuthority_) {
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;
lastTotalVotingPowerChangeTimestamp =uint40(block.timestamp);
// Sum up total amount of each token to withdraw.uint256[] memory withdrawAmounts =newuint256[](withdrawTokens.length);
{
IERC20 prevToken;
for (uint256 i; i < withdrawTokens.length; ++i) {
// Check if order of tokens to transfer is valid.// Prevent null and duplicate transfers.if (prevToken >= withdrawTokens[i]) revert InvalidTokenOrderError();
prevToken = withdrawTokens[i];
// Check token's balance.uint256 balance =address(withdrawTokens[i]) == ETH_ADDRESS
? address(this).balance
: withdrawTokens[i].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.
withdrawAmounts[i] += (balance * getVotingPowerShareOf(tokenIds[j])) /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, not an authority, or if there are duplicate token IDs.uint96 totalVotingPowerBurned = _burnAndUpdateVotingPower(tokenIds, !isAuthority_);
// Update total voting power of party.
_getSharedProposalStorage().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);
}
}
// Check amount is at least minimum.uint256 minAmount = minWithdrawAmounts[i];
if (amount < minAmount) {
revert BelowMinWithdrawAmountError(amount, minAmount);
}
if (amount >0) {
// 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 ERC721functiontransferFrom(address owner, address to, uint256 tokenId) publicoverride{
// Transfer voting along with token.
_transferVotingPower(owner, to, votingPowerByTokenId[tokenId]);
super.transferFrom(owner, to, tokenId);
}
/// @inheritdoc ERC721functionsafeTransferFrom(address owner, address to, uint256 tokenId) publicoverride{
// super.safeTransferFrom() will call transferFrom() first which will// transfer voting power.super.safeTransferFrom(owner, to, tokenId);
}
/// @inheritdoc ERC721functionsafeTransferFrom(address owner,
address to,
uint256 tokenId,
bytescalldata data
) publicoverride{
// 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.functionaddAuthority(address authority) externalonlySelf{
isAuthority[authority] =true;
emit AuthorityAdded(authority);
}
/// @notice Relinquish the authority role.functionabdicateAuthority() external{
_assertAuthority();
delete isAuthority[msg.sender];
emit AuthorityRemoved(msg.sender);
}
function_delegateToRenderer() privateview{
_readOnlyDelegateCall(
// Instance of IERC721Renderer.
_GLOBALS.getAddress(LibGlobals.GLOBAL_GOVERNANCE_NFT_RENDER_IMPL),
msg.data
);
assert(false); // Will not be reached.
}
}
Contract Source Code
File 52 of 57: ProposalStorage.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 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-bucketsabstractcontractProposalStorage{
usingLibRawResultforbytes;
structSharedProposalStorage {
IProposalExecutionEngine engineImpl;
ProposalEngineOpts opts;
GovernanceValues governanceValues;
}
/// @notice Governance values stored for a partystructGovernanceValues {
uint40 voteDuration;
uint40 executionDelay;
uint16 passThresholdBps;
uint96 totalVotingPower;
}
enumDistributionsConfig {
AllowedWithoutVote,
AllowedWithVote,
NotAllowed
}
structProposalEngineOpts {
// 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 be used.bool allowOperators;
// Distributions config for the party.
DistributionsConfig distributionsConfig;
}
uint256internalconstant PROPOSAL_FLAG_UNANIMOUS =0x1;
uint256privateconstant SHARED_STORAGE_SLOT =uint256(keccak256("ProposalStorage.SharedProposalStorage"));
function_initProposalImpl(IProposalExecutionEngine impl, bytesmemory initData) internal{
SharedProposalStorage storage stor = _getSharedProposalStorage();
IProposalExecutionEngine oldImpl = stor.engineImpl;
stor.engineImpl = impl;
(bool s, bytesmemory r) =address(impl).delegatecall(
abi.encodeCall(IProposalExecutionEngine.initialize, (address(oldImpl), initData))
);
if (!s) {
r.rawRevert();
}
}
function_getSharedProposalStorage()
internalpurereturns (SharedProposalStorage storage stor)
{
uint256 s = SHARED_STORAGE_SLOT;
assembly {
stor.slot:= s
}
}
}
Contract Source Code
File 53 of 57: ReadOnlyDelegateCall.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"./LibRawResult.sol";
interfaceIReadOnlyDelegateCall{
// Marked `view` so that `_readOnlyDelegateCall` can be `view` as well.functiondelegateCallAndRevert(address impl, bytesmemory callData) externalview;
}
// Inherited by contracts to perform read-only delegate calls.abstractcontractReadOnlyDelegateCall{
usingLibRawResultforbytes;
// Delegatecall into implement and revert with the raw result.functiondelegateCallAndRevert(address impl, bytesmemory callData) external{
// Attempt to gate to only `_readOnlyDelegateCall()` invocations.require(msg.sender==address(this));
(bool s, bytesmemory 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, bytesmemory callData) internalview{
try IReadOnlyDelegateCall(address(this)).delegateCallAndRevert(impl, callData) {
// Should never happen.assert(false);
} catch (bytesmemory r) {
(bool success, bytesmemory resultData) =abi.decode(r, (bool, bytes));
if (!success) {
resultData.rawRevert();
}
resultData.rawReturn();
}
}
}
// SPDX-License-Identifier: Beta Software// http://ipfs.io/ipfs/QmbGX2MFCaMAsMNMugRFND6DtYygRkwkvrqEyTKhTdBLo5pragmasolidity 0.8.20;import"openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import"./AuctionCrowdfundBase.sol";
/// @notice A crowdfund that can repeatedly bid on auctions for an NFT from a/// specific collection on a specific market (e.g. Nouns) and can/// continue bidding on new auctions until it wins.contractRollingAuctionCrowdfundisAuctionCrowdfundBase{
usingLibSafeERC721forIERC721;
usingLibSafeCastforuint256;
usingLibRawResultforbytes;
structRollOverArgs {
// The `tokenId` of the next NFT to bid on in the next auction.// Only used if the crowdfund lost the current auction.uint256 nextNftTokenId;
// The `auctionId` of the the next auction. Only used if the// crowdfund lost the current auction.uint256 nextAuctionId;
// The maximum bid the party can place for the next auction.// Only used if the crowdfund lost the current auction.uint96 nextMaximumBid;
// The Merkle proof used to verify that `nextAuctionId` and// `nextNftTokenId` are allowed. Only used if the crowdfund// lost the current auction.bytes32[] proof;
// If the caller is a host, this is the index of the caller in the// `governanceOpts.hosts` array. Only used if the crowdfund lost the// current auction AND host are allowed to choose any next auction.uint256 hostIndex;
}
eventAuctionUpdated(uint256 nextNftTokenId, uint256 nextAuctionId, uint256 nextMaximumBid);
errorBadNextAuctionError();
/// @notice Merkle root of list of allowed next auctions that can be rolled/// over to if the current auction loses. Each leaf should be hashed/// as `keccak256(abi.encodePacked(auctionId, tokenId)))` where/// `auctionId` is the auction ID of the auction to allow and/// `tokenId` is the `tokenId` of the NFT being auctioned.bytes32public allowedAuctionsMerkleRoot;
// Set the `Globals` contract.constructor(IGlobals globals) AuctionCrowdfundBase(globals) {}
/// @notice Initializer to be called prior to using the contract./// @param opts Options used to initialize the crowdfund. These are fixed/// and cannot be changed later./// @param allowedAuctionsMerkleRoot_ Merkle root of list of allowed next/// auctions that can be rolled over to/// if the current auction loses.functioninitialize(
AuctionCrowdfundBase.AuctionCrowdfundOptions memory opts,
bytes32 allowedAuctionsMerkleRoot_
) externalpayableonlyInitialize{
// Initialize the base contract.
AuctionCrowdfundBase._initialize(opts);
// Set the merkle root of allowed auctions.
allowedAuctionsMerkleRoot = allowedAuctionsMerkleRoot_;
}
/// @notice Calls `finalize()` on the market adapter, which will claim the NFT/// (if necessary) if we won, or recover our bid (if necessary)/// if the crowfund expired and we lost the current auction. If we/// lost but the crowdfund has not expired, this will revert. Only/// call this to finalize the result of a won or expired crowdfund,/// otherwise call `finalizeOrRollOver()`./// @param governanceOpts The options used to initialize governance in the/// `Party` instance created if the crowdfund wins./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @return party_ Address of the `Party` instance created if successful.functionfinalize(
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
) externalonlyDelegateCallreturns (Party party_) {
// Empty args because we don't need to roll over to another auction.
RollOverArgs memory args;
// If the crowdfund won, only `governanceOpts` is relevant. The rest are ignored.return finalizeOrRollOver(args, governanceOpts, proposalEngineOpts);
}
/// @notice Calls `finalize()` on the market adapter, which will claim the NFT/// (if necessary) if we won, or recover our bid (if necessary)/// if the crowfund expired and we lost. If we lost but the/// crowdfund has not expired, it will move on to the next auction/// specified (if allowed)./// @param args Arguments used to roll over to the next auction if the/// crowdfund lost the current auction./// @param governanceOpts The options used to initialize governance in the/// `Party` instance created if the crowdfund wins./// @param proposalEngineOpts The options used to initialize the proposal/// engine in the `Party` instance created if the/// crowdfund wins./// @param party_ Address of the `Party` instance created if successful.functionfinalizeOrRollOver(
RollOverArgs memory args,
FixedGovernanceOpts memory governanceOpts,
ProposalStorage.ProposalEngineOpts memory proposalEngineOpts
) publiconlyDelegateCallreturns (Party party_) {
// Check that the auction is still active and has not passed the `expiry` time.
CrowdfundLifecycle lc = getCrowdfundLifecycle();
if (lc != CrowdfundLifecycle.Active && lc != CrowdfundLifecycle.Expired) {
revert WrongLifecycleError(lc);
}
// Finalize the auction if it is not already finalized.uint96 lastBid_ = lastBid;
_finalizeAuction(lc, market, auctionId, lastBid_);
IERC721 nftContract_ = nftContract;
uint256 nftTokenId_ = nftTokenId;
// Are we now in possession of the NFT?if (nftContract_.safeOwnerOf(nftTokenId_) ==address(this) && lastBid_ !=0) {
// Create a governance party around the NFT.
party_ = _createParty(
governanceOpts,
proposalEngineOpts,
false,
nftContract,
nftTokenId
);
emit Won(lastBid, party_);
// Notify third-party platforms that the crowdfund NFT metadata has// updated for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
_bidStatus = AuctionCrowdfundStatus.Finalized;
} elseif (lc == CrowdfundLifecycle.Expired) {
// Crowdfund expired without NFT; finalize a loss.// Clear `lastBid` so `_getFinalPrice()` is 0 and people can redeem their// full contributions when they burn their participation NFTs.
lastBid =0;
emit Lost();
// Notify third-party platforms that the crowdfund NFT metadata has// updated for all tokens.emit BatchMetadataUpdate(0, type(uint256).max);
_bidStatus = AuctionCrowdfundStatus.Finalized;
} else {
// Move on to the next auction if this one has been lost (or, in// rare cases, if the NFT was acquired for free and funds remain// unused).if (allowedAuctionsMerkleRoot !=bytes32(0)) {
// Check that the next `auctionId` and `tokenId` for the next// auction to roll over have been allowed.if (
!MerkleProof.verify(
args.proof,
allowedAuctionsMerkleRoot,
// Hash leaf with extra (empty) 32 bytes to prevent a second// preimage attack by hashing >64 bytes.keccak256(
abi.encodePacked(bytes32(0), args.nextAuctionId, args.nextNftTokenId)
)
)
) {
revert BadNextAuctionError();
}
} else {
// Let the host change to any next auction.
_assertIsHost(msg.sender, governanceOpts, proposalEngineOpts, args.hostIndex);
}
// Check that the new auction can be bid on and is valid.
_validateAuction(market, args.nextAuctionId, nftContract, args.nextNftTokenId);
// Check that the next maximum bid is greater than the auction's minimum bid.uint256 minimumBid = market.getMinimumBid(args.nextAuctionId);
if (args.nextMaximumBid < minimumBid) {
revert MinimumBidExceedsMaximumBidError(minimumBid, args.nextMaximumBid);
}
// Update state for next auction.
nftTokenId = args.nextNftTokenId;
auctionId = args.nextAuctionId;
maximumBid = args.nextMaximumBid;
lastBid =0;
emit AuctionUpdated(args.nextNftTokenId, args.nextAuctionId, args.nextMaximumBid);
// Change back the auction status from `Busy` to `Active`.
_bidStatus = AuctionCrowdfundStatus.Active;
}
}
}
Contract Source Code
File 56 of 57: SSTORE2.sol
// SPDX-License-Identifier: AGPL-3.0-onlypragmasolidity >=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)librarySSTORE2{
uint256internalconstant DATA_OFFSET =1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called./*//////////////////////////////////////////////////////////////
WRITE LOGIC
//////////////////////////////////////////////////////////////*/functionwrite(bytesmemory data) internalreturns (address pointer) {
// Prefix the bytecode with a STOP opcode to ensure it cannot be called.bytesmemory runtimeCode =abi.encodePacked(hex"00", data);
bytesmemory 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-assemblyassembly {
// 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
//////////////////////////////////////////////////////////////*/functionread(address pointer) internalviewreturns (bytesmemory) {
return readBytecode(pointer, DATA_OFFSET, pointer.code.length- DATA_OFFSET);
}
functionread(address pointer, uint256 start) internalviewreturns (bytesmemory) {
start += DATA_OFFSET;
return readBytecode(pointer, start, pointer.code.length- start);
}
functionread(address pointer,
uint256 start,
uint256 end
) internalviewreturns (bytesmemory) {
start += DATA_OFFSET;
end += DATA_OFFSET;
require(pointer.code.length>= end, "OUT_OF_BOUNDS");
return readBytecode(pointer, start, end - start);
}
/*//////////////////////////////////////////////////////////////
INTERNAL HELPER LOGIC
//////////////////////////////////////////////////////////////*/functionreadBytecode(address pointer,
uint256 start,
uint256 size
) privateviewreturns (bytesmemory data) {
/// @solidity memory-safe-assemblyassembly {
// 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)
}
}
}
Contract Source Code
File 57 of 57: draft-IERC6093.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)pragmasolidity ^0.8.20;/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
*/interfaceIERC20Errors{
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/errorERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/errorERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/errorERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/errorERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/errorERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/errorERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/interfaceIERC721Errors{
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/errorERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/errorERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/errorERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/errorERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/errorERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/errorERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/errorERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/errorERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
*/interfaceIERC1155Errors{
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/errorERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/errorERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/errorERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/errorERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/errorERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/errorERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/errorERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}