// 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 2 of 30: 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 3 of 30: 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;
}
}
// 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 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;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 13 of 30: 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 14 of 30: 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 15 of 30: 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 16 of 30: 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 17 of 30: 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"../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 22 of 30: 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 23 of 30: 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 24 of 30: 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 25 of 30: 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 26 of 30: 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();
}
}
}
Contract Source Code
File 27 of 30: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)pragmasolidity ^0.8.19;/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/abstractcontractReentrancyGuard{
// Booleans are more expensive than uint256 or any type that takes up a full// word because each write operation emits an extra SLOAD to first read the// slot's contents, replace the bits taken up by the boolean, and then write// back. This is the compiler's defense against contract upgrades and// pointer aliasing, and it cannot be disabled.// The values being non-zero value makes deployment a bit more expensive,// but in exchange the refund on every call to nonReentrant will be lower in// amount. Since refunds are capped to a percentage of the total// transaction's gas, it is best to keep them low in cases like this one, to// increase the likelihood of the full refund coming into effect.uint256privateconstant _NOT_ENTERED =1;
uint256privateconstant _ENTERED =2;
uint256private _status;
/**
* @dev Unauthorized reentrant call.
*/errorReentrancyGuardReentrantCall();
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/modifiernonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function_nonReentrantBefore() private{
// On the first call to nonReentrant, _status will be _NOT_ENTEREDif (_status == _ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function_nonReentrantAfter() private{
// By storing the original value once again, a refund is triggered (see// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/function_reentrancyGuardEntered() internalviewreturns (bool) {
return _status == _ENTERED;
}
}
// 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 30 of 30: TokenDistributor.sol
// SPDX-License-Identifier: GPL-3.0pragmasolidity 0.8.20;import"openzeppelin/contracts/security/ReentrancyGuard.sol";
import"../globals/IGlobals.sol";
import"../globals/LibGlobals.sol";
import"../tokens/IERC20.sol";
import"../utils/LibAddress.sol";
import"../utils/LibERC20Compat.sol";
import"../utils/LibRawResult.sol";
import"../utils/LibSafeCast.sol";
import"./ITokenDistributor.sol";
/// @notice Creates token distributions for parties.contractTokenDistributorisITokenDistributor, ReentrancyGuard{
usingLibAddressforaddresspayable;
usingLibERC20CompatforIERC20;
usingLibRawResultforbytes;
usingLibSafeCastforuint256;
structDistributionState {
// The hash of the `DistributionInfo`.bytes32 distributionHash;
// The remaining member supply.uint128 remainingMemberSupply;
// Whether the distribution's feeRecipient has claimed its fee.bool wasFeeClaimed;
// Whether a governance token has claimed its distribution share.mapping(uint256=>bool) hasPartyTokenClaimed;
// Last token ID at time distribution was created that can claim.// Stored here instead of in `DistributionInfo` to avoid versioning// conflicts on the frontend with older parties.uint96 maxTokenId;
}
// Arguments for `_createDistribution()`.structCreateDistributionArgs {
Party party;
TokenType tokenType;
address token;
uint256 currentTokenBalance;
addresspayable feeRecipient;
uint16 feeBps;
}
eventEmergencyExecute(address target, bytes data);
errorOnlyPartyDaoError(address notDao, address partyDao);
errorInvalidDistributionInfoError(DistributionInfo info);
errorDistributionAlreadyClaimedByPartyTokenError(uint256 distributionId, uint256 partyTokenId);
errorDistributionFeeAlreadyClaimedError(uint256 distributionId);
errorMustOwnTokenError(address sender, address expectedOwner, uint256 partyTokenId);
errorTokenIdAboveMaxError(uint256 partyTokenId, uint256 maxTokenId);
errorEmergencyActionsNotAllowedError();
errorInvalidDistributionSupplyError(uint128 supply);
errorOnlyFeeRecipientError(address caller, address feeRecipient);
errorInvalidFeeBpsError(uint16 feeBps);
// Token address used to indicate a native distribution (i.e. distribution of ETH).addressprivateconstant NATIVE_TOKEN_ADDRESS =0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice The `Globals` contract storing global configuration values. This contract/// is immutable and it’s address will never change.
IGlobals publicimmutable GLOBALS;
/// @notice Timestamp when the DAO is no longer allowed to call emergency functions.uint40publicimmutable EMERGENCY_DISABLED_TIMESTAMP;
/// @notice Last distribution ID for a party.mapping(Party =>uint256) public lastDistributionIdPerParty;
/// Last known balance of a token, identified by an ID derived from the token./// Gets lazily updated when creating and claiming a distribution (transfers)./// Allows one to simply transfer and call `createDistribution()` without/// fussing with allowances.mapping(bytes32=>uint256) private _storedBalances;
// tokenDistributorParty => distributionId => DistributionStatemapping(Party =>mapping(uint256=> DistributionState)) private _distributionStates;
// msg.sender == DAOmodifieronlyPartyDao() {
{
address partyDao = GLOBALS.getAddress(LibGlobals.GLOBAL_DAO_WALLET);
if (msg.sender!= partyDao) {
revert OnlyPartyDaoError(msg.sender, partyDao);
}
}
_;
}
// emergencyActionsDisabled == falsemodifieronlyIfEmergencyActionsAllowed() {
if (block.timestamp> EMERGENCY_DISABLED_TIMESTAMP) {
revert EmergencyActionsNotAllowedError();
}
_;
}
// Set the `Globals` contract.constructor(IGlobals globals, uint40 emergencyDisabledTimestamp) {
GLOBALS = globals;
EMERGENCY_DISABLED_TIMESTAMP = emergencyDisabledTimestamp;
}
/// @inheritdoc ITokenDistributorfunctioncreateNativeDistribution(
Party party,
addresspayable feeRecipient,
uint16 feeBps
) externalpayablenonReentrantreturns (DistributionInfo memory info) {
info = _createDistribution(
CreateDistributionArgs({
party: party,
tokenType: TokenType.Native,
token: NATIVE_TOKEN_ADDRESS,
currentTokenBalance: address(this).balance,
feeRecipient: feeRecipient,
feeBps: feeBps
})
);
}
/// @inheritdoc ITokenDistributorfunctioncreateErc20Distribution(
IERC20 token,
Party party,
addresspayable feeRecipient,
uint16 feeBps
) externalnonReentrantreturns (DistributionInfo memory info) {
info = _createDistribution(
CreateDistributionArgs({
party: party,
tokenType: TokenType.Erc20,
token: address(token),
currentTokenBalance: token.balanceOf(address(this)),
feeRecipient: feeRecipient,
feeBps: feeBps
})
);
}
/// @inheritdoc ITokenDistributorfunctionclaim(
DistributionInfo calldata info,
uint256 partyTokenId
) publicnonReentrantreturns (uint128 amountClaimed) {
// Caller must own the party token.
{
address ownerOfPartyToken = info.party.ownerOf(partyTokenId);
if (msg.sender!= ownerOfPartyToken) {
revert MustOwnTokenError(msg.sender, ownerOfPartyToken, partyTokenId);
}
}
// DistributionInfo must be correct for this distribution ID.
DistributionState storage state = _distributionStates[info.party][info.distributionId];
if (state.distributionHash != _getDistributionHash(info)) {
revert InvalidDistributionInfoError(info);
}
// The partyTokenId must not have claimed its distribution yet.if (state.hasPartyTokenClaimed[partyTokenId]) {
revert DistributionAlreadyClaimedByPartyTokenError(info.distributionId, partyTokenId);
}
// Mark the partyTokenId as having claimed their distribution.
state.hasPartyTokenClaimed[partyTokenId] =true;
// Compute amount owed to partyTokenId.
amountClaimed = getClaimAmount(info, partyTokenId);
// Cap at the remaining member supply. Otherwise a malicious// party could drain more than the distribution supply.uint128 remainingMemberSupply = state.remainingMemberSupply;
amountClaimed = amountClaimed > remainingMemberSupply
? remainingMemberSupply
: amountClaimed;
state.remainingMemberSupply = remainingMemberSupply - amountClaimed;
// Transfer tokens owed.
_transfer(info.tokenType, info.token, payable(msg.sender), amountClaimed);
emit DistributionClaimedByPartyToken(
info.party,
partyTokenId,
msg.sender,
info.tokenType,
info.token,
amountClaimed
);
}
/// @inheritdoc ITokenDistributorfunctionclaimFee(
DistributionInfo calldata info,
addresspayable recipient
) publicnonReentrant{
// DistributionInfo must be correct for this distribution ID.
DistributionState storage state = _distributionStates[info.party][info.distributionId];
if (state.distributionHash != _getDistributionHash(info)) {
revert InvalidDistributionInfoError(info);
}
// Caller must be the fee recipient.if (info.feeRecipient !=msg.sender) {
revert OnlyFeeRecipientError(msg.sender, info.feeRecipient);
}
// Must not have claimed the fee yet.if (state.wasFeeClaimed) {
revert DistributionFeeAlreadyClaimedError(info.distributionId);
}
// Mark the fee as claimed.
state.wasFeeClaimed =true;
// Transfer the tokens owed.
_transfer(info.tokenType, info.token, recipient, info.fee);
emit DistributionFeeClaimed(
info.party,
info.feeRecipient,
info.tokenType,
info.token,
info.fee
);
}
/// @inheritdoc ITokenDistributorfunctionbatchClaim(
DistributionInfo[] calldata infos,
uint256[] calldata partyTokenIds
) externalreturns (uint128[] memory amountsClaimed) {
amountsClaimed =newuint128[](infos.length);
for (uint256 i =0; i < infos.length; ++i) {
amountsClaimed[i] = claim(infos[i], partyTokenIds[i]);
}
}
/// @inheritdoc ITokenDistributorfunctionbatchClaimFee(
DistributionInfo[] calldata infos,
addresspayable[] calldata recipients
) external{
for (uint256 i =0; i < infos.length; ++i) {
claimFee(infos[i], recipients[i]);
}
}
/// @inheritdoc ITokenDistributorfunctiongetClaimAmount(
DistributionInfo calldata info,
uint256 partyTokenId
) publicviewreturns (uint128) {
Party party = info.party;
// Token ID must not be greater than the max token ID. If totalVotingPower hasn't increased we allow the claim.uint96 maxTokenId = _distributionStates[party][info.distributionId].maxTokenId;
if (
partyTokenId > maxTokenId &&
info.party.getGovernanceValues().totalVotingPower > info.totalShares
) {
revert TokenIdAboveMaxError(partyTokenId, maxTokenId);
}
// Check which method to use for calculating claim amount based on// version of Party contract.
(bool success, bytesmemory response) =address(party).staticcall(
abi.encodeCall(party.VERSION_ID, ())
);
// Check the version ID.if (success &&abi.decode(response, (uint16)) >=1) {
uint256 shareOfSupply = ((info.party.getDistributionShareOf(partyTokenId)) *1e18) /
info.totalShares;
return// We round up here to prevent dust amounts getting trapped in this contract.
((shareOfSupply * info.memberSupply + (1e18-1)) /1e18)
.safeCastUint256ToUint128();
} else {
// Use method of calculating claim amount for backwards// compatibility with older parties where getDistributionShareOf()// returned the fraction of the memberSupply partyTokenId is// entitled to, scaled by 1e18.uint256 shareOfSupply = party.getDistributionShareOf(partyTokenId);
return// We round up here to prevent dust amounts getting trapped in this contract.
((shareOfSupply * info.memberSupply + (1e18-1)) /1e18)
.safeCastUint256ToUint128();
}
}
/// @inheritdoc ITokenDistributorfunctionwasFeeClaimed(Party party, uint256 distributionId) externalviewreturns (bool) {
return _distributionStates[party][distributionId].wasFeeClaimed;
}
/// @inheritdoc ITokenDistributorfunctionhasPartyTokenIdClaimed(
Party party,
uint256 partyTokenId,
uint256 distributionId
) externalviewreturns (bool) {
return _distributionStates[party][distributionId].hasPartyTokenClaimed[partyTokenId];
}
/// @inheritdoc ITokenDistributorfunctiongetRemainingMemberSupply(
Party party,
uint256 distributionId
) externalviewreturns (uint128) {
return _distributionStates[party][distributionId].remainingMemberSupply;
}
/// @notice As the DAO, execute an arbitrary delegatecall from this contract./// @dev Emergency actions must not be revoked for this to work./// @param targetAddress The contract to delegatecall into./// @param targetCallData The data to pass to the call.functionemergencyExecute(address targetAddress,
bytescalldata targetCallData
) externalonlyPartyDaoonlyIfEmergencyActionsAllowed{
(bool success, bytesmemory res) = targetAddress.delegatecall(targetCallData);
if (!success) {
res.rawRevert();
}
emit EmergencyExecute(targetAddress, targetCallData);
}
function_createDistribution(
CreateDistributionArgs memory args
) privatereturns (DistributionInfo memory info) {
if (args.feeBps >1e4) {
revert InvalidFeeBpsError(args.feeBps);
}
uint128 supply;
{
bytes32 balanceId = _getBalanceId(args.tokenType, args.token);
supply = (args.currentTokenBalance - _storedBalances[balanceId])
.safeCastUint256ToUint128();
// Supply must be nonzero.if (supply ==0) {
revert InvalidDistributionSupplyError(supply);
}
// Update stored balance.
_storedBalances[balanceId] = args.currentTokenBalance;
}
// Create a distribution.uint128 fee = (supply * args.feeBps) /1e4;
uint128 memberSupply = supply - fee;
info = DistributionInfo({
tokenType: args.tokenType,
distributionId: ++lastDistributionIdPerParty[args.party],
token: args.token,
party: args.party,
memberSupply: memberSupply,
feeRecipient: args.feeRecipient,
fee: fee,
totalShares: args.party.getGovernanceValues().totalVotingPower
});
DistributionState storage state = _distributionStates[args.party][info.distributionId];
state.distributionHash = _getDistributionHash(info);
state.remainingMemberSupply = memberSupply;
state.maxTokenId = args.party.tokenCount();
emit DistributionCreated(args.party, info);
}
function_transfer(
TokenType tokenType,
address token,
addresspayable recipient,
uint256 amount
) private{
bytes32 balanceId = _getBalanceId(tokenType, token);
_storedBalances[balanceId] -= amount;
if (tokenType == TokenType.Native) {
recipient.transferEth(amount);
} else {
assert(tokenType == TokenType.Erc20);
IERC20(token).compatTransfer(recipient, amount);
}
}
function_getDistributionHash(
DistributionInfo memory info
) internalpurereturns (bytes32 hash) {
assembly {
hash :=keccak256(info, 0x100)
}
}
function_getBalanceId(
TokenType tokenType,
address token
) privatepurereturns (bytes32 balanceId) {
if (tokenType == TokenType.Native) {
returnbytes32(uint256(uint160(NATIVE_TOKEN_ADDRESS)));
}
assert(tokenType == TokenType.Erc20);
returnbytes32(uint256(uint160(token)));
}
}